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

function useToasts() {
  const [toasts, setToasts] = React.useState([]);
  const add = (msg, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, msg, ...opts }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 2600);
  };
  return [toasts, add];
}

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 [query, setQuery] = React.useState("");
  const [modal, setModal] = React.useState(null);        // {type, ...}
  const [confirm, setConfirm] = React.useState(null);
  const [toasts, toast] = useToasts();

  // 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;

  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.
  const openDocSafe = (d) => {
    if (!d) return;
    if (!canView(d)) { askAccess("document", d, "view"); return; }
    setOpenDoc(d);
  };
  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]);

  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" }); }
  };
  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 }) => {
    if (!activeSection) return;
    try {
      const made = await api.createHtmlDocument({ section: activeSection.id, name, key, html });
      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.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(); };

  // ---- 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 });

  return (
    <BrandContext.Provider value={company}>
    <div className="shell">
      <Sidebar company={company} role={role} sections={visSections} docs={docs} active={q ? null : view} onNav={navSection} onLogout={logout} onOpenDoc={openDocSafe} />
      <div className="main">
        <Topbar crumbs={crumbs} role={role} setRole={setRoleId} roles={boot.roles} canPreview={me.dataroomAdmin} query={query} setQuery={setQuery} />
        <div className="content scroll">
          {q ? (
            <SearchResults query={query.trim()} hits={searchHits} sections={sections} onOpenDoc={openDocSafe} role={role} />
          ) : 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" })}
              onEditDoc={(d) => setModal({ type: "editdoc", doc: d })}
              onDeleteDoc={deleteDoc} />
          ) : (
            <Home company={company} role={role} sections={visSections} docs={docs} layout={t.homeLayout}
              onOpenSection={navSection} onOpenDoc={openDocSafe}
              onNewSection={(which) => setModal({ type: ["branding", "data", "members", "requests"].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={(d) => setModal({ type: "editcontent", doc: d })} />
      )}

      {modal?.type === "section" && <NewSectionModal onClose={() => setModal(null)} onCreate={createSection} />}
      {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={(d) => setModal({ type: "editcontent", doc: d })} />}
      {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)} onGrant={(r) => setModal({ type: "members", focusUserId: r.userId, seed: { key: r.key, mode: r.wantMode }, requestId: r.id })} />}

      <Confirm open={!!confirm} {...(confirm || {})} onCancel={() => setConfirm(null)} />
      <Toast toasts={toasts} />
      <TweaksUI t={t} setTweak={setTweak} />
    </div>
    </BrandContext.Provider>
  );
}

// ---- search results ----
function SearchResults({ query, hits, sections, onOpenDoc, role }) {
  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>
      ) : (
        <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 />);
