/* =========================================================================
   Veto · File hub — one page, no tabs.
   The file IS the work: seven checkpoints stacked, three states
   (Open / Recorded / Waiting). Right rail = Activity + Proof.
   ========================================================================= */

/* Seven checkpoints per line. Each needs exactly one input when Open. */
const CHECKPOINTS = {
  payoff: [
    { id: "p1", name: "Lien identified", note: "Recorded deed of trust matches the demand.", input: "file", placeholder: "Recorded deed of trust" },
    { id: "p2", name: "Payoff demand on file", note: "Statement on servicer letterhead, good-through date present.", input: "file", placeholder: "Payoff statement" },
    { id: "p3", name: "Math reconciles", note: "Principal + interest + per-diem through closing.", input: "amount", placeholder: "Payoff total" },
    { id: "p4", name: "Beneficiary of record", note: "Servicer is the beneficiary or holds authority to collect.", input: "confirm", confirmLabel: "Servicer authority matched to record" },
    { id: "p5", name: "Borrower signer chain", note: "Authorization to release ties to the vested borrower.", input: "signature" },
    { id: "p6", name: "Destination matches demand", note: "Wire account ties to the servicer on the demand.", input: "text", placeholder: "Account last 4" },
    { id: "p7", name: "Independent callback", note: "Callback to the servicer payoff desk · number not from the demand.", input: "confirm", confirmLabel: "Read-back matched · per-diem recorded" },
  ],
  seller: [
    { id: "s1", name: "Instructions on file", note: "Signed seller instructions govern the disbursement.", input: "file", placeholder: "Seller instructions" },
    { id: "s2", name: "Final settlement statement", note: "Final statement received and on file.", input: "file", placeholder: "Final settlement statement" },
    { id: "s3", name: "Variance within tolerance", note: "Net proceeds reconcile to the statement.", input: "amount", placeholder: "Net proceeds" },
    { id: "s4", name: "Seller identity source", note: "Vesting, identity, and signing evidence name the seller whose proceeds are being reviewed.", input: "confirm", confirmLabel: "Seller identity evidence recorded as source rows" },
    { id: "s5", name: "Proceeds destination captured", note: "Record the destination last four and account/payee evidence; full details stay restricted.", input: "text", placeholder: "Destination last 4" },
    { id: "s6", name: "Source limitations accepted", note: "Reviewer accepts what the identity and account evidence can and cannot support.", input: "signature" },
    { id: "s7", name: "Independent callback", note: "Seller at number on file · not the wire form.", input: "confirm", confirmLabel: "Read-back matched · amount and destination recorded" },
  ],
  buyer: [
    { id: "b1", name: "Closing disclosure on file", note: "Governing CD/loan instructions present.", input: "file", placeholder: "Closing disclosure" },
    { id: "b2", name: "Cash-to-close computed", note: "Down payment + costs − credits reconciles.", input: "amount", placeholder: "Cash to close" },
    { id: "b3", name: "Buyer identity & vesting", note: "ID reviewed; vesting instruction on file.", input: "confirm", confirmLabel: "Identity reviewed · vesting recorded" },
    { id: "b4", name: "Lender clear-to-close", note: "Funding authorization on lender letterhead.", input: "file", placeholder: "Clear-to-close / funding auth" },
    { id: "b5", name: "Funds posted", note: "EMD and closing funds posted to the file for review.", input: "amount", placeholder: "Funds received" },
    { id: "b6", name: "Funding source recorded", note: "Wire origin tied to the buyer of record.", input: "confirm", confirmLabel: "Wire origin matches buyer" },
    { id: "b7", name: "No open exceptions", note: "Recording package ready; title clear.", input: "signature" },
  ],
};

const RECEIPT_HREF = { payoff: "/file/$id/payoff-receipt", buyer: "/file/$id/buyer-receipt", seller: "/file/$id/receipt" };

function makeHash() {
  const h = "0123456789abcdef";
  let s = "";
  for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)];
  return s;
}
let clockMin = 14 * 60 + 12; // 2:12p base
function nextStamp() {
  clockMin += 1 + Math.floor(Math.random() * 6);
  const h24 = Math.floor(clockMin / 60) % 24, m = clockMin % 60;
  const ap = h24 >= 12 ? "p" : "a";
  const h12 = ((h24 + 11) % 12) + 1;
  return `${h12}:${String(m).padStart(2, "0")}${ap}`;
}

function FileHub({ id, line }) {
  const cf = CASE_FILES[id];
  const lineKey = line || (cf ? activeLineKey(cf) : "payoff");
  const defs = CHECKPOINTS[lineKey];
  const reviewer = cf?.reviewer || "Madeline Lane";

  // Seed: first 2 recorded, 3rd open, rest waiting.
  const [chk, setChk] = useState(() =>
    defs.map((d, i) => ({
      ...d,
      state: i < 2 ? "recorded" : i === 2 ? "open" : "waiting",
      by: i < 2 ? reviewer : null,
      at: i < 2 ? (i === 0 ? "1:48p" : "2:03p") : null,
      hash: i < 2 ? makeHash() : null,
    })),
  );
  const [activity, setActivity] = useState(() => [
    { who: reviewer, what: `recorded ${defs[1].name.toLowerCase()}`, at: "2:03p" },
    { who: reviewer, what: `recorded ${defs[0].name.toLowerCase()}`, at: "1:48p" },
    { who: reviewer, what: "opened the file", at: "1:42p" },
  ]);
  const [expanded, setExpanded] = useState({});
  const [shared, setShared] = useState(false);
  const [railOpen, setRailOpen] = useState({ activity: true, proof: true });

  const allDone = chk.every((c) => c.state === "recorded");
  const recordedCount = chk.filter((c) => c.state === "recorded").length;
  const actionState = lineKey === "seller" && typeof getActionState === "function" ? getActionState(id, "seller-proceeds-release") : null;
  const changeEvent = lineKey === "seller" && typeof getChangeEvents === "function" ? getChangeEvents(id).find((c) => c.actionKey === "seller-proceeds-release") : null;

  function record(idx, ok) {
    if (!ok) return;
    const at = nextStamp();
    setChk((prev) => {
      const next = prev.map((c) => ({ ...c }));
      next[idx].state = "recorded";
      next[idx].by = reviewer;
      next[idx].at = at;
      next[idx].hash = makeHash();
      if (!next.some((c) => c.state === "open")) {
        const w = next.findIndex((c) => c.state === "waiting");
        if (w !== -1) next[w].state = "open";
      }
      return next;
    });
    setActivity((a) => [{ who: reviewer, what: `recorded ${defs[idx].name.toLowerCase()}`, at }, ...a]);
  }
  function reopen(idx) {
    const at = nextStamp();
    setChk((prev) => prev.map((c, i) => {
      if (i === idx) return { ...c, state: "open", by: null, at: null, hash: null };
      if (i > idx && c.state === "open") return { ...c, state: "waiting" };
      return c;
    }));
    setActivity((a) => [{ who: reviewer, what: `reopened ${defs[idx].name.toLowerCase()}`, at }, ...a]);
    setExpanded((e) => ({ ...e, [defs[idx].id]: false }));
  }

  const lineLabel = lineLabelOf(lineKey);

  return (
    <AppShell activeLine={null} activeFileId={id} reviewer={{ name: "Madeline Lane", initials: "ML" }}>
      {/* Header strip */}
      <div className="border-b border-line bg-background">
        <div className="mx-auto w-full max-w-[1080px] px-6 py-6 lg:px-10">
          <div className="mb-3 flex items-center gap-3">
            <Link to="/file/$id" params={{ id }} className="inline-flex items-center gap-1.5 rounded-md px-2 py-1 -ml-2 text-[12.5px] text-ink-soft transition hover:bg-surface hover:text-ink">
              <Icon name="arrow-left" size={14} /> {streetOf(cf?.property) || id}
            </Link>
            <div className="inline-flex rounded-lg border border-line p-0.5 text-[12px]">
              <Link to="/file/$id" params={{ id }} className="rounded-md px-2.5 py-1 text-ink-soft transition hover:text-ink">Overview</Link>
              <span className="rounded-md bg-ink px-2.5 py-1 font-medium text-background">Workflow</span>
            </div>
          </div>
          <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
            <div className="min-w-0">
              <h1 className="truncate text-[22px] font-semibold tracking-[-0.015em] text-ink">{lineLabel}</h1>
              <div className="mt-1 truncate text-[12px] text-muted-foreground">
                {id} · {streetOf(cf?.property)} · <span className={allDone ? "text-[var(--ok)]" : "text-[var(--warn)]"}>{allDone ? "Recorded" : "In review"}</span>
              </div>
            </div>
            <div className="no-print flex shrink-0 items-center gap-1">
              <HeaderAction icon="printer" label="Print" onClick={() => window.print()} />
              <HeaderAction icon={shared === "Copied" ? "check" : "link"} label={shared || "Share link"} onClick={async () => { const ok = await copyText(shareUrlFor("/file/$id/review/$line", { id, line: lineKey })); setShared(ok ? "Copied" : "Copy failed"); setTimeout(() => setShared(false), 1600); }} />
            </div>
          </div>
        </div>
      </div>

      {/* Body: single column + right rail */}
      <div className="mx-auto w-full max-w-[1080px] px-6 py-8 lg:px-10 lg:py-10">
        <div className="print-flow grid grid-cols-1 gap-10 lg:grid-cols-[minmax(0,1fr)_280px]">
          {/* Column — the seven checkpoints */}
          <div className="mx-auto w-full max-w-[720px]">
            <div className="mb-5 flex items-center justify-between">
              <div className="text-[12px] text-ink-soft">
                <span className="font-medium text-ink">{recordedCount}</span> of {chk.length} checkpoints recorded
              </div>
              <ProgressTrack total={chk.length} done={recordedCount} />
            </div>
            {actionState && (
              <div className="mb-5 rounded-xl border border-[var(--warn)]/30 bg-[var(--warn-bg)] px-4 py-3.5">
                <div className="flex items-center gap-2 text-[13px] font-medium text-[var(--warn)]">
                  <Icon name="alert-circle" size={15} /> Create v2 record
                </div>
                <p className="mt-1 text-[12.5px] leading-relaxed text-ink-soft">{actionState.reason} Record the changed destination before release, or request a manager exception.</p>
                {changeEvent && (
                  <div className="mt-3 grid grid-cols-2 gap-3">
                    <div className="rounded-lg border border-[var(--warn)]/20 bg-background/60 px-3 py-2">
                      <div className="text-[10px] uppercase tracking-[0.06em] text-muted-foreground">Prior</div>
                      <div className="mt-1 font-mono text-[12px] text-ink-soft">{changeEvent.from}</div>
                    </div>
                    <div className="rounded-lg border border-[var(--warn)]/20 bg-background/80 px-3 py-2">
                      <div className="text-[10px] uppercase tracking-[0.06em] text-muted-foreground">v2 destination</div>
                      <div className="mt-1 font-mono text-[12px] text-ink">{changeEvent.to}</div>
                    </div>
                  </div>
                )}
              </div>
            )}
            {lineKey === "seller" && (
              <SellerProceedsFlowPanel id={id} cf={cf} actionState={actionState} changeEvent={changeEvent} />
            )}
            <ul className="flex flex-col gap-2.5">
              {chk.map((c, i) => (
                <CheckpointRow key={c.id} c={c} index={i}
                  expanded={!!expanded[c.id]} onToggle={() => setExpanded((e) => ({ ...e, [c.id]: !e[c.id] }))}
                  onRecord={(ok) => record(i, ok)} onReopen={() => reopen(i)} />
              ))}
            </ul>

            {allDone && (
              <div className="mt-6 rounded-xl border border-[var(--ok)]/30 bg-[var(--ok-bg)] px-5 py-4">
                <div className="flex items-center gap-2 text-[13px] font-medium text-[var(--ok)]">
                  <Icon name="check-circle" size={16} /> {lineLabel} review recorded
                </div>
                <p className="mt-1 text-[12.5px] text-ink-soft">Every checkpoint is recorded. The receipt is in the rail.</p>
              </div>
            )}
          </div>

          {/* Right rail */}
          <aside className="lg:sticky lg:top-6 lg:self-start">
            <RailSection title="Activity" open={railOpen.activity} onToggle={() => setRailOpen((r) => ({ ...r, activity: !r.activity }))}>
              <ul className="space-y-2.5">
                {activity.map((a, i) => (
                  <li key={i} className="flex gap-2 text-[12px] leading-snug">
                    <span className="mt-[3px] inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-ink-soft/40" />
                    <span className="text-ink-soft">
                      <span className="text-ink">{a.who.split(" ")[0]}</span> {a.what}
                      <span className="font-mono text-[11px] text-muted-foreground"> · {a.at}</span>
                    </span>
                  </li>
                ))}
              </ul>
            </RailSection>

            <RailSection title="Proof" open={railOpen.proof} onToggle={() => setRailOpen((r) => ({ ...r, proof: !r.proof }))}>
              {allDone ? (
                <Link to={RECEIPT_HREF[lineKey]} params={{ id }}
                  className="group flex items-center gap-2.5 rounded-lg border border-line bg-background px-3 py-2.5 transition hover:border-ink/25 hover:bg-surface/50">
                  <span className="grid h-8 w-8 shrink-0 place-items-center rounded-md bg-surface-2 text-ink-soft"><Icon name="file-text" size={15} /></span>
                  <span className="min-w-0 flex-1">
                    <span className="block truncate text-[12.5px] font-medium text-ink">{lineLabel} receipt</span>
                    <span className="block text-[11px] text-muted-foreground">{id} · signed</span>
                  </span>
                  <Icon name="arrow-right" size={14} className="text-ink-soft transition group-hover:translate-x-0.5" />
                </Link>
              ) : (
                <p className="text-[12px] leading-relaxed text-ink-soft">The receipt issues when the last checkpoint is recorded. It binds every source to the position the office took.</p>
              )}
            </RailSection>
          </aside>
        </div>
      </div>
    </AppShell>
  );
}

/* --------------------------------------------------------------- pieces */
function HeaderAction({ icon, label, onClick, danger }) {
  return (
    <button type="button" onClick={onClick}
      className={cn("inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-[12.5px] transition",
        danger ? "text-ink-soft hover:bg-[var(--warn-bg)] hover:text-[var(--warn)]" : "text-ink-soft hover:bg-surface hover:text-ink")}>
      <Icon name={icon} size={14} />{label}
    </button>
  );
}

function ProgressTrack({ total, done }) {
  return (
    <div className="flex items-center gap-1" aria-hidden>
      {Array.from({ length: total }).map((_, i) => (
        <span key={i} className={cn("h-1 w-4 rounded-full transition-colors", i < done ? "bg-[var(--ok)]" : "bg-line")} />
      ))}
    </div>
  );
}

function SellerProceedsFlowPanel({ id, cf, actionState, changeEvent }) {
  const rows = typeof getSourceRows === "function" ? getSourceRows(id, "seller-proceeds-release") : [];
  const records = typeof getRecordsForAction === "function" ? getRecordsForAction(id, "seller-proceeds-release") : [];
  const releaseGate = typeof getReleaseGateStatus === "function" ? getReleaseGateStatus(id, "seller-proceeds-release") : null;
  const policy = actionState?.policyId && typeof getPolicyControl === "function" ? getPolicyControl(actionState.policyId) : null;
  const staleRecord = records.find((record) => /stale|superseded/i.test(record.status)) || records[0];
  const requiredRecord = records.find((record) => record.id === releaseGate?.requiredRecordId) || records.find((record) => record.version === "v2") || records[1];
  const amount = releaseGate?.amountCents ? fmtUSD(releaseGate.amountCents / 100) : releaseGate?.amount ? fmtUSD(releaseGate.amount) : "$182,742";
  const payee = releaseGate?.payee || cf?.party || "Seller";
  const destination = releaseGate?.destination || changeEvent?.to || requiredRecord?.acceptedState || "Destination under review";
  const identityRows = rows.filter((row) => /seller|identity|callback|instruction/i.test(`${row.label} ${row.type || ""} ${row.sourceType || ""}`));
  const destinationRows = rows.filter((row) => /destination|account|payee|statement/i.test(`${row.label} ${row.type || ""} ${row.sourceType || ""}`));
  const displayRows = rows.length ? rows : [];

  return (
    <section className="mb-5 overflow-hidden rounded-xl border border-line bg-background">
      <div className="border-b border-line bg-surface/40 px-4 py-3.5">
        <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
          <div>
            <div className="text-[11px] uppercase tracking-[0.06em] text-muted-foreground">Seller proceeds flow</div>
            <h2 className="mt-1 text-[14px] font-semibold text-ink">Destination-control review</h2>
          </div>
          <span className="inline-flex w-fit items-center gap-1.5 rounded-full bg-[var(--warn-bg)] px-2.5 py-1 text-[11px] font-medium text-[var(--warn)]">
            <span className="h-1.5 w-1.5 rounded-full bg-[var(--warn)]" />Release blocked until current record
          </span>
        </div>
      </div>

      <div className="divide-y divide-line">
        <div className="grid grid-cols-1 gap-3 p-4 sm:grid-cols-3">
          <SellerFlowFact icon="user" label="Seller identity" value={payee} note={`${amount} proceeds · ${identityRows.length || 2} identity/contact source rows`} />
          <SellerFlowFact icon="banknote" label="Proceeds destination" value={destination} note={changeEvent ? `${changeEvent.from} staled · ${destinationRows.length || 2} destination source rows` : `${destinationRows.length || 2} destination source rows captured`} />
          <SellerFlowFact icon="arrow-right" label="Release support" value={releaseGate?.status || actionState?.state || "Blocked"} note={releaseGate?.reason || actionState?.reason || "Current Review Record or exception required"} warn />
        </div>

        {changeEvent && (
          <div className="p-4">
            <div className="mb-2 flex items-center gap-2 text-[12.5px] font-medium text-ink">
              <Icon name="alert-circle" size={14} className="text-[var(--warn)]" />Material change after reliance
            </div>
            <div className="grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] sm:items-center">
              <div className="rounded-lg border border-line bg-surface/40 px-3 py-2.5">
                <div className="text-[10px] uppercase tracking-[0.06em] text-muted-foreground">Prior accepted state</div>
                <div className="mt-1 font-mono text-[12px] text-ink-soft">{changeEvent.from}</div>
                <div className="mt-1 text-[11px] text-muted-foreground">{staleRecord?.type || "Seller Proceeds Record"} {staleRecord?.version || "v1"}</div>
              </div>
              <div className="hidden justify-center text-ink-soft sm:flex"><Icon name="arrow-right" size={16} /></div>
              <div className="rounded-lg border border-[var(--warn)]/25 bg-[var(--warn-bg)]/60 px-3 py-2.5">
                <div className="text-[10px] uppercase tracking-[0.06em] text-muted-foreground">Changed destination</div>
                <div className="mt-1 font-mono text-[12px] text-ink">{changeEvent.to}</div>
                <div className="mt-1 text-[11px] text-[var(--warn)]">Requires {requiredRecord?.version || "v2"} record or manager exception</div>
              </div>
            </div>
          </div>
        )}

        <div className="p-4">
          <div className="mb-3 flex items-baseline justify-between gap-3">
            <div>
              <div className="text-[12.5px] font-medium text-ink">Source rows</div>
              <p className="mt-0.5 text-[11.5px] text-ink-soft">Rows are evidence and limitations, not a release verdict.</p>
            </div>
            <span className="font-mono text-[11px] text-muted-foreground">{displayRows.length} rows</span>
          </div>
          <div className="space-y-2.5">
            {displayRows.map((row) => (
              <SellerSourceRow key={row.id} row={row} />
            ))}
          </div>
        </div>

        <div className="grid grid-cols-1 divide-y divide-line sm:grid-cols-2 sm:divide-x sm:divide-y-0">
          <SellerRecordCard title="Stale record" record={staleRecord} tone="warn" />
          <SellerRecordCard title="Required record" record={requiredRecord} tone="ok" />
        </div>

        <div className="bg-surface/30 px-4 py-3">
          <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
            <p className="text-[11.5px] leading-relaxed text-ink-soft">
              {policy?.version || "POL-SP-DEST-v1.4"}: identity proofing, callback, and account/payee checks support the review path. They do not prove right-to-sell, account control, fraud absence, or transfer safety.
            </p>
            <div className="flex shrink-0 flex-wrap gap-2">
              <Link to="/file/$id/change-impact" params={{ id }} className="inline-flex items-center gap-1.5 rounded-md border border-line bg-background px-3 py-1.5 text-[12px] font-medium text-ink transition hover:bg-surface">
                Impact record
              </Link>
              <Link to="/tasks" className="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">
                Finish v2 review
              </Link>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function SellerFlowFact({ icon, label, value, note, warn }) {
  return (
    <div className="rounded-lg border border-line bg-background px-3 py-3">
      <div className="flex items-center gap-2 text-[10px] uppercase tracking-[0.06em] text-muted-foreground">
        <Icon name={icon} size={12} />{label}
      </div>
      <div className="mt-2 truncate text-[13px] font-medium text-ink">{value}</div>
      <div className={cn("mt-1 text-[11.5px] leading-snug", warn ? "text-[var(--warn)]" : "text-ink-soft")}>{note}</div>
    </div>
  );
}

function SellerSourceRow({ row }) {
  const status = row.status || row.result || "recorded";
  const value = row.value || row.displayValue || row.claim || row.label;
  const shows = row.shows || row.supports || "The source value recorded for review.";
  const doesNotShow = row.doesNotShow || row.doesNotSupport || row.limitationSummary || "It does not create release authority or prove transfer safety.";
  const needsReview = /open|needs|not_run|unable|low|conflict|mismatch/i.test(status);

  return (
    <div className="rounded-lg border border-line bg-background px-3 py-3">
      <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
        <div className="min-w-0">
          <div className="truncate text-[12.5px] font-medium text-ink">{row.label}</div>
          <div className="mt-0.5 font-mono text-[10.5px] text-muted-foreground">{row.id} · {row.type || row.sourceType || "source"}</div>
        </div>
        <div className="flex shrink-0 items-center gap-2">
          {row.restricted && <span className="rounded-full border border-line px-2 py-0.5 text-[10.5px] text-muted-foreground">Restricted</span>}
          <span className={cn("rounded-full px-2 py-0.5 text-[10.5px] font-medium", needsReview ? "bg-[var(--warn-bg)] text-[var(--warn)]" : "bg-[var(--ok-bg)] text-[var(--ok)]")}>{status}</span>
        </div>
      </div>
      <div className="mt-2 grid gap-1.5 text-[12px] leading-relaxed text-ink-soft">
        <div><span className="text-ink">Value</span> {value}</div>
        <div><span className="text-ink">Supports</span> {shows}</div>
        <div><span className="text-ink">Does not support</span> {doesNotShow}</div>
      </div>
    </div>
  );
}

function SellerRecordCard({ title, record, tone }) {
  if (!record) {
    return (
      <div className="p-4">
        <div className="text-[12.5px] font-medium text-ink">{title}</div>
        <p className="mt-1 text-[12px] text-ink-soft">No record fixture is available for this action.</p>
      </div>
    );
  }
  const isWarn = tone === "warn";
  return (
    <div className="p-4">
      <div className="flex items-start justify-between gap-3">
        <div>
          <div className="text-[12.5px] font-medium text-ink">{title}</div>
          <div className="mt-1 font-mono text-[11px] text-muted-foreground">{record.id} · {record.version || "record"}</div>
        </div>
        <span className={cn("rounded-full px-2 py-0.5 text-[10.5px] font-medium", isWarn ? "bg-[var(--warn-bg)] text-[var(--warn)]" : "bg-surface-2 text-ink-soft")}>{record.status}</span>
      </div>
      <p className="mt-3 text-[12px] leading-relaxed text-ink-soft">{record.acceptedState}</p>
      <div className="mt-3 grid gap-1.5 text-[11.5px] leading-relaxed text-ink-soft">
        <div><span className="text-ink">Supports</span> {record.supports}</div>
        <div><span className="text-ink">Does not support</span> {record.doesNotSupport}</div>
      </div>
    </div>
  );
}

function RailSection({ title, open, onToggle, children }) {
  return (
    <section className="mb-4 border-b border-line pb-4 last:border-b-0">
      <button type="button" onClick={onToggle} className="flex w-full items-center justify-between py-1 text-left">
        <span className="text-[11px] uppercase tracking-[0.06em] text-muted-foreground">{title}</span>
        <Icon name="chevron-down" size={14} className={cn("text-ink-soft transition-transform", open ? "" : "-rotate-90")} />
      </button>
      {open && <div className="mt-3">{children}</div>}
    </section>
  );
}

function CheckpointRow({ c, index, expanded, onToggle, onRecord, onReopen }) {
  if (c.state === "recorded") {
    return (
      <li className="rounded-xl border border-line bg-background">
        <button type="button" onClick={onToggle} className="flex w-full items-center gap-3 px-4 py-3 text-left">
          <span className="grid h-5 w-5 shrink-0 place-items-center rounded-full bg-[var(--ok-bg)] text-[var(--ok)]"><Icon name="check" size={12} strokeWidth={3} /></span>
          <span className="min-w-0 flex-1 truncate text-[13px] text-ink">{c.name}</span>
          <span className="hidden shrink-0 items-center gap-2 font-mono text-[11px] text-muted-foreground sm:flex">
            <span>{c.by?.split(" ")[0]}</span><span>·</span><span>{c.at}</span><span className="text-ink-soft">{c.hash}</span>
          </span>
          <Icon name="chevron-down" size={14} className={cn("shrink-0 text-ink-soft transition-transform", expanded ? "" : "-rotate-90")} />
        </button>
        {expanded && (
          <div className="border-t border-line px-4 py-3 pl-12">
            <p className="text-[12.5px] text-ink-soft">{c.note}</p>
            <div className="mt-2 flex flex-wrap items-center justify-between gap-3">
              <div className="flex flex-wrap items-center gap-x-4 gap-y-1 font-mono text-[11px] text-muted-foreground">
                <span>recorded by {c.by}</span><span>at {c.at}</span><span>sha {c.hash}</span>
              </div>
              <button type="button" onClick={onReopen} className="rounded-md border border-line px-2.5 py-1 text-[11.5px] font-medium text-ink transition hover:bg-surface">Edit record</button>
            </div>
          </div>
        )}
      </li>
    );
  }

  if (c.state === "waiting") {
    return (
      <li className="rounded-xl border border-dashed border-line/70 bg-surface/30 px-4 py-3">
        <div className="flex items-center gap-3">
          <span className="grid h-5 w-5 shrink-0 place-items-center rounded-full border border-line text-muted-foreground">
            <span className="font-mono text-[10px]">{index + 1}</span>
          </span>
          <span className="text-[13px] text-muted-foreground">{c.name}</span>
        </div>
      </li>
    );
  }

  // open
  return (
    <li className="rounded-xl border border-ink/30 bg-background shadow-[0_1px_0_rgba(0,0,0,0.02)] ring-1 ring-ink/5">
      <div className="px-4 py-4">
        <div className="flex items-start gap-3">
          <span className="mt-px grid h-5 w-5 shrink-0 place-items-center rounded-full border-[1.5px] border-ink text-ink">
            <span className="font-mono text-[10px]">{index + 1}</span>
          </span>
          <div className="min-w-0 flex-1">
            <div className="text-[13.5px] font-medium text-ink">{c.name}</div>
            <p className="mt-0.5 text-[12.5px] text-ink-soft">{c.note}</p>
            <div className="mt-3.5"><CheckpointInput c={c} onRecord={onRecord} /></div>
          </div>
        </div>
      </div>
    </li>
  );
}

/* The single inline input each Open checkpoint needs. No modal. */
function CheckpointInput({ c, onRecord }) {
  const [val, setVal] = useState("");
  const [file, setFile] = useState(false);
  const [checked, setChecked] = useState(false);
  const [signed, setSigned] = useState(false);

  if (c.input === "file") {
    return (
      <div className="flex flex-col gap-2.5 sm:flex-row sm:items-center">
        <label className={cn("flex flex-1 cursor-pointer items-center gap-2.5 rounded-lg border border-dashed px-3.5 py-3 text-[12.5px] transition",
          file ? "border-ink/30 bg-surface/60 text-ink" : "border-line text-ink-soft hover:border-ink/25 hover:bg-surface/40")}>
          <Icon name={file ? "file-text" : "paperclip"} size={15} className="shrink-0" />
          <span className="truncate">{file ? `${c.placeholder} · attached` : `Drop ${c.placeholder?.toLowerCase()} or browse`}</span>
          <input type="file" className="hidden" onChange={() => setFile(true)} />
        </label>
        <RecordButton disabled={!file} onClick={() => onRecord(file)} />
      </div>
    );
  }
  if (c.input === "amount") {
    return (
      <div className="flex items-center gap-2.5">
        <div className="relative flex-1">
          <span className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[13px] text-ink-soft">$</span>
          <input inputMode="decimal" value={val} onChange={(e) => setVal(e.target.value.replace(/[^0-9.,]/g, ""))} placeholder={c.placeholder}
            className="h-10 w-full rounded-lg border border-line bg-background pl-7 pr-3 text-[13.5px] tabular-nums text-ink outline-none transition placeholder:text-muted-foreground focus:border-ink/40 focus:ring-1 focus:ring-ink/15" />
        </div>
        <RecordButton disabled={!val} onClick={() => onRecord(!!val)} />
      </div>
    );
  }
  if (c.input === "text") {
    return (
      <div className="flex items-center gap-2.5">
        <input value={val} onChange={(e) => setVal(e.target.value.replace(/[^0-9]/g, "").slice(0, 4))} placeholder={c.placeholder} inputMode="numeric"
          className="h-10 flex-1 rounded-lg border border-line bg-background px-3 font-mono text-[13.5px] tabular-nums text-ink outline-none transition placeholder:font-sans placeholder:text-muted-foreground focus:border-ink/40 focus:ring-1 focus:ring-ink/15" />
        <RecordButton disabled={val.length < 4} onClick={() => onRecord(val.length >= 4)} />
      </div>
    );
  }
  if (c.input === "signature") {
    return (
      <div className="flex flex-col gap-2.5 sm:flex-row sm:items-center">
        <button type="button" onClick={() => setSigned(true)}
          className={cn("flex h-12 flex-1 items-center justify-center rounded-lg border text-[15px] transition",
            signed ? "border-ink/30 bg-surface/60" : "border-dashed border-line hover:border-ink/25 hover:bg-surface/40")}>
          {signed ? <span style={{ fontFamily: "'IBM Plex Mono', monospace" }} className="text-ink">Madeline Lane ✓</span>
                  : <span className="text-[12.5px] text-ink-soft">Click to sign</span>}
        </button>
        <RecordButton disabled={!signed} onClick={() => onRecord(signed)} />
      </div>
    );
  }
  // confirm
  return (
    <div className="flex flex-col gap-2.5 sm:flex-row sm:items-center">
      <button type="button" onClick={() => setChecked((v) => !v)}
        className={cn("flex flex-1 items-center gap-2.5 rounded-lg border px-3.5 py-3 text-left text-[12.5px] transition",
          checked ? "border-ink/30 bg-surface/60 text-ink" : "border-line text-ink-soft hover:border-ink/25 hover:bg-surface/40")}>
        <span className={cn("grid h-4 w-4 shrink-0 place-items-center rounded border transition", checked ? "border-ink bg-ink text-background" : "border-line")}>
          {checked && <Icon name="check" size={10} strokeWidth={3} />}
        </span>
        <span>{c.confirmLabel}</span>
      </button>
      <RecordButton disabled={!checked} onClick={() => onRecord(checked)} />
    </div>
  );
}

function RecordButton({ disabled, onClick }) {
  return (
    <button type="button" disabled={disabled} onClick={onClick}
      className={cn("h-10 shrink-0 rounded-lg px-4 text-[12.5px] font-medium transition",
        disabled ? "cursor-not-allowed bg-surface-2 text-muted-foreground" : "bg-ink text-background hover:bg-ink/90")}>
      Record
    </button>
  );
}

Object.assign(window, { FileHub, CHECKPOINTS, RECEIPT_HREF });
