/* =========================================================================
   Veto · AppSidebar + AppShell
   Icon-led nav, hotkeys surfaced in collapsed popovers, Lines without
   noisy counts, Settings above the account row. Mounts overlays.
   ========================================================================= */
/* Money is the control system — grouped by direction (in / out) + the release
   gate. Keys stay seller/buyer/payoff so existing queue active-states match. */
const MONEY_LINES = [
  { key: "buyer", label: "Funding In", to: "/buyer-funding", icon: "buyer-funding", shortcut: "G B" },
  { key: "payoff", label: "Payoffs", to: "/payoff", icon: "payoff-demand", shortcut: "G P" },
  { key: "seller", label: "Proceeds", to: "/seller-proceeds", icon: "seller-proceeds", shortcut: "G S" },
  { key: "release", label: "Release Requests", to: "/release-requests", icon: "arrow-right", shortcut: "G R" },
  { key: "accounts", label: "Accounts", to: "/accounts", icon: "trust-account", shortcut: "G A" },
];

function VetoMark({ className }) {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-label="Veto" className={className}>
      <path d="M4 5h3.4l4.6 11.2L16.6 5H20l-6.3 14.6h-3.4L4 5z" />
    </svg>
  );
}
function PanelToggleIcon() {
  return (
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3.5" y="4.5" width="17" height="15" rx="2" /><line x1="9.5" y1="4.5" x2="9.5" y2="19.5" />
    </svg>
  );
}

/* Expanded nav row with leading icon. */
function NavRow({ to, onClick, icon, label, active, trailing }) {
  const cls = cn("group flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-left transition-colors duration-150",
    active ? "bg-surface text-ink" : "text-ink-soft hover:bg-surface hover:text-ink");
  const inner = (
    <>
      <Icon name={icon} size={15} className={cn("shrink-0", active ? "text-ink" : "text-ink-soft group-hover:text-ink")} />
      <span className={cn("flex-1 truncate text-[13px]", active && "font-medium")}>{label}</span>
      {trailing}
    </>
  );
  if (onClick) return <button type="button" onClick={onClick} className={cls}>{inner}</button>;
  return <Link to={to} className={cls}>{inner}</Link>;
}

/* Collapsed icon button with hotkey popover. */
function CollapsedIcon({ to, onClick, label, shortcut, active, icon, glyph }) {
  const cls = cn("grid h-8 w-8 cursor-pointer place-items-center rounded-md transition-colors",
    active ? "bg-surface text-ink" : "text-ink-soft hover:bg-surface hover:text-ink");
  const content = glyph || <Icon name={icon} size={15} />;
  const el = onClick
    ? <button type="button" onClick={onClick} aria-label={label} className={cls}>{content}</button>
    : <Link to={to} aria-label={label} className={cls}>{content}</Link>;
  return <Hint label={label} shortcut={shortcut}>{el}</Hint>;
}

function Collapsible({ title, children }) {
  const key = "veto.sb." + title;
  const [open, setOpen] = useState(() => { try { const v = localStorage.getItem(key); return v === null ? true : v === "1"; } catch { return true; } });
  const toggle = () => setOpen((o) => { const n = !o; try { localStorage.setItem(key, n ? "1" : "0"); } catch {} return n; });
  return (
    <div className="mt-4">
      <button type="button" onClick={toggle} className="group/sec flex w-full cursor-pointer items-center gap-1 rounded px-2 py-1 text-left">
        <span className="text-[11px] text-muted-foreground transition-colors group-hover/sec:text-ink-soft">{title}</span>
        <Icon name="chevron-down" size={12} className={cn("text-muted-foreground/50 transition-transform", open ? "" : "-rotate-90")} />
      </button>
      {open && <nav className="mt-0.5 flex flex-col gap-px">{children}</nav>}
    </div>
  );
}

/* Contextual Money + Records trees. Some leaves share focused detail pages
   under /scaffold/<key> so the whole navigation has a working screen. */
function useHashPath() {
  const [p, setP] = useState(() => { try { return location.hash.replace(/^#/, "") || "/"; } catch { return "/"; } });
  useEffect(() => {
    const on = () => { try { setP(location.hash.replace(/^#/, "") || "/"); } catch {} };
    window.addEventListener("hashchange", on);
    return () => window.removeEventListener("hashchange", on);
  }, []);
  return p;
}
const MONEY_TREE = [
  { group: "Money Controls", items: [
    { label: "Overview", to: "/funds", match: "/funds", live: true },
    { label: "Funding In", to: "/funds/funding-in", match: "/funds/funding-in", live: true },
    { label: "Payoffs & Obligations", to: "/funds/payoffs", match: "/funds/payoffs", live: true },
    { label: "Proceeds & Payees", to: "/funds/proceeds", match: "/funds/proceeds", live: true },
    { label: "Release Requests", scaffold: "money-release", live: true },
    { label: "Changes After Reliance", scaffold: "money-changes", live: true },
    { label: "Money Exceptions", scaffold: "money-exceptions", live: true },
  ] },
  { group: "Accounts", items: [
    { label: "Trust Accounts", scaffold: "money-trust", live: true },
    { label: "Destination Accounts", scaffold: "money-destinations", live: true },
    { label: "Funding Source Accounts", scaffold: "money-sources", live: true },
    { label: "Payee Directory", scaffold: "money-payees", live: true },
    { label: "Account Checks", scaffold: "money-checks", live: true },
    { label: "Restricted Details", scaffold: "money-restricted", live: true },
  ] },
  { group: "Activity", items: [
    { label: "Incoming", scaffold: "act-incoming", live: true },
    { label: "Outgoing", scaffold: "act-outgoing", live: true },
    { label: "Pending Release", scaffold: "act-pending", live: true },
    { label: "Matched", scaffold: "act-matched", live: true },
    { label: "Unmatched", scaffold: "act-unmatched", live: true },
    { label: "Exceptions", scaffold: "act-exceptions", live: true },
  ] },
];
const RECORDS_TREE = [
  { label: "All Records", scaffold: "rec-all", live: true },
  { label: "Release Request Records", scaffold: "rec-release", live: true },
  { label: "Change Impact Records", scaffold: "rec-change", live: true },
  { label: "Payoff Records", scaffold: "rec-payoff", live: true },
  { label: "Proceeds Records", scaffold: "rec-proceeds", live: true },
  { label: "Funding Records", scaffold: "rec-funding", live: true },
  { label: "Exception Records", scaffold: "rec-exception", live: true },
  { label: "Hold / Lock Records", scaffold: "rec-holdlock", live: true },
  { label: "Audit Packets", scaffold: "rec-audit", live: true },
];
const MONEY_NAV = [
  { label: "Overview", to: "/funds", match: "/funds", live: true },
  { label: "Funding In", to: "/funds/funding-in", match: "/funds/funding-in", live: true },
  { label: "Payoffs", to: "/funds/payoffs", match: "/funds/payoffs", live: true },
  { label: "Proceeds", to: "/funds/proceeds", match: "/funds/proceeds", live: true },
  { label: "Release Requests", to: "/funds/release-requests", match: "/funds/release-requests", live: true },
  { label: "Changes", to: "/funds/changes", match: "/funds/changes", live: true },
  { label: "Accounts", to: "/funds/accounts", match: "/funds/accounts", live: true },
  { label: "Activity", to: "/funds/activity", match: "/funds/activity", live: true },
  { label: "Matching", to: "/funds/matching", match: "/funds/matching", live: true },
];
const SOURCES_NAV = [
  { label: "Inbox", scaffold: "src-inbox", live: true },
  { label: "Source Rows", scaffold: "src-rows", live: true },
  { label: "Checks", scaffold: "src-checks", live: true },
  { label: "Callbacks", scaffold: "src-callbacks", live: true },
  { label: "Vault", scaffold: "src-vault", live: true },
  { label: "Limitations", scaffold: "src-limitations", live: true },
];
const GUARD_NAV = [
  { label: "Controls", scaffold: "guard-controls", live: true },
  { label: "Coverage", scaffold: "guard-coverage", live: true },
  { label: "Risks", scaffold: "guard-risks", live: true },
  { label: "Exceptions", scaffold: "guard-exceptions", live: true },
  { label: "Incidents", scaffold: "guard-incidents", live: true },
];
function SideLeaf({ item, path }) {
  const to = item.scaffold ? "/scaffold/$key" : item.to;
  const params = item.scaffold ? { key: item.scaffold } : undefined;
  const match = item.scaffold ? "/scaffold/" + item.scaffold : item.match;
  const active = path === match || (match !== "/funds" && path.startsWith(match + "/"));
  return (
    <Link to={to} params={params}
      className={cn("flex items-center gap-2 rounded-md px-2 py-1 transition-colors",
        active ? "bg-surface text-ink" : "text-ink-soft hover:bg-surface hover:text-ink")}>
      <span className={cn("flex-1 truncate text-[12.5px]", active && "font-medium")}>{item.label}</span>
    </Link>
  );
}
function SideSection({ icon, label, sectionKey, activeLine, path, groups, items, open, onHeader }) {
  const active = activeLine === sectionKey;
  return (
    <div>
      <button type="button" onClick={onHeader}
        className={cn("group flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left transition-colors", active ? "bg-surface text-ink" : "text-ink-soft hover:bg-surface hover:text-ink")}>
        <Icon name={icon} size={15} className={cn("shrink-0", active ? "text-ink" : "text-ink-soft group-hover:text-ink")} />
        <span className={cn("flex-1 truncate text-[13px]", active && "font-medium")}>{label}</span>
        <Icon name="chevron-down" size={12} className={cn("shrink-0 text-muted-foreground/50 transition-transform", open ? "" : "-rotate-90")} />
      </button>
      {open && (
        <div className="mb-1 ml-[15px] mt-0.5 flex flex-col gap-px border-l border-line pl-2">
          {groups ? groups.map((g) => (
            <React.Fragment key={g.group}>
              <div className="px-2 pb-0.5 pt-2 text-[10px] uppercase tracking-[0.07em] text-muted-foreground/70">{g.group}</div>
              {g.items.map((it) => <SideLeaf key={it.label} item={it} path={path} />)}
            </React.Fragment>
          )) : items.map((it) => <SideLeaf key={it.label} item={it} path={path} />)}
        </div>
      )}
    </div>
  );
}

function BookmarksSection({ bookmarks, onRemove, activeFileId }) {
  if (!bookmarks.length) return null;
  return (
    <Collapsible title="Pinned">
      {bookmarks.map((b) => (
        <div key={b.id} className={cn("group flex items-center gap-1 rounded-md transition-colors duration-150", b.id === activeFileId ? "bg-surface" : "hover:bg-surface")}>
          <Link to="/file/$id" params={{ id: b.id }} className="flex min-w-0 flex-1 items-center gap-2.5 px-2 py-1.5">
            <Icon name="bookmark" size={13} className={cn("shrink-0", b.id === activeFileId ? "text-ink" : "text-ink-soft")} />
            <span className={cn("min-w-0 flex-1 truncate text-[13px] transition-colors duration-150", b.id === activeFileId ? "font-medium text-ink" : "text-ink-soft group-hover:text-ink")}>{b.name}</span>
          </Link>
          <button type="button" onClick={() => onRemove(b.id)} aria-label={`Remove ${b.name}`}
            className="hidden h-6 w-6 shrink-0 cursor-pointer items-center justify-center pr-1 text-[14px] leading-none text-muted-foreground transition-colors hover:text-ink group-hover:flex">×</button>
        </div>
      ))}
    </Collapsible>
  );
}
function RecentsSection({ recents, activeFileId }) {
  if (!recents.length) return null;
  return (
    <Collapsible title="Recents">
      {recents.map((r) => (
        <Link key={r.id} to="/file/$id" params={{ id: r.id }}
          className={cn("group flex items-center gap-2.5 rounded-md px-2 py-1.5 transition-colors duration-150", r.id === activeFileId ? "bg-surface" : "hover:bg-surface")}>
          <Icon name="clock" size={13} className={cn("shrink-0", r.id === activeFileId ? "text-ink" : "text-muted-foreground")} />
          <span className={cn("min-w-0 flex-1 truncate text-[13px] transition-colors duration-150", r.id === activeFileId ? "font-medium text-ink" : "text-ink-soft group-hover:text-ink")}>{r.name}</span>
        </Link>
      ))}
    </Collapsible>
  );
}

function openSearch() { window.dispatchEvent(new CustomEvent("veto:cmdk")); }
function openHelp() { window.dispatchEvent(new CustomEvent("veto:help")); }

const DEMO_ROLE_KEY = "veto.demo.role";
const DEMO_STORAGE_PREFIX = "veto.";
function clearDemoStorageArea(storage) {
  try {
    for (const key of Object.keys(storage)) {
      if (key.startsWith(DEMO_STORAGE_PREFIX)) storage.removeItem(key);
    }
  } catch {}
}
function resetSandboxState() {
  clearDemoStorageArea(localStorage);
  clearDemoStorageArea(sessionStorage);
  window.VETO_DEMO_ROLE = "officer";
  window.dispatchEvent(new CustomEvent("veto:role", { detail: "officer" }));
  window.location.hash = "/";
  window.location.reload();
}
function readDemoRole() {
  try { const v = localStorage.getItem(DEMO_ROLE_KEY); return v === "owner" ? "owner" : "officer"; } catch { return "officer"; }
}
function reviewerForRole(role) {
  const key = role === "owner" ? "OP" : "ML";
  const r = (typeof REVIEWERS !== "undefined" && REVIEWERS[key]) || { name: "Madeline Lane", initials: "ML", roleLabel: "Escrow officer", role: "officer" };
  return { name: r.name, initials: r.initials, roleLabel: r.roleLabel, role: r.role || (role === "owner" ? "owner" : "officer") };
}
function useDemoRole() {
  const [role, setRoleState] = useState(() => readDemoRole());
  useEffect(() => {
    window.VETO_DEMO_ROLE = role;
    try { localStorage.setItem(DEMO_ROLE_KEY, role); } catch {}
    window.dispatchEvent(new CustomEvent("veto:role", { detail: role }));
  }, [role]);
  useEffect(() => {
    const onRole = (e) => { if (e.detail === "owner" || e.detail === "officer") setRoleState(e.detail); };
    window.addEventListener("veto:role", onRole);
    return () => window.removeEventListener("veto:role", onRole);
  }, []);
  const setRole = (next) => setRoleState(next === "owner" ? "owner" : "officer");
  return [role, setRole];
}
function useDemoRoleRead() {
  const [role, setRole] = useState(() => window.VETO_DEMO_ROLE || readDemoRole());
  useEffect(() => {
    const sync = () => setRole(window.VETO_DEMO_ROLE || readDemoRole());
    window.addEventListener("veto:role", sync);
    return () => window.removeEventListener("veto:role", sync);
  }, []);
  return role;
}
function RoleToggle({ role, onChange }) {
  const isOwner = role === "owner";
  return (
    <div className="inline-flex items-center rounded-full border border-line bg-surface/60 p-0.5">
      <button type="button" onClick={() => onChange("officer")}
        className={cn("rounded-full px-2.5 py-1 text-[11.5px] font-medium transition", !isOwner ? "bg-background text-ink shadow-sm" : "text-ink-soft hover:text-ink")}>
        <span className="sm:hidden">Officer</span><span className="hidden sm:inline">Officer (Madeline Lane)</span>
      </button>
      <button type="button" onClick={() => onChange("owner")}
        className={cn("rounded-full px-2.5 py-1 text-[11.5px] font-medium transition", isOwner ? "bg-background text-ink shadow-sm" : "text-ink-soft hover:text-ink")}>
        Owner view
      </button>
    </div>
  );
}

function AppSidebar({ collapsed, onToggle, activeLine, activeFileId, reviewer, demoRole = "officer" }) {
  const { list: bmStored, remove: removeBookmark } = useLocalList("veto.bookmarks");
  const bookmarks = bmStored;
  const recents = recentFiles(5);
  const path = useHashPath();
  const sectionOf = (l) => (l === "money" || l === "sources" || l === "guard") ? l : null;
  const [openSection, setOpenSection] = useState(() => sectionOf(activeLine));
  // Open a section whenever you enter it (via any link); collapse when you leave.
  // A manual collapse sticks while you stay, because activeLine doesn't change.
  useEffect(() => { setOpenSection(sectionOf(activeLine)); }, [activeLine]);
  const headerFor = (sec, to) => () => {
    if (openSection === sec) setOpenSection(null);
    else { setOpenSection(sec); navigate(to); }
  };

  if (collapsed) {
    return (
      <aside className="no-print hidden w-14 flex-col items-center border-r border-line bg-background lg:flex lg:sticky lg:top-0 lg:h-screen">
        <div className="flex w-full items-center justify-center px-2 pt-4 pb-3">
          <Hint label="Open sidebar" shortcut="[">
            <button onClick={onToggle} aria-label="Expand sidebar"
              className="group/brand grid h-8 w-8 cursor-pointer place-items-center rounded-md text-ink transition-colors hover:bg-surface">
              <VetoMark className="h-5 w-5 group-hover/brand:hidden" />
              <span className="hidden text-ink-soft group-hover/brand:inline-flex"><Icon name="panel-right" size={16} /></span>
            </button>
          </Hint>
        </div>
        <nav className="flex w-full flex-col items-center gap-0.5 px-2">
          <CollapsedIcon to="/files/new" label="New file" shortcut="⌘N" icon="plus" />
          <CollapsedIcon to="/" label="Home" shortcut="G H" active={activeLine === "home"} icon="home" />
          <CollapsedIcon to="/tasks" label="Tasks" shortcut="G T" active={activeLine === "notifications"} icon="tasks" />
          <CollapsedIcon to="/files" label="Files" shortcut="G F" active={activeLine === "files"} icon="files" />
          <CollapsedIcon to="/funds" label="Funds" shortcut="G M" active={activeLine === "money"} icon="banknote" />
          <CollapsedIcon to="/scaffold/sources" label="Sources" active={activeLine === "sources"} icon="book-open" />
          <CollapsedIcon to="/records" label="Records" shortcut="G C" active={activeLine === "records"} icon="file-text" />
          <CollapsedIcon to="/scaffold/guard" label="Guard" active={activeLine === "guard"} icon="shield" />
          <CollapsedIcon to="/scaffold/standard" label="Standard" active={activeLine === "standard"} icon="check-circle" />
          {demoRole === "owner" && <CollapsedIcon to="/overview" label="Review Register" shortcut="G O" active={activeLine === "overview"} icon="table" />}
        </nav>
        <div className="flex-1" />
        <div className="flex w-full flex-col items-center gap-1 border-t border-line px-2 py-3">
          <CollapsedIcon onClick={openHelp} label="Help" icon="life-buoy" />
          <CollapsedIcon to="/settings" label="Settings" shortcut="⌘," active={activeLine === "settings"} icon="settings" />
          <Hint label={reviewer.name}>
            <Link to="/reviewer/$initials" params={{ initials: reviewer.initials }} aria-label={reviewer.name}
              className="grid h-7 w-7 cursor-pointer place-items-center rounded-full bg-surface-2 text-[11px] font-medium text-ink transition-colors hover:bg-surface">{reviewer.initials}</Link>
          </Hint>
        </div>
      </aside>
    );
  }

  return (
    <aside className="no-print hidden w-[232px] flex-col border-r border-line bg-background lg:flex lg:sticky lg:top-0 lg:h-screen">
      <div className="flex items-center justify-between px-4 pt-5 pb-4">
        <Link to="/" className="block cursor-pointer" aria-label="Veto home"><VetoLogo className="text-[19px]" /></Link>
        <Hint label="Collapse sidebar" shortcut="[" side="bottom">
          <button onClick={onToggle} aria-label="Collapse sidebar"
            className="grid h-7 w-7 cursor-pointer place-items-center rounded-md text-ink-soft transition-colors hover:bg-surface hover:text-ink"><Icon name="panel-left" size={16} /></button>
        </Hint>
      </div>

      <div className="px-2 pb-2 pt-1">
        <Link to="/files/new"
          className="flex cursor-pointer items-center gap-2 rounded-md border border-line bg-surface/50 px-2.5 py-2 text-[13px] font-medium text-ink transition-colors hover:border-ink/20 hover:bg-surface">
          <Icon name="plus" size={15} className="text-ink-soft" /> New file
        </Link>
      </div>

      <div className="flex flex-col gap-px px-2 pb-1">
        <NavRow to="/" icon="home" label="Home" active={activeLine === "home"} />
        <NavRow to="/tasks" icon="tasks" label="Tasks" active={activeLine === "notifications"}
          trailing={(() => { const n = typeof window.openTaskCount === "function" ? window.openTaskCount() : 0; return n > 0 ? <span className="font-mono text-[11px] tabular-nums text-muted-foreground">{n}</span> : null; })()} />
        <NavRow to="/files" icon="files" label="Files" active={activeLine === "files"} />
        {demoRole === "owner" && <NavRow to="/overview" icon="table" label="Review Register" active={activeLine === "overview"} />}
      </div>

      <div className="min-h-0 flex-1 overflow-y-auto px-2 pb-4 pt-1">
        <div className="flex flex-col gap-px">
          <SideSection icon="banknote" label="Funds" sectionKey="money" activeLine={activeLine} path={path} items={MONEY_NAV} open={openSection === "money"} onHeader={headerFor("money", "/funds")} />
          <SideSection icon="book-open" label="Sources" sectionKey="sources" activeLine={activeLine} path={path} items={SOURCES_NAV} open={openSection === "sources"} onHeader={headerFor("sources", "/scaffold/sources")} />
          <NavRow to="/records" icon="file-text" label="Records" active={activeLine === "records"} />
        </div>
        <div className="mx-1 my-2 border-t border-line" />
        <div className="flex flex-col gap-px">
          <SideSection icon="shield" label="Guard" sectionKey="guard" activeLine={activeLine} path={path} items={GUARD_NAV} open={openSection === "guard"} onHeader={headerFor("guard", "/scaffold/guard")} />
          <NavRow to="/scaffold/standard" icon="check-circle" label="Standard" active={activeLine === "standard"} />
        </div>
        <BookmarksSection bookmarks={bookmarks} onRemove={removeBookmark} activeFileId={activeFileId} />
        <RecentsSection recents={recents} activeFileId={activeFileId} />
      </div>

      {/* Bottom: Settings above the account line */}
      <div className="flex flex-col gap-px px-2 pb-1">
        <NavRow to="/settings" icon="settings" label="Settings" active={activeLine === "settings"} />
      </div>
      <div className="border-t border-line p-1.5">
        <Link to="/reviewer/$initials" params={{ initials: reviewer.initials }} className="group/footer flex cursor-pointer items-center gap-2 rounded-md px-1.5 py-1 transition-colors duration-150 hover:bg-surface">
          <span className="grid h-6 w-6 shrink-0 place-items-center rounded-full bg-surface-2 text-[10.5px] font-medium text-ink transition-colors duration-150 group-hover/footer:bg-background">{reviewer.initials}</span>
          <span className="min-w-0 flex-1 truncate">
            <span className="block truncate text-[12.5px] text-ink">{reviewer.name}</span>
            {reviewer.roleLabel && <span className="block truncate text-[11px] text-ink-soft">{reviewer.roleLabel}</span>}
          </span>
          <Hint label="Sign out" side="top">
            <button type="button" aria-label="Sign out"
              onClick={(e) => { e.preventDefault(); e.stopPropagation(); navigate("/welcome"); }}
              className="grid h-6 w-6 shrink-0 cursor-pointer place-items-center rounded text-muted-foreground opacity-0 transition hover:text-ink group-hover/footer:opacity-100">
              <Icon name="log-out" size={13} />
            </button>
          </Hint>
        </Link>
      </div>
    </aside>
  );
}

/* Global g-prefixed nav + ⌘N / ⌘, shortcuts. */
function useGlobalNav() {
  useEffect(() => {
    let gAt = 0;
    const onKey = (e) => {
      const tag = (e.target.tagName || "").toLowerCase();
      const typing = tag === "input" || tag === "textarea" || tag === "select" || e.target.isContentEditable;
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "n") { e.preventDefault(); navigate("/files/new"); return; }
      if ((e.metaKey || e.ctrlKey) && e.key === ",") { e.preventDefault(); navigate("/settings"); return; }
      if (typing || e.metaKey || e.ctrlKey || e.altKey) return;
      const k = e.key.toLowerCase();
      if (k === "g") { gAt = Date.now(); return; }
      if (Date.now() - gAt < 1200) {
        const map = { h: "/", f: "/files", t: "/tasks", m: "/funds", c: "/records", o: "/overview", b: "/funds/funding-in", p: "/funds/payoffs", s: "/funds/proceeds", r: "/funds/release-requests", a: "/funds/accounts" };
        if (map[k]) { e.preventDefault(); navigate(map[k]); gAt = 0; }
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);
}

/* Global top bar — Mercury-style persistent search + notifications. */
function TopBar({ activeLine, demoRole, onRoleChange }) {
  return (
    <div className="no-print sticky top-0 z-30 flex h-14 items-center gap-3 bg-background/85 px-4 backdrop-blur-md lg:px-6">
      <Hint label="Search everywhere" shortcut="⌘K" side="bottom">
        <button type="button" onClick={openSearch}
          className="group/search -ml-1 flex h-9 cursor-pointer items-center gap-2.5 rounded-lg px-2 text-left text-[13.5px] text-muted-foreground transition-colors hover:bg-surface/70">
          <Icon name="search" size={15} className="shrink-0" />
          <span className="whitespace-nowrap sm:hidden">Search</span><span className="hidden whitespace-nowrap sm:inline">Search for anything</span>
        </button>
      </Hint>
      <div className="flex-1" />
      <Hint label="Reset sandbox state" side="bottom">
        <button type="button" onClick={resetSandboxState} aria-label="Reset sandbox state"
          className="inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-line bg-background px-2.5 text-[11.5px] font-medium text-ink-soft transition hover:bg-surface hover:text-ink">
          <Icon name="rotate-ccw" size={13} />
          Reset
        </button>
      </Hint>
      <RoleToggle role={demoRole} onChange={onRoleChange} />
    </div>
  );
}

/* AppShell — sidebar + main grid + overlays, used by most screens. */
function AppShell({ activeLine = null, activeFileId = null, reviewer: reviewerProp, maxWidth = "1320px", topBar = true, children }) {
  const [navCollapsed, toggleNavCollapsed] = useNavCollapsed();
  const [demoRole, setDemoRole] = useDemoRole();
  const reviewer = demoRole === "owner" ? reviewerForRole("owner") : (reviewerProp || reviewerForRole(demoRole));
  useGlobalNav();
  return (
    <div className="min-h-screen bg-background text-ink">
      <div className={cn("mx-auto grid min-h-screen w-full grid-cols-1 items-start",
        navCollapsed ? "lg:grid-cols-[56px_minmax(0,1fr)]" : "lg:grid-cols-[240px_minmax(0,1fr)]")}
        style={{ maxWidth }}>
        <AppSidebar collapsed={navCollapsed} onToggle={toggleNavCollapsed} activeLine={activeLine} activeFileId={activeFileId} reviewer={reviewer} demoRole={demoRole} />
        <main className="flex min-w-0 flex-col">
          {topBar && <TopBar activeLine={activeLine} demoRole={demoRole} onRoleChange={setDemoRole} />}
          {children}
        </main>
      </div>
      <CommandPalette />
      <HelpLauncher reviewer={reviewer} />
    </div>
  );
}

Object.assign(window, { AppSidebar, AppShell, readDemoRole, resetSandboxState, reviewerForRole, useDemoRole, useDemoRoleRead });
