/* global reviewDS, reviewInput, reviewActions, ReviewCheck, ReviewAnswers, ReviewCompletionDialog, FinalAwardDownloads */
/* Selection and allocation remain anonymous until the chair locks the round. */
function CommitteeApplication({ application, cohort, chair, finalized, busy, onDecision, onRecuse, onDirtyChange }) {
  const { Button } = reviewDS;
  const [eligibility, setEligibility] = React.useState(application.eligibility || '');
  const [decision, setDecision] = React.useState(application.decision || '');
  const [reason, setReason] = React.useState(application.reason || '');
  const [recusal, setRecusal] = React.useState(false);
  const dirty = eligibility !== (application.eligibility || '') || decision !== (application.decision || '') || reason !== (application.reason || '');
  const tools = window.WKYHBReviewExport;

  React.useEffect(() => { onDirtyChange(dirty); }, [dirty, onDirtyChange]);
  React.useEffect(() => () => onDirtyChange(false), [onDirtyChange]);

  if (application.recused) return <article className="wk-committee-application wk-committee-recused">
    <p className="wk-committee-eyebrow">Application {application.code}</p>
    <h3>You have recused yourself.</h3>
    <p>This application is excluded from your view and downloads. Continue with another application in the list.</p>
  </article>;

  return <article className="wk-committee-application">
    <div className="wk-committee-document"><ReviewAnswers application={application} /></div>
    <aside className="wk-committee-inspector" id="committee-review-tools" tabIndex="-1" aria-label={`Review tools for ${application.code}`}>
      <div className="wk-committee-inspector-section">
        <p className="wk-committee-eyebrow">{finalized ? 'Final outcome' : 'Committee decision'}</p>
        <h3>{application.eligibility === 'ineligible' ? 'Ineligible · Not selected' : tools.outcome(application.decision)}</h3>
        {!!application.amountCents && <p className="wk-committee-award">{tools.amount(application.amountCents)}</p>}
        {application.reason && <p className="wk-committee-recorded-reason">{application.reason}</p>}
        <p className="wk-committee-detail">Approved copy · Version {application.version}</p>
        {!!application.recusalCount && <p className="wk-committee-notice">{application.recusalCount} reviewer(s) reported recognition or a conflict. Exclude their participation in this decision.</p>}
        {finalized && <p>The committee has finalized this outcome. Decisions are locked.</p>}
        {!chair && !finalized && <p>Discuss this application using its code. The chair records the committee’s agreed decision.</p>}
      </div>

      {chair && !finalized && <div className="wk-committee-inspector-section">
        <h3>Record the committee’s decision</h3>
        <p className="wk-committee-detail">Check eligibility against the agreed criteria, then record the collective decision and its reason. Award amounts come in the next step.</p>
        <label className="wk-committee-field">Eligibility
          <select aria-label={`Eligibility for ${application.code}`} value={eligibility} onChange={(event) => { setEligibility(event.target.value); if (event.target.value === 'ineligible') setDecision('not_selected'); }} style={reviewInput} disabled={busy}>
            <option value="">Choose…</option><option value="eligible">Eligible</option><option value="ineligible">Ineligible</option>
          </select>
        </label>
        <label className="wk-committee-field">Outcome
          <select aria-label={`Outcome for ${application.code}`} value={decision} onChange={(event) => setDecision(event.target.value)} style={reviewInput} disabled={busy || eligibility === 'ineligible'}>
            <option value="">Choose…</option><option value="selected">Selected</option><option value="alternate">Alternate</option><option value="not_selected">Not selected</option>
          </select>
        </label>
        <label className="wk-committee-field">Brief reason for this decision
          <textarea aria-label={`Reason for ${application.code}`} value={reason} onChange={(event) => setReason(event.target.value)} rows="4" maxLength="4000" style={reviewInput} disabled={busy} placeholder="Explain how the application meets the criteria and why the committee reached this decision." />
        </label>
        {!!application.amountCents && dirty && <p className="wk-committee-notice">Saving a changed decision clears this application’s award amount. Set its amount again in step 2.</p>}
        <Button size="sm" disabled={busy || !dirty || !eligibility || !decision || !reason.trim()} onClick={() => onDecision({ code: application.code, version: application.version, cohort, eligibility, decision, reason: reason.trim() })}>Save decision</Button>
        <p className="wk-committee-save-state" role="status">{dirty ? 'Unsaved decision' : application.decision ? 'Decision saved' : 'No decision recorded'}</p>
      </div>}

      {!finalized && <div className="wk-committee-inspector-section">
        <h3>Recognize this applicant?</h3>
        <ReviewCheck checked={recusal} onChange={setRecusal}>I recognize this applicant or have a conflict and need to recuse myself.</ReviewCheck>
        {recusal && <Button size="sm" variant="outline" disabled={busy} onClick={() => {
          if (!dirty || window.confirm('Your decision has not been saved. Discard it and recuse yourself from this application?')) onRecuse({ code: application.code });
        }}>Confirm recusal and hide application</Button>}
      </div>}
    </aside>
  </article>;
}

function CommitteeRoundPlan({ plan, mode, chair, finalized, busy, onPlan, onDirtyChange }) {
  const { Button } = reviewDS;
  const [criteria, setCriteria] = React.useState(plan.criteria || '');
  const [selectionRationale, setSelectionRationale] = React.useState(plan.selectionRationale || '');
  const [allocationRationale, setAllocationRationale] = React.useState(plan.allocationRationale || '');
  const [budget, setBudget] = React.useState(plan.budgetCents ? (plan.budgetCents / 100).toFixed(2) : '');
  const budgetCents = budget === '' ? 0 : Math.round(Number(budget) * 100);
  const validBudget = /^\d+(?:\.\d{0,2})?$/.test(budget) && Number.isSafeInteger(budgetCents) && budgetCents >= 0;
  const selection = mode === 'selection';
  const dirty = criteria !== (plan.criteria || '') || selectionRationale !== (plan.selectionRationale || '') || allocationRationale !== (plan.allocationRationale || '') || budgetCents !== (plan.budgetCents || 0);
  React.useEffect(() => { onDirtyChange(dirty); }, [dirty, onDirtyChange]);
  React.useEffect(() => () => onDirtyChange(false), [onDirtyChange]);
  const editable = chair && !finalized;
  return <section className="wk-committee-round-plan" aria-label={selection ? 'Selection record' : 'Allocation plan'}>
    <div className="wk-committee-plan-heading"><p className="wk-committee-eyebrow">{selection ? 'Step 1 · Select recipients' : 'Step 2 · Allocate awards'}</p>
      <h3 tabIndex={-1}>{selection ? 'Document the committee’s reasoning' : 'Set the budget and allocation basis'}</h3>
      <p>{selection ? 'Record the criteria used for this cycle and a shared explanation of how the committee selected recipients. The chair can be the secretary or another assigned committee member.' : 'Recipients are selected. Agree on a total budget and how to divide it, then enter each award below. Names remain hidden until finalization.'}</p>
    </div>
    {!editable ? <div className="wk-committee-plan-record">{selection ? <React.Fragment><h4>Selection criteria</h4><p>{criteria || 'The chair has not recorded the criteria yet.'}</p><h4>Shared selection rationale</h4><p>{selectionRationale || 'The chair will record the committee’s shared reasoning after discussion.'}</p></React.Fragment> : <React.Fragment><h4>Budget available</h4><p>{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format((plan.budgetCents || 0) / 100)}</p><h4>How award amounts were determined</h4><p>{allocationRationale || 'The chair has not recorded the allocation basis yet.'}</p></React.Fragment>}</div> : selection ? <React.Fragment>
      <label className="wk-committee-field">Selection criteria
        <textarea aria-label="Selection criteria" rows="3" maxLength="12000" style={reviewInput} value={criteria} onChange={(event) => setCriteria(event.target.value)} disabled={busy || !editable} placeholder="Record the eligibility rules and criteria the committee agreed to use." />
      </label>
      <label className="wk-committee-field">Shared selection rationale
        <textarea aria-label="Shared selection rationale" rows="3" maxLength="12000" style={reviewInput} value={selectionRationale} onChange={(event) => setSelectionRationale(event.target.value)} disabled={busy || !editable} placeholder="Summarize the discussion, comparisons, and any tie-breaking that led to the decisions." />
      </label>
    </React.Fragment> : <React.Fragment>
      <label className="wk-committee-field wk-committee-budget">Budget available ($)
        <input aria-label="Budget available ($)" type="number" inputMode="decimal" min="0" step="0.01" style={reviewInput} value={budget} onChange={(event) => setBudget(event.target.value)} disabled={busy || !editable} />
      </label>
      <label className="wk-committee-field">How award amounts were determined
        <textarea aria-label="How award amounts were determined" rows="3" maxLength="12000" style={reviewInput} value={allocationRationale} onChange={(event) => setAllocationRationale(event.target.value)} disabled={busy || !editable} placeholder="Explain how the available funds were divided. If enrollment year influenced amounts, record that here." />
      </label>
    </React.Fragment>}
    {editable && <div style={reviewActions}>
      <Button size="sm" disabled={busy || !dirty || (!selection && !validBudget)} onClick={() => onPlan({ version: plan.version || 0, criteria: criteria.trim(), selectionRationale: selectionRationale.trim(), allocationRationale: allocationRationale.trim(), budgetCents })}>{selection ? 'Save selection record' : 'Save allocation plan'}</Button>
      <p className="wk-committee-save-state" role="status">{dirty ? 'Unsaved committee record' : 'Committee record saved'}</p>
    </div>}
  </section>;
}

function CommitteeMeetingRecord({ meeting, roster, session, finalized, busy, onMeeting, onDirtyChange }) {
  const { Button } = reviewDS;
  const [date, setDate] = React.useState(meeting.date || '');
  const [attendees, setAttendees] = React.useState(meeting.attendees || []);
  const [confirmed, setConfirmed] = React.useState(false);
  const dirty = date !== (meeting.date || '') || JSON.stringify([...attendees].sort()) !== JSON.stringify([...(meeting.attendees || [])].sort()) || confirmed;
  React.useEffect(() => { onDirtyChange(dirty); }, [dirty, onDirtyChange]);
  React.useEffect(() => () => onDirtyChange(false), [onDirtyChange]);
  const editable = session.chair && !finalized;
  const attestedByChair = meeting.attestedBy === session.email;
  return <section className="wk-committee-round-plan" aria-label="Meeting record">
    <h3>Meeting record</h3><p>Record when the committee met and who actually took part. This is separate from the assigned roster. Recusals remain recorded with each application.</p>
    {!editable ? <div className="wk-committee-plan-record"><h4>Committee meeting date</h4><p>{date || 'Not recorded yet'}</p><h4>Actual participants</h4><p>{attendees.length ? attendees.join(', ') : 'Not recorded yet'}</p>{meeting.attestedBy && <p>Confirmed by {meeting.attestedBy} on {new Date(meeting.attestedAt).toLocaleDateString()}.</p>}</div> : <React.Fragment>
      <label className="wk-committee-field wk-committee-meeting-date">Committee meeting date<input aria-label="Committee meeting date" type="date" style={reviewInput} value={date} disabled={busy} onChange={(event) => { setDate(event.target.value); setConfirmed(false); }} /></label>
      <fieldset className="wk-committee-attendees"><legend>Actual participants</legend>{roster.map((email) => <ReviewCheck key={email} checked={attendees.includes(email)} disabled={busy} onChange={(checked) => { setAttendees((current) => checked ? [...current, email] : current.filter((entry) => entry !== email)); setConfirmed(false); }}>{email}</ReviewCheck>)}</fieldset>
      <ReviewCheck checked={confirmed} disabled={busy} onChange={setConfirmed}>{meeting.attestation || 'I confirm that the listed participants took part in the committee meeting, that recorded decisions and award amounts reflect the committee’s agreement, and that applicants remained anonymous during selection and allocation.'}</ReviewCheck>
      {!attendees.includes(session.email) && <p className="wk-committee-detail">The chair recording this meeting must be one of its participants.</p>}
      {meeting.version > 0 && !attestedByChair && <p className="wk-committee-notice">The chair assignment changed. Review the meeting record and confirm it as the current chair before finalizing.</p>}
      <Button disabled={busy || !date || !confirmed || !attendees.includes(session.email)} onClick={() => onMeeting({ version: meeting.version || 0, date, attendees, confirm: true })}>Save meeting record</Button>
      <p className="wk-committee-save-state" role="status">{dirty ? 'Unsaved meeting record' : meeting.version ? `Meeting recorded by ${meeting.attestedBy}` : 'Meeting record needed before finalization'}</p>
    </React.Fragment>}
  </section>;
}

function CommitteeAllocationRow({ application, cohort, chair, busy, onAllocation, onDirtyChange }) {
  const { Button } = reviewDS;
  const tools = window.WKYHBReviewExport;
  const [amount, setAmount] = React.useState(application.amountCents ? (application.amountCents / 100).toFixed(2) : '');
  const amountCents = amount === '' ? 0 : Math.round(Number(amount) * 100);
  const dirty = amountCents !== (application.amountCents || 0);
  const validAmount = /^\d+(?:\.\d{0,2})?$/.test(amount) && Number.isSafeInteger(amountCents) && amountCents > 0;
  React.useEffect(() => { onDirtyChange(application.code, dirty); }, [application.code, dirty, onDirtyChange]);
  React.useEffect(() => () => onDirtyChange(application.code, false), [application.code, onDirtyChange]);
  return <div className="wk-committee-allocation-row">
    <div><h4>{application.code}</h4><p>{application.answers?.enrollLevel || 'Enrollment year not provided'}</p><p className="wk-committee-detail">{application.reason}</p></div>
    {chair ? <div className="wk-committee-allocation-controls">
      <label className="wk-committee-field">Award amount ($)<input aria-label={`Award amount for ${application.code}`} type="number" inputMode="decimal" min="0.01" step="0.01" value={amount} onChange={(event) => setAmount(event.target.value)} style={reviewInput} disabled={busy} /></label>
      <Button size="sm" aria-label={`Save award for ${application.code}`} disabled={busy || !dirty || !validAmount} onClick={() => onAllocation({ code: application.code, version: application.version, cohort, amountCents })}>Save award</Button>
      <p className="wk-committee-save-state" role="status">{dirty ? 'Unsaved award' : application.amountCents ? 'Award saved' : 'Amount needed'}</p>
    </div> : <p className="wk-committee-award">{tools.amount(application.amountCents) || 'Amount pending'}</p>}
  </div>;
}

function CommitteeFinalResults({ session, data, results, busy, onResults, onReport }) {
  const { Button } = reviewDS;
  const tools = window.WKYHBReviewExport;
  return <section className="wk-committee-results" aria-label="Finalized selection record">
    <p className="wk-committee-eyebrow">Completed · Decisions and amounts locked</p><h2 tabIndex={-1}>Final awards and records</h2>
    <p>The anonymous review is complete. These original finalized decisions remain preserved. The chair or coordinator records later amendments and notification, response, and payment status in Recipient follow-up and amendments. Download fresh records before notifying recipients.</p>
    <div style={reviewActions}>
      <Button variant="outline" disabled={busy} onClick={onResults}>{results ? 'Refresh finalized results' : 'View finalized results'}</Button>
    </div>
    {(results?.awards || []).map((award) => <article key={award.code} className="wk-committee-result-row"><div><h3>{award.name}</h3><p>{award.code} · {award.eligibility === 'ineligible' ? 'Ineligible · Not selected' : tools.outcome(award.decision)} {tools.amount(award.amountCents)}</p><p>{award.reason}</p></div>
      {session.chair && award.contacts && <address>{award.contacts.email && <p><a href={`mailto:${award.contacts.email}`}>{award.contacts.email}</a></p>}{award.contacts.phone && <p>{award.contacts.phone}</p>}{award.contacts.address && <p>{award.contacts.address}</p>}</address>}
    </article>)}
    {results?.awards?.length === 0 && <p>No results are available to this account.</p>}
    <p className="wk-committee-detail">{data.total} applications in this round. Keep applicant information and the decision record confidential.</p>
  </section>;
}

function CommitteeWorkspace({ session, data, results, busy, onExport, onDecision, onRecuse, onFinalize, onPlan, onMeeting, onAllocation, onReport, onArchive, onResults, onDirtyChange }) {
  const { Button } = reviewDS;
  const [selected, setSelected] = React.useState(data.applications.find((entry) => !entry.recused && !entry.decision)?.code || '');
  const [confirm, setConfirm] = React.useState(false);
  const [decisionDirty, setDecisionDirty] = React.useState(false);
  const [planDirty, setPlanDirty] = React.useState(false);
  const [meetingDirty, setMeetingDirty] = React.useState(false);
  const [allocationDirty, setAllocationDirty] = React.useState({});
  const [view, setView] = React.useState(data.finalized ? 'finalized' : data.stage === 'allocation' ? 'allocation' : 'selection');
  const [reset, setReset] = React.useState(0);
  const [completion, setCompletion] = React.useState(null);
  const [progressNotice, setProgressNotice] = React.useState('');
  const [focusRequest, setFocusRequest] = React.useState(null);
  const workspaceRef = React.useRef(null);
  const selectedIndex = Math.max(0, data.applications.findIndex((application) => application.code === selected));
  const application = data.applications[selectedIndex];
  const available = data.applications.filter((entry) => !entry.recused).length;
  const plan = data.roundPlan || { version: 0, criteria: '', selectionRationale: '', allocationRationale: '', budgetCents: 0 };
  const meeting = data.meeting || { version: 0, date: '', attendees: [] };
  const meetingReady = !!meeting.version && meeting.attestedBy === session.email && meeting.attendees.includes(session.email);
  const recipients = data.applications.filter((entry) => entry.decision === 'selected' && !entry.recused);
  const allocatedCents = data.allocatedCents ?? recipients.reduce((sum, entry) => sum + (entry.amountCents || 0), 0);
  const unallocated = data.unallocated ?? recipients.filter((entry) => !entry.amountCents).length;
  const remaining = plan.budgetCents - allocatedCents;
  const awaitingOpening = data.awaitingOpening || data.phase === 'scheduled';
  const allocationAvailable = !data.undecided && data.total > 0 && !data.pending && !data.applicationsOpen && !awaitingOpening;
  const hasRecusal = data.applications.some((entry) => entry.recused);
  const canFinalize = meetingReady && allocationAvailable && !unallocated && !hasRecusal && !!plan.criteria.trim() && !!plan.selectionRationale.trim() && !!plan.allocationRationale.trim() && remaining >= 0;
  const dirty = decisionDirty || planDirty || meetingDirty || Object.values(allocationDirty).some(Boolean);
  const tools = window.WKYHBReviewExport;
  const currency = (cents) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(cents / 100);
  const reportAllocationDirty = React.useCallback((code, value) => setAllocationDirty((current) => current[code] === value ? current : { ...current, [code]: value }), []);

  React.useEffect(() => { setConfirm(false); }, [data.cohort]);
  React.useEffect(() => { if (data.finalized) setView('finalized'); }, [data.finalized]);
  React.useLayoutEffect(() => {
    if (!focusRequest || completion) return;
    const selector = focusRequest.target === 'plan' ? '.wk-committee-round-plan h3'
      : focusRequest.target === 'results' ? '.wk-final-award-downloads h2' : '.wk-committee-document .wk-sheet-title h3';
    const heading = workspaceRef.current?.querySelector(selector);
    if (heading) { heading.tabIndex = -1; heading.focus({ preventScroll: true }); heading.scrollIntoView({ block: 'start' }); }
  }, [focusRequest, completion, view]);
  React.useEffect(() => { onDirtyChange?.(dirty); }, [dirty, onDirtyChange]);
  React.useEffect(() => () => onDirtyChange?.(false), [onDirtyChange]);
  React.useEffect(() => {
    if (!dirty) return;
    const warn = (event) => { event.preventDefault(); event.returnValue = ''; };
    window.addEventListener('beforeunload', warn);
    return () => window.removeEventListener('beforeunload', warn);
  }, [dirty]);

  const selectApplication = (next) => {
    if (!next || next.code === application?.code || busy) return;
    if (decisionDirty && !window.confirm('Your decision has not been saved. Discard it and open another application?')) return;
    setDecisionDirty(false); setSelected(next.code);
  };
  const changeView = (next) => {
    if (next === view || busy) return;
    if (dirty && !window.confirm('Discard unsaved changes before changing steps? Save your work first to keep it.')) return;
    setDecisionDirty(false); setPlanDirty(false); setMeetingDirty(false); setAllocationDirty({}); setReset((value) => value + 1); setView(next);
  };
  const closeCompletion = () => { setCompletion(null); setFocusRequest({ target: 'document' }); };
  const completeSelectionRecord = () => {
    setCompletion(null); setFocusRequest({ target: 'plan' });
  };
  const selectionRecordReady = !planDirty && !!plan.criteria.trim() && !!plan.selectionRationale.trim();
  const continueToAllocation = () => {
    if (busy) return;
    if (!selectionRecordReady) { completeSelectionRecord(); return; }
    setCompletion(null); setView('allocation'); setFocusRequest({ target: 'plan' });
  };
  const saveDecision = async (payload) => {
    const result = await onDecision(payload);
    if (!result?.success || !result.data) return result;
    const fresh = result.data.applications;
    const currentIndex = fresh.findIndex((entry) => entry.code === payload.code);
    const ordered = [...fresh.slice(currentIndex + 1), ...fresh.slice(0, currentIndex + 1)];
    const next = ordered.find((entry) => !entry.recused && !entry.decision);
    setDecisionDirty(false);
    if (next) {
      setSelected(next.code); setProgressNotice(`Decision saved. ${next.code} is ready for the next committee decision.`);
      setFocusRequest({ target: 'document' });
    } else {
      setProgressNotice(''); setCompletion({ remaining: result.data.undecided });
    }
    return result;
  };
  const finalizeAwards = async (payload) => {
    const result = await onFinalize(payload);
    if (result?.success && result.data?.finalized) {
      setView('finalized'); setFocusRequest({ target: 'results' });
    }
    return result;
  };

  if (!data.finalized && (awaitingOpening || data.applicationsOpen || data.phase === 'applications' || data.phase === 'redaction' || data.pending > 0)) return <section className="wk-committee-workspace" aria-label="Committee review workspace">
    <div className="wk-committee-empty"><p className="wk-committee-eyebrow">{session.cycle} · Committee review</p>
      <h2>{awaitingOpening ? 'Applications have not opened yet' : data.applicationsOpen || data.phase === 'applications' ? 'Applications are still open' : 'Redaction review is in progress'}</h2><p>{data.total ?? data.count ?? 0} applications received</p>
      <p>{awaitingOpening ? 'The application window is scheduled to open later. After it closes, the redaction reviewer will prepare the anonymous copies.' : data.applicationsOpen || data.phase === 'applications' ? 'After the window closes, the redaction reviewer will prepare the anonymous copies.' : 'The reviewer is checking every application for identifying information.'} Committee review opens once every application has been processed.</p><p>Use Refresh to check again.</p>
    </div>
  </section>;

  return <section className="wk-committee-workspace" aria-label="Committee review workspace" ref={workspaceRef}>
    <header className="wk-committee-header"><div><p className="wk-committee-eyebrow">{session.cycle} · {data.finalized ? 'Completed review' : 'Committee review'}</p><h2>{data.finalized ? 'Final award decisions' : 'Anonymous applications'}</h2><p>{available} applications available to you · {data.undecided} awaiting a committee decision</p></div>
      {!data.finalized && <div className="wk-committee-header-actions" style={reviewActions}><Button size="sm" disabled={busy || dirty || !available} onClick={() => onExport('packet')}>Download printable packet</Button><Button size="sm" variant="outline" disabled={busy || dirty || !available} onClick={() => onExport('csv')}>Download CSV</Button></div>}
      {dirty && <p className="wk-committee-download-guidance">Save your changes before downloading committee copies or finalizing.</p>}
    </header>
    {data.finalized && <React.Fragment>
      <FinalAwardDownloads busy={busy} contactsIncluded={results?.contactsIncluded} onReport={onReport} onArchive={onArchive} />
      <details className="wk-supporting-downloads"><summary>Application copies (for reference)</summary>
        <p>These are the anonymous applications used during review. The final award packet above contains the award results and decision record.</p>
        <div style={reviewActions}><Button size="sm" variant="outline" disabled={busy || dirty || !available} onClick={() => onExport('packet')}>Download application packet</Button><Button size="sm" variant="outline" disabled={busy || dirty || !available} onClick={() => onExport('csv')}>Download application CSV</Button></div>
      </details>
    </React.Fragment>}
    {!data.finalized && <div className="wk-committee-guidance"><p>Evaluate applications by code. Membership is not required. Consider volunteer service with schools, nonprofits, community groups, other organizations, and the chapter on the same basis.</p><details><summary>Using downloaded copies</summary><p>The packet opens in your browser and can be printed or saved as PDF. Each download checks for the latest approved versions. Keep copies private and discard earlier versions after corrections.</p></details></div>}
    <nav className="wk-committee-steps" aria-label="Committee review steps">
      <button type="button" aria-current={view === 'selection' ? 'step' : undefined} disabled={busy} onClick={() => changeView('selection')}>1. Select recipients<span>{data.undecided ? `${data.undecided} decisions remaining` : 'Decisions recorded'}</span></button>
      <button type="button" aria-current={view === 'allocation' ? 'step' : undefined} disabled={busy || !allocationAvailable} onClick={() => changeView('allocation')}>2. Allocate awards<span>{allocationAvailable ? `${unallocated} amounts remaining` : 'After every selection decision'}</span></button>
      {data.finalized && <button type="button" aria-current={view === 'finalized' ? 'step' : undefined} disabled={busy} onClick={() => changeView('finalized')}>Final selection record<span>Decisions locked · Names available</span></button>}
    </nav>

    {view === 'finalized' && data.finalized && <CommitteeFinalResults session={session} data={data} results={results} busy={busy} onResults={onResults} onReport={onReport} />}
    {view === 'selection' && <React.Fragment>
      <CommitteeRoundPlan key={`selection-${plan.version}-${reset}`} plan={plan} mode="selection" chair={session.chair} finalized={data.finalized} busy={busy} onPlan={onPlan} onDirtyChange={setPlanDirty} />
      <CommitteeMeetingRecord key={`meeting-${meeting.version}-${reset}`} meeting={meeting} roster={data.meetingRoster || []} session={session} finalized={data.finalized} busy={busy} onMeeting={onMeeting} onDirtyChange={setMeetingDirty} />
      {progressNotice && <p className="wk-committee-progress-notice" role="status">{progressNotice}</p>}
      {!data.applications.length ? <div className="wk-committee-empty"><h3>Waiting for approved applications</h3><p>No approved applications are available yet. Use Refresh to check again.</p></div> : <div className="wk-committee-layout">
        <aside className="wk-committee-sidebar" aria-label="Application list"><div className="wk-committee-sidebar-title"><h3>Applications</h3><span>{data.applications.length}</span></div>
          <label className="wk-committee-mobile-picker">Choose an application<select aria-label="Choose an application" value={application?.code || ''} disabled={busy} onChange={(event) => selectApplication(data.applications.find((entry) => entry.code === event.target.value))}>{data.applications.map((entry, index) => <option key={entry.code} value={entry.code}>{index + 1}. {entry.code} · {entry.recused ? 'Recused' : tools.outcome(entry.decision)}</option>)}</select></label>
          <nav aria-label="Anonymous applications">{data.applications.map((entry, index) => <button key={entry.code} type="button" className={`wk-committee-list-item${application?.code === entry.code ? ' is-current' : ''}`} aria-current={application?.code === entry.code ? 'true' : undefined} aria-label={`Open application ${entry.code}`} disabled={busy} onClick={() => selectApplication(entry)}><span className="wk-committee-list-number">{String(index + 1).padStart(2, '0')}</span><span><strong>{entry.code}</strong><span className={`wk-committee-list-status${entry.recused ? ' is-recused' : ''}`}>{entry.recused ? 'Recused · hidden' : entry.eligibility === 'ineligible' ? 'Ineligible · Not selected' : tools.outcome(entry.decision)}{entry.amountCents ? ` · ${tools.amount(entry.amountCents)}` : ''}</span></span></button>)}</nav>
        </aside>
        <div className="wk-committee-reading-pane"><div className="wk-committee-document-toolbar"><p>Application {selectedIndex + 1} of {data.applications.length}{decisionDirty && <span> · Unsaved decision</span>}</p><div style={reviewActions}><Button size="sm" variant="outline" aria-label="Previous application" disabled={busy || selectedIndex === 0} onClick={() => selectApplication(data.applications[selectedIndex - 1])}>Previous</Button><Button size="sm" variant="outline" aria-label="Next application" disabled={busy || selectedIndex >= data.applications.length - 1} onClick={() => selectApplication(data.applications[selectedIndex + 1])}>Next</Button></div>
          {application && !application.recused && <a className="wk-committee-mobile-jump" href="#committee-review-tools" onClick={(event) => { event.preventDefault(); const inspector = document.getElementById('committee-review-tools'); inspector?.scrollIntoView({ block: 'start' }); inspector?.focus({ preventScroll: true }); }}>Jump to decision and review tools</a>}
        </div>{application && <CommitteeApplication key={`${application.code}-${application.version}-${application.eligibility}-${application.decision}-${application.reason}-${application.recused}-${reset}`} application={application} cohort={data.cohort} chair={session.chair} finalized={data.finalized} busy={busy} onDecision={saveDecision} onRecuse={onRecuse} onDirtyChange={setDecisionDirty} />}</div>
      </div>}
      {allocationAvailable && !data.finalized && <div className="wk-committee-next-step"><h3>All decisions saved</h3><p>Every application has a recorded decision. {selectionRecordReady ? 'Next, divide the available funds among the selected recipients. Names remain hidden.' : 'Save the selection criteria and shared rationale before continuing to award amounts.'}</p><Button disabled={busy || decisionDirty} onClick={continueToAllocation}>{selectionRecordReady ? 'Continue to award allocation' : 'Complete selection record'}</Button></div>}
    </React.Fragment>}

    {view === 'allocation' && <React.Fragment>
      <CommitteeRoundPlan key={`allocation-${plan.version}-${reset}`} plan={plan} mode="allocation" chair={session.chair} finalized={data.finalized} busy={busy} onPlan={onPlan} onDirtyChange={setPlanDirty} />
      <section className="wk-committee-allocation" aria-label="Award allocation"><div className="wk-committee-budget-summary"><div><span>Available budget</span><strong>{currency(plan.budgetCents)}</strong></div><div><span>Saved awards</span><strong>{currency(allocatedCents)}</strong></div><div className={remaining < 0 ? 'is-over-budget' : ''}><span>{remaining < 0 ? 'Over budget' : 'Remaining'}</span><strong>{currency(Math.abs(remaining))}</strong></div></div>
        <p className="wk-committee-detail">{recipients.length} selected recipients. Enrollment year is shown to help the committee apply its agreed allocation basis. Amounts are entered individually; no year-based formula is imposed.</p>
        {recipients.map((entry) => <CommitteeAllocationRow key={`${entry.code}-${entry.version}-${entry.amountCents}-${reset}`} application={entry} cohort={data.cohort} chair={session.chair && !data.finalized} busy={busy} onAllocation={onAllocation} onDirtyChange={reportAllocationDirty} />)}
        {!recipients.length && <p>No recipients were selected. Document the reason in the shared selection rationale before finalizing a round with no awards.</p>}
      </section>
      {session.chair && !data.finalized && <section className="wk-committee-finalize" aria-label="Finalize awards"><div><p className="wk-committee-eyebrow">After both anonymous steps</p><h2>Finalize awards</h2><p>Finalization locks the criteria, decision reasons, and amounts. It then reveals names and outcomes to the assigned committee, with contact details available to the chair and an optional coordinator.</p>
        {!plan.criteria.trim() || !plan.selectionRationale.trim() ? <p>Complete the selection criteria and shared rationale in step 1.</p> : null}
        {!meetingReady && <p>Complete the meeting date, actual participants, and current chair’s confirmation in step 1.</p>}
        {!plan.allocationRationale.trim() && <p>Record how award amounts were determined.</p>}{!!unallocated && <p>{unallocated} selected recipients still need an award amount.</p>}{remaining < 0 && <p>Saved awards exceed the budget by {currency(-remaining)}.</p>}
        {hasRecusal && <p>You have a recusal. Arrange for another eligible committee member to act as chair before finalizing.</p>}{dirty && <p>Save all changes before finalizing.</p>}
      </div><div><ReviewCheck checked={confirm} disabled={busy || dirty || !canFinalize} onChange={setConfirm}>The committee has completed its anonymous evaluation, resolved ties, and agreed to these final outcomes and amounts.</ReviewCheck><Button disabled={busy || dirty || !canFinalize || !confirm} onClick={() => finalizeAwards({ cohort: data.cohort, confirm: true })}>Finalize awards and reveal results</Button></div></section>}
    </React.Fragment>}
    {completion && <ReviewCompletionDialog title={completion.remaining ? 'Your available decisions are saved' : 'All decisions saved'} onClose={closeCompletion} actions={completion.remaining
      ? <Button onClick={closeCompletion}>Review saved decisions</Button>
      : <Button onClick={continueToAllocation}>{selectionRecordReady ? 'Continue to award allocation' : 'Complete selection record'}</Button>}>
      {completion.remaining ? <p>Your remaining applications have recorded decisions. An eligible chair still needs to record {completion.remaining === 1 ? 'a decision' : `${completion.remaining} decisions`} for applications hidden from you because of a recusal.</p>
        : <React.Fragment><p>Every application now has a saved committee decision. The next step is to allocate the available budget among the selected recipients while names remain hidden.</p>
          {!selectionRecordReady && <p>{planDirty ? 'Your shared selection record has unsaved changes. ' : ''}Complete and save the selection criteria and shared rationale first. Your draft will stay in place when you return to it.</p>}
        </React.Fragment>}
    </ReviewCompletionDialog>}
  </section>;
}
