// Designing the workflow (0095) — the Administrator's screen.
//
// MIKE, 2026-08-22: "enable the Admin to have a user friendly UI for designing
// custom workflow for their own company."
//
// WHY THIS IS NOT THE SETTINGS PANE. All four switches below are operational
// governance settings, so they ALREADY appear on the settings screen as a
// key/value list an Administrator can edit. That list is correct and it is not
// a design surface: `waiver_needs_second_name = false` tells somebody what the
// row holds and nothing about what turning it on would do to their company.
//
// WHAT A DESIGN SURFACE HAS TO SHOW, and this is the whole brief:
//
//   1. THE ROUTING AS A GRID, not as a list of rules. The interesting cells are
//      the EMPTY ones — a category and severity nobody has referred anywhere —
//      and a list of the rules that exist cannot show an absence. This is the
//      same reason 0092's load report LEFT JOINs from cw.discipline.
//   2. EACH SWITCH IN THE WORDS OF ITS CONSEQUENCE, not its key. "A waiver
//      needs a second lawyer's name" is the decision; `waiver_needs_second_name`
//      is the row it is stored in.
//   3. WHAT IS ALREADY TRUE UNDERNEATH IT. A required referral with nobody
//      seated in that discipline is a workflow that will stop every ticket and
//      wait for a person who does not exist. The screen says so, because the
//      Administrator designing it is the one person who can fix it.
//
// AFFORDANCES, NOT PERMISSIONS. Every control here is offered to whoever holds
// the tab, and cw.consultation_rule's policy and cw.governance_setting's write
// policy refuse anybody else in their own words. The screen teaches the
// boundary; the database enforces it.

const { useState: useWfState, useMemo: useWfMemo } = React;

function ContractTicketSource({ ticket }) {
  const source=usePane(()=>API.contractTicketSource(ticket.ticket_id),[ticket.ticket_id]);
  return <div className="mt-3">
    {source.status==='failed' && <p role="alert">{source.reason}</p>}
    {source.rows.map(s=><p key={s.source_id}>Customer original: <a href={`#/contract-onboarding/${s.source_id}`}>{s.filename}</a>
      {' · '}Source units {s.first_unit+1}–{s.last_unit+1} · Submitted for review by {s.linked_by}</p>)}
  </div>;
}

function HistoricalObligations({ me, sourceId = null }) {
  const data = usePane(() => API.historicalObligations());
  const [draft,setDraft] = useRetainedState(`historical-obligation-${sourceId || 'book'}`,{});
  const [evidence,setEvidence] = useWfState({});
  const [reply,setReply] = useWfState(null);
  const acts = useActs();
  const legal = ['legal_admin','legal_reviewer'].includes(me.role);
  const declare = () => acts.run('declare',async()=>{
    const r=await API.declareHistoricalObligation({...draft,source_id:sourceId});setReply(r);
    if(r.ok){setDraft({});data.reload();}
  });
  const complete = (row) => acts.run(`complete-${row.historical_obligation_id}`,async()=>{
    const r=await API.completeHistoricalObligation({historical_obligation_id:row.historical_obligation_id,evidence:evidence[row.historical_obligation_id] || ''});
    setReply(r);if(r.ok)data.reload();
  });
  const original = (row) => acts.run(`original-${row.source_id}`,async()=>{
    const r=await API.contractOriginal(row.source_id);setReply(r);
    if(r.ok){const url=URL.createObjectURL(r.blob);const a=document.createElement('a');a.href=url;a.download=r.filename;a.click();URL.revokeObjectURL(url);}
  });
  const rows = data.rows.filter(r=>!sourceId || String(r.source_id)===String(sourceId));
  return <section className="panel mt-4" aria-label="Declared historical obligations">
    <h3>Obligations declared from historical paper</h3>
    <p className="caption">Named people record these duties against the original. A blank due date means the date still needs confirmation. They are separate from obligations generated by approved clauses.</p>
    {reply && <p role={reply.ok?'status':'alert'}>{reply.ok?'Recorded.':reply.reason}</p>}
    {data.status==='loading' && <p role="status">Loading declared obligations…</p>}
    {data.status==='failed' && <p role="alert">{data.reason}</p>}
    {data.status==='loaded' && <p>{rows.length} declared · {rows.filter(r=>!r.completed_at).length} outstanding · {rows.filter(r=>r.overdue).length} overdue</p>}
    {rows.map(r=><div className="panel mt-3" key={r.historical_obligation_id}>
      <strong>{r.summary}</strong><p>{r.owner_person} · Due: {r.due_on || 'Not confirmed'} · {r.completed_at ? 'Completed' : r.overdue ? 'Overdue' : 'Outstanding'}</p>
      <p className="caption">Source {r.source_id} · {r.source_location} · Declared by {r.declared_by}</p>
      {me.role!=='requester' && <a href={`#/contract-onboarding/${r.source_id}`}>Open original record</a>}
      {me.role==='requester' && <ActButton className="btn" onClick={()=>original(r)} disabled={acts.busy}>Download original</ActButton>}
      {r.completed_at ? <p>Evidence: {r.evidence}</p> : (legal || (me.role==='requester' && me.person===r.owner_person)) && <>
        <label className="co-field">Completion evidence<input value={evidence[r.historical_obligation_id] || ''} onChange={e=>setEvidence({...evidence,[r.historical_obligation_id]:e.target.value})}/></label>
        <ActButton className="btn" onClick={()=>complete(r)} disabled={acts.busy || !evidence[r.historical_obligation_id]}>Record completion</ActButton>
      </>}
    </div>)}
    {sourceId && legal && <details className="mt-3"><summary>Declare an obligation from this original</summary>
      <div className="co-fields mt-3">{[['summary','Duty or entitlement','text'],['source_location','Page or clause reference','text'],['owner_person','Accountable person (existing account)','text'],['due_on','Due date (if confirmed)','date']].map(([key,label,type])=><label key={key} className="co-field">{label}<input type={type} value={draft[key] || ''} onChange={e=>setDraft({...draft,[key]:e.target.value})}/></label>)}</div>
      <ActButton className="btn" onClick={declare} disabled={acts.busy || !draft.summary || !draft.source_location || !draft.owner_person}>Confirm and record obligation</ActButton>
    </details>}
  </section>;
}

function ContractSourceDetail({ id, me, changed }) {
  const source = usePane(() => API.contractSource(id), [id]);
  const categories = usePane(() => API.categories());
  const people = usePane(() => API.people());
  const suppliers = usePane(() => API.suppliers());
  const [draft, setDraft] = useRetainedState(`contract-source-${id}`, {
    first_unit: '1', last_unit: '1', category_key: '', severity: 'Standard',
    document_kind: 'agreement', agreement_kind: 'standalone',
    signatories: [{ name:'', party:'ours', method:'wet_ink', signed_on:'' },{ name:'', party:'theirs', method:'wet_ink', signed_on:'' }],
  });
  const [confirmed, setConfirmed] = useWfState(false);
  const [reply, setReply] = useWfState(null);
  const [transcription, setTranscription] = useWfState(null);
  const feedback = React.useRef(null);
  const detail = React.useRef(null);
  React.useEffect(() => { if(source.status==='loaded') { detail.current?.focus({preventScroll:true}); detail.current?.scrollIntoView({block:'start'}); } }, [source.status]);
  React.useEffect(() => { if (reply) { feedback.current?.focus(); feedback.current?.scrollIntoView({block:'nearest'}); } }, [reply]);
  const acts = useActs();
  const legal = ['legal_admin', 'legal_reviewer'].includes(me.role);
  const item = source.rows[0];
  const set = (key, value) => { setDraft({ ...draft, [key]: value }); setConfirmed(false); setReply(null); };
  const field = (key, label, type = 'text', options = null) => <label className="co-field" key={key}>
    <span>{label}</span>{options
      ? <select value={draft[key] || ''} onChange={(e) => set(key, e.target.value)}>
          {options.map(([value, name]) => <option key={value} value={value}>{name}</option>)}
        </select>
      : <input type={type} value={draft[key] || ''} onChange={(e) => set(key, e.target.value)} />}
  </label>;
  const download = () => acts.run('original', async () => {
    const r = await API.contractOriginal(id); setReply(r);
    if (r.ok) {
      const url = URL.createObjectURL(r.blob); const a = document.createElement('a');
      a.href = url; a.download = r.filename; a.click(); URL.revokeObjectURL(url);
    }
  });
  const submit = () => acts.run('submit', async () => {
    if (!confirmed || !item) return;
    const body = { ...draft, first_unit: String(Number(draft.first_unit)-1), last_unit: String(Number(draft.last_unit)-1), source_id: id, sha256: item.sha256 };
    const r = item.purpose === 'template'
      ? await API.reviewContractSource(body) : await API.fileContractSource(body);
    setReply(r); setConfirmed(false);
    if (r.ok) { source.reload(); changed(); }
  });
  const addTranscription = () => acts.run('transcription', async () => {
    const r = await API.transcribeContractSource(item.purpose, id, transcription);
    setReply(r); if (r.ok) { setTranscription(null); source.reload(); changed(); }
  });
  if (source.status === 'loading') return <p role="status">Loading original…</p>;
  if (source.status === 'failed') return <p role="alert">{source.reason}</p>;
  if (!item) return <p>This source is unavailable.</p>;
  const links = source.body?.links || [];
  return <section ref={detail} tabIndex={-1} className="panel co-detail" aria-label={item.filename}>
    <PaneHead title={item.filename} kicker={item.purpose === 'template' ? 'Template source' : 'Signed original'}
      sub={`Uploaded by ${item.submitted_by} · ${item.byte_size} bytes`} />
    <p className="caption">{item.index_note}</p>
    <details><summary>Original fingerprint</summary><p className="mono co-hash">{item.sha256}</p></details>
    <ActButton className="btn" onClick={download} disabled={acts.busy || item.redacted}>Download original</ActButton>
    {reply && <div ref={feedback} tabIndex={-1} role={reply.ok ? 'status' : 'alert'} className="mt-3">
      {reply.ok ? (reply.blob ? 'Original downloaded.' : 'Recorded. The linked result is shown below.') : reply.reason}</div>}
    {item.transcription_of && <p>This is a supplied transcription of <a href={`#/contract-onboarding/${item.transcription_of}`}>original {item.transcription_of}</a>. Check it against that original.</p>}
    {(source.body?.transcriptions || []).map(t => <p key={t.source_id}>Readable transcription: <a href={`#/contract-onboarding/${t.source_id}`}>{t.filename}</a></p>)}
    {!item.transcription_of && !item.redacted && ['administrator','legal_admin','legal_reviewer'].includes(me.role) && <details className="mt-3">
      <summary>Attach a readable transcription</summary><p className="caption">The original stays unchanged. A transcription is separately attributed and searchable.</p>
      <label>Transcription file<input type="file" accept=".pdf,.docx,.txt" onChange={e=>setTranscription(e.target.files?.[0] || null)} /></label>
      <ActButton className="btn" onClick={addTranscription} disabled={!transcription || acts.busy}>Attach transcription</ActButton>
    </details>}
    {links.map((link) => <p key={link.link_id} className="caption">
      {link.ticket_id ? <>Review ticket {link.ticket_id} · {legal && <a href={`#/review-desk/${encodeURIComponent(link.ticket_id)}`}>Open Legal review</a>}</>
        : <>Filed against {link.agreement_id}</>}
    </p>)}
    {(source.body?.filings || []).map(f => <div key={f.agreement_id} className="panel mt-3">
      <h3>Historical contract · {f.agreement_id}</h3><p>{f.counterparty} · Accountable person: {f.requester}</p>
      <p>Executed {f.executed_on} · Effective {f.effective_on} · Term end: {f.term_end || 'Not recorded'}</p>
      <p>{f.agreement_kind}{f.parent_agreement_id ? ` under ${f.parent_agreement_id}` : ''} · {f.run_id ? `Assembly ${f.run_id}` : 'Imported historical paper; no Clausewerk assembly'}</p>
    </div>)}
    {source.body?.filings?.length > 0 && <HistoricalObligations me={me} sourceId={id} />}
    <div className="co-columns">
      <div><h3>Source text</h3>
        <p className="caption">Extracted text is a reading aid. Formatting, tables and scans may need comparison with the original.</p>
        <div className="co-source" tabIndex="0" aria-label="Extracted source text">
          {item.units.length ? item.units.map((unit, i) => <section key={i}>
            <h4>{i + 1} · {unit.location}</h4><p>{unit.text}</p>
          </section>) : <p>No text was indexed. Upload a readable text or Word transcription as a new source; keep the original reference with it.</p>}
        </div>
      </div>
      <div><h3>{item.purpose === 'template' ? 'Send wording to Legal' : 'Confirm the historical record'}</h3>
        {!legal ? <p>Legal completes this step. Uploading or indexing does not approve wording or attest a signature.</p>
          : item.redacted ? <p>This original has been redacted.</p>
          : item.purpose === 'signed' && item.transcription_of ? <p>File the linked signed original. This transcription is a reading aid.</p>
          : item.purpose === 'signed' && links.length ? <p>This document has been filed. Its original remains the evidence.</p>
          : <>
            <div className="co-fields">
            {item.purpose === 'template' ? <>
              {field('first_unit', 'First source unit', 'number')}
              {field('last_unit', 'Last source unit (inclusive)', 'number')}
              {field('category_key', 'Clause category', 'text', [['', 'Choose a category'], ...categories.rows.map(c => [c.key, c.label])])}
              {field('severity', 'Review severity', 'text', [['Standard','Standard'],['High','High']])}
            </> : <>
              {field('document_kind', 'This document is', 'text', [['agreement','An agreement'],['amendment','An amendment'],['exhibit','An exhibit'],['counterpart','A counterpart']])}
              {field('agreement_id', draft.document_kind === 'agreement' ? 'New agreement reference' : 'Existing agreement reference')}
              {field('signed_on', 'Date signed', 'date')}
              {draft.document_kind === 'amendment' && field('supersedes_seq', 'Document sequence amended', 'number')}
              {draft.document_kind === 'agreement' && <>
                {field('counterparty','Counterparty name')}{field('supplier_id','Supplier (optional)','text',[['','Not yet linked'],...suppliers.rows.map(s=>[String(s.supplier_id),s.name || s.canonical_name || s.supplier_id])])}
                {field('owner_person','Accountable person','text',[['','Choose a person'],...people.rows.filter(p=>p.state==='active').map(p=>[p.person,p.display_name || p.person])])}
                {field('executed_on','Execution date','date')}{field('effective_on','Effective date','date')}
                {field('term_end','Term end (if known)','date')}
                {field('agreement_kind','Agreement relationship','text',[['standalone','Standalone'],['master','Master agreement'],['sow','Statement of work']])}
                {draft.agreement_kind === 'sow' && field('parent_agreement_id','Existing master agreement reference')}
                <div className="co-signatories"><h4>Who signed this agreement</h4>
                  {(draft.signatories || []).map((s,i)=><div className="panel mt-2" key={i}>
                    {[['name','Name','text'],['signed_on','Date signed','date']].map(([key,label,type])=><label className="co-field" key={key}>{`Signatory ${i+1} · ${label}`}
                      <input type={type} value={s[key]} onChange={e=>set('signatories',draft.signatories.map((v,n)=>n===i?{...v,[key]:e.target.value}:v))}/></label>)}
                    <label className="co-field">{`Signatory ${i+1} · Party`}<select value={s.party} onChange={e=>set('signatories',draft.signatories.map((v,n)=>n===i?{...v,party:e.target.value}:v))}><option value="ours">Our side</option><option value="theirs">Their side</option></select></label>
                    <label className="co-field">{`Signatory ${i+1} · Method`}<select value={s.method} onChange={e=>set('signatories',draft.signatories.map((v,n)=>n===i?{...v,method:e.target.value}:v))}><option value="wet_ink">Wet ink</option><option value="electronic">Electronic</option></select></label>
                    {draft.signatories.length>2 && <button className="btn mt-2" onClick={()=>set('signatories',draft.signatories.filter((_,n)=>n!==i))}>Remove signatory {i+1}</button>}
                  </div>)}
                  <button className="btn mt-2" disabled={draft.signatories?.length>=100} onClick={()=>set('signatories',[...(draft.signatories || []),{name:'',party:'theirs',method:'wet_ink',signed_on:''}])}>Add signatory</button>
                </div>
              </>}
            </>}
            </div>
            <p className="caption mt-3">{item.purpose === 'template'
              ? 'This sends the exact selected source text to the existing review desk. It becomes reusable only after named Legal approval.'
              : 'Confirm these facts against the signed original. This files a historical contract with no Clausewerk assembly. No obligations are inferred from its prose.'}</p>
            <label className="co-confirm"><input type="checkbox" checked={confirmed} onChange={(e) => setConfirmed(e.target.checked)} />
              I checked this original and the details above.</label>
            {item.purpose === 'signed' && draft.document_kind === 'agreement' && me.role !== 'legal_admin' && <p>A Legal admin files a new historical agreement. Reviewers may attach an amendment, exhibit or counterpart to an existing agreement.</p>}
            <ActButton className="btn" onClick={submit} disabled={!confirmed || acts.busy || (item.purpose === 'template' && !item.units.length) || (item.purpose === 'signed' && draft.document_kind === 'agreement' && me.role !== 'legal_admin')}>
              {item.purpose === 'template' ? 'Send selected text for review' : 'File signed original'}
            </ActButton>
          </>}
      </div>
    </div>
  </section>;
}

function ContractOnboardingPane({ me }) {
  const [search, setSearch] = useWfState('');
  const [query, setQuery] = useWfState('');
  const sources = usePane(() => API.contractSources(query), [query]);
  const [selected, setSelected] = useAddressedRecord('contract-onboarding');
  const [purpose, setPurpose] = useWfState('template');
  const [files, setFiles] = useWfState([]);
  const [batch, setBatch] = useWfState([]);
  const acts = useActs();
  const canUpload = ['administrator','legal_admin','legal_reviewer'].includes(me.role);
  const receive = () => acts.run('batch', async () => {
    setBatch([]);
    for (const file of files) {
      const r = await API.uploadContractSource(purpose, file);
      setBatch(old => [...old, { name: file.name, ok: r.ok, reason: r.reason,
        duplicate: r.body?.duplicate, id: r.body?.rows?.[0]?.source_id }]);
    }
    sources.reload();
  });
  return <div className="co-workspace">
    <PaneHead title="Contract onboarding" kicker="Bring your existing paper" sub="Templates become review candidates. Signed originals become historical contract records." />
    <div className="panel mt-4">
      <h3>1 · Bring in the originals</h3>
      <p className="caption">PDF, Word (.docx) or UTF-8 text · 20 MB per file · up to 50 files per batch.
        Each file gets its own receipt. Duplicates return the original receipt.</p>
      {canUpload && <div className="co-upload">
        <label>Destination<select value={purpose} disabled={acts.busy} onChange={e => setPurpose(e.target.value)}>
          <option value="template">Templates and clauses</option><option value="signed">Signed agreements</option>
        </select></label>
        <label>Original files<input type="file" multiple accept=".pdf,.docx,.txt" disabled={acts.busy}
          onChange={e => { setFiles(Array.from(e.target.files || [])); setBatch([]); }} /></label>
        <ActButton className="btn" onClick={receive} disabled={acts.busy || !files.length || files.length > 50}>Upload {files.length || ''} files</ActButton>
      </div>}
      {files.length > 50 && <p role="alert">Choose at most 50 files for this batch.</p>}
      <div aria-live="polite">{batch.length > 0 && <p>{batch.length} of {files.length} processed · {batch.filter(r=>r.ok).length} received or already present · {batch.filter(r=>!r.ok).length} refused</p>}</div>
      {batch.map((r,i) => <p key={i}>{r.name} · {r.ok ? (r.duplicate ? 'Already present' : 'Received') : r.reason}
        {r.id && <button className="btn ml-2" onClick={() => setSelected(r.id)}>Inspect receipt</button>}</p>)}
    </div>
    <section className="panel mt-4">
      <h3>2 · Find and finish the imported work</h3>
      <form className="co-upload" onSubmit={e => {e.preventDefault(); setQuery(search);}}>
        <label>Search filenames and document text<input value={search} maxLength="500" onChange={e=>setSearch(e.target.value)} /></label>
        <button className="btn" type="submit">Search</button>
      </form>
      {sources.status === 'loading' && <p role="status">Loading sources…</p>}
      {sources.status === 'failed' && <p role="alert">{sources.reason}</p>}
      {sources.status === 'loaded' && <>
        <p className="caption">{sources.rows.length} sources shown (up to 500). Search narrows this collection.
          {' '}{sources.rows.filter(s=>s.indexed_units>0).length} with searchable text · {sources.rows.filter(s=>!s.redacted && !Number(s.links)).length} awaiting a destination.</p>
        {!sources.rows.length && <p>No sources found. Upload a batch to begin, or change the search.</p>}
        <div className="co-cards">{sources.rows.map(s => <button key={s.source_id} className="co-card" onClick={()=>setSelected(s.source_id)}>
          <strong>{s.filename}</strong><span>{s.purpose === 'template' ? 'Template' : 'Signed original'} · {s.redacted ? 'Redacted' : s.indexed_units ? `${s.indexed_units} source units indexed` : 'Needs readable text'}</span>
          <span>{Number(s.links) ? `${s.links} recorded destinations` : 'Awaiting Legal'}</span>
        </button>)}</div>
      </>}
    </section>
    {selected && <ContractSourceDetail key={String(selected)} id={selected} me={me} changed={sources.reload} />}
  </div>;
}

// The switches, in the order somebody designing a workflow meets them, and in
// the words of what they DO. Each `explain` is what changes when it is on —
// never a restatement of the key.
const WORKFLOW_SWITCHES = [
  {
    key: 'waiver_needs_second_name',
    title: 'A waiver needs a second lawyer',
    off: 'One lawyer can waive a required expert review, with a reason on the record.',
    on: 'A waiver releases nothing until a second lawyer countersigns it — and never '
      + 'the one who waived it. The ticket stays held until they do.',
    cost: 'Slower on exactly the deals already under deadline pressure. That is what '
        + 'the waiver exists to relieve, so turning this on tightens the one release valve.',
  },
  {
    key: 'requester_may_request_waiver',
    title: 'A requester may ask for a waiver',
    off: 'Only Legal can waive. A requester whose deal is held can see why, and has to '
       + 'pick up the phone.',
    on: 'The requester can ask, on the record, with their reason. It waives nothing and '
      + 'confers nothing — Legal still decides, and the ticket is still held.',
    cost: 'Commercial pressure arrives as a formal ask rather than a conversation. That '
        + 'is either exactly what you want recorded, or a second route that will drift '
        + 'from the override flow.',
  },
  {
    key: 'outstanding_suggestion_nudges',
    title: 'Unacted AI suggestions nudge Legal',
    off: 'A suggestion nobody acted on is recorded and counted, and chases nobody.',
    on: 'While a ticket is still open, an AI suggestion nobody has acted on appears on '
      + "Legal's waiting list. Never once the ticket is decided — at that point there "
      + 'is nothing left to act on.',
    cost: 'A busier list. A list with noise in it is a list people stop reading, which '
        + 'costs more than the nudge is worth if the suggestions are weak.',
  },
  // The two sourcing switches 0099 added (SRC-4). Until 2026-08-26 they were
  // editable only as raw key/value rows on the settings screen — PRODUCT.md
  // named the gap. The wording follows the migration's own rationale.
  {
    key: 'sourcing_terms_preview_required',
    title: 'An RFP or RFQ must carry the draft contract terms',
    off: 'The draft terms go out with a sourcing document only when the person '
       + 'building it attaches them.',
    on: 'Where an engagement already has an assembled contract, the terms go out '
      + 'with its RFP or RFQ — a build asking to leave them off is refused, and '
      + 'told why. An engagement with nothing assembled yet builds exactly as '
      + 'before, because there is nothing to attach.',
    cost: 'It bites only where a contract has been assembled, so a team in a hurry '
        + 'can still go to market before assembling one. The discipline this buys '
        + 'is only as strong as the habit of assembling first.',
  },
  {
    key: 'sourcing_preview_needs_open_gate',
    title: 'Draft terms may not ride a build that failed its checks',
    off: 'The terms may go out on an assembly whose validation gate is closed. '
       + 'The document and the record both say plainly that it was.',
    on: 'They may not — for a company that will not show a supplier language its '
      + 'own rules flagged. The build is refused until the findings are cleared '
      + 'or overridden.',
    cost: 'A blocked contract build now blocks the RFP too, on exactly the deal '
        + 'already waiting on Legal. Whether the gate was open is recorded on the '
        + 'document either way — that is a measured fact, not this setting.',
  },
];

function truthy(value) {
  return ['true', 'on', 'yes', '1'].includes(String(value).trim().toLowerCase());
}

// ── The screen ─────────────────────────────────────────────────────────────
function WorkflowDesignPane({ me }) {
  const rules       = usePane(() => API.panelRules());
  const categories  = usePane(() => API.categories());
  const disciplines = usePane(() => API.panelDisciplines());
  const seats       = usePane(() => API.panelSeats());
  const settings    = usePane(() => API.settings());
  const acts = useActs();
  const [refused, setRefused] = useWfState(null);

  // Which cell is being edited. Held here, not in the address: a half-made
  // routing decision is not a place to send a colleague.
  const [editing, setEditing] = useWfState(null);

  const seated = useWfMemo(() => {
    const live = (seats.rows || []).filter((s) => !s.closed_at);
    return new Set(live.map((s) => s.discipline_key));
  }, [seats.rows]);

  const byCell = useWfMemo(() => {
    const m = {};
    for (const r of rules.rows || []) {
      const k = `${r.category_key}|${r.severity}`;
      (m[k] = m[k] || []).push(r);
    }
    return m;
  }, [rules.rows]);

  if (rules.status === 'loading' || categories.status === 'loading')
    return <Loading />;
  if (rules.status === 'failed') return <LoadFailed reason={rules.reason} />;
  if (categories.status === 'failed') return <LoadFailed reason={categories.reason} />;

  const reloadAll = () => { rules.reload(); settings.reload(); };

  const setSwitch = (key, on) => acts.run(`switch-${key}`, async () => {
    const r = await API.setSetting({ key, value: on ? 'true' : 'false' });
    if (!r.ok) { setRefused(r.reason); return; }
    setRefused(null);
    settings.reload();
  });

  const addRule = (category, severity, discipline, necessity) =>
    acts.run(`add-${category}-${severity}-${discipline}`, async () => {
      const r = await API.addConsultationRule({
        category_key: category, severity, discipline_key: discipline, necessity });
      if (!r.ok) { setRefused(r.reason); return; }
      setRefused(null); setEditing(null); reloadAll();
    });

  const removeRule = (r) =>
    acts.run(`rm-${r.category_key}-${r.severity}-${r.discipline_key}`, async () => {
      const done = await API.removeConsultationRule({
        category_key: r.category_key, severity: r.severity,
        discipline_key: r.discipline_key });
      if (!done.ok) { setRefused(done.reason); return; }
      setRefused(null); reloadAll();
    });

  const settingValue = (key) => {
    const row = (settings.rows || []).find((s) => s.key === key);
    return row ? truthy(row.value) : false;
  };

  const severities = ['Standard', 'High'];

  return (
    <div>
      <PaneHead
        title="Designing the workflow"
        sub="Which risks need an expert before a contract term is approved, and how strict the process is. These are your company's choices — the system holds no opinion about them." />

      {refused && <Refused what="That was refused." reason={refused} />}

      {/* ── 1 · The routing, as a grid ───────────────────────────────────── */}
      <div className="section-label mt-4">Who gets asked, and when</div>
      <p className="caption">
        Every risk category, at each severity. A cell with nothing in it means a
        ticket of that kind goes straight to a lawyer with no expert consulted —
        which is a choice, not an oversight, and the grid shows it as one.
      </p>

      <div className="panel mt-2" style={{ overflowX: 'auto' }}>
        <table className="ledger">
          <thead>
            <tr>
              <th>risk category</th>
              {severities.map((s) => <th key={s}>{s.toLowerCase()}</th>)}
            </tr>
          </thead>
          <tbody>
            {(categories.rows || []).map((c) => (
              <tr key={c.key}>
                <td>{c.label}</td>
                {severities.map((sev) => {
                  const cell = byCell[`${c.key}|${sev}`] || [];
                  const key = `${c.key}|${sev}`;
                  return (
                    <td key={sev} style={{ verticalAlign: 'top' }}>
                      {cell.length === 0 && editing !== key && (
                        <span className="caption" style={{ color: 'var(--mute-2)' }}>
                          nobody
                        </span>
                      )}
                      {cell.map((r) => (
                        <div key={r.discipline_key} className="flex items-center gap-2 mb-1">
                          <Status
                            state={r.necessity === 'required' ? 'pending' : 'neutral'}
                            title={r.necessity === 'required'
                              ? 'The ticket cannot be approved until this discipline answers or a lawyer waives it.'
                              : 'Asked, and gating nothing.'}>
                            {r.necessity}
                          </Status>
                          <span>{r.discipline}</span>
                          {/* A REQUIRED REFERRAL WITH NOBODY SEATED will stop
                              every ticket of this kind and wait for a person who
                              does not exist. The Administrator designing this is
                              the one person who can fix it, so it is said here
                              rather than discovered later. */}
                          {r.necessity === 'required' && !seated.has(r.discipline_key) && (
                            <Status state="refused"
                              title="Nothing will be able to answer this, so every ticket of this kind will wait until a lawyer waives it. Seat somebody on the panel, or make this advisory.">
                              nobody seated
                            </Status>
                          )}
                          <button className="btn btn-sm"
                                  aria-label={`Stop referring ${c.label} ${sev} to ${r.discipline}`}
                                  disabled={acts.busy === `rm-${c.key}-${sev}-${r.discipline_key}`}
                                  onClick={() => removeRule(r)}>
                            remove
                          </button>
                        </div>
                      ))}
                      {editing === key
                        ? <AddReferral
                            disciplines={(disciplines.rows || []).filter(
                              (d) => !cell.find((r) => r.discipline_key === d.discipline_key))}
                            busy={acts.busy}
                            onCancel={() => setEditing(null)}
                            onAdd={(disc, nec) => addRule(c.key, sev, disc, nec)} />
                        : <button className="btn btn-sm mt-1"
                                  aria-label={`Refer ${c.label} ${sev} to a discipline`}
                                  onClick={() => { setEditing(key); setRefused(null); }}>
                            refer…
                          </button>}
                    </td>
                  );
                })}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* ── 2 · The switches, in the words of their consequence ──────────── */}
      <div className="section-label mt-6">How strict the process is</div>
      <p className="caption">
        Each of these is off unless you turn it on, and each says what changes
        when you do — and what it costs. Nothing here changes a decision anybody
        has already taken.
      </p>

      {WORKFLOW_SWITCHES.map((sw) => {
        const on = settingValue(sw.key);
        return (
          <div className="panel mt-2" key={sw.key} data-testid={`switch-${sw.key}`}>
            <div className="flex items-start justify-between gap-3">
              <div>
                <div style={{ fontSize: 15 }}>{sw.title}</div>
                <p className="caption mt-1">{on ? sw.on : sw.off}</p>
              </div>
              <button className="btn btn-sm"
                      aria-label={`${on ? 'Turn off' : 'Turn on'}: ${sw.title}`}
                      aria-pressed={on}
                      disabled={acts.busy === `switch-${sw.key}`}
                      onClick={() => setSwitch(sw.key, !on)}>
                {acts.busy === `switch-${sw.key}`
                  ? 'saving…'
                  : on ? 'turn off' : 'turn on'}
              </button>
            </div>
            {/* THE COST, SHOWN WHETHER OR NOT IT IS ON. A design surface that
                only warned you on the way in would be arguing for its default
                rather than describing a choice. */}
            <p className="caption mt-2" style={{ color: 'var(--mute-2)' }}>
              <strong>What it costs:</strong> {sw.cost}
            </p>
          </div>
        );
      })}

      <p className="caption mt-4">
        Everything here is recorded as a change to your company's settings, with
        your name on it. None of it can change a decision already taken on a
        ticket, and none of it decides anything inside the workflow — that stays
        with Legal.
      </p>
    </div>
  );
}

// One cell's editor. Kept out of the grid so the grid reads as a grid.
function AddReferral({ disciplines, busy, onAdd, onCancel }) {
  const [discipline, setDiscipline] = useWfState('');
  const [necessity, setNecessity] = useWfState('advisory');

  return (
    <div className="mt-1">
      <select className="block" aria-label="Discipline to refer this to"
              value={discipline} onChange={(e) => setDiscipline(e.target.value)}>
        <option value="">choose a discipline…</option>
        {disciplines.map((d) => (
          <option key={d.discipline_key} value={d.discipline_key}>{d.label}</option>
        ))}
      </select>
      <div className="flex gap-2 mt-1">
        {/* TWO WORDS, AND THE DIFFERENCE BETWEEN THEM IS THE WHOLE FEATURE. */}
        <button className={`btn btn-sm${necessity === 'advisory' ? ' active' : ''}`}
                aria-label="advisory" aria-pressed={necessity === 'advisory'}
                title="Asked, and gating nothing. The ticket moves whether or not anybody answers."
                onClick={() => setNecessity('advisory')}>advisory</button>
        <button className={`btn btn-sm${necessity === 'required' ? ' active' : ''}`}
                aria-label="required" aria-pressed={necessity === 'required'}
                title="The ticket cannot be approved until this discipline answers, or a lawyer waives it with a reason."
                onClick={() => setNecessity('required')}>required</button>
      </div>
      <p className="caption mt-1">
        {necessity === 'required'
          ? 'The ticket cannot be approved until this discipline answers — or a lawyer waives it, with a reason on the record.'
          : 'Asked, and gating nothing. The ticket moves whether or not anybody answers.'}
      </p>
      <div className="flex gap-2 mt-1">
        <button className="btn btn-sm" disabled={!discipline || busy}
                onClick={() => onAdd(discipline, necessity)}>refer</button>
        <button className="btn btn-sm" onClick={onCancel}>cancel</button>
      </div>
    </div>
  );
}

// S469 — one deterministic document-to-plan-to-approval doorway. The browser
// parses JSON only to give immediate file feedback; the service parses and
// validates it again and is the authority for every answer and operation.
function WorkflowOnboardingPane() {
  const schema = usePane(() => API.onboardingSchema());
  const plans = usePane(() => API.onboardingPlans());
  const [document, setDocument] = useWfState(null);
  const [documentText, setDocumentText] = useWfState('');
  const [filename, setFilename] = useWfState('');
  const [preview, setPreview] = useWfState(null);
  const [response, setResponse] = useWfState(null);
  const [confirmations, setConfirmations] = useWfState({});
  const acts = useActs();

  const choose = async (event) => {
    const file = event.target.files && event.target.files[0];
    setPreview(null); setResponse(null);
    if (!file) { setDocument(null); setDocumentText(''); setFilename(''); return; }
    setFilename(file.name);
    try {
      const text = await file.text();
      const parsed = JSON.parse(text);
      setDocumentText(text);
      setDocument(parsed);
    } catch (_) {
      setDocument(null);
      setDocumentText('');
      setResponse({ ok: false, reason: 'That file is not valid JSON.' });
    }
  };

  const validate = () => acts.run('validate', async () => {
    const result = await API.validateOnboarding(documentText);
    setPreview(result.ok ? result.body : null);
    setResponse(result);
  });
  const downloadTemplate = () => acts.run('template', async () => {
    const result = await API.onboardingTemplate();
    setResponse(result);
    if (!result.ok) return;
    const url = URL.createObjectURL(result.blob);
    const anchor = globalThis.document.createElement('a');
    anchor.href = url; anchor.download = result.filename; anchor.click();
    URL.revokeObjectURL(url);
  });
  const create = () => acts.run('plan', async () => {
    const result = await API.planOnboarding(documentText);
    setResponse(result);
    if (result.ok) { setPreview(null); plans.reload(); }
  });
  const approve = (plan) => acts.run(`approve-${plan.plan_id}`, async () => {
    const result = await API.approveOnboarding({
      plan_id: plan.plan_id,
      plan_fingerprint: confirmations[plan.plan_id] || '',
    });
    setResponse(result); if (result.ok) plans.reload();
  });
  const apply = (plan) => acts.run(`apply-${plan.plan_id}`, async () => {
    const result = await API.applyOnboarding({ plan_id: plan.plan_id });
    setResponse(result); if (result.ok) plans.reload();
  });
  const rollback = (plan) => acts.run(`rollback-${plan.plan_id}`, async () => {
    const result = await API.rollbackOnboarding({ plan_id: plan.plan_id });
    setResponse(result); if (result.ok) plans.reload();
  });

  if (schema.status === 'loading' || plans.status === 'loading') return <Loading />;
  if (schema.status === 'failed') return <LoadFailed reason={schema.reason} />;
  if (plans.status === 'failed') return <LoadFailed reason={plans.reason} />;

  return (
    <div>
      <PaneHead title="Workflow setup"
        sub="Load your completed workflow template, inspect every change, then approve and apply that exact plan." />

      {response && !response.ok &&
        <Refused what="Workflow setup was refused." reason={response.reason} />}
      {response && response.ok &&
        <div className="panel mt-3" role="status">The requested step completed.</div>}

      <div className="panel mt-4">
        <div className="section-label">1 · Load and validate</div>
        <p className="caption">
          Template version {schema.rows?.[0]?.schema_version || '1'}.
          People and roles are not part of this setup. An empty list means you deliberately want
          none; an omitted list is an unanswered question.
        </p>
        {schema.rows?.[0] && <details className="mt-2">
          <summary>available identifiers and current setting values</summary>
          <div className="caption mt-2">
            <p><strong>Settings:</strong> {(schema.rows[0].operational_settings || [])
              .map((row) => `${row.key} (${row.value})`).join(', ') || 'none'}</p>
            <p><strong>Categories:</strong> {(schema.rows[0].categories || [])
              .map((row) => row.key).join(', ') || 'none'}</p>
            <p><strong>Disciplines:</strong> {(schema.rows[0].disciplines || [])
              .map((row) => row.discipline_key).join(', ') || 'none'}</p>
            <p><strong>Existing people:</strong> {(schema.rows[0].eligible_people || [])
              .map((row) => row.person).join(', ') || 'none'}</p>
            <p><strong>Agreements:</strong> {(schema.rows[0].agreements || [])
              .map((row) => row.agreement_id).join(', ') || 'none'}</p>
          </div>
        </details>}
        <input className="mt-2" type="file" accept="application/json,.json"
               aria-label="Completed workflow template" onChange={choose} />
        {filename && <p className="caption mt-1">Loaded: {filename}</p>}
        <div className="flex gap-2 mt-2" style={{ flexWrap: 'wrap' }}>
          <button className="btn btn-sm" disabled={acts.busy === 'template'}
                  onClick={downloadTemplate}>download blank template</button>
          <button className="btn btn-sm" disabled={!document || acts.busy === 'validate'}
                  onClick={validate}>validate</button>
          <button className="btn btn-sm" disabled={!document || acts.busy === 'plan'}
                  onClick={create}>save exact plan</button>
        </div>
      </div>

      {preview && (
        <div className="panel mt-3">
          <div className="section-label">Validation result</div>
          <p>{preview.valid ? 'The document is complete and internally consistent.'
                            : `${preview.questions.length} question(s) need an answer.`}</p>
          {(preview.questions || []).map((q, i) =>
            <p className="caption mt-1" key={`${q.field}-${i}`}><strong>{q.field}:</strong> {q.reason}</p>)}
          <p className="caption mt-2">Source: {preview.source_fingerprint}</p>
          <p className="caption">Semantic choices: {preview.semantic_fingerprint || 'not available'}</p>
          <div className="mt-2">{(preview.operations || []).map((op, i) =>
            <div className="ledger-row" key={`${op.kind}-${op.target}-${i}`}>
              <Status state={op.action === 'unchanged' ? 'neutral' : 'pending'}>{op.action}</Status>
              <span className="ml-2">{op.consequence}</span>
            </div>)}</div>
        </div>
      )}

      <div className="section-label mt-6">2 · Saved plans</div>
      {(plans.rows || []).length === 0 && <Empty title="No workflow plans yet" />}
      {(plans.rows || []).map((plan) => (
        <div className="panel mt-3" key={plan.plan_id}>
          <div className="flex items-start justify-between gap-3" style={{ flexWrap: 'wrap' }}>
            <div>
              <div>Plan {plan.plan_id} · <Status state={plan.state === 'applied' ? 'effective' : 'pending'}>{plan.state}</Status></div>
              <p className="caption mt-1">{plan.operations.length} recorded item(s)</p>
              <p className="caption" style={{ overflowWrap: 'anywhere' }}>{plan.plan_fingerprint}</p>
              {plan.approval_expired && <p className="caption">Approval expired; make a fresh plan.</p>}
            </div>
            <div className="flex gap-2" style={{ flexWrap: 'wrap' }}>
              {plan.state === 'planned' && <>
                <input aria-label={`Confirm fingerprint for plan ${plan.plan_id}`}
                       placeholder="paste exact fingerprint"
                       value={confirmations[plan.plan_id] || ''}
                       onChange={(e) => setConfirmations({
                         ...confirmations, [plan.plan_id]: e.target.value })} />
                <button className="btn btn-sm"
                        disabled={acts.busy === `approve-${plan.plan_id}`}
                        onClick={() => approve(plan)}>approve exact plan</button>
              </>}
              {plan.state === 'approved' && !plan.approval_expired &&
                <button className="btn btn-sm" disabled={acts.busy === `apply-${plan.plan_id}`}
                        onClick={() => apply(plan)}>apply all changes</button>}
              {plan.state === 'applied' &&
                <button className="btn btn-sm" disabled={acts.busy === `rollback-${plan.plan_id}`}
                        onClick={() => rollback(plan)}>prepare rollback plan</button>}
            </div>
          </div>
          <div className="mt-2">{plan.operations.map((op, i) =>
            <p className="caption" key={`${op.target}-${i}`}>
              {op.action}: {op.consequence}
            </p>)}</div>
          {plan.completion && <p className="caption mt-2">
            Completed by {plan.applied_by}: {plan.completion.applied} applied,
            {' '}{plan.completion.unchanged} unchanged.
          </p>}
          {(plan.attempts || []).filter((a) => a.outcome === 'failed').map((a) =>
            <p className="caption mt-1" key={a.attempt_id}>Failed attempt: {a.result.reason}</p>)}
        </div>
      ))}
    </div>
  );
}
