/* Public practice page. Its request functions talk only to a dedicated browser
   Worker. They never fall back to the live API, including on startup failure. */
const practiceActors = {
  redactor: 'Redaction reviewer', member: 'Committee member', chair: 'Committee chair',
  coordinator: 'Coordinator', observer: 'Chapter member (count only)', president: 'President (count only)', 'signed-out': 'Signed out (access denied)',
};

async function createPracticeEngine() {
  const manifestResponse = await fetch('/scholarship-demo-assets/manifest.json', { cache: 'no-store', credentials: 'omit' });
  if (!manifestResponse.ok) throw new Error('The practice files could not be loaded. Reload to try again.');
  const manifest = await manifestResponse.json();
  if (!/^runtime-[a-f0-9]{16}\.js$/.test(manifest.worker)) throw new Error('The practice files are incomplete. Reload to try again.');
  const worker = new Worker(`/scholarship-demo-assets/${manifest.worker}`);
  const pending = new Map();
  let id = 0, stopped = false;
  const stop = (message) => {
    stopped = true; worker.terminate();
    for (const task of pending.values()) { clearTimeout(task.timer); task.reject(new Error(message)); }
    pending.clear();
  };
  worker.onmessage = ({ data }) => {
    const task = pending.get(data.id);
    if (!task) return;
    pending.delete(data.id); clearTimeout(task.timer);
    data.error ? task.reject(new Error(data.error)) : task.resolve(data.result);
  };
  worker.onerror = (event) => { event.preventDefault(); stop('The practice workspace could not start. Reload in an up-to-date browser to try again.'); };
  return {
    send(message) {
      if (stopped) return Promise.reject(new Error('The practice workspace stopped. Reload to start again.'));
      return new Promise((resolve, reject) => {
        const requestId = ++id;
        const timer = setTimeout(() => stop('The practice workspace took too long to respond. Reload to start again.'), 30000);
        pending.set(requestId, { resolve, reject, timer });
        worker.postMessage({ ...message, id: requestId });
      });
    },
    close() { stop('Practice workspace closed.'); },
  };
}

function ScholarshipDemoPage() {
  const startWithApplication = new URLSearchParams(window.location.search).get('view') === 'application';
  const [engine, setEngine] = React.useState(null);
  const [state, setState] = React.useState(null);
  const [view, setView] = React.useState(startWithApplication ? 'application' : 'workspace');
  const [revision, setRevision] = React.useState(0);
  const [busy, setBusy] = React.useState(true);
  const [error, setError] = React.useState('');
  const dirty = React.useRef(false);
  const reportDirty = React.useCallback((value) => { dirty.current = value; }, []);
  const canChange = () => !dirty.current || window.confirm('Discard unsaved practice edits? Save your draft or decision first to keep it in this tab.');
  const cycle = window.WKYHB.scholarship.year;
  React.useEffect(() => {
    let active = true, runtime;
    (async () => {
      try {
        runtime = await createPracticeEngine();
        if (!active) { runtime.close(); return; }
        let initial = await runtime.send({ type: 'init', cycle, serviceAreaCounties: window.WKYHB.scholarship.serviceAreaCounties });
        if (startWithApplication) initial = await runtime.send({ type: 'control', action: 'intake', open: true });
        if (active) { setEngine(runtime); setState(initial); setBusy(false); }
      } catch (failure) {
        if (runtime) runtime.close();
        if (active) { setError(failure.message); setBusy(false); }
      }
    })();
    return () => { active = false; if (runtime) runtime.close(); };
  }, []);

  const change = async (action, values = {}, nextView = 'workspace') => {
    if (!canChange()) return;
    setBusy(true); setError('');
    try {
      const updated = await engine.send({ type: 'control', action, ...values });
      setState(updated); setRevision((value) => value + 1); setView(nextView);
    } catch (failure) { setError(failure.message); }
    finally { setBusy(false); }
  };
  const requestReview = async (endpoint, body) => {
    const result = await engine.send({ type: 'review', endpoint, body });
    if (result.status >= 400 || !result.body.success) throw new Error(result.body.message || 'The practice request failed.');
    return result.body;
  };
  const submitPractice = async (_path, options) => {
    const result = await engine.send({ type: 'submit', body: JSON.parse(options.body) });
    setState(await engine.send({ type: 'control', action: 'status' }));
    return new Response(JSON.stringify(result.body), { status: result.status, headers: { 'Content-Type': 'application/json' } });
  };
  const Workspace = window.Pages.admin;
  const Application = window.Pages.scholarship;
  return <React.Fragment>
    <aside className="wk-practice-panel" aria-label="Scholarship practice controls">
      <h1>Scholarship practice demo</h1>
      <p><strong>Fictional data only — nothing is submitted to the chapter.</strong> No email login is needed. Each tab has its own practice round. Reloading or leaving this page resets your progress.</p>
      <p><a href="/scholarship-demo?view=application" target="_blank" rel="noopener">Open test application form</a> — opens an available practice form in a new tab, even if review has already started here. Your current practice round stays in this tab.</p>
      <div className="wk-practice-actions">
        <label>Try a role <select disabled={busy || !state} value={state?.actor || 'redactor'} onChange={(event) => change('role', { actor: event.target.value })}>
          {Object.entries(practiceActors).map(([value, label]) => <option key={value} value={value}>{label}</option>)}
        </select></label>
        <button disabled={busy || !state} onClick={() => change('intake', { open: !state.open }, state.open ? 'workspace' : 'application')}>{state?.open ? 'Close practice applications' : 'Open practice applications'}</button>
        {(view === 'application' || state?.open) && <button disabled={busy || !state} onClick={() => { if (canChange()) setView(view === 'workspace' ? 'application' : 'workspace'); }}>{view === 'workspace' ? 'View application form' : 'Back to review workspace'}</button>}
        <button disabled={busy || !state} onClick={() => change('reset')}>Reset sample data</button>
      </div>
      <p role="status">{state ? `${cycle} · ${state.count} practice applications · Intake ${state.open ? 'open' : 'closed'}` : 'Preparing your practice workspace…'}</p>
      {error && <p role="alert">{error}</p>}
      <details><summary>Walk through the process</summary><ol>
        <li>As redactor, complete the declaration after intake closes. Original and redacted copies keep the same questions. Select identifying text, then Redact selection; Find text locates an exact phrase. Save draft keeps progress. Review redactions can restore a mistaken saved narrative mask; identity fields stay locked. Finish redaction opens the final check, then Release to committee opens the next unfinished application. A completion dialog appears after the last release.</li>
        <li>Committee members can read anonymous copies after every application is released. Try a packet or CSV download and recusal.</li>
        <li>As committee chair, record the agreed criteria, each applicant’s eligibility, selection outcome and brief reason, plus the shared selection rationale. Saving a decision opens the next unfinished application; the last save explains the next step.</li>
        <li>Continue to award allocation. Enter the available budget, explain the allocation basis, and assign amounts while applicants remain anonymous. Record the meeting date and actual participants. Finalize once the committee agrees.</li>
        <li>The committee then sees names and outcomes and can download the final award packet. The chair and optional coordinator can also download a recipient contact list and readable full audit log. Use Recipient follow-up and amendments to record notifications, acceptance, payment, and later changes. Download complete archive saves the packet, approved copies, audit, and supporting records together. Chapter accounts without a role see only the count.</li>
        <li>As chair or coordinator, use Scholarship cycle to explore the fictional prior year’s completed award, record, and activity history. Return to the current cycle to continue practice.</li>
        <li>To try the application form, reset first and open intake before starting redaction. Use fictional information. Reset or reload to start a new practice round.</li>
      </ol></details>
      <p className="wk-practice-note">This practice page exercises the application and review workflow. Live email delivery, sign-in, and Cloudflare storage need a separate deployment check.</p>
    </aside>
    {engine && state && !busy && (view === 'application'
      ? <Application key={`application-${revision}`} practice onDirtyChange={reportDirty} submitRequest={submitPractice} scholarship={{ ...window.WKYHB.scholarship, open: state.open, opensAt: '', closesAt: '', turnstileSiteKey: '' }} />
      : <Workspace key={`workspace-${revision}`} practice request={requestReview} onDirtyChange={reportDirty} onPracticeSignOut={() => change('role', { actor: 'signed-out' })} />)}
  </React.Fragment>;
}

window.Pages = window.Pages || {};
window.Pages['scholarship-demo'] = ScholarshipDemoPage;
