/* global ReviewAnswers, ReviewCheck, ReviewCompletionDialog */
/* Reviewers choose ranges only. The server independently renders those ranges
   against immutable original answers; this screen never submits answer text. */
function RedactionTextFinder({ questions, base, masks, busy, onRedact, onClose }) {
  const [query, setQuery] = React.useState('');
  const [matchCase, setMatchCase] = React.useState(true);
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current?.focus(); }, []);
  const matches = [];
  if (query.trim()) {
    const pattern = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), matchCase ? 'g' : 'gi');
    for (const question of questions.filter((item) => !item.private)) {
      for (const part of window.WKYHBRedaction.segments(base[question.key], masks[question.key]) || []) {
        if (part.redacted) continue;
        for (const match of part.text.matchAll(pattern)) {
          if (matches.length === 100) break;
          const offset = match.index;
          matches.push({ key: question.key, label: question.label, start: part.start + offset, end: part.start + offset + match[0].length,
            text: match[0], before: part.text.slice(Math.max(0, offset - 45), offset), after: part.text.slice(offset + match[0].length, offset + match[0].length + 45) });
        }
      }
    }
  }
  return <section className="wk-find-redaction" aria-label="Find text to redact">
    <div className="wk-find-heading"><h3>Find and redact</h3><button className="wk-tool-button" onClick={onClose}>Close finder</button></div>
    <p>Find a word or phrase in the answers, then choose which occurrences to redact. Spacing must match; you can choose whether capitalization matters.</p>
    <label>Text to find<input ref={inputRef} value={query} maxLength={500} autoComplete="off" spellCheck={false} onChange={(event) => setQuery(event.target.value)} /></label>
    <label className="wk-find-case"><input type="checkbox" checked={matchCase} onChange={(event) => setMatchCase(event.target.checked)} /> Match capitalization</label>
    {query.trim() && <React.Fragment>
      <p role="status">{matches.length === 100 ? 'Showing the first 100 matches. Use a longer phrase to narrow the results.' : `${matches.length} ${matches.length === 1 ? 'match' : 'matches'} in unredacted answers.`}</p>
      {matches.length > 1 && <button className="wk-tool-button is-primary" disabled={busy} onClick={() => onRedact(matches)}>Redact all {matches.length} matches</button>}
      <ol className="wk-find-results">{matches.map((match) => <li key={`${match.key}-${match.start}`}>
        <h4>{match.label}</h4><p>{match.before}<mark>{match.text}</mark>{match.after}</p>
        <button className="wk-tool-button" disabled={busy} onClick={() => onRedact([match])}>Redact this occurrence</button>
      </li>)}</ol>
    </React.Fragment>}
  </section>;
}

function RedactionEditor({ application, finalized, onSave, onHistory, busy, onDirtyChange }) {
  const questions = application.questions || window.WKYHBScholarship.reviewQuestions(application.year, application.schemaVersion || 3);
  const tools = window.WKYHBRedaction;
  const base = application.baseAnswers;
  const emptyAnswer = (key) => (key === 'studentCountyExplanation' && base.studentCounty !== 'Other / not listed')
    || (key === 'acceptedOther' && base.accepted !== 'Other (explain)') ? 'Not applicable' : 'Not provided';
  const initial = tools.normalize(base, application.redactions || {});
  const [masks, setMasks] = React.useState(initial);
  const answers = tools.render(base, masks);
  const [undoStack, setUndoStack] = React.useState([]);
  const [selection, setSelection] = React.useState(null);
  const [finding, setFinding] = React.useState(false);
  const [restoring, setRestoring] = React.useState({});
  const [compare, setCompare] = React.useState(false);
  const [mobileCopy, setMobileCopy] = React.useState('redacted');
  const [checks, setChecks] = React.useState({});
  const [preview, setPreview] = React.useState(false);
  const [notice, setNotice] = React.useState('');
  const [audit, setAudit] = React.useState(null);
  const [auditBusy, setAuditBusy] = React.useState(false);
  const [auditError, setAuditError] = React.useState('');
  const documentRef = React.useRef(null);
  const previewRef = React.useRef(null);
  const positionRef = React.useRef(null);
  const dirty = JSON.stringify(masks) !== JSON.stringify(initial);
  React.useEffect(() => { onDirtyChange(dirty); return () => onDirtyChange(false); }, [dirty, onDirtyChange]);
  React.useLayoutEffect(() => {
    const position = positionRef.current;
    if (!position) return;
    const row = [...documentRef.current.querySelectorAll('[data-question-key]')].find((element) => element.dataset.questionKey === position.key);
    if (row) window.scrollBy(0, row.getBoundingClientRect().top - position.top);
    positionRef.current = null;
  }, [mobileCopy]);
  React.useEffect(() => {
    const capture = () => {
      const selected = window.getSelection();
      if (!selected || !selected.rangeCount || selected.isCollapsed) return;
      const range = selected.getRangeAt(0);
      const element = range.startContainer.nodeType === Node.ELEMENT_NODE ? range.startContainer : range.startContainer.parentElement;
      const field = element?.closest('[data-review-field]');
      if (!field || !documentRef.current?.contains(field) || !field.contains(range.endContainer)) { setSelection(null); return; }
      const prefix = range.cloneRange();
      prefix.selectNodeContents(field); prefix.setEnd(range.startContainer, range.startOffset);
      const start = prefix.toString().length;
      const text = range.toString();
      const rect = range.getBoundingClientRect();
      if (text.trim()) setFinding(false);
      setSelection(text.trim() ? { key: field.dataset.reviewField, start, end: start + text.length, text,
        left: Math.min(Math.max(12, rect.left), window.innerWidth - 200), top: Math.max(12, Math.min(rect.bottom + 8, window.innerHeight - 58)) } : null);
    };
    const reposition = () => { if (!window.getSelection()?.isCollapsed) capture(); };
    document.addEventListener('selectionchange', capture);
    window.addEventListener('scroll', reposition, { passive: true });
    return () => { document.removeEventListener('selectionchange', capture); window.removeEventListener('scroll', reposition); };
  }, []);
  const switchMobileCopy = (copy) => {
    if (copy === mobileCopy) return;
    const rows = [...documentRef.current.querySelectorAll('[data-question-key]')];
    const row = rows.find((element) => element.getBoundingClientRect().bottom > 150);
    if (row && row.getBoundingClientRect().top < window.innerHeight) positionRef.current = { key: row.dataset.questionKey, top: row.getBoundingClientRect().top };
    setSelection(null); window.getSelection()?.removeAllRanges(); setMobileCopy(copy);
  };
  const invalidate = () => { setChecks({}); setPreview(false); setSelection(null); };
  const redact = (ranges) => {
    if (finalized || busy || !ranges.length) return;
    const proposed = Object.fromEntries(Object.entries(masks).map(([key, values]) => [key, [...values]]));
    for (const range of ranges) {
      if (!questions.some((question) => question.key === range.key && !question.private)) return;
      proposed[range.key].push({ start: range.start, end: range.end });
    }
    const next = tools.normalize(base, proposed);
    if (!next) { setNotice('That passage cannot be redacted. Select it again.'); return; }
    if (JSON.stringify(next) === JSON.stringify(masks)) { setNotice('That passage is already redacted.'); return; }
    setUndoStack((previous) => [...previous.slice(-29), masks]);
    setMasks(next); invalidate(); setFinding(false); window.getSelection()?.removeAllRanges();
    if (finding) setTimeout(() => {
      const field = documentRef.current?.querySelector(`[data-review-field="${ranges[0].key}"]`);
      field?.scrollIntoView({ block: 'center' }); field?.focus({ preventScroll: true });
    }, 0);
    setNotice('Redaction added. Save your draft to record it, or finish redaction when you have reviewed every answer.');
  };
  const redactSelection = () => {
    if (!selection || answers[selection.key].slice(selection.start, selection.end) !== selection.text) {
      setSelection(null); setNotice('Select the passage again before redacting.'); return;
    }
    const range = tools.selectionRange(base[selection.key], masks[selection.key], selection.start, selection.end);
    if (range) redact([{ key: selection.key, ...range }]);
  };
  const undo = () => {
    if (!undoStack.length) return;
    setMasks(undoStack[undoStack.length - 1]); setUndoStack(undoStack.slice(0, -1)); invalidate();
    setNotice('Last unsaved redaction change undone.');
  };
  const restore = (key, range) => {
    if (finalized || busy || !questions.some((question) => question.key === key && !question.private)) return;
    const next = tools.normalize(base, { ...masks, [key]: masks[key].filter((mask) => mask.start !== range.start || mask.end !== range.end) });
    if (!next || JSON.stringify(next) === JSON.stringify(masks)) return;
    setUndoStack((previous) => [...previous.slice(-29), masks]);
    setMasks(next); invalidate(); window.getSelection()?.removeAllRanges();
    setTimeout(() => documentRef.current?.querySelector(`[data-review-field="${key}"]`)?.focus({ preventScroll: true }), 0);
    setNotice('Original text restored. Save your draft to record this correction, then review the complete copy again before release.');
  };
  const showPreview = () => {
    setFinding(false); setSelection(null); setChecks({}); setPreview(true);
    window.getSelection()?.removeAllRanges();
    setTimeout(() => { previewRef.current?.focus(); previewRef.current?.scrollIntoView({ block: 'start' }); }, 0);
  };
  const save = (approve) => onSave({ id: application.id, version: application.version, sourceHash: application.sourceHash,
    redactions: masks, approve, ...checks });
  const loadHistory = async (before) => {
    setAuditBusy(true); setAuditError('');
    try {
      const result = await onHistory(application.code, before);
      setAudit((current) => ({ entries: before ? [...current.entries, ...result.entries] : result.entries, nextCursor: result.nextCursor }));
    } catch (error) { setAuditError(error.message); }
    finally { setAuditBusy(false); }
  };
  if (!answers) return <p role="alert">This application needs a fresh redaction copy. Refresh the workspace before continuing.</p>;
  return <div className="wk-document-workbench">
    <header className="wk-document-heading">
      <div><span className="wk-workspace-kicker">Redaction desk</span><h2 tabIndex={-1}>{application.code}</h2></div>
      <span className={`wk-review-state ${dirty ? 'is-unsaved' : ''}`}>{dirty ? 'Unsaved redactions' : application.approved ? 'Released to committee' : 'Private draft'}</span>
    </header>
    <ol className="wk-redaction-steps" aria-label="Redaction progress">
      <li aria-current={!preview && (!application.approved || dirty) ? 'step' : undefined}><span>1</span> Redact</li>
      <li aria-current={preview ? 'step' : undefined}><span>2</span> Review</li>
      <li aria-current={application.approved && !dirty && !preview ? 'step' : undefined}><span>3</span> Release</li>
    </ol>
    <p className="wk-redaction-instruction">{finalized ? 'This scholarship round is finalized. You can compare the original with the released copy and read your redaction history.' : <React.Fragment><span className="wk-desktop-redaction-tip">Select identifying text in the redacted copy, then choose <strong>Redact selection</strong>.</span><span className="wk-mobile-redaction-tip">Long-press identifying text and adjust the selection handles, then tap <strong>Redact selection</strong> below.</span> You can also use <strong>Find text</strong>. Open <strong>Review redactions</strong> beneath an answer to correct a saved or suggested mask. The committee decides eligibility; your job is to remove identifying details.</React.Fragment>}</p>
    {application.sourceChanged && <p role="status">A new redaction baseline is required. Review this copy again before release.</p>}
    {application.supported === false && <p role="alert">This application contains questions this workspace cannot safely display. It cannot be released until the questionnaire is updated.</p>}
    <div className="wk-document-toolbar" aria-label="Document tools">
      <button className="wk-tool-button wk-compare-toggle" aria-pressed={compare} disabled={preview} onClick={() => setCompare(!compare)}>{compare ? 'Hide original' : 'Compare original'}</button>
      {!finalized && <button className="wk-tool-button" aria-expanded={finding} disabled={preview || busy} onClick={() => { setFinding(!finding); setSelection(null); }}>Find text</button>}
      <button className="wk-tool-button" disabled={auditBusy || !onHistory} onClick={() => audit ? setAudit(null) : loadHistory()}>{audit ? 'Hide redaction history' : 'Show redaction history'}</button>
    </div>
    {auditError && <p role="alert">{auditError}</p>}
    {audit && <section className="wk-redaction-history" aria-label="Redaction audit log">
      <h3>Redaction history</h3><p>Saved changes record the reviewer, time, version, and redacted ranges. The original application remains fixed.</p>
      {audit.entries.map((entry) => <details key={entry.id}><summary>{entry.action} · {entry.version ? `Version ${entry.version} · ` : ''}{new Date(entry.created_at).toLocaleString()}</summary><p>{entry.actor}</p>{entry.details && <pre>{JSON.stringify(entry.details, null, 2)}</pre>}</details>)}
      {!audit.entries.length && <p>No saved redactions yet.</p>}
      {audit.nextCursor && <button className="wk-tool-button" disabled={auditBusy} onClick={() => loadHistory(audit.nextCursor)}>Load earlier redactions</button>}
    </section>}
    {notice && <p role="status" className="wk-edit-notice">{notice}</p>}
    {finding && !preview && <RedactionTextFinder questions={questions} base={base} masks={masks} busy={busy} onRedact={redact} onClose={() => setFinding(false)} />}
    {!preview && <React.Fragment>
      <div className="wk-mobile-copy-switch" role="group" aria-label="Application copy">
        <button aria-pressed={mobileCopy === 'redacted'} onClick={() => switchMobileCopy('redacted')}>Redacted copy</button>
        <button aria-pressed={mobileCopy === 'original'} onClick={() => switchMobileCopy('original')}>Original</button>
      </div>
      <article className={`wk-document-sheet wk-redaction-document wk-aligned-document ${compare ? 'is-comparing' : ''} is-mobile-${mobileCopy}`} ref={documentRef} aria-label="Read-only application for redaction">
        <header className="wk-sheet-title"><span className="wk-workspace-kicker">Scholarship application</span><h3>{application.code}</h3><p>Review version {application.version} · {finalized ? 'Finalized' : 'Original wording is preserved in both copies'}</p></header>
        <div className="wk-copy-headings"><div className="wk-original-cell"><h3>Private original</h3><p>For your reference only</p></div><div className="wk-redacted-cell"><h3>Redacted copy</h3><p>Identifying details are replaced with a visible placeholder</p></div></div>
        {questions.map((question, index) => <React.Fragment key={question.key}>
          {question.section !== questions[index - 1]?.section && <h3 className="wk-question-section">{question.section}</h3>}
          <div className="wk-question-row" data-question-key={question.key}>
            <section className="wk-document-section wk-original-cell"><h4>{question.label}</h4>{question.description?.map((text, i) => <div key={i} className="wk-question-description">{text}</div>)}<p>{base[question.key] || <em>{emptyAnswer(question.key)}</em>}</p></section>
            <section className="wk-document-section wk-redacted-cell"><h4>{question.label}</h4>{question.description?.map((text, i) => <div key={i} className="wk-question-description">{text}</div>)}
              <p data-review-field={!question.private && !finalized ? question.key : undefined} tabIndex={!question.private && !finalized ? 0 : undefined} aria-label={question.label}>{answers[question.key]
                ? tools.segments(base[question.key], masks[question.key]).map((part, partIndex) => part.redacted ? <mark key={partIndex} className="wk-redaction-mark">{part.text}</mark> : part.text)
                : <em>{emptyAnswer(question.key)}</em>}</p>
              {question.private && !!base[question.key] && <span className="wk-automatic-redaction">Automatically redacted</span>}
              {!question.private && !finalized && masks[question.key]?.length > 0 && <details className="wk-restorable-redactions"
                onToggle={(event) => { const open = event.currentTarget.open; setRestoring((previous) => previous[question.key] === open ? previous : { ...previous, [question.key]: open }); }}>
                <summary>Review redactions ({masks[question.key].length})</summary>
                {restoring[question.key] && <React.Fragment><p>Private original passages are shown below. Restore text only when it is safe for the committee to read. Known names and contact details must be masked before release. Every saved correction remains in the audit history.</p>
                  <ol>{masks[question.key].map((range, maskIndex) => <li key={`${range.start}-${range.end}`}>
                    <p>{base[question.key].slice(range.start, range.end)}</p>
                    <button className="wk-tool-button" disabled={busy} aria-label={`Restore original text in ${question.label}, redaction ${maskIndex + 1}`} onClick={() => restore(question.key, range)}>Restore original text</button>
                  </li>)}</ol>
                </React.Fragment>}
              </details>}
            </section>
          </div>
        </React.Fragment>)}
      </article>
    </React.Fragment>}
    {preview && !finalized && <section className="wk-release-panel" aria-label="Review and release">
      <div className="wk-release-heading"><div><span className="wk-workspace-kicker">Step 2 · Review the complete copy</span><h2 tabIndex={-1} ref={previewRef}>Ready to release?</h2></div><button className="wk-tool-button" onClick={() => setPreview(false)}>Back to redaction</button></div>
      <p>Read the copy below exactly as the committee will receive it. Then confirm the checks and choose <strong>Release to committee</strong>. All applications must be released before committee review begins.</p>
      <div className="wk-committee-preview"><ReviewAnswers application={{ ...application, answers }} /></div>
      <div className="wk-release-checks">
        <h3>Step 3 · Release this application</h3>
        <ReviewCheck checked={checks.identifiersRemoved} onChange={(value) => setChecks({ ...checks, identifiersRemoved: value })}>I verified that names, contact details, schools, and other identifying information have been removed.</ReviewCheck>
        <ReviewCheck checked={checks.contextReviewed} onChange={(value) => setChecks({ ...checks, contextReviewed: value })}>I reviewed narrative context and combinations of details that could identify the applicant.</ReviewCheck>
        <ReviewCheck checked={checks.meaningPreserved} onChange={(value) => setChecks({ ...checks, meaningPreserved: value })}>I checked the complete preview and preserved the evidence needed for a fair evaluation.</ReviewCheck>
        <button className="wk-tool-button is-primary" disabled={busy || application.supported === false || !checks.identifiersRemoved || !checks.contextReviewed || !checks.meaningPreserved} onClick={() => save(true)}>{busy ? 'Releasing…' : 'Release to committee'}</button>
        <p className="wk-document-hint">Release records your approval in the audit log. Eligibility and scholarship decisions belong to the committee.</p>
      </div>
    </section>}
    {!finalized && !preview && !finding && <div className="wk-redaction-actions" aria-label="Redaction controls">
      {selection && <div className="wk-selection-action" style={{ '--selection-left': `${selection.left}px`, '--selection-top': `${selection.top}px` }}>
        <span>{selection.text.length} characters selected</span><button className="wk-tool-button is-primary" disabled={busy} onMouseDown={(event) => event.preventDefault()} onClick={redactSelection}>Redact selection</button><button className="wk-selection-dismiss" aria-label="Cancel selection" onClick={() => { setSelection(null); window.getSelection()?.removeAllRanges(); }}>×</button>
      </div>}
      <p>{dirty ? 'Your redactions have not been saved.' : application.approved ? 'This application has been released.' : 'Finished checking every answer? Continue to the final review.'}</p>
      <div className="wk-redaction-save-actions"><button className="wk-tool-button" disabled={busy || !undoStack.length} onClick={undo}>Undo redaction</button><button className="wk-tool-button" disabled={busy || (!dirty && !application.approved)} onClick={() => save(false)}>Save draft</button><button className="wk-tool-button is-primary" disabled={busy || application.supported === false} onClick={showPreview}>Finish redaction</button></div>
    </div>}
    {!finalized && <p className="wk-document-hint wk-save-explanation">Saving a draft keeps your work private and records a revision. Re-saving an application already released withdraws its release and invalidates earlier committee decisions.</p>}
  </div>;
}

function RedactorWorkspace({ data, onSave, onHistory, busy, onDirtyChange }) {
  const [selected, setSelected] = React.useState(data.applications.find((item) => !item.approved)?.code || data.applications[0]?.code || '');
  const [dirty, setDirty] = React.useState(false);
  const [completion, setCompletion] = React.useState(false);
  const [reviewingCompleted, setReviewingCompleted] = React.useState(false);
  const [progressNotice, setProgressNotice] = React.useState('');
  const [focusRequest, setFocusRequest] = React.useState(0);
  const deskRef = React.useRef(null);
  const application = data.applications.find((item) => item.code === selected) || data.applications[0];
  const released = data.applications.filter((item) => item.approved).length;
  const complete = data.applications.length > 0 && released === data.applications.length;
  const reportDirty = React.useCallback((value) => { setDirty(value); onDirtyChange?.(value); }, [onDirtyChange]);
  React.useEffect(() => {
    const leave = (event) => { if (dirty) { event.preventDefault(); event.returnValue = ''; } };
    window.addEventListener('beforeunload', leave); return () => window.removeEventListener('beforeunload', leave);
  }, [dirty]);
  React.useLayoutEffect(() => {
    if (!focusRequest || completion) return;
    const heading = deskRef.current?.querySelector(complete && !reviewingCompleted ? '.wk-redaction-complete h2' : '.wk-document-heading h2');
    heading?.focus({ preventScroll: true }); heading?.scrollIntoView({ block: 'start' });
  }, [focusRequest, completion, complete, reviewingCompleted]);
  const select = (code) => {
    if ((code === application?.code && (!complete || reviewingCompleted)) || busy) return;
    if (dirty && !window.confirm('Discard unsaved redactions and open another application? Save your draft first to keep them.')) return;
    setSelected(code); setReviewingCompleted(true); setFocusRequest((value) => value + 1);
  };
  const closeCompletion = () => { setCompletion(false); setFocusRequest((value) => value + 1); };
  const reviewCompleted = () => { setReviewingCompleted(true); closeCompletion(); };
  const saveApplication = async (payload) => {
    const result = await onSave(payload);
    if (!result?.success || !result.data || !payload.approve) return result;
    const fresh = result.data.applications;
    const currentIndex = fresh.findIndex((item) => item.id === payload.id);
    const ordered = [...fresh.slice(currentIndex + 1), ...fresh.slice(0, currentIndex + 1)];
    const next = ordered.find((item) => !item.approved);
    reportDirty(false);
    if (next) {
      setSelected(next.code); setReviewingCompleted(false);
      setProgressNotice(`Application released. ${next.code} is ready for your review.`);
      setFocusRequest((value) => value + 1);
    } else if (fresh.length) {
      setReviewingCompleted(false); setProgressNotice(''); setCompletion(true);
    }
    return result;
  };
  const index = data.applications.indexOf(application);
  return <div className="wk-review-desk" ref={deskRef}>
    <aside className="wk-application-sidebar" aria-label="Redaction review queue">
      <div className="wk-queue-heading"><span className="wk-workspace-kicker">Applications</span><h2>Redaction review queue</h2><p>{released} of {data.applications.length} released</p></div>
      <label className="wk-mobile-application-picker">Choose an application<select value={application?.code || ''} disabled={busy || !application} onChange={(event) => select(event.target.value)}>{data.applications.map((item) => <option key={item.code} value={item.code}>{item.name} · {item.approved ? 'Released' : 'Needs review'}</option>)}</select></label>
      <nav aria-label="Choose an application">{data.applications.map((item) => <button key={item.code} className="wk-application-item" aria-label={`Open application ${item.name}`} aria-current={item.code === application?.code ? 'true' : undefined} disabled={busy} onClick={() => select(item.code)}>
        <strong>{item.name}</strong><span>{item.code}</span><span className={`wk-queue-status ${item.approved ? 'is-complete' : ''}`}>{item.approved ? 'Released' : 'Needs review'}</span>
      </button>)}</nav>
      {!data.applications.length && <p>No applications received yet.</p>}
      <p className="wk-queue-help">Remove identifying details, review the anonymous copy, then release each application. Committee review starts when every application is released.</p>
    </aside>
    <div className="wk-desk-main">
      {progressNotice && <p role="status" className="wk-edit-notice">{progressNotice}</p>}
      {complete && !reviewingCompleted ? <section className="wk-redaction-complete" aria-label="Completed redaction review">
        <span className="wk-workspace-kicker">All {released} applications released</span><h2 tabIndex={-1}>Redaction complete</h2>
        <p>{data.finalized ? 'Every application was reviewed and released, and the committee has finalized the awards.' : 'Every application has been reviewed and released. The committee can now evaluate the anonymous copies and select recipients.'}</p>
        <p>Your approvals and redaction revisions are saved in the audit history. You can close the workspace or return to a released application for reference.</p>
        <button className="wk-tool-button" onClick={reviewCompleted}>Open released applications</button>
      </section> : application ? <React.Fragment>
        {released === data.applications.length && <p role="status" className="wk-edit-notice">All applications are released. The committee can now begin its review.</p>}
        <RedactionEditor key={`${application.code}-${application.version}-${application.sourceHash}`} application={application} finalized={data.finalized} onSave={saveApplication} onHistory={onHistory} busy={busy} onDirtyChange={reportDirty} />
        <div className="wk-document-navigation"><button className="wk-tool-button" disabled={busy || index <= 0} onClick={() => select(data.applications[index - 1].code)}>Previous application</button><span>{index + 1} of {data.applications.length}</span><button className="wk-tool-button" disabled={busy || index >= data.applications.length - 1} onClick={() => select(data.applications[index + 1].code)}>Next application</button></div>
      </React.Fragment> : <div className="wk-document-empty"><h2>Your review desk is ready</h2><p>Applications will appear after intake closes.</p></div>}
    </div>
    {completion && <ReviewCompletionDialog title="All applications released" onClose={closeCompletion} actions={<button className="wk-tool-button is-primary" onClick={reviewCompleted}>Review completed applications</button>}>
      <p>Your redaction review is complete. Every application has been released, and the committee can now begin its anonymous review.</p>
      <p>Your work and approvals are saved in the audit history. No further redaction action is needed.</p>
    </ReviewCompletionDialog>}
  </div>;
}
