/* ============================================================
   beedle dataroom — root app
   ============================================================ */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "homeLayout": "grid",
  "loginStyle": "split",
  "viewerChrome": "framed",
  "startLoggedIn": false
}/*EDITMODE-END*/;

// useToasts / Toast live in toast.jsx (shared with the forecaster page).

function App({ boot, t, setTweak, reload }) {
  const me = boot.me;
  // Track the previewed identity by unique user id (default: yourself), not by
  // role template id — otherwise a second admin resolves to the first "admin".
  const [roleId, setRoleId] = React.useState(String(me.id || me._id));
  const [sections, setSections] = React.useState(boot.sections);
  const [docs, setDocs] = React.useState(boot.documents);
  const [company, setCompany] = React.useState(boot.company);
  const [view, setView] = React.useState(null);          // null = home, else section id
  const [openDoc, setOpenDoc] = React.useState(null);
  const [present, setPresent] = React.useState(false);    // full-screen page viewer over the open doc
  const [query, setQuery] = React.useState("");
  const [modal, setModal] = React.useState(null);        // {type, ...}
  const [confirm, setConfirm] = React.useState(null);
  const [toasts, toast] = useToasts();
  // ≤920px the sidebar gives way to a hamburger + slide-in drawer (same Sidebar inside).
  const compact = useIsCompact();
  const [navOpen, setNavOpen] = React.useState(false);

  // Admins can preview as another user; everyone else is locked to their own access.
  // Identity is keyed on the unique user id (see identity.js) — never on the
  // shared role template id, which would alias users who share a role.
  const role = resolveActiveRole(me, boot.roles, roleId);

  // ---- access modes (mirror the server; honors the previewed role) ----
  // `role` carries { admin, keyring }; accessMode/docEffectiveMode (modes.js) read it.
  const RANKS = window.MODE_RANK;
  const secMode = (s) => accessMode(s, role, { kind: "section" });
  const docMode = (d) => docEffectiveMode(d, sections.find(s => s.id === d.section), role);
  const itemRank = (item) => item ? ("section" in item ? docMode(item) : secMode(item)) : 0;
  const see = (item) => itemRank(item) >= RANKS.list;     // at least "list" (see it exists)
  const canView = (item) => itemRank(item) >= RANKS.view;  // can open the content
  const modeOf = (item) => modeName(itemRank(item));
  const canEditSection = (s) => secMode(s) >= RANKS.edit;
  // A section in construction mode is sealed for everyone who can't edit it: they
  // see it exists (sidebar/home) but get a "coming soon" notice instead of content.
  const underConstruction = (s) => !!(s && s.construction) && !canEditSection(s);

  const visSections = sections.filter(see);
  const secDocs = (sid) => docs.filter(d => d.section === sid);
  // Annotate each visible doc with the caller's effective mode so Folder can render
  // list-only rows (Request access) vs viewable rows correctly.
  const visDocs = (sid) => secDocs(sid).filter(see).map(d => ({ ...d, mode: modeOf(d) }));

  // ---- request access (list-only items) ----
  const askAccess = (itemType, item, wantMode = "view") =>
    setModal({ type: "requestaccess", itemType, item, wantMode });
  const submitAccessRequest = async ({ itemType, item, wantMode, note }) => {
    try {
      await api.requestAccess({ itemType, itemId: item.id, key: item.key, wantMode, note });
      toast(T("Access requested — an admin will review it."), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Could not send request"), { icon: "info" }); }
    setModal(null);
  };
  // Open a doc only if viewable; otherwise prompt to request access.
  // File-backed docs stream (and audit) via /raw, so they open straight from the
  // list row. Authored pages + mock docs carry no file: fetch the single doc so
  // its `html` body arrives through the audited GET (which logs document.viewed)
  // — the list never carries it. The server re-checks VIEW, so a forbidden race
  // just surfaces a toast instead of opening.
  const openDocSafe = async (d) => {
    if (!d) return;
    if (!canView(d)) { askAccess("document", d, "view"); return; }
    let doc = d;
    if (!d.hasFile) {
      try { doc = await api.getDoc(d.id); }
      catch (e) { toast(e.message || T("Could not open this document"), { icon: "info" }); return; }
    }
    setOpenDoc(doc);
  };
  // The content editor needs an authored page's `html`, which no longer rides in
  // the list. Fetch it (audited) when it's missing — opening from an already-open
  // viewer reuses the body it hydrated, so this doesn't re-log a view.
  const openContentEditor = async (d) => {
    let doc = d;
    if (d && d.html == null) {
      try { doc = await api.getDoc(d.id); }
      catch (e) { toast(e.message || T("Could not open the page"), { icon: "info" }); return; }
    }
    setModal({ type: "editcontent", doc });
  };
  const navSection = (id) => {
    const s = id && sections.find(x => x.id === id);
    if (s && !canView(s)) { askAccess("section", s, "view"); return; }
    setQuery(""); setView(id);
  };

  // keep current section valid for role
  React.useEffect(() => {
    if (view && !visSections.find(s => s.id === view)) { setView(null); setOpenDoc(null); }
  }, [roleId]);

  // Greet an admin with any pending "Request access" clicks on entry, so a member's
  // request never goes unseen. Pops the requests modal once per load when the queue
  // isn't empty (and a toast with the count as a lighter cue). Forecaster share-link
  // visitors' knob requests are greeted too — notify-only, their inbox is the
  // forecaster's Share links manager.
  const [reqCount, setReqCount] = React.useState(0); // open access requests (badges the Home button)
  React.useEffect(() => {
    if (!me.dataroomAdmin) return;
    let cancelled = false;
    api.listAccessRequests("open").then(rows => {
      if (cancelled || !rows) return;
      setReqCount(rows.length);
      if (!rows.length) return;
      toast(rows.length === 1 ? T("1 access request is waiting") : T("{n} access requests are waiting", { n: rows.length }), { icon: "key" });
      setModal(m => m ? m : { type: "requests" });
    }).catch(() => {});
    api.listShareLinks().then(links => {
      const n = api.countShareNotes(links);
      if (cancelled || !n) return;
      toast(n === 1 ? T("1 visitor request is waiting in the forecaster") : T("{n} visitor requests are waiting in the forecaster", { n }), { icon: "shield" });
    }).catch(() => {});
    return () => { cancelled = true; };
  }, []);
  // Granting via the member editor resolves a request outside the requests modal,
  // so refresh the badge count whenever the last modal closes.
  const hadModal = React.useRef(false);
  React.useEffect(() => {
    if (modal) { hadModal.current = true; return; }
    if (!hadModal.current || !me.dataroomAdmin) return;
    hadModal.current = false;
    api.listAccessRequests("open").then(rows => setReqCount(rows.length)).catch(() => {});
  }, [modal]);

  const activeSection = view ? sections.find(s => s.id === view) : null;

  // ---- actions (persisted via the API) ----
  const download = async (d) => {
    try { await api.download(d); toast(T("Downloading “{name}”…", { name: d.name }), { icon: "download", good: true }); }
    catch (e) { toast(e.message || T("Download not allowed"), { icon: "info" }); }
  };
  const createSection = async (s) => {
    try {
      const created = await api.createSection(s);
      setSections(p => [...p, created]); setModal(null);
      toast(T("Section “{name}” created", { name: created.name }), { icon: "folder" }); setView(created.id);
    } catch (e) { toast(e.message || T("Could not create section"), { icon: "info" }); }
  };
  // Edit the active section's name/description/look (any .edit holder) and, for
  // admins, its visibility key. The server re-grades visibility for everyone on a
  // re-key, so reflect the returned section straight into state.
  const editSection = async (patch) => {
    if (!activeSection) return;
    try {
      const saved = await api.updateSection(activeSection.id, patch);
      setSections(p => p.map(x => x.id === saved.id ? saved : x)); setModal(null);
      toast(T("Section updated"), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Could not update section"), { icon: "info" }); }
  };
  const uploadDocs = async (files) => {
    const sid = activeSection.id;
    try {
      const made = await api.createDocuments(sid, files);
      setDocs(p => [...made, ...p]); setModal(null);
      toast(made.length !== 1 ? T("{n} documents added", { n: made.length }) : T("1 document added"), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Upload failed"), { icon: "info" }); }
  };
  const saveDoc = async (d) => {
    try {
      const saved = await api.updateDocument(d.id, d);
      setDocs(p => p.map(x => x.id === saved.id ? saved : x)); setModal(null);
      toast(T("Document updated"), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Could not save"), { icon: "info" }); }
  };
  // Author a new file-less HTML page in the active section, then open it.
  const createHtmlDoc = async ({ name, key, html, width }) => {
    if (!activeSection) return;
    try {
      const made = await api.createHtmlDocument({ section: activeSection.id, name, key, html, width });
      setDocs(p => [made, ...p]); setModal(null); setOpenDoc(made);
      toast(T("Page created"), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Could not create page"), { icon: "info" }); }
  };
  // Save edited HTML content (plus any metadata carried in from the settings
  // modal); also refresh the open viewer so it re-renders.
  const saveDocContent = async (d) => {
    try {
      const patch = { name: d.name, html: d.html };
      if (typeof d.by === "string") patch.by = d.by;
      if (typeof d.key === "string") patch.key = d.key;
      if (typeof d.width === "string") patch.width = d.width;
      if (typeof d.downloadable === "boolean") patch.downloadable = d.downloadable;
      const saved = await api.updateDocument(d.id, patch);
      setDocs(p => p.map(x => x.id === saved.id ? saved : x));
      setOpenDoc(o => (o && o.id === saved.id ? saved : o));
      setModal(null);
      toast(T("Page saved"), { icon: "check", good: true });
    } catch (e) { toast(e.message || T("Could not save"), { icon: "info" }); }
  };
  const deleteDoc = (d) => { setConfirm({
    title: T("Delete document?"), body: T("“{name}” will be removed from the data room. This can’t be undone.", { name: d.name }), danger: true, confirmLabel: T("Delete"),
    onConfirm: async () => {
      try { await api.deleteDocument(d.id); setDocs(p => p.filter(x => x.id !== d.id)); toast(T("Document deleted"), { icon: "trash" }); }
      catch (e) { toast(e.message || T("Could not delete"), { icon: "info" }); }
      setModal(null); setConfirm(null);
    },
  }); };
  const logout = async () => { await api.logout(); reload(); };
  const logoutEverywhere = () => { setConfirm({
    title: T("Sign out of all devices?"),
    body: T("This signs you out everywhere and invalidates any document links you've opened. You'll need to sign in again."),
    confirmLabel: T("Sign out everywhere"),
    onConfirm: async () => { await api.logoutAll(); setConfirm(null); reload(); },
  }); };

  // ---- qualified-investor notice (first sign-in) ----
  // Blocks the room until the user confirms their qualification once; the server
  // stamps investorAck on their record so the gate never reappears. Mutating
  // boot.me keeps the flag through admin-triggered reloads within this session.
  const [ackDone, setAckDone] = React.useState(!!me.investorAck);
  const acknowledge = async () => {
    me.investorAck = await api.acknowledgeNotice();
    setAckDone(true);
  };

  // ---- data export / import (admin) ----
  const exportData = async () => {
    try {
      const data = await api.exportData();
      api.saveJson(data, "dataroom-export.json");
      toast(T("Export downloaded"), { icon: "download", good: true });
    } catch (e) { toast(e.message || T("Could not export"), { icon: "info" }); }
  };
  const importData = async (payload) => {
    try {
      const r = await api.importData(payload);
      const n = r && r.counts ? Object.values(r.counts).reduce((a, b) => a + b, 0) : 0;
      toast(T("{n} records imported", { n }), { icon: "check", good: true });
      setModal(null);
      reload(); // re-bootstrap so sections/docs/branding reflect the imported data
    } catch (e) { toast(e.message || T("Could not import"), { icon: "info" }); }
  };

  // ---- search ----
  const q = query.trim().toLowerCase();
  const searchHits = q ? docs.filter(d => canView(d) && (d.name.toLowerCase().includes(q) || d.desc.toLowerCase().includes(q))) : [];

  // ---- crumbs ----
  let crumbs = [{ label: T("Data room"), onClick: q ? () => setQuery("") : (view ? () => setView(null) : null) }];
  if (q) crumbs.push({ label: T("Search “{q}”", { q: query.trim() }) });
  else if (activeSection) crumbs.push({ label: activeSection.name });

  // ---- full-screen page viewer ("present") ----
  // Closing the doc also closes present mode (it lives over the doc).
  React.useEffect(() => { if (!openDoc) setPresent(false); }, [openDoc]);
  // Auto-open present mode when a phone with a doc open rotates into landscape —
  // the moment the native viewer gets cramped. A ref remembers a manual exit in
  // the current landscape session so closing doesn't immediately re-trigger;
  // rotating back to portrait clears it. Only page-bearing docs qualify.
  const phoneLandscape = useMediaQuery("(max-height: 640px) and (orientation: landscape) and (pointer: coarse)");
  const dismissedLandscape = React.useRef(false);
  // Mirrors canPresent in viewer.jsx — real uploaded .pptx is excluded (no inline
  // render yet), the mock deck (type ppt, no file) is fine.
  const presentable = (d) => !!d && (d.type === "pdf" || d.type === "image" || (d.type === "ppt" && !d.hasFile));
  React.useEffect(() => {
    if (!phoneLandscape) { dismissedLandscape.current = false; return; }
    if (openDoc && presentable(openDoc) && !present && !dismissedLandscape.current) setPresent(true);
  }, [phoneLandscape, openDoc]);
  // Rotating back to portrait leaves present mode (it's the landscape view). Only
  // the landscape→portrait transition fires this (phoneLandscape dep), so the
  // "Present" button on desktop/portrait — where phoneLandscape never flips — is
  // left alone.
  React.useEffect(() => { if (!phoneLandscape) setPresent(false); }, [phoneLandscape]);
  const closePresent = () => { if (phoneLandscape) dismissedLandscape.current = true; setPresent(false); };

  // ---- back-button "menu tree" (browser Back closes the innermost layer) ----
  // Ordered outermost → innermost; reuses the same dismiss logic as the
  // breadcrumb / close buttons. The investor-notice gate is intentionally
  // omitted — it must be acknowledged, not dismissed by Back.
  const navLayers = [];
  if (q) navLayers.push({ key: "search", close: () => setQuery("") });
  else if (view) navLayers.push({ key: "section:" + view, close: () => setView(null) });
  if (openDoc) navLayers.push({ key: "doc:" + openDoc.id, close: () => setOpenDoc(null) });
  if (openDoc && present) navLayers.push({ key: "present:" + openDoc.id, close: closePresent });
  if (modal) navLayers.push({ key: "modal:" + modal.type, close: () => setModal(null) });
  if (confirm) navLayers.push({ key: "confirm", close: () => setConfirm(null) });
  if (compact && navOpen) navLayers.push({ key: "nav", close: () => setNavOpen(false) });
  useBackStack(navLayers);

  return (
    <BrandContext.Provider value={company}>
    <div className="shell">
      {!compact && (
      <Sidebar company={company} role={role} sections={visSections} docs={docs} active={q ? null : view} onNav={navSection} onLogout={logout} onLogoutAll={logoutEverywhere} onOpenDoc={openDocSafe}
        onForecasterRequest={() => askAccess("forecaster", { id: "forecaster", name: "Financial Forecaster", key: "forecaster" }, "view")} />
      )}
      {compact && navOpen && (
        <div className="drawer-scrim" onClick={() => setNavOpen(false)}>
          <div className="drawer" onClick={e => e.stopPropagation()}>
            <Sidebar company={company} role={role} sections={visSections} docs={docs} active={q ? null : view}
              onNav={(id) => { navSection(id); setNavOpen(false); }} onLogout={logout} onLogoutAll={logoutEverywhere}
              onOpenDoc={(d) => { openDocSafe(d); setNavOpen(false); }}
              onForecasterRequest={() => { setNavOpen(false); askAccess("forecaster", { id: "forecaster", name: "Financial Forecaster", key: "forecaster" }, "view"); }} />
          </div>
        </div>
      )}
      <div className="main">
        <Topbar crumbs={crumbs} role={role} setRole={setRoleId} roles={boot.roles} canPreview={me.dataroomAdmin} query={query} setQuery={setQuery}
          onMenu={compact ? () => setNavOpen(true) : null} />
        <div className="content scroll">
          {q ? (
            <SearchResults query={query.trim()} hits={searchHits} sections={sections} onOpenDoc={openDocSafe} role={role} />
          ) : activeSection && underConstruction(activeSection) ? (
            <ConstructionNotice section={activeSection} />
          ) : activeSection ? (
            <Folder section={activeSection} role={role} docs={visDocs(activeSection.id)}
              canEdit={canEditSection(activeSection)}
              onOpenDoc={openDocSafe} onDownload={download}
              onRequestAccess={(d) => askAccess("document", d, "view")}
              onAddDocs={() => setModal({ type: "adddocs" })}
              onNewPage={() => setModal({ type: "newhtmldoc" })}
              onEditSection={() => setModal({ type: "editsection" })}
              onEditDoc={(d) => setModal({ type: "editdoc", doc: d })}
              onDeleteDoc={deleteDoc} />
          ) : (
            <Home company={company} role={role} sections={visSections} docs={docs} layout={t.homeLayout} requestCount={reqCount}
              onOpenSection={navSection} onOpenDoc={openDocSafe}
              onNewSection={(which) => setModal({ type: ["branding", "data", "members", "requests", "audit"].includes(which) ? which : "section" })} />
          )}
        </div>
      </div>

      {openDoc && (
        <Viewer doc={openDoc} section={sections.find(s => s.id === openDoc.section)} role={role}
          canEdit={role.admin || docMode(openDoc) >= RANKS.edit}
          chrome={t.viewerChrome} onClose={() => setOpenDoc(null)} onDownload={download}
          onEditContent={openContentEditor}
          onPresent={() => setPresent(true)} presenting={present} />
      )}
      {openDoc && present && <PageViewer doc={openDoc} onClose={closePresent} />}

      {modal?.type === "section" && <NewSectionModal onClose={() => setModal(null)} onCreate={createSection} />}
      {modal?.type === "editsection" && activeSection && <EditSectionModal section={activeSection} isAdmin={role.admin} onClose={() => setModal(null)} onSave={editSection} />}
      {modal?.type === "adddocs" && <AddDocsModal section={activeSection} isAdmin={role.admin} onClose={() => setModal(null)} onUpload={uploadDocs} />}
      {modal?.type === "editdoc" && <EditDocModal doc={modal.doc} isAdmin={role.admin} onClose={() => setModal(null)} onSave={saveDoc} onDelete={deleteDoc} onEditContent={openContentEditor} />}
      {modal?.type === "newhtmldoc" && <NewHtmlDocModal section={activeSection} isAdmin={role.admin} onClose={() => setModal(null)} onCreate={createHtmlDoc} />}
      {modal?.type === "editcontent" && <EditContentModal doc={modal.doc} onClose={() => setModal(null)} onSave={saveDocContent} />}
      {modal?.type === "branding" && <BrandingModal company={company} onClose={() => setModal(null)} onSave={async (patch) => {
        try { const c = await api.saveCompany(patch); setCompany(c); toast(T("Branding saved"), { icon: "check", good: true }); }
        catch (e) { toast(e.message || T("Could not save branding"), { icon: "info" }); }
        setModal(null);
      }} />}
      {modal?.type === "data" && <DataModal onClose={() => setModal(null)} onExport={exportData} onImport={importData} />}
      {modal?.type === "requestaccess" && <RequestAccessModal itemType={modal.itemType} item={modal.item} wantMode={modal.wantMode} onClose={() => setModal(null)} onSubmit={submitAccessRequest} />}
      {modal?.type === "members" && <MembersModal seed={modal.seed} focusUserId={modal.focusUserId} requestId={modal.requestId} onClose={() => setModal(null)} toast={toast} />}
      {modal?.type === "requests" && <AccessRequestsModal onClose={() => setModal(null)} onCountChange={setReqCount} onGrant={(r) => setModal({ type: "members", focusUserId: r.userId, seed: { key: r.key, mode: r.wantMode }, requestId: r.id })} />}
      {modal?.type === "audit" && <AuditLogModal onClose={() => setModal(null)} />}

      <Confirm open={!!confirm} {...(confirm || {})} onCancel={() => setConfirm(null)} />
      {/* last in the DOM so it overlays every other modal at the same z-index */}
      {!ackDone && <InvestorNoticeModal onAgree={acknowledge} onSignOut={logout} />}
      <Toast toasts={toasts} />
      <TweaksUI t={t} setTweak={setTweak} />
    </div>
    </BrandContext.Provider>
  );
}

// ---- search results ----
function SearchResults({ query, hits, sections, onOpenDoc, role }) {
  // The desktop rows pin their grid via an inline style, which no media query can
  // override — on phones render the shared doc-card markup instead.
  const isMobile = useIsMobile();
  return (
    <div className="page">
      <div className="eyebrow">{T("Search")}</div>
      <h1 className="page-title" style={{ marginTop: 10 }}>{hits.length !== 1 ? T("{n} results for “{q}”", { n: hits.length, q: query }) : T("1 result for “{q}”", { q: query })}</h1>
      {hits.length === 0 ? (
        <div className="empty"><div className="ic"><Icon name="search" size={26} /></div><h3>{T("No matches")}</h3><p>{T("Try a different term, or browse the sections in the sidebar.")}</p></div>
      ) : isMobile ? (
        <div className="dcards" style={{ marginTop: 24 }}>
          {hits.map(d => {
            const sec = sections.find(s => s.id === d.section);
            return (
              <div key={d.id} className="dcard" onClick={() => onOpenDoc(d)}>
                <DocType type={d.type} />
                <div className="dc-main">
                  <div className="t">{d.name}</div>
                  <div className="s">{sec.name} · {d.updated}</div>
                </div>
              </div>
            );
          })}
        </div>
      ) : (
        <div className="dtable" style={{ marginTop: 24 }}>
          {hits.map(d => {
            const sec = sections.find(s => s.id === d.section);
            return (
              <div key={d.id} className="drow" style={{ gridTemplateColumns: "46px 1fr 160px 120px 96px" }} onClick={() => onOpenDoc(d)}>
                <DocType type={d.type} />
                <div className="dnm"><div className="t">{d.name}</div><div className="s">{d.desc}</div></div>
                <div className="cell"><span className="pill" style={{ background: shade(sec.color, 52), color: sec.color, borderColor: "transparent" }}><Icon name={sec.icon} size={11} />{sec.name}</span></div>
                <div className="cell dim">{d.updated}</div>
                <div className="cell dim tnum" style={{ textAlign: "right" }}>{d.size}</div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ---- tweaks panel ----
function TweaksUI({ t, setTweak }) {
  return (
    <TweaksPanel>
      <TweakSection label="Home layout" />
      <TweakRadio label="Browse style" value={t.homeLayout} options={["grid", "list", "sections"]} onChange={v => setTweak("homeLayout", v)} />
      <TweakSection label="Sign-in screen" />
      <TweakRadio label="Login style" value={t.loginStyle} options={["split", "centered", "fullbleed"]} onChange={v => setTweak("loginStyle", v)} />
      <TweakToggle label="Start signed in" value={t.startLoggedIn} onChange={v => setTweak("startLoggedIn", v)} />
      <TweakSection label="Document viewer" />
      <TweakRadio label="Viewer chrome" value={t.viewerChrome} options={["minimal", "framed", "immersive"]} onChange={v => setTweak("viewerChrome", v)} />
    </TweaksPanel>
  );
}

// ---- map an API user record to the role-shaped object the UI expects ----
// userToRole / resolveActiveRole live in identity.js (loaded before this file).

// ---- loading splash while the data room boots ----
function Splash() {
  return (
    <div style={{ position: "fixed", inset: 0, display: "grid", placeItems: "center", background: "var(--paper, #F7F4EE)" }}>
      <div style={{ textAlign: "center", color: "var(--ink-3, #7E8E94)" }}>
        <Brand size={34} />
        <div style={{ marginTop: 14, fontSize: 13 }}>{T("Opening the data room…")}</div>
      </div>
    </div>
  );
}

// ---- root: owns auth + the initial data fetch, then mounts <App> ----
function Root() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [boot, setBoot] = React.useState(undefined); // undefined = loading, null = login, object = ready
  // Public branding so the logo shows on the login screen and boot splash, before
  // auth. Once signed in, <App> provides its own live company over this.
  const [brand, setBrand] = React.useState(null);
  React.useEffect(() => {
    api.getPublicCompany().then(c => setBrand(c.company || null)).catch(() => {});
  }, []);

  const load = React.useCallback(async () => {
    if (!api.isAuthed()) { setBoot(null); return; }
    try {
      const me = await api.me();
      if (window.i18n) i18n.setLangLocal(me.lang || "en");
      const [companyRes, sections, documents] = await Promise.all([
        api.getCompany(), api.getSections(), api.getDocuments(),
      ]);
      if (window.setKeys) setKeys(companyRes.keys); // hydrate the dynamic key catalog
      let roles = [userToRole(me)];
      if (me.dataroomAdmin) { try { roles = (await api.listUsers()).map(userToRole); } catch (e) { /* keep self */ } }
      setBoot({ me, company: companyRes.company, sections, documents, roles });
    } catch (e) {
      api.clearToken();
      setBoot(null);
    }
  }, []);

  React.useEffect(() => { load(); }, [load]);

  if (boot === undefined) return <BrandContext.Provider value={brand}><Splash /></BrandContext.Provider>;
  if (boot === null) {
    return (
      <BrandContext.Provider value={brand}>
        <Login variant={t.loginStyle} onLogin={load} />
        <TweaksUI t={t} setTweak={setTweak} />
      </BrandContext.Provider>
    );
  }
  return <App boot={boot} t={t} setTweak={setTweak} reload={load} />;
}

ReactDOM.createRoot(document.getElementById("root")).render(<Root />);
