/* =========================================================================
   Veto · Tasks — the gap list.

   Vanta-for-escrow: Home shows control posture, Tasks is the flat list of
   gaps to clear before a file can move. Mercury-simple on the surface —
   one row per gap, Incomplete / Completed. The Veto-specific proof (why it
   exists, what the evidence does and doesn't show) lives in the row's
   detail, never in the list.
   ========================================================================= */

const TASK_KIND = {
  hold:        { label: "Hold",            icon: "alert-triangle",  action: "Clear hold" },
  callback:    { label: "Callback",        icon: "phone",           action: "Log the call" },
  discrepancy: { label: "Discrepancy",     icon: "alert-circle",    action: "Request updated demand" },
  funds:       { label: "Funding review",  icon: "arrow-down-left", action: "Review funding" },
  evidence:    { label: "Evidence",        icon: "file-text",       action: "Accept & resolve" },
  change:      { label: "Material change", icon: "alert-circle",    action: "Create v2 record" },
  record:      { label: "Ready to record", icon: "check-circle",    action: "Sign to record" },
  receipt:     { label: "Receipt",         icon: "banknote",        action: "Send receipt" },
};

const SEED = [
  {
    id: "t-change-sp0214", kind: "change", file: "SP-0214", street: "17133 Santa Lucia St",
    line: "Seller Proceeds", title: "Payout destination linked to the acting officer", due: "May 22", dueMs: Date.parse("May 22, 2026"),
    whyLong: "The payout destination (Whitfield Coast Holdings LLC) names an entity whose managing member of record matches the acting officer on this file. Held pending an owner exception record. Veto records the association. It does not allege intent.",
    produces: "Owner exception record",
    sources: [
      { label: "California SOS entity record", proves: "Whitfield Coast Holdings LLC lists Madeline Lane as managing member of record", limit: "does not prove intent, authority to receive proceeds, or account control" },
      { label: "Seller Proceeds instruction", proves: "James Whitfield named Whitfield Coast Holdings LLC as payout destination", limit: "does not resolve the officer association or prove authority to redirect proceeds" },
      { label: "Acting officer on file", proves: "Madeline Lane is the acting officer on SP-0214", limit: "not a finding of wrongdoing — Veto records the association only" },
    ],
    detail: {
      amount: 182742,
      action: "Release seller proceeds",
      change: { label: "Payout destination", from: "James Whitfield (seller)", to: "Whitfield Coast Holdings LLC", detected: "May 21 · 3:18 PM" },
      staled: "Release held",
      policy: "Officer-linked payout destination requires owner exception record",
      policyVersion: "POL-SP-OFFICER-LINK-v1.2",
      releaseStatus: "Held",
      exception: "Owner exception required — routed to Review Register.",
    },
  },
  {
    id: "t-callback-0214", kind: "callback", file: "SP-0214", street: "17133 Santa Lucia St",
    line: "Payoff Demand", title: "Confirm beneficiary by callback", due: "May 22", dueMs: Date.parse("May 22, 2026"),
    whyLong: "The payoff demand can't be relied on until the beneficiary is reached on a number the office trusts. Until then, the demand stays unsupported.",
    produces: "Callback Record",
    sources: [{ label: "Office contact file", proves: "the number on file for the lienholder of record", limit: "not a guarantee the line reaches the party with authority over the lien" }],
    detail: { contact: "Beneficiary of record", role: "Payoff Demand · 1st lien", phone: "(949) 555-0162", source: "Office contact file", confirm: "Beneficiary identity and the current payoff figure for office review", rule: "Use the number on the office contact file — never the number printed on the demand." },
  },
  {
    id: "t-evidence-0214", kind: "evidence", file: "SP-0214", street: "17133 Santa Lucia St",
    line: "Buyer Funding", title: "Finish source-of-funds review", due: "May 22", dueMs: Date.parse("May 22, 2026"),
    whyLong: "The source-of-funds chain is missing its middle leg. Buyer Funding can't clear until the money is traceable, unbroken, from origin to escrow.",
    produces: "Buyer Funding Record",
    detail: { resolved: 9, total: 12, submittedBy: "Sebastian Heyneman Wells", missing: ["Wire: Heyneman Holdings → escrow (leg 2 of 3)"], note: "The source-of-funds chain is missing its middle leg. It must be unbroken from origin to escrow before the line can clear." },
  },
  {
    id: "t-record-0318", kind: "record", file: "BF-0318", street: "85 Linden Pl #4",
    line: "Buyer Funding", title: "Sign Buyer Funding to record", due: "May 22", dueMs: Date.parse("May 22, 2026"),
    whyLong: "Every checkpoint on Buyer Funding is recorded. Signing records the line and issues the receipt — nothing else is waiting on it.",
    produces: "Buyer Funding Record · receipt",
    detail: { lineLabel: "Buyer Funding", party: "Priya Mehta", amount: 469500, closing: "May 22, 2026", checks: [["Earnest money", "Recorded"], ["Closing funds", "Recorded"], ["Source of funds", "Recorded"]] },
  },
  {
    id: "t-discrepancy-0213", kind: "discrepancy", file: "SP-0213", street: "412 Marigold Ave",
    line: "Payoff Demand", title: "Resolve payoff short of closing", due: "May 23", dueMs: Date.parse("May 23, 2026"),
    whyLong: "The demand's good-through date falls a day before closing. The amount used in the settlement statement would be short by one per-diem — a current demand is required before release.",
    produces: "Payoff Change Impact Record v2",
    detail: { a: { label: "Demand good through", value: "May 22, 2026" }, b: { label: "Closing date", value: "May 23, 2026" }, perDiem: "$84.10 / day", note: "The demand lapses the day before closing. A payoff wired on closing day would be one per-diem short and could leave a balance on the lien." },
  },
  {
    id: "t-funds-0211", kind: "funds", file: "SP-0211", street: "2208 Westcliff Dr",
    line: "Buyer Funding", title: "Review closing funds status", due: "May 27", dueMs: Date.parse("May 27, 2026"),
    whyLong: "Buyer closing funds posted to the file. Before the office can rely on them, the source evidence and collection status need to be recorded under office policy.",
    produces: "Buyer Funding Record",
    sources: [
      { label: "Wire receipt", proves: "the amount posted to the file", limit: "posting is not the same as collection status" },
      { label: "Account / payee check", proves: "the origin account exists and the name matches", limit: "not live bank confirmation, and not authority to send" },
    ],
    detail: { amount: 372140, from: "Hiroshi Tanaka", posted: "Today · 1:02 PM", method: "Wire", origin: "First Republic ··5521", checks: ["Origin bank matches the buyer's named account", "Amount matches the Closing Disclosure", "Posted as collected funds, not memo-only"] },
  },
  {
    id: "t-hold-0211", kind: "hold", file: "SP-0211", street: "2208 Westcliff Dr",
    line: "Seller Proceeds", title: "Clear hold: successor trustee letter", due: "May 27", dueMs: Date.parse("May 27, 2026"),
    whyLong: "The seller is a trust. Proceeds belong to the trust, and the office can't read trustee authority to send them elsewhere without a letter from counsel.",
    produces: "Seller Proceeds Record",
    sources: [{ label: "Trust certification", proves: "the trust exists and names a trustee", limit: "does not by itself prove authority to sell or to redirect proceeds" }],
    detail: { item: "Successor trustee letter", condition: "Seller Proceeds · trust certification", cure: "Letter from outside counsel naming the successor trustee, with authority to sell and disburse.", raisedBy: "Javier Reyes", raised: "May 22 · 9:30 AM" },
  },
  {
    id: "t-receipt-0312", kind: "receipt", file: "BF-0312", street: "1408 Garnet Way",
    line: "Buyer Funding", title: "Issue buyer receipt", due: "—", dueMs: Number.MAX_SAFE_INTEGER,
    whyLong: "Buyer Funding is recorded. The receipt is generated from the record and just needs to be sent to the named party.",
    produces: "Delivered receipt",
    detail: { kind: "Buyer Funding receipt", recipient: "Liam Brennan", amount: 469500, recordedOn: "May 19", preview: "/file/$id/buyer-receipt" },
  },

  /* completed */
  {
    id: "t-callback-0142", kind: "callback", file: "RF-0142", street: "2208 Westcliff Dr", line: "Payoff Demand",
    title: "Reached servicer payoff desk", due: "May 28", dueMs: Date.parse("May 28, 2026"), whyLong: "", produces: "Callback Record",
    seedDone: { on: "Yesterday · 3:18 PM", by: "ML" },
    detail: { contact: "Servicer payoff desk", role: "Payoff Demand · 1st lien", phone: "(800) 555-0143", source: "Servicer website", confirm: "Beneficiary identity and the current payoff figure for office review", rule: "Use the number on the office contact file — never the number printed on the demand." },
  },
  {
    id: "t-funds-0209", kind: "funds", file: "SP-0209", street: "1408 Garnet Way", line: "Buyer Funding",
    title: "Recorded closing funds review", due: "May 19", dueMs: Date.parse("May 19, 2026"), whyLong: "", produces: "Buyer Funding Record",
    seedDone: { on: "Yesterday · 11:40 AM", by: "ML" },
    detail: { amount: 469500, from: "Liam Brennan", posted: "May 19 · 9:12 AM", method: "Wire", origin: "Chase ··8841", checks: ["Origin bank matches the buyer's named account", "Amount matches the Closing Disclosure", "Posted as collected funds, not memo-only"] },
  },
  {
    id: "t-record-0209", kind: "record", file: "SP-0209", street: "1408 Garnet Way", line: "Seller Proceeds",
    title: "Recorded Seller Proceeds", due: "May 19", dueMs: Date.parse("May 19, 2026"), whyLong: "", produces: "Seller Proceeds Record",
    seedDone: { on: "2 days ago", by: "ML" },
    detail: { lineLabel: "Seller Proceeds", party: "Liam Brennan", amount: 455100, closing: "May 19, 2026", checks: [["Settlement statement", "Recorded"], ["Seller instructions", "Recorded"], ["Disbursement authority", "Recorded"]] },
  },
];

/* ---------------------------------------------------------- done store */
const DONE_KEY = "veto.tasks.done";
function readDone() { try { return JSON.parse(localStorage.getItem(DONE_KEY) || "{}"); } catch { return {}; } }
function buildTasks() {
  const done = readDone();
  return SEED.map((t) => {
    const d = done[t.id] || (t.seedDone ? t.seedDone : null);
    return { ...t, done: !!d, completedOn: d?.on || null, completedBy: d?.by || "ML" };
  });
}
function openTaskCount() { return buildTasks().filter((t) => !t.done).length; }
function useTasks() {
  const [tasks, setTasks] = useState(buildTasks);
  const write = (mut) => { const d = readDone(); mut(d); try { localStorage.setItem(DONE_KEY, JSON.stringify(d)); } catch {} setTasks(buildTasks()); };
  return {
    tasks,
    complete: (id) => write((d) => { d[id] = { on: "Just now", by: "ML" }; }),
    reopen: (id) => write((d) => { delete d[id]; }),
  };
}

const FX = `
@media (prefers-reduced-motion: no-preference){ .vt-pop{animation:vt-pop .16s cubic-bezier(.2,.7,.3,1)} .vt-fade{animation:vt-fade .22s ease-out} }
@keyframes vt-pop{from{opacity:0;transform:translateY(5px) scale(.985)}to{opacity:1;transform:none}}
@keyframes vt-fade{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
`;

/* ============================================================ detail bits */
function DLabel({ children }) { return <div className="text-[11px] uppercase tracking-[0.05em] text-muted-foreground">{children}</div>; }
function Field({ label, children, mono }) { return <div><DLabel>{label}</DLabel><div className={cn("mt-1 text-[13px] text-ink", mono && "font-mono tabular-nums")}>{children}</div></div>; }
function CheckList({ items }) {
  return <ul className="flex flex-col gap-2">{items.map((s, i) => (
    <li key={i} className="flex items-start gap-2.5 text-[12.5px] leading-relaxed text-ink"><span aria-hidden="true" className="mt-0.5 grid h-4 w-4 shrink-0 place-items-center rounded-full bg-[var(--ok-bg)] text-[var(--ok)]"><Icon name="check" size={11} strokeWidth={3} /></span><span>{s}</span></li>
  ))}</ul>;
}
function CopyRow({ value }) {
  const [copied, setCopied] = useState(false);
  const copy = () => { try { navigator.clipboard?.writeText(value); } catch {} setCopied(true); window.setTimeout(() => setCopied(false), 1400); };
  return (
    <button type="button" onClick={copy} aria-label={`Copy phone number ${value}`}
      className="group flex w-full items-center justify-between rounded-lg border border-line bg-surface/50 px-3.5 py-2.5 text-left outline-none transition hover:border-ink/20 focus-visible:ring-2 focus-visible:ring-ink/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background">
      <span className="font-mono text-[15px] tabular-nums text-ink">{value}</span>
      <span className="flex items-center gap-1.5 text-[11.5px] text-ink-soft">{copied ? <><Icon name="check" size={13} strokeWidth={2.5} className="text-[var(--ok)]" aria-hidden="true" /> Copied</> : <><Icon name="link" size={13} aria-hidden="true" /> Copy</>}</span>
    </button>
  );
}
function SourceProof({ sources }) {
  return (
    <div>
      <DLabel>What the evidence shows</DLabel>
      <ul className="mt-2 flex flex-col gap-2.5">
        {sources.map((s, i) => (
          <li key={i} className="rounded-lg border border-line bg-surface/40 px-3.5 py-2.5">
            <div className="text-[12.5px] font-medium text-ink">{s.label}</div>
            <div className="mt-1 flex items-start gap-2 text-[12px] leading-relaxed text-ink-soft"><Icon name="check" size={12} strokeWidth={3} className="mt-0.5 shrink-0 text-[var(--ok)]" aria-hidden="true" /><span>Shows {s.proves}.</span></div>
            <div className="mt-1 flex items-start gap-2 text-[12px] leading-relaxed text-ink-soft"><Icon name="minus" size={12} className="mt-0.5 shrink-0 text-[var(--warn)]" aria-hidden="true" /><span>But {s.limit}.</span></div>
          </li>
        ))}
      </ul>
    </div>
  );
}
function TaskPrimitives({ t }) {
  const d = t.detail;
  switch (t.kind) {
    case "callback": return (
      <div className="flex flex-col gap-4">
        <CopyRow value={d.phone} />
        <div className="grid grid-cols-2 gap-4"><Field label="Number from">{d.source}</Field><Field label="Review">{d.confirm}</Field></div>
        <div className="flex items-start gap-2.5 rounded-lg bg-[var(--warn-bg)] px-3.5 py-3 text-[12px] leading-relaxed text-[var(--warn)]"><Icon name="alert-triangle" size={14} className="mt-0.5 shrink-0" aria-hidden="true" /><span>{d.rule}</span></div>
      </div>);
    case "discrepancy": return (
      <div className="flex flex-col gap-4">
        <div className="grid grid-cols-2 gap-3">{[d.a, d.b].map((v, i) => (
          <div key={i} className={cn("rounded-xl border px-3.5 py-3", i === 0 ? "border-[var(--warn)]/40 bg-[var(--warn-bg)]" : "border-line bg-surface/50")}><DLabel>{v.label}</DLabel><div className={cn("mt-1 font-mono text-[15px] tabular-nums", i === 0 ? "text-[var(--warn)]" : "text-ink")}>{v.value}</div></div>
        ))}</div>
        <Field label="Per-diem" mono>{d.perDiem}</Field>
      </div>);
    case "funds": return (
      <div className="flex flex-col gap-4">
        <div className="rounded-xl border border-line bg-surface/50 px-4 py-3.5"><div className="font-mono text-[28px] font-semibold leading-none tabular-nums tracking-[-0.02em] text-ink">{fmtUSD(d.amount)}</div><div className="mt-1.5 text-[11px] uppercase tracking-[0.05em] text-muted-foreground">{d.method} · {d.posted}</div></div>
        <div className="grid grid-cols-2 gap-4"><Field label="From">{d.from}</Field><Field label="Origin" mono>{d.origin}</Field></div>
        <div><DLabel>Good-funds checklist</DLabel><div className="mt-2"><CheckList items={d.checks} /></div></div>
      </div>);
    case "evidence": return (
      <div className="flex flex-col gap-4">
        <div><div className="flex items-center justify-between text-[12.5px]"><span className="text-ink-soft">Checkpoints resolved</span><span className="font-mono tabular-nums text-ink">{d.resolved} / {d.total}</span></div>
          <div className="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-line" role="progressbar" aria-valuenow={d.resolved} aria-valuemin={0} aria-valuemax={d.total}><div className="h-full rounded-full bg-ink/70" style={{ width: `${(d.resolved / d.total) * 100}%` }} /></div></div>
        <div><DLabel>Still open</DLabel><ul className="mt-2 flex flex-col gap-2">{d.missing.map((m, i) => (
          <li key={i} className="flex items-start gap-2.5 text-[12.5px] leading-relaxed text-ink"><span aria-hidden="true" className="mt-0.5 grid h-4 w-4 shrink-0 place-items-center rounded-full border border-line text-muted-foreground"><Icon name="minus" size={10} /></span><span>{m}</span></li>
        ))}</ul></div>
      </div>);
    case "change": return (
      <div className="flex flex-col gap-4">
        <div className="rounded-xl border border-[var(--warn)]/30 bg-[var(--warn-bg)] px-4 py-3.5">
          <div className="flex items-start justify-between gap-4">
            <div>
              <DLabel>Controlled action</DLabel>
              <div className="mt-1 text-[13px] font-medium text-ink">{d.action}</div>
            </div>
            <span className="inline-flex items-center gap-1.5 rounded-full bg-background/70 px-2 py-0.5 text-[11px] font-medium text-[var(--warn)]">
              <span className="h-1.5 w-1.5 rounded-full bg-[var(--warn)]" />{d.releaseStatus}
            </span>
          </div>
          <div className="mt-3 font-mono text-[24px] font-semibold leading-none tabular-nums tracking-[-0.02em] text-ink">{fmtUSD(d.amount)}</div>
        </div>
        <div className="grid grid-cols-2 gap-3">
          <div className="rounded-xl border border-line bg-surface/50 px-3.5 py-3">
            <DLabel>Prior destination</DLabel>
            <div className="mt-1 font-mono text-[13px] text-ink-soft">{d.change.from}</div>
          </div>
          <div className="rounded-xl border border-ink/20 bg-background px-3.5 py-3">
            <DLabel>New destination</DLabel>
            <div className="mt-1 font-mono text-[13px] text-ink">{d.change.to}</div>
          </div>
        </div>
        <div className="grid grid-cols-2 gap-4">
          <Field label="Staled record">{d.staled}</Field>
          <Field label="Detected" mono>{d.change.detected}</Field>
        </div>
        <div className="rounded-lg border border-line bg-surface/50 px-3.5 py-3">
          <DLabel>Policy effect</DLabel>
          <p className="mt-1 text-[12.5px] leading-relaxed text-ink">{d.policy}. <span className="font-mono text-muted-foreground">{d.policyVersion}</span></p>
        </div>
        <div className="flex items-start gap-2.5 rounded-lg border border-line bg-background px-3.5 py-3 text-[12px] leading-relaxed text-ink-soft">
          <Icon name="shield" size={14} className="mt-0.5 shrink-0 text-ink-soft" aria-hidden="true" />
          <span>{d.exception}</span>
        </div>
      </div>);
    case "hold": return (
      <div className="flex flex-col gap-4">
        <Field label="What's held">{d.item}</Field>
        <div className="rounded-lg border border-line bg-surface/50 px-3.5 py-3"><DLabel>To clear</DLabel><p className="mt-1 text-[12.5px] leading-relaxed text-ink">{d.cure}</p></div>
        <div className="text-[12px] text-ink-soft">Raised by {d.raisedBy} · {d.raised}</div>
      </div>);
    case "record": return (
      <div className="flex flex-col gap-4">
        <div className="rounded-xl border border-line bg-surface/50 px-4 py-3.5"><div className="flex items-baseline justify-between"><span className="text-[13px] text-ink">{d.lineLabel}</span><span className="font-mono text-[15px] font-semibold tabular-nums text-ink">{fmtUSD(d.amount)}</span></div><div className="mt-0.5 text-[12px] text-ink-soft">{d.party} · closes {d.closing}</div></div>
        <ul className="flex flex-col divide-y divide-line rounded-lg border border-line">{d.checks.map(([label, st], i) => (
          <li key={i} className="flex items-center justify-between px-3.5 py-2.5 text-[12.5px]"><span className="text-ink">{label}</span><span className="inline-flex items-center gap-1.5 text-[var(--ok)]"><Icon name="check" size={12} strokeWidth={3} aria-hidden="true" /> {st}</span></li>
        ))}</ul>
      </div>);
    case "receipt": return (
      <div className="flex flex-col gap-4">
        <Field label="Receipt">{d.kind}</Field>
        <div className="grid grid-cols-2 gap-4"><Field label="Recipient">{d.recipient}</Field><Field label="Amount" mono>{fmtUSD(d.amount)}</Field></div>
        <Field label="Recorded">{d.recordedOn}</Field>
      </div>);
    default: return null;
  }
}
function TaskDetail({ t }) {
  return (
    <div className="flex flex-col gap-5">
      {t.whyLong && <section><DLabel>Why this needs you</DLabel><p className="mt-1.5 text-[13px] leading-relaxed text-ink-soft">{t.whyLong}</p></section>}
      <TaskPrimitives t={t} />
      {t.sources && <SourceProof sources={t.sources} />}
      <section className="border-t border-line pt-4"><DLabel>Completing this creates</DLabel>
        <div className="mt-2 inline-flex items-center gap-2 rounded-lg border border-line bg-surface/50 px-3 py-1.5 text-[12.5px] text-ink"><Icon name="file-text" size={13} className="text-ink-soft" aria-hidden="true" /> {t.produces}</div>
      </section>
    </div>
  );
}

/* ============================================================ accessible tabs */
function Tabs({ tabs, value, onChange }) {
  const ref = useRef(null);
  const onKeyDown = (e) => {
    const i = tabs.findIndex((t) => t.key === value); let n = i;
    if (e.key === "ArrowRight" || e.key === "ArrowDown") n = (i + 1) % tabs.length;
    else if (e.key === "ArrowLeft" || e.key === "ArrowUp") n = (i - 1 + tabs.length) % tabs.length;
    else if (e.key === "Home") n = 0; else if (e.key === "End") n = tabs.length - 1; else return;
    e.preventDefault(); onChange(tabs[n].key);
    requestAnimationFrame(() => ref.current?.querySelector(`#tab-${tabs[n].key}`)?.focus());
  };
  return (
    <div role="tablist" aria-label="Task status" ref={ref} onKeyDown={onKeyDown} className="flex items-center gap-6">
      {tabs.map((t) => {
        const sel = t.key === value;
        return (
          <button key={t.key} id={`tab-${t.key}`} role="tab" type="button" aria-selected={sel} aria-controls={`panel-${t.key}`} tabIndex={sel ? 0 : -1} onClick={() => onChange(t.key)}
            className={cn("-mb-px flex items-center gap-1.5 rounded-sm border-b-2 py-2.5 text-[13.5px] outline-none transition focus-visible:ring-2 focus-visible:ring-ink/35 focus-visible:ring-offset-4 focus-visible:ring-offset-background",
              sel ? "border-ink font-medium text-ink" : "border-transparent text-ink-soft hover:text-ink")}>
            <span>{t.label}</span><span className={cn("font-mono text-[11px] tabular-nums", sel ? "text-ink-soft" : "text-muted-foreground")}>{t.count}</span>
          </button>
        );
      })}
    </div>
  );
}

/* ============================================================ row */
function TaskRow({ t, done, onOpen, selected, compact, today }) {
  const k = TASK_KIND[t.kind];
  const right = done ? `${t.completedOn} · ${t.completedBy}` : (today ? "Today" : (t.due !== "—" ? t.due : null));
  const sr = done ? `${k.label}: ${t.title}. Completed ${t.completedOn} by ${t.completedBy}.` : `${k.label}: ${t.title}. ${t.street}.${right ? ` Due ${right}.` : ""}`;
  return (
    <li>
      <button type="button" onClick={onOpen} aria-label={sr} aria-current={selected ? "true" : undefined}
        className={cn("group flex w-full items-center gap-3.5 text-left outline-none transition focus-visible:ring-2 focus-visible:ring-ink/35 focus-visible:ring-inset",
          compact ? "border-l-2 px-3.5 py-3" : "px-2 py-3",
          selected ? "border-ink bg-surface" : compact ? "border-transparent hover:bg-surface/50" : "hover:bg-surface/50")}>
        <Icon name={k.icon} size={16} aria-hidden="true" className="shrink-0 text-ink-soft" />
        <div className="min-w-0 flex-1">
          <div className={cn("truncate text-[13.5px]", done ? "text-ink-soft" : "font-medium text-ink")}>{t.title}</div>
          <div className="mt-0.5 flex items-center gap-1.5 truncate text-[12px] text-ink-soft"><span className="truncate">{t.street}</span><span className="font-mono text-[11px] text-muted-foreground">{t.file}</span></div>
        </div>
        <div className="flex shrink-0 items-center gap-3">
          {right && <span className={cn("font-mono text-[11.5px] tabular-nums", today ? "font-medium text-[var(--warn)]" : "text-ink-soft")}>{right}</span>}
          {!compact && <Icon name="chevron-right" size={15} aria-hidden="true" className="text-muted-foreground/0 transition group-hover:translate-x-0.5 group-hover:text-muted-foreground" />}
        </div>
      </button>
    </li>
  );
}

/* ============================================================ modal */
function TaskModal({ t, onClose, onComplete, onReopen }) {
  const k = TASK_KIND[t.kind];
  const cardRef = useRef(null), primaryRef = useRef(null);
  useEffect(() => {
    const prev = document.activeElement; primaryRef.current?.focus();
    let t0 = null; if (document.activeElement !== primaryRef.current) t0 = setTimeout(() => primaryRef.current?.focus(), 0);
    const onKey = (e) => {
      if (e.key === "Escape") { e.preventDefault(); onClose(); return; }
      if (e.key === "Tab") {
        const f = cardRef.current?.querySelectorAll('button, a[href], [tabindex]:not([tabindex="-1"])'); if (!f?.length) return;
        const list = Array.from(f).filter((el) => !el.disabled && el.offsetParent !== null); if (!list.length) return;
        const first = list[0], last = list[list.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    };
    document.addEventListener("keydown", onKey);
    return () => { if (t0) clearTimeout(t0); document.removeEventListener("keydown", onKey); prev?.focus?.(); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return (
    <div className="fixed inset-0 z-[100] flex items-center justify-center bg-ink/25 px-4 backdrop-blur-[2px]" onMouseDown={onClose}>
      <div ref={cardRef} role="dialog" aria-modal="true" aria-labelledby="task-modal-title" onMouseDown={(e) => e.stopPropagation()}
        className="vt-pop flex max-h-[88vh] w-full max-w-[468px] flex-col overflow-hidden rounded-2xl border border-line bg-background shadow-[0_24px_64px_-24px_rgba(0,0,0,0.4)]">
        <div className="flex items-start gap-3.5 border-b border-line px-5 py-4">
          <span aria-hidden="true" className="grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-line bg-surface text-ink-soft"><Icon name={k.icon} size={18} /></span>
          <div className="min-w-0 flex-1">
            <div className="text-[11px] uppercase tracking-[0.06em] text-muted-foreground">{k.label}</div>
            <h2 id="task-modal-title" className="om-display mt-0.5 text-[16px] font-semibold leading-snug tracking-[-0.01em] text-ink">{t.title}</h2>
            <div className="mt-1 truncate text-[12px] text-ink-soft">{t.street} · {t.line}</div>
          </div>
          <button ref={t.done ? primaryRef : null} type="button" onClick={onClose} aria-label="Close" className="grid h-8 w-8 shrink-0 place-items-center rounded-md text-muted-foreground outline-none transition hover:bg-surface hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35"><Icon name="x" size={15} /></button>
        </div>
        <div className="overflow-y-auto px-5 py-5"><TaskDetail t={t} /></div>
        <div className="flex items-center justify-between gap-3 border-t border-line bg-surface/40 px-5 py-3.5">
          <div className="min-w-0 text-[12px]">
            {t.done ? <span className="text-ink-soft">Completed {t.completedOn} · {t.completedBy}</span>
              : <Link to="/file/$id" params={{ id: t.file }} className="rounded-sm text-ink-soft underline decoration-line underline-offset-4 outline-none transition hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35">Open in file →</Link>}
          </div>
          <div className="flex shrink-0 items-center gap-2.5">
            {t.done ? <button type="button" onClick={() => onReopen(t.id)} className="rounded-lg px-3 py-2 text-[12.5px] text-ink-soft outline-none transition hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35">Reopen</button>
              : <>
                <button type="button" onClick={onClose} className="rounded-lg px-3 py-2 text-[12.5px] text-ink-soft outline-none transition hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35">Close</button>
                <button ref={primaryRef} type="button" onClick={() => onComplete(t)} className="inline-flex items-center gap-1.5 rounded-lg bg-ink px-4 py-2 text-[12.5px] font-medium text-background outline-none transition hover:bg-ink/90 focus-visible:ring-2 focus-visible:ring-ink/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background">{k.action}</button>
              </>}
          </div>
        </div>
      </div>
    </div>
  );
}

function Toast({ msg }) {
  return (
    <div className="no-print pointer-events-none fixed bottom-6 left-1/2 z-[120] -translate-x-1/2" role="status" aria-live="polite">
      <div className="vt-fade flex items-center gap-2.5 rounded-full bg-ink px-4 py-2.5 text-[12.5px] font-medium text-background shadow-[0_12px_32px_-12px_rgba(0,0,0,0.5)]"><span aria-hidden="true" className="grid h-4 w-4 place-items-center rounded-full bg-[var(--ok)] text-background"><Icon name="check" size={11} strokeWidth={3} /></span>{msg}</div>
    </div>
  );
}
function toastFor(kind) { return ({ hold: "Hold resolved", callback: "Call logged", discrepancy: "Updated demand requested", funds: "Funds recorded", evidence: "Evidence resolved", change: "v2 record queued", record: "Line recorded", receipt: "Receipt sent" })[kind] + " · moved to Done"; }

/* ============================================================ split detail */
function SplitDetail({ t, onComplete, onReopen }) {
  if (!t) return <div className="rounded-2xl border border-dashed border-line bg-surface/40 px-6 py-16 text-center text-[13px] text-ink-soft">Select a task to see what it needs.</div>;
  const k = TASK_KIND[t.kind];
  return (
    <div className="overflow-hidden rounded-2xl border border-line bg-background">
      <div className="flex items-start gap-3.5 border-b border-line px-5 py-4">
        <span aria-hidden="true" className="grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-line bg-surface text-ink-soft"><Icon name={k.icon} size={18} /></span>
        <div className="min-w-0 flex-1">
          <div className="text-[11px] uppercase tracking-[0.06em] text-muted-foreground">{k.label}</div>
          <h2 className="om-display mt-0.5 text-[16px] font-semibold leading-snug tracking-[-0.01em] text-ink">{t.title}</h2>
          <div className="mt-1 truncate text-[12px] text-ink-soft">{t.street} · {t.line} · <span className="font-mono">{t.file}</span></div>
        </div>
      </div>
      <div className="px-5 py-5"><TaskDetail t={t} /></div>
      <div className="flex items-center justify-between gap-3 border-t border-line bg-surface/40 px-5 py-3.5">
        {t.done ? <span className="text-[12px] text-ink-soft">Completed {t.completedOn} · {t.completedBy}</span>
          : <Link to="/file/$id" params={{ id: t.file }} className="rounded-sm text-[12px] text-ink-soft underline decoration-line underline-offset-4 outline-none transition hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35">Open in file →</Link>}
        {t.done ? <button type="button" onClick={() => onReopen(t.id)} className="rounded-lg px-3 py-2 text-[12.5px] text-ink-soft outline-none transition hover:text-ink focus-visible:ring-2 focus-visible:ring-ink/35">Reopen</button>
          : <button type="button" onClick={() => onComplete(t)} className="inline-flex items-center gap-1.5 rounded-lg bg-ink px-4 py-2 text-[12.5px] font-medium text-background outline-none transition hover:bg-ink/90 focus-visible:ring-2 focus-visible:ring-ink/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background">{k.action}</button>}
      </div>
    </div>
  );
}

/* List ↔ Split, persisted. An owner can keep the calm list; an operator
   living in the queue can keep the two-pane. */
function useView() {
  const [v, setV] = useState(() => { try { return localStorage.getItem("veto.tasks.view") || "list"; } catch { return "list"; } });
  return [v, (x) => { try { localStorage.setItem("veto.tasks.view", x); } catch {} setV(x); }];
}
const VIEW_ICONS = {
  list: <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true"><line x1="4" y1="7" x2="20" y2="7" /><line x1="4" y1="12" x2="20" y2="12" /><line x1="4" y1="17" x2="20" y2="17" /></svg>,
  split: <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2" /><line x1="10" y1="5" x2="10" y2="19" /></svg>,
};
function ViewToggle({ view, setView }) {
  return (
    <div role="group" aria-label="Layout" className="flex items-center gap-0.5 pb-1.5">
      {[["list", "List view"], ["split", "Split view"]].map(([key, label]) => (
        <Hint key={key} label={label} side="top">
          <button type="button" onClick={() => setView(key)} aria-pressed={view === key} aria-label={label}
            className={cn("grid h-7 w-7 place-items-center rounded-md outline-none transition focus-visible:ring-2 focus-visible:ring-ink/35",
              view === key ? "bg-surface text-ink" : "text-muted-foreground hover:bg-surface/60 hover:text-ink")}>{VIEW_ICONS[key]}</button>
        </Hint>
      ))}
    </div>
  );
}

/* ============================================================ page */
function Tasks() {
  const { tasks, complete, reopen } = useTasks();
  const [view, setView] = useView();
  const [tab, setTab] = useState("incomplete");
  const [openId, setOpenId] = useState(null);
  const [selId, setSelId] = useState(null);
  const [toast, setToast] = useState(null);
  const signals = typeof getSignals === "function" ? getSignals("SP-0214") : [];
  const immediate = signals[0] || { id: "SIG-SP0214-CONFLICT", createdAt: "May 21, 3:18 pm" };
  const IMMEDIATE_TITLE = "Payout destination linked to the acting officer";
  const IMMEDIATE_DETAIL = "The payout destination (Whitfield Coast Holdings LLC) names an entity whose managing member of record matches the acting officer on this file. Held pending an owner exception record. Veto records the association. It does not allege intent.";

  const incomplete = tasks.filter((t) => !t.done).sort((a, b) => a.dueMs - b.dueMs);
  const completed = tasks.filter((t) => t.done);
  const anchor = incomplete.length ? Math.min(...incomplete.map((t) => t.dueMs)) : Infinity;
  const isToday = (t) => !t.done && Number.isFinite(anchor) && t.dueMs === anchor;
  const list = tab === "incomplete" ? incomplete : completed;
  const done = tab === "completed";
  const active = tasks.find((t) => t.id === openId) || null;
  const sel = list.find((t) => t.id === selId) || list[0] || null;

  const fireToast = (kind) => { setToast(toastFor(kind)); window.clearTimeout(Tasks._t); Tasks._t = window.setTimeout(() => setToast(null), 2400); };
  const completeList = (t) => { complete(t.id); setOpenId(null); fireToast(t.kind); };
  const completeSplit = (t) => { const i = list.findIndex((x) => x.id === t.id); const rest = list.filter((x) => x.id !== t.id); setSelId((rest[Math.min(i, rest.length - 1)] || {}).id || null); complete(t.id); fireToast(t.kind); };

  const TABS = [{ key: "incomplete", label: "Incomplete", count: incomplete.length }, { key: "completed", label: "Completed", count: completed.length }];

  return (
    <AppShell activeLine="notifications" reviewer={{ name: "Madeline Lane", initials: "ML" }}>
      <style dangerouslySetInnerHTML={{ __html: FX }} />
      <div className="mx-auto w-full max-w-[940px] px-6 py-10 lg:px-12 lg:py-12">
        <h1 className="om-display text-[28px] font-semibold tracking-[-0.02em] text-ink">Tasks</h1>
        <p className="mt-1.5 text-[13.5px] text-ink-soft">{incomplete.length === 0 ? "You're all caught up." : "Gaps to clear before a file can move."}</p>
        {immediate && (
          <div className="mt-5 rounded-xl border border-[var(--warn)]/25 bg-[var(--warn-bg)] px-4 py-3">
            <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
              <div>
                <div className="flex items-center gap-2 text-[12.5px] font-medium text-[var(--warn)]">
                  <Icon name="alert-circle" size={14} /> {IMMEDIATE_TITLE}
                </div>
                <p className="mt-1.5 text-[12px] leading-relaxed text-ink-soft">{IMMEDIATE_DETAIL}</p>
                <p className="mt-1 text-[11.5px] text-muted-foreground">Blocker · Held · routed to Review Register · <span className="font-mono">SP-0214</span></p>
                <Link to="/overview" className="mt-2.5 inline-flex items-center gap-1.5 rounded-md bg-ink px-3 py-1.5 text-[12px] font-medium text-background transition hover:bg-ink/90">
                  Open Review Register <Icon name="arrow-right" size={12} />
                </Link>
              </div>
              <span className="shrink-0 font-mono text-[11px] text-muted-foreground">{immediate.createdAt}</span>
            </div>
          </div>
        )}

        <div className="mt-7 flex items-center justify-between border-b border-line">
          <Tabs tabs={TABS} value={tab} onChange={(k) => { setTab(k); setSelId(null); }} />
          <ViewToggle view={view} setView={setView} />
        </div>

        <div role="tabpanel" id={`panel-${tab}`} aria-labelledby={`tab-${tab}`} className="mt-6">
          {list.length === 0 ? (
            tab === "incomplete"
              ? <EmptyState icon={<Icon name="check-circle" size={18} />} title="A clear desk" note="Nothing needs you right now. New gaps land here the moment we catch them." />
              : <EmptyState icon={<Icon name="check-circle" size={18} />} title="Nothing done yet today" note="As you clear gaps, they collect here with who finished them and when." />
          ) : view === "split" ? (
            <div className="grid grid-cols-1 items-start gap-6 md:grid-cols-[300px_minmax(0,1fr)] md:gap-7">
              <ul role="listbox" aria-label="Tasks" className="flex flex-col divide-y divide-line overflow-hidden rounded-2xl border border-line bg-background">
                {list.map((t) => <TaskRow key={t.id} t={t} done={done} compact selected={sel && sel.id === t.id} today={isToday(t)} onOpen={() => setSelId(t.id)} />)}
              </ul>
              <SplitDetail t={sel} onComplete={completeSplit} onReopen={(id) => reopen(id)} />
            </div>
          ) : (
            <ul className="flex flex-col divide-y divide-line">
              {list.map((t) => <TaskRow key={t.id} t={t} done={done} today={isToday(t)} onOpen={() => setOpenId(t.id)} />)}
            </ul>
          )}
        </div>

        <footer className="mt-14 border-t border-line pt-5"><p className="text-[12px] text-ink-soft">Veto records the review. The office decides.</p></footer>
      </div>
      {active && view === "list" && <TaskModal t={active} onClose={() => setOpenId(null)} onComplete={completeList} onReopen={(id) => { reopen(id); setOpenId(null); }} />}
      {toast && <Toast msg={toast} />}
    </AppShell>
  );
}

const Notifications = Tasks;
Object.assign(window, { Notifications, Tasks, openTaskCount });
