/* Shell — sidebar + topbar chrome for the signed-in app. Assigns window.LedgerShell */
(function () {
  // Lane switcher — the persistent scope, not a filter (v1 §3.3). It sits in the
  // chrome next to the brand rather than on any page, because every list, calendar
  // and search inherits it; a control that lives on one page reads as that page's
  // filter and gets re-picked each time.
  //
  // Counts are on the chips on purpose: after A4 the split is roughly 2,950 trials
  // to 9,600 police videos, and the number is what tells you why the list you are
  // looking at is 50 rows of 9,600 rather than 50 of everything.
  function LaneSwitcher({ lane, onLane, counts }) {
    const lanes = window.LEDGER_LANES || [];
    return (
      <div className="lanesw" role="group" aria-label="Lane">
        {lanes.map((l) => {
          const n = counts ? counts[l.id === "all" ? "all" : l.db] : null;
          return (
            <button
              key={l.id}
              type="button"
              className={"lanesw__b" + (lane === l.id ? " lanesw__b--on" : "")}
              aria-pressed={lane === l.id}
              onClick={() => onLane(l.id)}
              title={`Scope everything to ${l.label}`}
            >
              <i data-lucide={l.icon} />
              <span>{l.label}</span>
              {typeof n === "number" && <b className="lanesw__n">{n.toLocaleString()}</b>}
            </button>
          );
        })}
      </div>
    );
  }

  function Sidebar({ active, onNav, user, onSignOut, caps, home }) {
    const { Avatar } = window.LawCrimeDesignSystem_e18d68;
    const name = user?.user_metadata?.full_name || user?.email || "Signed in";
    const email = user?.email || "";
    // One rule: a tab appears if the user holds the capability it declares in
    // LEDGER_NAV (config.js). Adding an app doesn't touch this file. RLS is the real
    // gate — this only keeps other people's surfaces out of sight.
    const nav = window.LEDGER_NAV.filter((n) => !n.capability || caps[n.capability]);
    // GROUPED BY APP (2026-09-18). The sidebar used to be one flat list with a
    // "Workspace" label over it, so Engineering's Approvals sat at the same level
    // as Matters and nothing said which app a tab belonged to. Now each app in
    // LEDGER_APPS is a heading and its pages sit under it. An app appears only if
    // the user can open at least one of its pages.
    //
    // Only the CURRENT app is expanded. The platform will keep gaining apps, and a
    // sidebar that lists every page of every app grows without bound; one click on
    // a heading opens that app at its home and expands it.
    const apps = (window.LEDGER_APPS || [])
      .map((a) => ({ ...a, items: nav.filter((n) => n.app === a.id) }))
      .filter((a) => a.items.length);
    const activeItem = nav.find((n) => n.id === active);
    const activeApp = activeItem ? activeItem.app : null;
    // Someone who holds exactly one app gets it expanded regardless.
    const single = apps.length === 1;
    return (
      <aside className="side">
        {/* The wordmark goes home (Jessica 2026-08-28), like every other site's logo. */}
        <div className="side__brand">
          <button type="button" className="side__home" title="Home" onClick={() => home && onNav && onNav(home)}>
            <img src="vendor/design/assets/logos/lawandcrime-wordmark-white.png" alt="Law and Crime: home" />
          </button>
          <span className="side__tape">PLATFORM</span>
        </div>
        <nav className="side__nav">
          {apps.map((a) => {
            const isIn = a.id === activeApp || single;
            // The heading opens the app's home — if this person can see it — else
            // the app's first page they can see.
            const target = a.items.find((n) => n.id === a.home) || a.items[0];
            const here = target && target.appHome && active === target.id;
            const subs = a.items.filter((n) => !n.appHome);
            return (
              <div key={a.id} className={"appgroup" + (isIn ? " appgroup--in" : "")}>
                <button
                  type="button"
                  className={"appnav" + (isIn ? " appnav--in" : "") + (here ? " appnav--here" : "")}
                  aria-current={here ? "page" : undefined}
                  aria-expanded={subs.length ? isIn : undefined}
                  onClick={() => onNav(target.id)}
                >
                  <i data-lucide={a.icon || "app-window"} />
                  <span>{a.label}</span>
                  {/* Keyed on state: a changed data-lucide on an <i> lucide has already
                      swapped for an <svg> never shows, so the arrow never flipped. A new
                      keyed span gives lucide a fresh <i> to draw. */}
                  {subs.length > 0 && !single && (
                    <span key={isIn ? "open" : "closed"} className="appnav__chev">
                      <i data-lucide={isIn ? "chevron-down" : "chevron-right"} />
                    </span>
                  )}
                </button>
                {isIn && subs.length > 0 && (
                  <div className="appgroup__subs">
                    {subs.map((n) => (
                      <button
                        key={n.id}
                        type="button"
                        className={"navitem navitem--sub" + (active === n.id ? " navitem--active" : "")}
                        aria-current={active === n.id ? "page" : undefined}
                        onClick={() => onNav(n.id)}
                      >
                        <i data-lucide={n.icon} />
                        {/* A tab's name is fixed. It used to follow the lane (`laneLabel`),
                            so choosing Police Videos renamed "Newsroom" to "Worklist" — a
                            label that moves under you is a landmark you cannot navigate by.
                            Removed 2026-09-11 with the surface swap that went with it. */}
                        <span>{n.label}</span>
                      </button>
                    ))}
                  </div>
                )}
              </div>
            );
          })}
        </nav>
        <div className="side__foot">
          <Avatar name={name} status />
          <div className="u">
            <b>{name}</b>
            <span>{email}</span>
          </div>
          <button className="signout" title="Sign out" onClick={onSignOut}>
            <i data-lucide="log-out" />
          </button>
        </div>
      </aside>
    );
  }

  const TA_META = {
    matter: { icon: "folder-open", label: "Matter" },
    individual: { icon: "user", label: "Person" },
    entity: { icon: "building-2", label: "Entity" },
    tag: { icon: "tag", label: "Tag" },
  };

  // Type-ahead: live results appear as you type (debounced), so people find a
  // matter/person/agency without a full search round-trip. Tags matching the term
  // are surfaced first (client-side against the taxonomy) so you can jump to every
  // matter behind a tag. Enter / "see all" still falls through to the full Search page.
  function Topbar({ onSearch, onOpenMatter, onOpenTag, lane, onLane, laneCounts }) {
    const { IconButton } = window.LawCrimeDesignSystem_e18d68;
    const [q, setQ] = React.useState("");
    const [rows, setRows] = React.useState(null);
    const [open, setOpen] = React.useState(false);
    const [busy, setBusy] = React.useState(false);
    const [hi, setHi] = React.useState(-1); // keyboard highlight
    const [tax, setTax] = React.useState([]); // taxonomy tags with a usage count
    const box = React.useRef(null);

    // Load the tag taxonomy + usage counts once; keep only tags that are in use
    // (a tag with no matters isn't a useful search landing).
    React.useEffect(() => {
      Promise.all([window.LedgerAPI.listTags(), window.LedgerAPI.tagCounts()])
        .then(([ts, cs]) => setTax((ts || []).map((t) => ({ ...t, count: cs[t.tag_key] || 0 })).filter((t) => t.count > 0)))
        .catch(() => {});
    }, []);

    // Tag matches (up to 4), shaped like RPC rows so pick()/render treat them uniformly.
    const tagMatches = React.useMemo(() => {
      const term = q.trim().toLowerCase();
      if (term.length < 2) return [];
      return tax
        .filter((t) => t.label.toLowerCase().includes(term) || t.tag_key.toLowerCase().includes(term))
        .slice(0, 4)
        .map((t) => ({ result_type: "tag", result_id: t.tag_key, display_text: t.label,
          secondary: `${t.count} matter${t.count === 1 ? "" : "s"}` }));
    }, [q, tax]);

    // Unified list the menu renders + keyboard-navigates: tags first, then people/matters/entities.
    const items = React.useMemo(() => tagMatches.concat(rows || []), [tagMatches, rows]);

    React.useEffect(() => {
      const term = q.trim();
      if (term.length < 2) { setRows(null); setBusy(false); return; }
      setBusy(true);
      const t = setTimeout(async () => {
        // Type-ahead inherits the lane too — with only 7 slots, in-lane-first is the
        // difference between finding your matter and paging past someone else's desk.
        try { const r = await window.LedgerAPI.search(term, 7, lane); setRows(r); setOpen(true); setHi(-1); }
        catch { setRows([]); }
        finally { setBusy(false); }
      }, 200);
      return () => clearTimeout(t);
    }, [q, lane]);

    React.useEffect(() => {
      function onDoc(e) { if (box.current && !box.current.contains(e.target)) setOpen(false); }
      document.addEventListener("mousedown", onDoc);
      return () => document.removeEventListener("mousedown", onDoc);
    }, []);
    React.useEffect(() => { setTimeout(() => window.lucide && window.lucide.createIcons(), 20); }, [rows, open]);

    function pick(r) {
      setOpen(false); setQ("");
      if (r.result_type === "tag" && onOpenTag) onOpenTag(r.result_id);
      else if (r.result_type === "matter" && onOpenMatter) onOpenMatter(r.result_id);
      else if (onSearch) onSearch(r.display_text || q); // person/entity → full Search (profile panel)
    }
    function seeAll() { setOpen(false); if (onSearch) onSearch(q); }
    function onKey(e) {
      if (e.key === "Enter") { if (open && hi >= 0 && items[hi]) pick(items[hi]); else seeAll(); }
      else if (e.key === "ArrowDown") { e.preventDefault(); setOpen(true); setHi((i) => Math.min(items.length - 1, i + 1)); }
      else if (e.key === "ArrowUp") { e.preventDefault(); setHi((i) => Math.max(-1, i - 1)); }
      else if (e.key === "Escape") { setOpen(false); }
    }

    return (
      <header className="topbar">
        <div className="topbar__search ta" ref={box}>
          <div className="ta-field">
            <i data-lucide="search" className="ta-ic" />
            <input className="ta-input" value={q} placeholder="Search matters, people, agencies, tags…"
              onChange={(e) => setQ(e.target.value)} onFocus={() => { if (items.length) setOpen(true); }} onKeyDown={onKey} />
            {/* lucide REPLACES an <i data-lucide> with an <svg> behind React's back. An
                icon React later unmounts must therefore sit in a node React owns, or React
                tries to remove the <i> that is no longer in the page and the whole app
                unmounts ("removeChild … not a child of this node"). Real typing hit it:
                the spinner flips on and off while earlier searches land. */}
            {busy && <span className="ta-spin-wrap"><i data-lucide="loader" className="ta-spin" /></span>}
          </div>
          {open && rows && (
            <div className="ta-menu">
              {items.length === 0 && <div className="ta-empty">No matches for “{q}”.</div>}
              {items.map((r, i) => {
                const meta = TA_META[r.result_type] || { icon: "circle", label: r.result_type };
                return (
                  <button key={r.result_type + r.result_id} type="button"
                    className={"ta-item" + (i === hi ? " ta-item--hi" : "")}
                    onMouseEnter={() => setHi(i)} onClick={() => pick(r)}>
                    <i data-lucide={meta.icon} className="ta-item-ic" />
                    <span className="ta-item-main"><b>{r.display_text}</b>{r.secondary ? <span>{r.secondary}</span> : null}</span>
                    <span className="ta-item-kind">{meta.label}</span>
                  </button>
                );
              })}
              {items.length > 0 && (
                <button type="button" className="ta-all" onClick={seeAll}>
                  <i data-lucide="search" /> See all results for “{q}”
                </button>
              )}
            </div>
          )}
        </div>
        <div className="topbar__spacer" />
        <LaneSwitcher lane={lane} onLane={onLane} counts={laneCounts} />
        <div className="topbar__actions">
          <IconButton icon="bell" label="Alerts" />
          <IconButton icon="settings" label="Settings" />
        </div>
      </header>
    );
  }

  function Shell({ active, onNav, user, onSignOut, onSearch, onOpenMatter, onOpenTag,
                   home, caps, lane, onLane, laneCounts, children }) {
    React.useEffect(() => { setTimeout(() => window.lucide && window.lucide.createIcons(), 30); });
    // The topbar is MATTERS chrome — a matter type-ahead and the trial/police-video lane
    // scope. A surface that is not about matters inherits neither, and showing them there
    // implies a scope that does not apply to what is on screen. Nav items opt out with
    // `ownChrome`, so this stays one flag per surface rather than a list of route names
    // here — which is also the seam the multi-app sidebar will grow from.
    const navItem = (window.LEDGER_NAV || []).find((n) => n.id === active);
    const ownChrome = !!(navItem && navItem.ownChrome);
    return (
      <div className="app">
        <Sidebar active={active} onNav={onNav} user={user} onSignOut={onSignOut} home={home}
                 caps={caps} />
        <div className="main">
          {!ownChrome && (
            <Topbar onSearch={onSearch} onOpenMatter={onOpenMatter} onOpenTag={onOpenTag}
                    lane={lane} onLane={onLane} laneCounts={laneCounts} />
          )}
          <div className="content">{children}</div>
        </div>
      </div>
    );
  }

  window.LedgerShell = Shell;
})();
