/* =========================================================================
   Veto · shared lib  (ported from src/lib + TanStack router shims)
   Exposes: cn, hrefFor, navigate, Link, useHashRoute, matchRoute,
            useNavCollapsed, useLocalList,
            CASE_FILES, REVIEWERS, LINE_LABEL, getReadiness,
            buildReadinessFrom, deriveRecording
   ========================================================================= */
const { useState, useEffect, useCallback, useMemo, useRef, createContext, useContext } = React;

/* ----------------------------------------------------------------- cn */
function cn(...args) {
  const out = [];
  const walk = (a) => {
    if (!a) return;
    if (typeof a === "string" || typeof a === "number") out.push(a);
    else if (Array.isArray(a)) a.forEach(walk);
    else if (typeof a === "object") for (const k in a) if (a[k]) out.push(k);
  };
  args.forEach(walk);
  return out.join(" ");
}

/* ------------------------------------------------------- router shims */
function hrefFor(to, params) {
  let p = to || "/";
  if (params) for (const k in params) p = p.replace("$" + k, encodeURIComponent(params[k]));
  return "#" + p;
}
function navigate(to, params) {
  const href = typeof to === "string" ? hrefFor(to, params) : hrefFor(to.to, to.params);
  window.location.hash = href.slice(1);
}

async function copyText(value) {
  const text = String(value || "");
  if (!text) return false;
  try {
    if (navigator.clipboard?.writeText) {
      await navigator.clipboard.writeText(text);
      return true;
    }
  } catch {}
  try {
    const el = document.createElement("textarea");
    el.value = text;
    el.setAttribute("readonly", "");
    el.style.position = "fixed";
    el.style.left = "-9999px";
    document.body.appendChild(el);
    el.select();
    const ok = document.execCommand("copy");
    document.body.removeChild(el);
    return ok;
  } catch {
    return false;
  }
}

function shareUrlFor(to, params) {
  return window.location.href.split("#")[0] + hrefFor(to, params);
}

/* TanStack <Link to params> → <a href="#/..."> */
function Link({ to, params, children, className, onClick, ...rest }) {
  const href = hrefFor(to, params);
  return (
    <a
      href={href}
      className={className}
      onClick={(e) => { if (onClick) onClick(e); }}
      {...rest}
    >
      {children}
    </a>
  );
}

function currentPath() {
  const h = window.location.hash.replace(/^#/, "");
  return h || "/";
}
function useHashRoute() {
  const [path, setPath] = useState(currentPath);
  useEffect(() => {
    const on = () => { setPath(currentPath()); window.scrollTo(0, 0); };
    window.addEventListener("hashchange", on);
    return () => window.removeEventListener("hashchange", on);
  }, []);
  return path;
}
/* match "/file/$id" against "/file/SP-0214" → { id: "SP-0214" } or null */
function matchRoute(pattern, path) {
  const pp = pattern.split("/").filter(Boolean);
  const xp = path.split("/").filter(Boolean);
  if (pp.length !== xp.length) return null;
  const params = {};
  for (let i = 0; i < pp.length; i++) {
    if (pp[i].startsWith("$")) params[pp[i].slice(1)] = decodeURIComponent(xp[i]);
    else if (pp[i] !== xp[i]) return null;
  }
  return params;
}

/* ------------------------------------------------------ persisted nav */
const NAV_KEY = "veto.nav.collapsed";
const NAV_EVENT = "veto:nav-collapsed";
function readNav() { try { return localStorage.getItem(NAV_KEY) === "1"; } catch { return false; } }
function useNavCollapsed() {
  const [collapsed, setCollapsed] = useState(readNav);
  useEffect(() => {
    const sync = () => setCollapsed(readNav());
    const onStorage = (e) => { if (e.key === NAV_KEY) sync(); };
    window.addEventListener("storage", onStorage);
    window.addEventListener(NAV_EVENT, sync);
    sync();
    return () => { window.removeEventListener("storage", onStorage); window.removeEventListener(NAV_EVENT, sync); };
  }, []);
  const toggle = useCallback(() => {
    const next = !readNav();
    try { localStorage.setItem(NAV_KEY, next ? "1" : "0"); } catch {}
    window.dispatchEvent(new Event(NAV_EVENT));
  }, []);
  return [collapsed, toggle];
}

/* simple persisted list store (bookmarks / recents) */
function useLocalList(key) {
  const read = () => { try { return JSON.parse(localStorage.getItem(key) || "[]"); } catch { return []; } };
  const [list, setList] = useState(read);
  useEffect(() => {
    const onSync = (e) => { if (!e.detail || e.detail.key === key) setList(read()); };
    window.addEventListener("veto:locallist", onSync);
    return () => window.removeEventListener("veto:locallist", onSync);
  }, [key]);
  const write = (next) => {
    try { localStorage.setItem(key, JSON.stringify(next)); } catch {}
    setList(next);
    window.dispatchEvent(new CustomEvent("veto:locallist", { detail: { key } }));
  };
  const remove = useCallback((id) => { write(read().filter((x) => x.id !== id)); }, [key]);
  const add = useCallback((item) => { const cur = read(); if (!cur.some((x) => x.id === item.id)) write([...cur, item]); }, [key]);
  const toggle = useCallback((item) => {
    const cur = read();
    write(cur.some((x) => x.id === item.id) ? cur.filter((x) => x.id !== item.id) : [...cur, item]);
  }, [key]);
  const has = useCallback((id) => list.some((x) => x.id === id), [list]);
  return { list, remove, add, toggle, has };
}

/* ============================================================ FIXTURES */
const LINE_LABEL = { seller: "Seller Proceeds", buyer: "Buyer Funding", payoff: "Payoff Demand" };

function signed(id, party, property, when, line, receiptHref) {
  const empty = { status: "Not opened" };
  const s = { status: "Signed", when, reviewer: "Madeline Lane", receiptHref, receiptParams: { id } };
  return {
    id, party, property, closing: when, reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: { seller: line === "seller" ? s : empty, buyer: line === "buyer" ? s : empty, payoff: line === "payoff" ? s : empty },
  };
}

const CASE_FILES = {
  "SP-0214": {
    id: "SP-0214", party: "James Whitfield",
    property: "4125 Lakeview Canyon Rd, Westlake Village CA 91362",
    closing: "May 22, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    kind: "entity", kindLabel: "Officer-linked entity",
    lines: {
      seller: { status: "In review", opened: "May 21", reviewer: "Madeline Lane", href: "/" },
      buyer: { status: "Not opened" },
      payoff: { status: "In review", opened: "May 20", reviewer: "Madeline Lane", href: "/payoff" },
    },
  },
  "SP-0213": {
    id: "SP-0213", party: "Adaeze Okafor", property: "412 Marigold Ave, Long Beach CA 90803",
    closing: "May 23, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "In review", opened: "May 21", reviewer: "Madeline Lane", href: "/" },
      buyer: { status: "Not opened" }, payoff: { status: "Not opened" },
    },
  },
  "SP-0211": {
    id: "SP-0211", party: "Hiroshi Tanaka", property: "2208 Westcliff Dr, Westlake Village CA 91362",
    closing: "May 27, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "In review", opened: "May 22", reviewer: "Madeline Lane", href: "/" },
      buyer: { status: "Not opened" }, payoff: { status: "Not opened" },
    },
  },
  "BF-0318": {
    id: "BF-0318", party: "Priya Mehta", property: "85 Linden Pl #4, Pasadena CA 91103",
    closing: "May 22, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "Not opened" },
      buyer: { status: "In review", opened: "May 21", reviewer: "Madeline Lane", href: "/buyer-funding" },
      payoff: { status: "Not opened" },
    },
  },
  "BF-0317": {
    id: "BF-0317", party: "Adaeze Okafor", property: "412 Marigold Ave, Long Beach CA 90803",
    closing: "May 24, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "Not opened" },
      buyer: { status: "In review", opened: "May 22", reviewer: "Madeline Lane", href: "/buyer-funding" },
      payoff: { status: "Not opened" },
    },
  },
  "BF-0315": {
    id: "BF-0315", party: "Hiroshi Tanaka", property: "2208 Westcliff Dr, Westlake Village CA 91362",
    closing: "May 28, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "Not opened" },
      buyer: { status: "In review", opened: "May 23", reviewer: "Madeline Lane", href: "/buyer-funding" },
      payoff: { status: "Not opened" },
    },
  },
  "RF-0142": {
    id: "RF-0142", party: "Hiroshi Tanaka", property: "2208 Westcliff Dr, Westlake Village CA 91362",
    closing: "May 28, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "Not opened" }, buyer: { status: "Not opened" },
      payoff: { status: "In review", opened: "May 22", reviewer: "Madeline Lane", href: "/payoff" },
    },
  },
  "SP-0209": signed("SP-0209", "Liam Brennan", "1408 Garnet Way, Camarillo CA 93010", "May 19", "seller", "/file/$id/receipt"),
  "SP-0207": signed("SP-0207", "Sofia Alvarez", "770 Crescent Bay Dr, Santa Barbara CA 93103", "May 16", "seller", "/file/$id/receipt"),
  "SP-0204": signed("SP-0204", "Ji-won Park", "63 Harbor Light Ln, Camarillo CA 93012", "May 14", "seller", "/file/$id/receipt"),
  "BF-0312": signed("BF-0312", "Liam Brennan", "1408 Garnet Way, Camarillo CA 93010", "May 19", "buyer", "/file/$id/buyer-receipt"),
  "BF-0309": signed("BF-0309", "Sofia Alvarez", "770 Crescent Bay Dr, Santa Barbara CA 93103", "May 16", "buyer", "/file/$id/buyer-receipt"),
  "BF-0306": signed("BF-0306", "Ji-won Park", "63 Harbor Light Ln, Camarillo CA 93012", "May 14", "buyer", "/file/$id/buyer-receipt"),
};

const REVIEWERS = {
  ML: { initials: "ML", name: "Madeline Lane", role: "officer", roleLabel: "Escrow officer",
    office: "805 Escrow · Westlake Village", email: "madeline.lane@pacificcoast.example",
    openFiles: ["SP-0214", "SP-0213", "SP-0211", "BF-0318", "BF-0317", "BF-0315", "RF-0142"],
    signedReceipts: ["SP-0209", "SP-0207", "SP-0204", "BF-0312", "BF-0309", "BF-0306"] },
  JR: { initials: "JR", name: "Javier Reyes", role: "manager", roleLabel: "Branch manager",
    office: "805 Escrow · Westlake Village", email: "javier.reyes@pacificcoast.example",
    openFiles: [], signedReceipts: ["SP-0209", "SP-0207"] },
  OP: { initials: "OP", name: "Office Principal", role: "owner", roleLabel: "Owner / Principal",
    office: "805 Escrow · Westlake Village", email: "owner@805escrow.example",
    openFiles: ["SP-0214", "SP-0213", "SP-0211", "BF-0318", "BF-0317", "BF-0315", "RF-0142"],
    signedReceipts: [] },
  AK: { initials: "AK", name: "Anita Kuroda", role: "restricted", roleLabel: "Restricted reviewer · read-only",
    office: "805 Escrow · Westlake Village", email: "anita.kuroda@pacificcoast.example",
    openFiles: [], signedReceipts: [] },
};

/* ============================================================ READINESS */
function deriveRecording(lines, holds) {
  const openHolds = holds.filter((h) => h.state !== "Cleared");
  const blocking = lines.filter((l) => l.status !== "Ready to sign" && l.status !== "Recorded");
  if (openHolds.length === 0 && blocking.length === 0)
    return { state: "Clear to record", summary: "All three lines ready. No open holds.", blockingLines: [], openHolds: 0 };
  if (openHolds.length > 0) {
    const word = openHolds.length === 1 ? "hold" : "holds";
    return { state: "Holds open", summary: `${openHolds.length} ${word} to cure before recording.`,
      blockingLines: blocking.map((l) => l.key), openHolds: openHolds.length };
  }
  const names = blocking.map((l) => LINE_LABEL[l.key]).join(", ");
  return { state: "Evidence open", summary: `Evidence still open on ${names}.`, blockingLines: blocking.map((l) => l.key), openHolds: 0 };
}

const RAW = {
  "SP-0214": {
    fileNo: "SP-0214", closing: "May 22, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "Ready to sign", blocker: null, owner: "Madeline Lane", due: null, evidence: { resolved: 28, total: 28 }, lastChange: "May 21, 3:40 pm" },
      buyer: { status: "In review", blocker: "Source-of-funds narrative missing one transfer leg", owner: "Madeline Lane", due: "May 22", evidence: { resolved: 9, total: 12 }, lastChange: "May 21, 2:10 pm" },
      payoff: { status: "Awaiting evidence", blocker: "Beneficiary callback pending", owner: "Javier Reyes", due: "May 22", evidence: { resolved: 4, total: 7 }, lastChange: "May 21, 4:12 pm", blockerConditionId: "payoff.independent.confirmation" },
    },
    holds: [
      { id: "HOLD-014", line: "payoff", raised: "May 21, 4:12 pm", owner: "Javier Reyes", cure: "Confirm beneficiary phone against office contact file.", state: "Open" },
      { id: "HOLD-011", line: "buyer", raised: "May 20, 11:08 am", owner: "Madeline Lane", cure: "Add missing transfer leg to source-of-funds narrative.", state: "In cure" },
    ],
  },
  "SP-0213": {
    fileNo: "SP-0213", closing: "May 23, 2026", reviewer: "Madeline Lane", reviewerInitials: "ML",
    lines: {
      seller: { status: "In review", blocker: "Final settlement statement not received", owner: "Madeline Lane", due: "May 22", evidence: { resolved: 22, total: 28 }, lastChange: "May 21, 10:02 am" },
      buyer: { status: "Ready to sign", blocker: null, owner: "Madeline Lane", due: null, evidence: { resolved: 12, total: 12 }, lastChange: "May 20, 5:18 pm" },
      payoff: { status: "In review", blocker: "Demand current through May 24 · short of closing", owner: "Javier Reyes", due: "May 22", evidence: { resolved: 5, total: 7 }, lastChange: "May 19, 1:44 pm", blockerConditionId: "payoff.completeness.math" },
    },
    holds: [],
  },
  "SP-0211": {
    fileNo: "SP-0211", closing: "May 27, 2026", reviewer: "Javier Reyes", reviewerInitials: "JR",
    lines: {
      seller: { status: "Awaiting evidence", blocker: "Trust certification not on file", owner: "Javier Reyes", due: "May 24", evidence: { resolved: 15, total: 28 }, lastChange: "May 22, 9:30 am" },
      buyer: { status: "Awaiting evidence", blocker: "Buyer wire scheduled May 24", owner: "Javier Reyes", due: "May 24", evidence: { resolved: 4, total: 12 }, lastChange: "May 21, 6:02 pm" },
      payoff: { status: "In review", blocker: null, owner: "Javier Reyes", due: null, evidence: { resolved: 6, total: 7 }, lastChange: "May 21, 11:20 am" },
    },
    holds: [{ id: "HOLD-018", line: "seller", raised: "May 22, 9:30 am", owner: "Javier Reyes", cure: "Successor trustee letter from outside counsel.", state: "Open" }],
  },
};

const LINE_ORDER = ["seller", "buyer", "payoff"];
function getReadiness(fileNo) {
  const raw = RAW[fileNo];
  if (!raw) return null;
  const lines = LINE_ORDER.map((key) => ({ key, ...raw.lines[key] }));
  return { fileNo: raw.fileNo, closing: raw.closing, reviewer: raw.reviewer, reviewerInitials: raw.reviewerInitials,
    lines, holds: raw.holds, recording: deriveRecording(lines, raw.holds) };
}
function buildReadinessFrom(fileNo, reviewer, reviewerInitials, closing) {
  const lines = LINE_ORDER.map((key) => ({ key, status: "Awaiting evidence", blocker: "Not opened", owner: reviewer, due: null, evidence: { resolved: 0, total: 0 }, lastChange: "Not yet" }));
  return { fileNo, closing, reviewer, reviewerInitials, lines, holds: [], recording: deriveRecording(lines, []) };
}

/* ------------------------------------------------------ file helpers */
/* A file is identified by its property address; file number is secondary. */
function streetOf(property) { return (property || "").split(",")[0].trim(); }
function cityOf(property) {
  const parts = (property || "").split(",");
  return parts.length > 1 ? parts.slice(1).join(",").trim() : "";
}
function lineLabelOf(key) { return LINE_LABEL[key] || key; }

/* Which line is the "active" review for a file (first In review, else first Signed). */
function activeLineKey(cf) {
  const order = ["payoff", "buyer", "seller"];
  let inReview = null, signed = null;
  for (const k of order) {
    const l = cf.lines[k];
    if (l.status === "In review" && !inReview) inReview = k;
    if (l.status === "Signed" && !signed) signed = k;
  }
  return inReview || signed || "seller";
}
function fileStatusLabel(cf) {
  const states = Object.values(cf.lines).map((l) => l.status);
  if (states.includes("In review")) return "In review";
  if (states.includes("Signed")) return "Recorded";
  return "Not opened";
}

/* All files as rows for /files (includes signed/closed), recent activity desc. */
function allFilesRows() {
  const order = Object.keys(CASE_FILES);
  return order.map((id, i) => {
    const cf = CASE_FILES[id];
    const active = activeLineKey(cf);
    return {
      id, street: streetOf(cf.property), city: cityOf(cf.property), party: cf.party,
      line: active, lineLabel: lineLabelOf(active), status: fileStatusLabel(cf),
      closing: cf.closing, activityRank: i,
    };
  });
}

/* Files whose given line is open or signed — the line queue. */
function lineQueueRows(lineKey) {
  const rows = [];
  for (const [id, cf] of Object.entries(CASE_FILES)) {
    const l = cf.lines[lineKey];
    if (!l || l.status === "Not opened") continue;
    rows.push({ id, street: streetOf(cf.property), city: cityOf(cf.property), party: cf.party,
      status: l.status === "Signed" ? "Recorded" : l.status, when: l.when || l.opened || cf.closing,
      closing: cf.closing, reviewer: cf.reviewer, reviewerInitials: cf.reviewerInitials });
  }
  return rows;
}

/* Recents for the sidebar — open files first, by file order. */
function recentFiles(n) {
  const open = REVIEWERS.ML.openFiles.map((id) => ({ id, name: streetOf(CASE_FILES[id]?.property || id) }));
  return open.slice(0, n || 6);
}
/* Seeded bookmarks so the section reads as real. */
const DEFAULT_BOOKMARKS = [
  { id: "SP-0214", name: "4125 Lakeview Canyon Rd" },
  { id: "BF-0318", name: "85 Linden Pl #4" },
];
/* Seed the bookmark store once so pinning/unpinning works against a real list. */
try { if (localStorage.getItem("veto.bookmarks") == null) localStorage.setItem("veto.bookmarks", JSON.stringify(DEFAULT_BOOKMARKS)); } catch {}

function shortClosing(s) { const parts = (s || "").split(","); return (parts[0] ?? s ?? "").trim(); }

/* ============================================================ MONEY MODEL
   A file is the escrow control record. Inside it: money movements —
   DEPOSITS (money in) and DISBURSEMENTS (money out). Each movement carries
   its own review workflow (checkpoints) and produces a receipt.
   A file is clear to close only when in == out and every movement is recorded.
   ========================================================================= */
function fmtUSD(n) {
  return "$" + Math.round(n).toLocaleString("en-US");
}

/* line: which checkpoint workflow a movement uses (buyer/seller/payoff) or null (fee/admin). */
const FILE_MOVEMENTS = {
  "SP-0211": {
    deposits: [
      { id: "m1", label: "Buyer earnest money", party: "Hiroshi Tanaka", amount: 55000, line: "buyer", status: "Recorded" },
      { id: "m2", label: "Buyer closing funds", party: "Hiroshi Tanaka", amount: 372140, line: "buyer", status: "In review" },
      { id: "m3", label: "Lender wire", party: "First Republic · loan #5521", amount: 1480000, line: "buyer", status: "Not started" },
    ],
    disbursements: [
      { id: "m4", label: "Payoff · 1st lien", party: "Wells Fargo Home Mortgage", amount: 812460, line: "payoff", status: "In review" },
      { id: "m5", label: "Seller proceeds", party: "Hiroshi Tanaka", amount: 1029450, line: "seller", status: "In review" },
      { id: "m6", label: "Broker commissions", party: "Listing + buyer brokers", amount: 46250, line: null, status: "Not started" },
      { id: "m7", label: "Title & escrow fees", party: "805 Escrow", amount: 9100, line: null, status: "Not started" },
      { id: "m8", label: "Recording & transfer tax", party: "Ventura County", amount: 9880, line: null, status: "Not started" },
    ],
  },
};

/* Each property has its own closing value, so the same address never shows two
   different files with an identical amount. Drives every screen consistently. */
const PROPERTY_VALUE = {
  "4125 Lakeview Canyon Rd": 612400,
  "412 Marigold Ave": 845000,
  "2208 Westcliff Dr": 1907140,
  "85 Linden Pl #4": 469500,
  "1408 Garnet Way": 738200,
  "770 Crescent Bay Dr": 1284600,
  "63 Harbor Light Ln": 996300,
};
function propValue(cf) { return PROPERTY_VALUE[streetOf(cf.property)] || 540000; }

function genericMovements(cf) {
  // Fallback: derive a plausible ledger from which lines are opened.
  // The settlement statement always balances: seller proceeds is the plug,
  // so money in always equals money out (just like a real escrow).
  const deposits = [], disbursements = [];
  const has = (k) => cf.lines[k] && cf.lines[k].status !== "Not opened";
  const st = (k) => (cf.lines[k]?.status === "Signed" ? "Recorded" : cf.lines[k]?.status === "In review" ? "In review" : "Not started");
  const val = propValue(cf);
  const earnest = Math.round((val * 0.05) / 1000) * 1000;
  if (has("buyer")) {
    deposits.push({ id: "d1", label: "Buyer earnest money", party: cf.party, amount: earnest, line: "buyer", status: "Recorded" });
    deposits.push({ id: "d2", label: "Buyer closing funds", party: cf.party, amount: val - earnest, line: "buyer", status: st("buyer") });
  } else {
    deposits.push({ id: "d2", label: "Buyer closing funds", party: cf.party, amount: val, line: "buyer", status: "Recorded" });
  }
  const totalIn = deposits.reduce((n, x) => n + x.amount, 0);
  const fees = Math.round((val * 0.025) / 100) * 100;
  const payoffAmt = has("payoff") ? Math.round((val * 0.52) / 1000) * 1000 : 0;
  if (has("payoff")) disbursements.push({ id: "p1", label: "Payoff · 1st lien", party: "Servicer of record", amount: payoffAmt, line: "payoff", status: st("payoff") });
  // Seller proceeds is the balancing figure — whatever is left after liens and fees.
  disbursements.push({ id: "s1", label: "Seller proceeds", party: cf.party, amount: totalIn - payoffAmt - fees, line: "seller", status: has("seller") ? st("seller") : "Not started" });
  disbursements.push({ id: "f1", label: "Commissions & fees", party: "Brokers · escrow · county", amount: fees, line: null, status: "Recorded" });
  return { deposits, disbursements };
}

function fileMovements(id) {
  const cf = CASE_FILES[id];
  if (!cf) return { deposits: [], disbursements: [], totalIn: 0, totalOut: 0, net: 0 };
  const m = FILE_MOVEMENTS[id] || genericMovements(cf);
  const totalIn = m.deposits.reduce((n, x) => n + x.amount, 0);
  const totalOut = m.disbursements.reduce((n, x) => n + x.amount, 0);
  return { ...m, totalIn, totalOut, net: totalIn - totalOut };
}

/* Map a workflow line to its receipt route. */
const MOVEMENT_RECEIPT = { seller: "/file/$id/receipt", buyer: "/file/$id/buyer-receipt", payoff: "/file/$id/payoff-receipt" };

/* ====================================================== CONTROL GRAPH
   Veto Core in miniature: a controlled action, the policy that governs it,
   the source rows that support the accepted state, the material change that
   stales the prior record, and the release gate that keeps the support path
   blocked until v2 or a manager exception is recorded. */
const VETO_CORE_PRIMITIVES = [
  { key: "guard", label: "Guard", surface: "Settings / Risk & Controls", meaning: "Policy controls that decide whether an action is recordable, blocked, held, or exceptioned." },
  { key: "sources", label: "Sources", surface: "Evidence", meaning: "Source rows with claims and visible limitations." },
  { key: "checks", label: "Checks", surface: "Evidence", meaning: "Narrow verification inputs. A check is evidence, not permission." },
  { key: "queue", label: "Queue", surface: "Tasks", meaning: "Policy-generated work required before an office action can proceed." },
  { key: "proof", label: "Proof", surface: "Records", meaning: "Durable action records, exceptions, holds, and audit packets." },
  { key: "funds", label: "Funds", surface: "Funds", meaning: "The money-control domain: funding in, payoffs, proceeds, releases, activity, and matches." },
  { key: "signals", label: "Signals", surface: "Alerts / Home", meaning: "Events such as material changes, stale records, expiring release codes, and unmatched outgoing activity." },
  { key: "vault", label: "Vault", surface: "Evidence / Restricted Details", meaning: "Restricted fields and raw provider responses behind access logs." },
  { key: "releases", label: "Releases", surface: "Funds / Release Requests", meaning: "The pre-wire gate that binds an outgoing money request to a current record or exception." },
  { key: "matches", label: "Matches", surface: "Funds / Activity", meaning: "Post-action comparison of actual money movement to the release-supporting record." },
  { key: "standard", label: "Standard", surface: "Risk & Controls", meaning: "A practical control framework, not a safety certification." },
];

const POLICY_CONTROLS = {
  "seller.destination.v2": {
    id: "seller.destination.v2",
    name: "Seller proceeds destination change requires v2 record",
    category: "Seller Proceeds",
    actionControlled: "Release seller proceeds",
    trigger: "Destination account or payee changes after a Seller Proceeds Record is signed.",
    scope: "All seller proceeds releases over $25,000 and every trust, entity, POA, or last-minute destination change.",
    requiredEvidence: [
      "Updated seller instruction",
      "Current settlement statement",
      "Account / payee source check",
      "Independent callback to seller contact on file",
    ],
    requiredSourceLimitations: [
      "Account / payee check supports account existence and payee match only.",
      "Callback supports the read-back from the reached party only.",
      "Settlement statement supports the current amount reviewed, not authority to redirect proceeds.",
    ],
    effect: "Require v2 record",
    exceptionPath: "Manager exception with limitation, expiry, and second reviewer if the release remains time-sensitive.",
    approverRole: "Escrow Manager",
    receiptVisibility: "Show policy version, changed value, relied-on sources, and limitations.",
    version: "POL-SP-DEST-v1.4",
    effectiveDate: "May 1, 2026",
    changedBy: "Javier Reyes",
    core: true,
    downgradeLogged: true,
  },
};

const SOURCE_ROWS = {
  "SP-0214:seller-proceeds-release": [
    {
      id: "SRC-SP0214-01",
      type: "Seller instruction",
      label: "Updated seller proceeds instruction",
      claim: "Seller requested proceeds to Whitfield Coast Holdings LLC at Chase account ending 4431.",
      value: "Whitfield Coast Holdings LLC · Chase ••4431",
      status: "Current",
      reviewer: "Madeline Lane",
      timestamp: "May 21, 3:18 pm",
      shows: "the instruction received by the office and the payee entity the reviewer accepted for review",
      doesNotShow: "that the entity is entitled to proceeds, that the account is controlled by the seller, or that the office is legally required to release there",
      restricted: true,
    },
    {
      id: "SRC-SP0214-02",
      type: "Settlement statement",
      label: "Final settlement statement",
      claim: "Net seller proceeds remain $182,742.",
      value: "$182,742",
      status: "Current",
      reviewer: "Madeline Lane",
      timestamp: "May 21, 3:22 pm",
      shows: "the proceeds amount reviewed against the current settlement statement",
      doesNotShow: "authority to redirect proceeds to an entity payee or the receiving account's control",
      restricted: false,
    },
    {
      id: "SRC-SP0214-03",
      type: "Account / payee check",
      label: "Account and payee source check",
      claim: "Account exists; payee name on file is Whitfield Coast Holdings LLC.",
      value: "Entity payee · account exists",
      status: "Needs review",
      reviewer: "Madeline Lane",
      timestamp: "May 21, 3:31 pm",
      shows: "account existence and payee-name match returned by the check provider",
      doesNotShow: "live bank confirmation, account control, or authority to receive funds",
      restricted: true,
    },
    {
      id: "SRC-SP0214-04",
      type: "Independent callback",
      label: "Seller callback on number of record",
      claim: "James Whitfield read back entity payee and Chase account ending 4431.",
      value: "Pending callback",
      status: "Open",
      reviewer: "Madeline Lane",
      timestamp: "May 21, 3:35 pm",
      shows: "what was read back during the call once recorded",
      doesNotShow: "identity beyond the call procedure or legal authority to name an entity payee",
      restricted: false,
    },
    {
      id: "SRC-SP0214-05",
      type: "Business entity lookup",
      label: "Secretary of State — business entity",
      claim: "CA SOS record for payee LLC",
      value: "Whitfield Coast Holdings LLC · Managing member: M. Lane",
      status: "Mismatch",
      reviewer: "System",
      timestamp: "May 21, 3:36 pm",
      shows: "Payee is a registered CA LLC; managing member of record is Madeline Lane.",
      doesNotShow: "intent, account control, release authority, or transfer safety",
      restricted: false,
    },
  ],
};

const CHANGE_EVENTS = {
  "SP-0214": [
    {
      id: "CHG-SP0214-DEST",
      fileId: "SP-0214",
      actionKey: "seller-proceeds-release",
      action: "Release seller proceeds",
      field: "Seller proceeds payee",
      from: "James Whitfield · Schwab ••9876",
      to: "Whitfield Coast Holdings LLC · Chase ••4431",
      sourceId: "SRC-SP0214-01",
      detectedAt: "May 21, 3:18 pm",
      age: "42m",
      staledRecordId: "REC-SP0214-v1",
      staledRecordLabel: "Seller Proceeds Record v1",
      policyId: "seller.destination.v2",
      requiredNextStep: "Create Seller Proceeds Record v2 or request owner exception after conflict review.",
      status: "Blocked",
    },
  ],
};

const PROCEEDS_RECORDS = {
  "SP-0214:seller-proceeds-release": [
    {
      id: "REC-SP0214-v1",
      fileId: "SP-0214",
      actionKey: "seller-proceeds-release",
      type: "Seller Proceeds Record",
      version: "v1",
      status: "Signed · Superseded",
      signedBy: "Madeline Lane",
      signedAt: "May 21, 2:46 pm",
      acceptedState: "Seller proceeds to James Whitfield · Schwab ••9876 for $182,742.",
      policyId: "seller.destination.v2",
      supports: "the office review of seller proceeds to the individual seller before the entity payee change",
      doesNotSupport: "release to Whitfield Coast Holdings LLC or any release after the payee changed",
      sourceIds: ["SRC-SP0214-02"],
    },
    {
      id: "REC-SP0214-v2",
      fileId: "SP-0214",
      actionKey: "seller-proceeds-release",
      type: "Seller Proceeds Record",
      version: "v2",
      status: "Draft",
      signedBy: null,
      signedAt: null,
      acceptedState: "Seller proceeds to Whitfield Coast Holdings LLC · Chase ••4431 for $182,742.",
      policyId: "seller.destination.v2",
      supports: "a new office review of the entity payee and destination if all required source rows are accepted and the conflict signal is resolved",
      doesNotSupport: "live bank confirmation, account control, legal advice, or proof of intent",
      sourceIds: ["SRC-SP0214-01", "SRC-SP0214-02", "SRC-SP0214-03", "SRC-SP0214-04", "SRC-SP0214-05"],
    },
  ],
};

const EXCEPTION_RECORDS = {
  "SP-0214:seller-proceeds-release": [
    {
      id: "EXC-SP0214-01",
      fileId: "SP-0214",
      actionKey: "seller-proceeds-release",
      status: "Available path",
      approverRole: "Escrow Manager",
      limitationAccepted: "Destination changed after reliance and callback remains open.",
      expires: "May 21, 5:30 pm",
      note: "Exception would permit a time-limited release request with the limitation visible on the record.",
    },
  ],
};

const RELEASE_REQUESTS = {
  "SP-0214:seller-proceeds-release": {
    id: "REL-SP0214-01",
    fileId: "SP-0214",
    actionKey: "seller-proceeds-release",
    action: "Release seller proceeds",
    amount: 182742,
    payee: "Whitfield Coast Holdings LLC",
    destination: "Chase ••4431",
    status: "Blocked",
    reason: "Payout destination names an entity whose managing member of record matches the acting officer on this file.",
    requiredRecordId: "REC-SP0214-v2",
    exceptionAllowed: true,
    policyId: "seller.destination.v2",
    conflictPolicyId: "officer.conflict.v1",
  },
};

const OFFICE_ACTIONS = [
  {
    id: "ACT-SP0214-SELLER-RELEASE",
    fileId: "SP-0214",
    actionKey: "seller-proceeds-release",
    action: "Release seller proceeds",
    domain: "Money out",
    amount: 182742,
    party: "James Whitfield",
    state: "Blocked",
    reason: "Payout destination names an entity whose managing member of record matches the acting officer on this file.",
    currentRecordId: "REC-SP0214-v2",
    priorRecordId: "REC-SP0214-v1",
    changeId: "CHG-SP0214-DEST",
    policyId: "seller.destination.v2",
    owner: "Madeline Lane",
    ownerInitials: "ML",
    next: ["Create v2 record", "Request owner exception"],
    taskId: "t-change-sp0214",
    freshness: "42m",
    route: "/tasks",
  },
];

const ALERT_SIGNALS = [
  {
    id: "SIG-SP0214-CONFLICT",
    fileId: "SP-0214",
    level: "Blocker",
    title: "Payout destination linked to the acting officer",
    state: "Converted to task",
    routedTo: "Review Register",
    taskId: "t-change-sp0214",
    createdAt: "May 21, 3:18 pm",
    resolvedBy: null,
  },
];

function controlledActions() {
  return OFFICE_ACTIONS.map((a) => ({ ...a }));
}
function getActionState(fileId, actionKey) {
  return OFFICE_ACTIONS.find((a) => a.fileId === fileId && a.actionKey === actionKey) || null;
}
function getChangeEvents(fileId) {
  return (CHANGE_EVENTS[fileId] || []).map((c) => ({ ...c }));
}
function getPolicyControl(controlId) {
  return POLICY_CONTROLS[controlId] || null;
}
function getSourceRows(fileId, actionKey) {
  return (SOURCE_ROWS[`${fileId}:${actionKey}`] || []).map((s) => ({ ...s }));
}
function getRecordsForAction(fileId, actionKey) {
  return (PROCEEDS_RECORDS[`${fileId}:${actionKey}`] || []).map((r) => ({ ...r }));
}
function getCurrentRecord(fileId, actionKey) {
  const records = getRecordsForAction(fileId, actionKey);
  return records.find((r) => r.status === "Signed · Active") || records.find((r) => r.status === "Draft") || records[0] || null;
}
function getReleaseGateStatus(fileId, actionKey) {
  const rel = RELEASE_REQUESTS[`${fileId}:${actionKey}`];
  return rel ? { ...rel } : null;
}
function getSignals(scope) {
  const all = ALERT_SIGNALS.map((s) => ({ ...s }));
  if (!scope) return all;
  return all.filter((s) => s.fileId === scope || s.level === scope || s.state === scope);
}
function getVetoStandardCoverage() {
  return [
    { control: "No release without current record", state: "Failing", evidence: "SP-0214 release blocked until v2 or exception." },
    { control: "Material changes stale prior records", state: "Configured", evidence: "Destination change staled Seller Proceeds Record v1." },
    { control: "Source limitations visible", state: "Configured", evidence: "Account / payee check limitation shown on task and record." },
    { control: "Exception recorded with approver", state: "Configured", evidence: "Manager exception path names role and expiry." },
  ];
}

/* Mock Google-Maps-style address suggestions. */
const ADDRESS_SUGGESTIONS = [
  "1428 Donlyn Dr, Westlake Village CA 91362",
  "82 Cresta Vista Dr, Westlake Village CA 91362",
  "316 Harbor Light Ln, Camarillo CA 93012",
  "4125 Lakeview Canyon Rd, Westlake Village CA 91362",
  "2208 Westcliff Dr, Westlake Village CA 91362",
  "412 Marigold Ave, Long Beach CA 90803",
  "85 Linden Pl #4, Pasadena CA 91103",
  "770 Crescent Bay Dr, Santa Barbara CA 93103",
];


/* ------------------------------------------------------ review kinds */
const REVIEW_KINDS = {
  payoff: [
    { id: "institutional", label: "Institutional servicer", blurb: "Bank or sub-servicer of record.",
      requiredDocs: ["Payoff statement on servicer letterhead", "Good-through date and per-diem", "Wire instructions on servicer letterhead"],
      callback: "Servicer payoff desk · number from servicer website, not the demand." },
    { id: "private", label: "Private lender", blurb: "Individual or non-institutional note holder.",
      requiredDocs: ["Recorded deed of trust matching the demand", "Beneficiary statement signed by the named holder", "Wire instructions independently checked"],
      callback: "Beneficiary of record at a number not supplied by the demand." },
    { id: "agency", label: "Agency (IRS / FTB / HOA / judgment)", blurb: "Government lien, HOA assessment, or court judgment.",
      requiredDocs: ["Lien or judgment of record", "Agency demand on letterhead with good-through date", "Statutory release language"],
      callback: "Agency line from official directory. HOA via management company of record." },
    { id: "originator", label: "Originator-issued", blurb: "Demand from originator rather than current servicer.",
      requiredDocs: ["Originator demand", "Servicer support that originator has authority to collect", "Wire instructions on servicer letterhead"],
      callback: "Current servicer to review authority support, then originator." },
  ],
  seller: [
    { id: "standard", label: "Standard sale", blurb: "Single titled owner, no third-party approvals.",
      requiredDocs: ["Signed seller instructions", "Government ID", "Wire instructions"], callback: "Seller at number on file, not on the wire form." },
    { id: "short", label: "Short sale", blurb: "Lienholder approval required for proceeds.",
      requiredDocs: ["Short-sale approval letter, current", "Net sheet matching approval", "Lender-imposed conditions list"], callback: "Approving lender loss-mit desk." },
    { id: "estate", label: "Estate / trust", blurb: "Trustee, executor, or administrator selling.",
      requiredDocs: ["Certification of trust or letters testamentary", "Trustee/executor ID", "Authority to disburse to named accounts"], callback: "Trustee or executor of record. Not the listing agent." },
    { id: "entity", label: "Entity seller", blurb: "LLC, corporation, or partnership.",
      requiredDocs: ["Operating agreement or resolution naming signer", "Good standing certificate", "Signer ID"], callback: "Authorized signer at entity-of-record contact." },
  ],
  buyer: [
    { id: "institutional", label: "Institutional lender", blurb: "Bank or licensed mortgage lender.",
      requiredDocs: ["Closing disclosure", "Lender wire instructions on letterhead", "Funding authorization"], callback: "Lender funding desk at published number." },
    { id: "private", label: "Private lender", blurb: "Individual or non-institutional money source.",
      requiredDocs: ["Loan documents naming the lender", "Source-of-funds evidence", "Wire instructions independently checked"], callback: "Named lender at a number not supplied by the loan packet." },
    { id: "cash", label: "All-cash", blurb: "No financing.",
      requiredDocs: ["Proof of funds within 30 days", "Buyer ID", "Source-of-funds statement"], callback: "Buyer at number on file. Confirm wire origin bank." },
    { id: "1031", label: "1031 exchange", blurb: "Qualified intermediary holding buyer funds.",
      requiredDocs: ["Exchange agreement", "QI wire instructions on letterhead", "Identification of replacement property"], callback: "Qualified intermediary at published QI number." },
  ],
};

Object.assign(window, {
  useState, useEffect, useCallback, useMemo, useRef, createContext, useContext,
  cn, hrefFor, navigate, copyText, shareUrlFor, Link, useHashRoute, matchRoute, useNavCollapsed, useLocalList,
  CASE_FILES, REVIEWERS, LINE_LABEL, getReadiness, buildReadinessFrom, deriveRecording,
  streetOf, cityOf, lineLabelOf, activeLineKey, fileStatusLabel, allFilesRows,
  lineQueueRows, recentFiles, DEFAULT_BOOKMARKS, shortClosing, REVIEW_KINDS,
  fmtUSD, fileMovements, MOVEMENT_RECEIPT,
  VETO_CORE_PRIMITIVES, POLICY_CONTROLS, SOURCE_ROWS, CHANGE_EVENTS,
  PROCEEDS_RECORDS, EXCEPTION_RECORDS, RELEASE_REQUESTS, OFFICE_ACTIONS,
  ALERT_SIGNALS, controlledActions, getActionState, getChangeEvents,
  getPolicyControl, getSourceRows, getRecordsForAction, getCurrentRecord,
  getReleaseGateStatus, getSignals, getVetoStandardCoverage,
  ADDRESS_SUGGESTIONS,
});
