/* ============================================================
   beedle dataroom — admin modals (new section / drop docs / edit / branding)
   ============================================================ */
const ICON_CHOICES = ["compass", "cpu", "trend", "chart", "scale", "people", "shield", "heart", "building", "folder", "globe", "gauge"];
const COLOR_CHOICES = ["#15B4E7", "#0E97C6", "#1E9E6A", "#E0762B", "#7A57D1", "#EC2B3B", "#F3BB3A", "#42565F"];

// Inline "create a new visibility key" — registers it in the server catalog and
// hydrates the local KEYS so it appears in every picker + the member editor.
function NewKeyInline({ onAdded }) {
  const [adding, setAdding] = React.useState(false);
  const [name, setName] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const add = async () => {
    const slug = name.trim();
    if (!slug) return;
    setBusy(true);
    try {
      const r = await api.createKey({ key: slug, label: slug });
      if (window.setKeys) setKeys(r.keys);
      onAdded(r.key);
      setName(""); setAdding(false);
    } catch (e) { window.alert(e.message || T("Could not add key")); }
    finally { setBusy(false); }
  };
  if (!adding) return (
    <button type="button" className="btn btn-ghost" style={{ marginTop: 8 }} onClick={() => setAdding(true)}>
      <Icon name="plus" size={14} />{T("New key")}
    </button>
  );
  return (
    <div style={{ display: "flex", gap: 8, marginTop: 8 }}>
      <input className="input" autoFocus value={name} onChange={e => setName(e.target.value)}
        placeholder={T("e.g. forecast-seedround")} onKeyDown={e => { if (e.key === "Enter") add(); if (e.key === "Escape") { setAdding(false); setName(""); } }} style={{ flex: 1 }} />
      <button type="button" className="btn btn-primary" disabled={busy || !name.trim()} onClick={add}>{T("Add")}</button>
      <button type="button" className="btn btn-ghost" onClick={() => { setAdding(false); setName(""); }}>{T("Cancel")}</button>
    </div>
  );
}

function KeyPicker({ value, onChange }) {
  return (
    <div className="keypick">
      {Object.entries(contentKeys()).map(([k, meta]) => (
        <div key={k} className={"keyopt" + (value === k ? " on" : "")} onClick={() => onChange(k)}>
          <span className="radio" />
          <div style={{ flex: 1 }}>
            <div className="nm">
              <Icon name={k === "all" ? "unlock" : "lock"} size={14} style={{ color: k === "all" ? "var(--green)" : "var(--ink-3)" }} />
              {meta.label}
            </div>
            <div className="ds">{k === "all" ? T("Visible to every authenticated member") : T("Only members whose keyring holds this key")}</div>
          </div>
          {meta.tone && <span className={"pill pill-" + meta.tone}>{k}</span>}
        </div>
      ))}
      <NewKeyInline onAdded={onChange} />
    </div>
  );
}

// Draft/publish switch for a section. When on, the section still appears in the
// sidebar but its contents are sealed off from viewers (server-enforced); only
// admins and .edit holders can enter while it's being built.
function ConstructionToggle({ value, onChange }) {
  return (
    <div className="toggle" onClick={() => onChange(!value)} style={{ cursor: "pointer" }}>
      <div>
        <div className="nm">{T("Construction mode")}</div>
        <div className="ds">{T("Hide this section’s contents from viewers while you build it. Editors and admins keep full access.")}</div>
      </div>
      <span className={"sw" + (value ? " on" : "")} />
    </div>
  );
}

function NewSectionModal({ onClose, onCreate }) {
  const [name, setName] = React.useState("");
  const [desc, setDesc] = React.useState("");
  const [icon, setIcon] = React.useState("folder");
  const [color, setColor] = React.useState("#15B4E7");
  const [key, setKey] = React.useState("all");
  const [construction, setConstruction] = React.useState(false);
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("New section")}</h2>
            <p>{T("Create a folder in the data room and set who can see it.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div style={{ display: "flex", gap: 14, alignItems: "center" }}>
              <span className="sec-ico" style={{ width: 54, height: 54, borderRadius: 15, background: `linear-gradient(150deg,${color},${shade(color, -16)})` }}><Icon name={icon} size={26} sw={1.9} /></span>
              <div style={{ flex: 1 }}>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Section name")}</label>
                <input className="input" value={name} onChange={e => setName(e.target.value)} placeholder={T("e.g. Customer References")} autoFocus />
              </div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Description")}</label>
              <input className="input" value={desc} onChange={e => setDesc(e.target.value)} placeholder={T("One line about what lives here")} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Icon")}</label>
              <div className="iconpick">
                {ICON_CHOICES.map(ic => <button key={ic} className={icon === ic ? "on" : ""} onClick={() => setIcon(ic)}><Icon name={ic} size={19} /></button>)}
              </div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Accent")}</label>
              <div className="swatch-row">
                {COLOR_CHOICES.map(c => (
                  <button key={c} onClick={() => setColor(c)} style={{ width: 30, height: 30, borderRadius: 9, background: c, border: color === c ? "2px solid var(--ink)" : "2px solid transparent", boxShadow: color === c ? "0 0 0 2px #fff inset" : "none", cursor: "pointer" }} />
                ))}
              </div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Visibility key")}</label>
              <KeyPicker value={key} onChange={setKey} />
            </div>
            <ConstructionToggle value={construction} onChange={setConstruction} />
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" disabled={!name.trim()} onClick={() => onCreate({ id: "sec_" + Date.now(), name: name.trim(), desc: desc.trim() || "—", icon, color, key, construction })}>
            <Icon name="plus" size={16} />{T("Create section")}
          </button>
        </div>
      </div>
    </div>
  );
}

// Edit an existing section's name, description, icon and accent. Admins may also
// re-key it — the server treats changing `key` as an access-control act and
// re-grades section visibility for everyone at once, so the key picker is
// admin-only (.edit holders can edit the text but not change visibility).
function EditSectionModal({ section, isAdmin, onClose, onSave }) {
  const [name, setName] = React.useState(section.name || "");
  const [desc, setDesc] = React.useState(section.desc && section.desc !== "—" ? section.desc : "");
  const [icon, setIcon] = React.useState(section.icon || "folder");
  const [color, setColor] = React.useState(section.color || "#15B4E7");
  const [key, setKey] = React.useState(section.key || "all");
  const [construction, setConstruction] = React.useState(!!section.construction);
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Edit section")}</h2>
            <p>{isAdmin ? T("Update this section’s name, description, look and who can see it.") : T("Update this section’s name, description and look.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div style={{ display: "flex", gap: 14, alignItems: "center" }}>
              <span className="sec-ico" style={{ width: 54, height: 54, borderRadius: 15, background: `linear-gradient(150deg,${color},${shade(color, -16)})` }}><Icon name={icon} size={26} sw={1.9} /></span>
              <div style={{ flex: 1 }}>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Section name")}</label>
                <input className="input" value={name} onChange={e => setName(e.target.value)} placeholder={T("e.g. Customer References")} autoFocus />
              </div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Description")}</label>
              <input className="input" value={desc} onChange={e => setDesc(e.target.value)} placeholder={T("One line about what lives here")} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Icon")}</label>
              <div className="iconpick">
                {ICON_CHOICES.map(ic => <button key={ic} className={icon === ic ? "on" : ""} onClick={() => setIcon(ic)}><Icon name={ic} size={19} /></button>)}
              </div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Accent")}</label>
              <div className="swatch-row">
                {COLOR_CHOICES.map(c => (
                  <button key={c} onClick={() => setColor(c)} style={{ width: 30, height: 30, borderRadius: 9, background: c, border: color === c ? "2px solid var(--ink)" : "2px solid transparent", boxShadow: color === c ? "0 0 0 2px #fff inset" : "none", cursor: "pointer" }} />
                ))}
              </div>
            </div>
            {isAdmin && (
              <div>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Visibility key")}</label>
                <KeyPicker value={key} onChange={setKey} />
              </div>
            )}
            <ConstructionToggle value={construction} onChange={setConstruction} />
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" disabled={!name.trim()} onClick={() => onSave({ name: name.trim(), desc: desc.trim() || "—", icon, color, construction, ...(isAdmin ? { key } : {}) })}><Icon name="check" size={16} />{T("Save section")}</button>
        </div>
      </div>
    </div>
  );
}

// classify a real File for the type badge (server re-derives authoritatively)
function fileType(file) {
  const mime = (file.type || "").toLowerCase();
  const ext = (file.name.split(".").pop() || "").toLowerCase();
  if (mime.includes("pdf") || ext === "pdf") return "pdf";
  if (mime.startsWith("image/") || ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(ext)) return "image";
  if (mime.startsWith("video/") || ["mp4", "mov", "webm", "m4v"].includes(ext)) return "video";
  if (mime.includes("presentation") || mime.includes("powerpoint") || ["ppt", "pptx"].includes(ext)) return "ppt";
  if (["html", "htm"].includes(ext)) return "html";
  return "file";
}
function fmtBytes(b) {
  if (b >= 1048576) return (b / 1048576).toFixed(1) + " MB";
  return Math.max(1, Math.round(b / 1024)) + " KB";
}

function AddDocsModal({ section, isAdmin, onClose, onUpload }) {
  const [drag, setDrag] = React.useState(false);
  const [staged, setStaged] = React.useState([]);
  const [busy, setBusy] = React.useState(false);
  const [, bumpKeys] = React.useState(0); // re-render the key <select>s when the catalog grows
  const inputRef = React.useRef(null);

  const addFiles = (fileList) => {
    const items = Array.from(fileList).map((file, i) => ({
      id: "stg_" + Date.now() + "_" + i + "_" + Math.round(Math.random() * 1e4),
      file,
      name: file.name.replace(/\.[a-z0-9]+$/i, ""),
      type: fileType(file),
      size: fmtBytes(file.size),
      key: section.key,
      downloadable: true,
    }));
    setStaged(s => [...s, ...items]);
  };
  const toggle = (id, field) => setStaged(s => s.map(x => x.id === id ? { ...x, [field]: !x[field] } : x));
  const setKey = (id, k) => setStaged(s => s.map(x => x.id === id ? { ...x, key: k } : x));
  const setName = (id, v) => setStaged(s => s.map(x => x.id === id ? { ...x, name: v } : x));
  const removeItem = (id) => setStaged(s => s.filter(x => x.id !== id));

  const submit = async () => {
    if (!staged.length) return;
    setBusy(true);
    try { await onUpload(staged); } catch (e) { /* App toasts the error */ } finally { setBusy(false); }
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 580 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Add documents")}</h2>
            <p>{T("Uploading into")} <strong>{section.name}</strong>. {T("Set downloadable & key per file.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <input ref={inputRef} type="file" multiple style={{ display: "none" }}
            onChange={e => { addFiles(e.target.files); e.target.value = ""; }} />
          <div className={"dropzone" + (drag ? " drag" : "")}
            onDragOver={e => { e.preventDefault(); setDrag(true); }}
            onDragLeave={() => setDrag(false)}
            onDrop={e => { e.preventDefault(); setDrag(false); if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files); }}
            onClick={() => inputRef.current && inputRef.current.click()}>
            <div className="ic"><Icon name="upload" size={22} /></div>
            <div className="t">{T("Drag files here or click to browse")}</div>
            <div className="h">{T("PDF · images · HTML · video · slides — up to 120 MB each")}</div>
          </div>
          {isAdmin && <NewKeyInline onAdded={() => bumpKeys(v => v + 1)} />}
          {!isAdmin && staged.length > 0 && (
            <div style={{ fontSize: 12, color: "var(--ink-3)", margin: "4px 2px" }}>{T("Files inherit this section’s visibility key.")}</div>
          )}
          {staged.map(f => (
            <div key={f.id} className="stage-item">
              <DocType type={f.type} size="sm" />
              <div className="meta">
                <input className="input" value={f.name} onChange={e => setName(f.id, e.target.value)}
                  style={{ fontSize: 13, padding: "5px 8px", marginBottom: 5 }} />
                <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>{f.type.toUpperCase()} · {f.size}</div>
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 7, alignItems: "flex-end" }}>
                <label className="opt" style={{ cursor: "pointer" }} onClick={() => toggle(f.id, "downloadable")}>
                  <span className={"sw" + (f.downloadable ? " on" : "")} style={{ width: 34, height: 20 }} />
                  {T("Downloadable")}
                </label>
                {isAdmin && (
                  <select value={f.key} onChange={e => setKey(f.id, e.target.value)} style={{ fontSize: 12, border: "1px solid var(--line)", borderRadius: 8, padding: "3px 6px", color: "var(--ink-2)", background: "#fff" }}>
                    {Object.entries(contentKeys()).map(([k, m]) => <option key={k} value={k}>{m.label}</option>)}
                  </select>
                )}
              </div>
              <button className="xbtn" style={{ width: 28, height: 28 }} title={T("Remove")} onClick={() => removeItem(f.id)}><Icon name="x" size={15} /></button>
            </div>
          ))}
        </div>
        <div className="modal-foot">
          <span style={{ fontSize: 13, color: "var(--ink-3)" }}>{staged.length !== 1 ? T("{n} files ready", { n: staged.length }) : T("1 file ready")}</span>
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" disabled={!staged.length || busy} onClick={submit}><Icon name="check" size={16} />{busy ? T("Uploading…") : staged.length === 1 ? T("Add document") : T("Add {n} documents", { n: staged.length })}</button>
        </div>
      </div>
    </div>
  );
}

function EditDocModal({ doc, isAdmin, onClose, onSave, onDelete, onEditContent }) {
  const [name, setName] = React.useState(doc.name);
  const [downloadable, setDownloadable] = React.useState(doc.downloadable);
  const [key, setKey] = React.useState(doc.key);
  const [by, setBy] = React.useState(doc.by || "");
  const isPage = doc.type === "html" && !doc.hasFile;   // an authored page has editable content
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Document settings")}</h2>
            <p style={{ display: "flex", alignItems: "center", gap: 8 }}><DocType type={doc.type} size="sm" /> {doc.type.toUpperCase()} · {doc.size}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Display name")}</label>
              <input className="input" value={name} onChange={e => setName(e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Author")}</label>
              <input className="input" value={by} onChange={e => setBy(e.target.value)} placeholder={T("e.g. Dana Whitfield")} />
            </div>
            {isPage && onEditContent && (
              <div className="toggle" onClick={() => onEditContent({ ...doc, name: name.trim() || doc.name, downloadable, key, by: by.trim() || doc.by })} style={{ cursor: "pointer" }}>
                <div>
                  <div className="nm">{T("Page content")}</div>
                  <div className="ds">{T("Open the rich-text editor to write or paste the page.")}</div>
                </div>
                <span className="btn btn-ghost" style={{ pointerEvents: "none" }}><Icon name="edit" size={15} />{T("Edit content")}</span>
              </div>
            )}
            {isAdmin && (
              <div className="toggle" onClick={() => setDownloadable(d => !d)} style={{ cursor: "pointer" }}>
                <div>
                  <div className="nm">{T("Allow download")}</div>
                  <div className="ds">{T("Members can save a copy. Off = view-only in the room.")}</div>
                </div>
                <span className={"sw" + (downloadable ? " on" : "")} />
              </div>
            )}
            {isAdmin && (
              <div>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Visibility key")}</label>
                <KeyPicker value={key} onChange={setKey} />
              </div>
            )}
          </div>
        </div>
        <div className="modal-foot">
          <button className="btn btn-danger" onClick={() => onDelete(doc)}><Icon name="trash" size={16} />{T("Delete")}</button>
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" onClick={() => onSave({ ...doc, name: name.trim() || doc.name, downloadable, key, by: by.trim() || doc.by })}><Icon name="check" size={16} />{T("Save")}</button>
        </div>
      </div>
    </div>
  );
}

// Shared label style for the doc-authoring modals.
const FIELD_LBL = { display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 };

// Page-width picker for authored HTML pages. Mirrors the server's DOC_WIDTHS.
// "auto" adapts per element; the rest are explicit sheet-width overrides.
const WIDTH_CHOICES = [["auto", "Auto"], ["normal", "Normal"], ["wide", "Wide"], ["full", "Full width"]];
const WIDTH_HINTS = {
  auto: "Fits the content — text stays readable, wide tables and images break out.",
  normal: "A classic ~760px reading column.",
  wide: "A roomier sheet for table- or chart-heavy pages.",
  full: "Fills the viewer.",
};
function WidthPicker({ value, onChange }) {
  const v = value || "auto";
  return (
    <div>
      <div className="seg" role="group" aria-label={T("Page width")}>
        {WIDTH_CHOICES.map(([k, label]) => (
          <button key={k} type="button" className={v === k ? "on" : ""} onClick={() => onChange(k)}>{T(label)}</button>
        ))}
      </div>
      <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 6 }}>{T(WIDTH_HINTS[v])}</div>
    </div>
  );
}

// Author/edit a data-room HTML page with the full rich-text editor (admin only).
// Reuses RichText in `full` mode: paste HTML, paste/insert graphics (uploaded via
// api.uploadAsset). Saved HTML is sanitised on the way out and again at render.
function EditContentModal({ doc, onClose, onSave }) {
  const [name, setName] = React.useState(doc.name);
  const [html, setHtml] = React.useState(doc.html || "");
  const [width, setWidth] = React.useState(doc.width || "auto");
  return (
    <div className="scrim rt-doc-scrim" onClick={onClose}>
      <div className="modal rt-doc-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Edit page")}</h2>
            <p>{T("Rich HTML — paste formatted text and images. Admins only.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <label style={FIELD_LBL}>{T("Page title")}</label>
          <input className="input" value={name} onChange={e => setName(e.target.value)} style={{ marginBottom: 14 }} />
          <label style={FIELD_LBL}>{T("Page width")}</label>
          <div style={{ marginBottom: 14 }}><WidthPicker value={width} onChange={setWidth} /></div>
          <RichText full value={html} onChange={setHtml} uploadAsset={api.uploadAsset} minHeight="52vh"
            ariaLabel={T("Page content")} placeholder={T("Write or paste your page…")}
            onError={(e) => window.alert((e && e.message) || T("Image upload failed"))} />
        </div>
        <div className="modal-foot">
          <div className="spacer" style={{ flex: 1 }} />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" onClick={() => onSave({ ...doc, name: name.trim() || doc.name, html, width })}><Icon name="check" size={16} />{T("Save")}</button>
        </div>
      </div>
    </div>
  );
}

// Create a new file-less HTML page in the active section.
function NewHtmlDocModal({ section, isAdmin, onClose, onCreate }) {
  const [name, setName] = React.useState("");
  const [key, setKey] = React.useState((section && section.key) || "all");
  const [html, setHtml] = React.useState("");
  const [width, setWidth] = React.useState("auto");
  return (
    <div className="scrim rt-doc-scrim" onClick={onClose}>
      <div className="modal rt-doc-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("New page")}</h2>
            <p>{section ? T("Added to “{name}”", { name: section.name }) : ""}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <label style={FIELD_LBL}>{T("Page title")}</label>
          <input className="input" value={name} onChange={e => setName(e.target.value)} placeholder={T("e.g. Investment memo")} style={{ marginBottom: 14 }} />
          {isAdmin && (
            <React.Fragment>
              <label style={FIELD_LBL}>{T("Visibility key")}</label>
              <div style={{ marginBottom: 14 }}><KeyPicker value={key} onChange={setKey} /></div>
            </React.Fragment>
          )}
          <label style={FIELD_LBL}>{T("Page width")}</label>
          <div style={{ marginBottom: 14 }}><WidthPicker value={width} onChange={setWidth} /></div>
          <label style={FIELD_LBL}>{T("Content")}</label>
          <RichText full value={html} onChange={setHtml} uploadAsset={api.uploadAsset} minHeight="44vh"
            ariaLabel={T("Page content")} placeholder={T("Write or paste your page…")}
            onError={(e) => window.alert((e && e.message) || T("Image upload failed"))} />
        </div>
        <div className="modal-foot">
          <div className="spacer" style={{ flex: 1 }} />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" disabled={!name.trim()} onClick={() => onCreate({ name: name.trim(), key, html, width })}><Icon name="check" size={16} />{T("Create page")}</button>
        </div>
      </div>
    </div>
  );
}

const LOGO_MAX_BYTES = 512 * 1024; // ~512KB — kept small so it fits the JSON body.
const LOGO_TYPES = ["image/png", "image/svg+xml", "image/jpeg", "image/webp", "image/gif"];

function BrandingModal({ company, onClose, onSave }) {
  const [name, setName] = React.useState(company.name);
  const [desc, setDesc] = React.useState(company.description);
  const [accent, setAccent] = React.useState(company.colors.blue);
  const [logo, setLogo] = React.useState(company.logo || "");
  const [err, setErr] = React.useState("");
  const fileRef = React.useRef(null);

  // Sign-in screen copy. Prefill each field with the stored value, falling back to
  // the login page's built-in default so the admin edits the text they actually see.
  const loginDefs = loginTextDefaults();
  const storedLogin = company.login || {};
  const [loginText, setLoginText] = React.useState(() => {
    const init = {};
    for (const k of ["eyebrow", "heading", "subtitle", "quote", "quoteBy", "secureBadge", "regionBadge"]) {
      init[k] = storedLogin[k] && storedLogin[k].trim() ? storedLogin[k] : loginDefs[k];
    }
    return init;
  });
  const [stats, setStats] = React.useState(() =>
    (Array.isArray(storedLogin.stats) && storedLogin.stats.length ? storedLogin.stats : loginDefs.stats)
      .map(s => ({ k: s.k || "", v: s.v || "" })));
  const setLoginField = (k, v) => setLoginText(p => ({ ...p, [k]: v }));
  const setStat = (i, key, v) => setStats(p => p.map((s, j) => (j === i ? { ...s, [key]: v } : s)));

  function pickLogo(e) {
    const file = e.target.files && e.target.files[0];
    e.target.value = ""; // allow re-picking the same file
    if (!file) return;
    if (!LOGO_TYPES.includes(file.type)) { setErr(T("Use a PNG, SVG, JPEG, WebP or GIF image.")); return; }
    if (file.size > LOGO_MAX_BYTES) { setErr(T("Logo is too large (max 512KB).")); return; }
    const reader = new FileReader();
    reader.onload = () => { setLogo(reader.result); setErr(""); };
    reader.onerror = () => setErr(T("Could not read that file."));
    reader.readAsDataURL(file);
  }

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Company branding")}</h2>
            <p>{T("Stored in")} <code style={{ fontSize: 12 }}>dataroom/company</code> — {T("shown across the room.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Logo")}</label>
              <div style={{ display: "flex", gap: 14, alignItems: "center" }}>
                <span className={"sb-logo" + (logo ? " has-img" : "")} style={{ width: 56, height: 56, borderRadius: 14, background: logo ? "#fff" : `linear-gradient(150deg,${accent},${shade(accent, -22)})` }}>
                  {logo ? <img src={logo} alt={T("Logo")} /> : <Icon name="heart" size={26} />}
                </span>
                <input ref={fileRef} type="file" accept={LOGO_TYPES.join(",")} style={{ display: "none" }} onChange={pickLogo} />
                <button className="btn btn-ghost" onClick={() => fileRef.current && fileRef.current.click()}><Icon name="upload" size={16} />{T("Upload logo")}</button>
                {logo && <button className="btn btn-ghost" onClick={() => setLogo("")}><Icon name="trash" size={16} />{T("Remove")}</button>}
                <span style={{ fontSize: 12, color: "var(--ink-3)" }}>PNG/SVG · transparent</span>
              </div>
              {err && <div style={{ fontSize: 12, color: "var(--red, #EC2B3B)", marginTop: 7 }}>{err}</div>}
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Company name")}</label>
              <input className="input" value={name} onChange={e => setName(e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Description")}</label>
              <textarea className="input" rows={3} style={{ resize: "vertical" }} value={desc} onChange={e => setDesc(e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 9 }}>{T("Brand accent")}</label>
              <div className="swatch-row">
                {COLOR_CHOICES.map(c => (
                  <button key={c} onClick={() => setAccent(c)} style={{ width: 30, height: 30, borderRadius: 9, background: c, border: accent === c ? "2px solid var(--ink)" : "2px solid transparent", boxShadow: accent === c ? "0 0 0 2px #fff inset" : "none", cursor: "pointer" }} />
                ))}
              </div>
            </div>

            <div style={{ borderTop: "1px solid var(--line, #E7E2D8)", margin: "4px 0 2px" }} />
            <div>
              <div style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{T("Sign-in screen")}</div>
              <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3 }}>{T("The text on the login page. Leave a field blank to use the default.")}</div>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Eyebrow")}</label>
              <input className="input" value={loginText.eyebrow} placeholder={loginDefs.eyebrow} onChange={e => setLoginField("eyebrow", e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Heading")}</label>
              <input className="input" value={loginText.heading} placeholder={loginDefs.heading} onChange={e => setLoginField("heading", e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Subtitle")}</label>
              <textarea className="input" rows={2} style={{ resize: "vertical" }} value={loginText.subtitle} placeholder={loginDefs.subtitle} onChange={e => setLoginField("subtitle", e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Sidebar quote")}</label>
              <textarea className="input" rows={2} style={{ resize: "vertical" }} value={loginText.quote} placeholder={loginDefs.quote} onChange={e => setLoginField("quote", e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Quote attribution")}</label>
              <input className="input" value={loginText.quoteBy} placeholder={loginDefs.quoteBy} onChange={e => setLoginField("quoteBy", e.target.value)} />
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Sidebar stats")}</label>
              <div style={{ display: "grid", gap: 8 }}>
                {stats.map((s, i) => (
                  <div key={i} style={{ display: "flex", gap: 8 }}>
                    <input className="input" style={{ flex: 1 }} value={s.k} placeholder={T("Label")} onChange={e => setStat(i, "k", e.target.value)} />
                    <input className="input" style={{ flex: 1 }} value={s.v} placeholder={T("Value")} onChange={e => setStat(i, "v", e.target.value)} />
                  </div>
                ))}
              </div>
            </div>
            <div style={{ display: "flex", gap: 18 }}>
              <div style={{ flex: 1 }}>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Security badge")}</label>
                <input className="input" value={loginText.secureBadge} placeholder={loginDefs.secureBadge} onChange={e => setLoginField("secureBadge", e.target.value)} />
              </div>
              <div style={{ flex: 1 }}>
                <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Region badge")}</label>
                <input className="input" value={loginText.regionBadge} placeholder={loginDefs.regionBadge} onChange={e => setLoginField("regionBadge", e.target.value)} />
              </div>
            </div>
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" onClick={() => onSave({ name, description: desc, logo, colors: { blue: accent }, login: { ...loginText, stats } })}><Icon name="check" size={16} />{T("Save branding")}</button>
        </div>
      </div>
    </div>
  );
}

// Export / import the room's content collections as JSON (admin only).
// Export downloads a self-contained snapshot; import merges it back in (upsert,
// never deletes). Document file bytes are NOT included — only their metadata.
function DataModal({ onClose, onExport, onImport }) {
  const [busy, setBusy] = React.useState(false);
  const [staged, setStaged] = React.useState(null); // parsed { collections, ... }
  const [err, setErr] = React.useState("");
  const fileRef = React.useRef(null);

  const counts = staged && staged.collections
    ? ["config", "sections", "documents", "forecasts"]
        .map((k) => `${(staged.collections[k] || []).length} ${k}`)
        .join(" · ")
    : "";

  const doExport = async () => {
    setBusy(true);
    try { await onExport(); } catch (e) { /* App toasts */ } finally { setBusy(false); }
  };

  const pickFile = (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = ""; // allow re-picking the same file
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const obj = JSON.parse(reader.result);
        if (!obj || typeof obj !== "object" || !obj.collections) throw new Error("bad");
        setStaged(obj); setErr("");
      } catch { setStaged(null); setErr(T("That doesn’t look like a data-room export file.")); }
    };
    reader.onerror = () => { setStaged(null); setErr(T("Could not read that file.")); };
    reader.readAsText(file);
  };

  const doImport = async () => {
    if (!staged) return;
    setBusy(true);
    try { await onImport(staged); } catch (e) { /* App toasts */ } finally { setBusy(false); }
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Export / import data")}</h2>
            <p>{T("Content collections as JSON — branding, sections, documents & saved models. User accounts and uploaded file contents are not included.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Export")}</label>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 9 }}>{T("Download a JSON snapshot of the current data room.")}</div>
              <button className="btn btn-ghost" disabled={busy} onClick={doExport}><Icon name="download" size={16} />{T("Download export")}</button>
            </div>
            <div>
              <label style={{ display: "block", fontSize: 13, fontWeight: 600, color: "var(--ink-2)", marginBottom: 7 }}>{T("Import")}</label>
              <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 9 }}>{T("Merge a JSON export back in. Existing records are updated and new ones added — nothing is deleted.")}</div>
              <input ref={fileRef} type="file" accept="application/json,.json" style={{ display: "none" }} onChange={pickFile} />
              <button className="btn btn-ghost" disabled={busy} onClick={() => fileRef.current && fileRef.current.click()}><Icon name="upload" size={16} />{T("Choose JSON file")}</button>
              {staged && <div style={{ fontSize: 12.5, color: "var(--ink-2)", marginTop: 9 }}>{T("Ready to import")}: {counts}</div>}
              {err && <div style={{ fontSize: 12, color: "var(--red, #EC2B3B)", marginTop: 9 }}>{err}</div>}
            </div>
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Close")}</button>
          <button className="btn btn-primary" disabled={!staged || busy} onClick={doImport}><Icon name="check" size={16} />{busy ? T("Importing…") : T("Import")}</button>
        </div>
      </div>
    </div>
  );
}

// ---- Request access (shown to a list-only member who clicks a locked item) ----
function RequestAccessModal({ itemType, item, wantMode, onClose, onSubmit }) {
  const [note, setNote] = React.useState("");
  const label = KEYS[item.key] ? KEYS[item.key].label : item.key;
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 480 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Would you like to request access?")}</h2>
            <p>{T("“{name}” isn’t open to you yet — we’ll let an admin know you’re interested.", { name: item.name })}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          <div className="form-grid">
            <div style={{ display: "flex", gap: 10, alignItems: "center", fontSize: 13, color: "var(--ink-2)" }}>
              <span className="pill"><Icon name="lock" size={11} />{label}</span>
              <span style={{ color: "var(--ink-3)" }}>{T("requesting")} <strong>{wantMode}</strong> {T("access")}</span>
            </div>
            <div>
              <label style={FIELD_LBL}>{T("Optional note")}</label>
              <textarea className="input" rows={3} style={{ resize: "vertical" }} value={note} onChange={e => setNote(e.target.value)} placeholder={T("Anything you’d like to add (optional)")} autoFocus />
            </div>
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn btn-primary" onClick={() => onSubmit({ itemType, item, wantMode, note: note.trim() })}><Icon name="key" size={16} />{T("Request access")}</button>
        </div>
      </div>
    </div>
  );
}

// Per-key mode selector — turns the KEYS catalog into a set of dotted grants.
function GrantEditor({ keyring, onChange }) {
  const map = {};
  for (const g of keyring || []) {
    const i = g.indexOf(".");
    map[i === -1 ? g : g.slice(0, i)] = i === -1 ? "view" : g.slice(i + 1);
  }
  const MODES = ["—", "list", "view", "tune", "edit"];
  const setMode = (k, m) => {
    const next = { ...map };
    if (m === "—") delete next[k]; else next[k] = m;
    onChange(Object.entries(next).map(([k, m]) => `${k}.${m}`));
  };
  return (
    <div>
      {Object.entries(KEYS).map(([k, meta]) => (
        <div key={k} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 0", borderBottom: "1px solid var(--line)" }}>
          <div style={{ flex: 1, display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontSize: 13.5, fontWeight: 600 }}>{meta.label}</span>
            {meta.tone && <span className={"pill pill-" + meta.tone}>{k}</span>}
          </div>
          <select value={map[k] || "—"} onChange={e => setMode(k, e.target.value)} style={{ fontSize: 12, border: "1px solid var(--line)", borderRadius: 8, padding: "4px 7px", background: "#fff", color: "var(--ink-2)" }}>
            {MODES.map(m => <option key={m} value={m}>{m === "—" ? T("No access") : m}</option>)}
          </select>
        </div>
      ))}
      <NewKeyInline onAdded={(k) => setMode(k, "view")} />
    </div>
  );
}

// Members & access — edit each member's key grants and (optionally) their password.
function MembersModal({ seed, focusUserId, requestId, onClose, toast }) {
  const [users, setUsers] = React.useState(null);
  const [sel, setSel] = React.useState(null);
  const [pw, setPw] = React.useState("");
  const [busy, setBusy] = React.useState(false);

  function startEdit(u) {
    let keyring = (u.keyring || []).slice();
    if (seed && seed.key) {
      const i = keyring.findIndex(g => g.split(".")[0] === seed.key);
      const dotted = seed.key + "." + (seed.mode || "view");
      if (i >= 0) keyring[i] = dotted; else keyring.push(dotted);
    }
    setSel({ ...u, keyring }); setPw("");
  }

  // Start a blank draft member. Every authenticated member holds "all" at view by
  // default, so seed the keyring to match the server's default.
  function startNew() {
    setSel({ isNew: true, name: "", email: "", keyring: ["all.view"] }); setPw("");
  }

  React.useEffect(() => { (async () => {
    try {
      const list = await api.listUsers();
      setUsers(list);
      const initial = focusUserId ? list.find(u => String(u.id || u._id) === String(focusUserId)) : list[0];
      if (initial) startEdit(initial);
    } catch (e) { toast && toast(e.message || T("Could not load members"), { icon: "info" }); }
  })(); }, []);

  const save = async () => {
    if (!sel) return;
    if (sel.isNew) {
      const email = (sel.email || "").trim();
      if (!email) return toast && toast(T("An email is required"), { icon: "info" });
      if (!pw.trim()) return toast && toast(T("A password is required for a new member"), { icon: "info" });
      setBusy(true);
      try {
        const created = await api.createUser({ email, name: (sel.name || "").trim() || email, password: pw, keyring: sel.keyring });
        setUsers(us => [...(us || []), created]);
        startEdit(created);
        toast && toast(T("Member created"), { icon: "check", good: true });
      } catch (e) { toast && toast(e.message || T("Could not create member"), { icon: "info" }); }
      finally { setBusy(false); }
      return;
    }
    setBusy(true);
    try {
      const patch = { keyring: sel.keyring };
      if (pw.trim()) patch.password = pw;
      const updated = await api.updateUser(sel.id || sel._id, patch);
      setUsers(us => us.map(u => (u.id || u._id) === (updated.id || updated._id) ? updated : u));
      if (requestId) { try { await api.resolveAccessRequest(requestId); } catch (e) { /* non-fatal */ } }
      toast && toast(T("Member updated"), { icon: "check", good: true });
      setPw("");
    } catch (e) { toast && toast(e.message || T("Could not save member"), { icon: "info" }); }
    finally { setBusy(false); }
  };

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 680 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Members & access")}</h2>
            <p>{T("Grant each member a key at a mode — list, view, tune or edit. Set a password if needed.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll" style={{ display: "flex", gap: 16, minHeight: 320 }}>
          <div style={{ width: 200, borderRight: "1px solid var(--line)", paddingRight: 12, overflow: "auto" }}>
            <button className={"btn btn-ghost" + (sel && sel.isNew ? " sel" : "")} style={{ width: "100%", marginBottom: 8, justifyContent: "flex-start" }} onClick={startNew}>
              <Icon name="plus" size={15} />{T("New member")}
            </button>
            {!users ? <div style={{ fontSize: 13, color: "var(--ink-3)" }}>{T("Loading…")}</div> :
              users.map(u => {
                const id = u.id || u._id; const on = sel && (sel.id || sel._id) === id;
                return (
                  <button key={id} className={"menu-item" + (on ? " sel" : "")} style={{ width: "100%" }} onClick={() => startEdit(u)}>
                    <Avatar role={{ name: u.name, initials: u.initials, avatar: u.avatar, admin: u.dataroomAdmin }} size={28} />
                    <div style={{ flex: 1, minWidth: 0, textAlign: "left" }}>
                      <div className="nm" style={{ fontSize: 13 }}>{u.name}</div>
                      <div className="ds" style={{ fontSize: 11 }}>{u.dataroomAdmin ? T("Admin") : u.email}</div>
                    </div>
                  </button>
                );
              })}
          </div>
          <div style={{ flex: 1 }}>
            {!sel ? <div style={{ fontSize: 13, color: "var(--ink-3)" }}>{T("Select a member.")}</div> : sel.isNew ? (
              <div className="form-grid">
                <div style={{ fontSize: 14, fontWeight: 700 }}>{T("New member")}</div>
                <div>
                  <label style={FIELD_LBL}>{T("Email")}</label>
                  <input className="input" type="email" value={sel.email} onChange={e => setSel(s => ({ ...s, email: e.target.value }))} placeholder="name@company.com" autoComplete="off" />
                </div>
                <div>
                  <label style={FIELD_LBL}>{T("Name")}</label>
                  <input className="input" value={sel.name} onChange={e => setSel(s => ({ ...s, name: e.target.value }))} placeholder={T("Defaults to the email")} autoComplete="off" />
                </div>
                <div>
                  <label style={FIELD_LBL}>{T("Key grants")}</label>
                  <GrantEditor keyring={sel.keyring} onChange={(kr) => setSel(s => ({ ...s, keyring: kr }))} />
                </div>
                <div>
                  <label style={FIELD_LBL}>{T("Password")}</label>
                  <input className="input" type="password" value={pw} onChange={e => setPw(e.target.value)} placeholder={T("Required")} autoComplete="new-password" />
                </div>
              </div>
            ) : (
              <div className="form-grid">
                <div style={{ fontSize: 14, fontWeight: 700 }}>{sel.name} {sel.dataroomAdmin && <span className="pill pill-gold">{T("Admin")}</span>}</div>
                <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: -8 }}>{sel.email}</div>
                {sel.dataroomAdmin && <div style={{ fontSize: 12, color: "var(--ink-3)" }}>{T("Admins hold every key at edit — grants below don’t restrict them.")}</div>}
                <div>
                  <label style={FIELD_LBL}>{T("Key grants")}</label>
                  <GrantEditor keyring={sel.keyring} onChange={(kr) => setSel(s => ({ ...s, keyring: kr }))} />
                </div>
                <div>
                  <label style={FIELD_LBL}>{T("Set / change password")}</label>
                  <input className="input" type="password" value={pw} onChange={e => setPw(e.target.value)} placeholder={T("Leave blank to keep current password")} autoComplete="new-password" />
                </div>
              </div>
            )}
          </div>
        </div>
        <div className="modal-foot">
          <div className="spacer" />
          <button className="btn btn-ghost" onClick={onClose}>{T("Close")}</button>
          <button className="btn btn-primary" disabled={!sel || busy} onClick={save}><Icon name="check" size={16} />{busy ? T("Saving…") : sel && sel.isNew ? T("Create member") : T("Save member")}</button>
        </div>
      </div>
    </div>
  );
}

// Admin list of open access requests; Grant opens the member editor pre-seeded.
// onCountChange keeps the caller's pending badge in sync as requests are handled.
function AccessRequestsModal({ onClose, onGrant, onCountChange }) {
  const [rows, setRows] = React.useState(null);
  const [err, setErr] = React.useState("");
  const load = async () => {
    try {
      const r = await api.listAccessRequests("open");
      setRows(r); setErr("");
      if (onCountChange) onCountChange(r.length);
    } catch (e) { setErr(e.message || T("Could not load requests")); }
  };
  React.useEffect(() => { load(); }, []);
  const dismiss = async (r) => { try { await api.resolveAccessRequest(r.id); load(); } catch (e) { /* ignore */ } };
  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 640 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Access requests")}</h2>
            <p>{T("Members who asked to be granted a higher mode on an item.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll">
          {!rows ? <div style={{ fontSize: 13, color: "var(--ink-3)" }}>{T("Loading…")}</div> :
            rows.length === 0 ? (
              <div className="empty"><div className="ic"><Icon name="check" size={26} /></div><h3>{T("No open requests")}</h3><p>{T("You’re all caught up.")}</p></div>
            ) : rows.map(r => (
              <div key={r.id} className="stage-item" style={{ alignItems: "center" }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600 }}>{r.userName} <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>{T("wants")} </span><span className="pill"><Icon name="lock" size={11} />{r.wantMode}</span></div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3 }}>{r.itemType} · {r.itemName} · {(KEYS[r.key] ? KEYS[r.key].label : r.key)}{r.currentMode ? ` · ${T("now")}: ${r.currentMode}` : ""}</div>
                  {r.note && <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 5, fontStyle: "italic" }}>“{r.note}”</div>}
                </div>
                <button className="btn btn-ghost" onClick={() => dismiss(r)}>{T("Dismiss")}</button>
                <button className="btn btn-primary" onClick={() => onGrant(r)}><Icon name="key" size={15} />{T("Grant")}</button>
              </div>
            ))}
          {err && <div style={{ fontSize: 12, color: "var(--red,#EC2B3B)", marginTop: 9 }}>{err}</div>}
        </div>
        <div className="modal-foot"><div className="spacer" /><button className="btn btn-ghost" onClick={onClose}>{T("Close")}</button></div>
      </div>
    </div>
  );
}

// Admin audit log — reads the access trail (GET /api/audit) newest first, with
// member + event filters fed by /api/audit/facets and cursor-based "Load more".
// Each row is a human sentence; clicking it expands the complete entry.
function AuditLogModal({ onClose }) {
  const [facets, setFacets] = React.useState({ events: [], users: [] });
  const [user, setUser] = React.useState("");   // "" = everyone, "none" = visitors
  const [event, setEvent] = React.useState(""); // "" = all, else a name/prefix
  const [rows, setRows] = React.useState(null); // null = first page in flight
  const [cursor, setCursor] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState("");
  const [open, setOpen] = React.useState(() => new Set()); // expanded entry _ids
  const toggle = (id) => setOpen(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });

  const load = async (before) => {
    setBusy(true);
    try {
      const r = await api.listAudit({ limit: 50, before, event, user });
      setRows(prev => (before && prev ? [...prev, ...r.entries] : r.entries));
      setCursor(r.nextBefore);
      setErr("");
    } catch (e) {
      setErr(e.message || T("Could not load the audit log"));
      if (!before) { setRows([]); setCursor(null); }
    } finally { setBusy(false); }
  };

  React.useEffect(() => { api.auditFacets().then(setFacets).catch(() => { /* pickers stay empty */ }); }, []);
  React.useEffect(() => { setRows(null); setCursor(null); load(); }, [user, event]);

  const fmtAt = (d, withSeconds) => {
    try { return new Date(d).toLocaleString(i18n.getLang() === "fr" ? "fr-CA" : [], { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", ...(withSeconds ? { second: "2-digit" } : {}) }); }
    catch { return String(d); }
  };

  // One human sentence per event, built from the names denormalized into the
  // entry at write time. Legacy rows without the expected name fall through to
  // the raw event string; the complete entry is always in the expansion.
  const sentenceFor = (e) => {
    const d = e.details || {}, r = e.resource || {};
    const named = e.userName || e.userId; // a real, attributable actor (vs. anonymous visitor)
    const who = named || T("Visitor");
    const name = r.name, ev = e.event;
    const visitor = d.email || T("Visitor");
    if (ev === "auth.login") {
      if (e.ok) return T("{who} signed in", { who });
      if (d.reason === "locked") return T("Sign-in blocked for {email} (too many attempts)", { email: d.email || who });
      return T("Failed sign-in for {email}", { email: d.email || who });
    }
    if (ev === "auth.notice_acknowledged") return T("{who} accepted the investor notice", { who });
    if (ev === "company.updated") return T("{who} updated company branding", { who });
    if (ev === "data.exported") return T("{who} exported the room data", { who });
    if (ev === "data.imported") return T("{who} imported room data", { who });
    if (ev === "share.link_denied" && !name) return T("A visitor was denied an invalid share link");
    if (!name) return ev; // every sentence below names its resource
    switch (ev) {
      case "user.created": return T("{who} created member {name}", { who, name });
      case "user.updated": return T("{who} updated member {name}", { who, name });
      case "user.deleted": return T("{who} deleted member {name}", { who, name });
      case "document.uploaded": return T("{who} uploaded “{name}” to {section}", { who, name, section: r.section || "—" });
      case "document.created": return T("{who} created page “{name}” in {section}", { who, name, section: r.section || "—" });
      case "document.updated": return T("{who} edited “{name}”", { who, name });
      case "document.deleted": return T("{who} deleted “{name}”", { who, name });
      case "document.viewed": return T("{who} viewed “{name}”", { who, name });
      case "document.downloaded": return T("{who} downloaded “{name}”", { who, name });
      case "section.created": return T("{who} created section {name}", { who, name });
      case "section.updated": return T("{who} updated section {name}", { who, name });
      case "section.deleted": return T("{who} deleted section {name}", { who, name });
      case "forecast.created": return T("{who} created forecast model “{name}”", { who, name });
      case "forecast.updated": return T("{who} saved forecast model “{name}”", { who, name });
      case "forecast.archived": return T("{who} archived forecast model “{name}”", { who, name });
      case "forecast.recovered": return T("{who} recovered forecast model “{name}”", { who, name });
      case "forecast.deleted": return T("{who} deleted forecast model “{name}”", { who, name }); // legacy rows

      case "access.requested": return T("{who} requested {mode} access to “{name}”", { who, name, mode: d.wantMode || "view" });
      case "access.resolved": return d.status === "open"
        ? T("{who} reopened the access request from {requester} for “{name}”", { who, name, requester: d.requestedBy || "—" })
        : T("{who} resolved the access request from {requester} for “{name}”", { who, name, requester: d.requestedBy || "—" });
      case "company.key_saved": return T("{who} saved visibility key “{name}”", { who, name });
      case "share.link_created": return T("{who} created a share link for “{name}”", { who, name });
      case "share.link_revoked": return d.revoked
        ? T("{who} disabled the share link for “{name}”", { who, name })
        : T("{who} re-enabled the share link for “{name}”", { who, name });
      // Public share routes attribute the caller when they also hold an account
      // session; otherwise the open/denial is genuinely anonymous ("A visitor").
      case "share.link_opened": return named
        ? T("{who} opened the share link for “{name}”", { who, name })
        : T("A visitor opened the share link for “{name}”", { name });
      case "share.link_denied": return named
        ? T("{who} was denied the share link for “{name}” ({reason})", { who, name, reason: d.reason || "—" })
        : T("A visitor was denied the share link for “{name}” ({reason})", { name, reason: d.reason || "—" });
      case "share.session_created": return T("{email} started a session on “{name}”", { email: visitor, name });
      case "share.tuning_saved": return T("{email} saved tuning on “{name}”", { email: visitor, name });
      case "share.knob_requested": return T("{email} requested a tunable variable on “{name}”", { email: visitor, name });
      case "share.note_left": return T("{email} left a note on the expired link for “{name}”", { email: visitor, name });
      default: return ev + " · " + name;
    }
  };

  // Flatten resource/details into ["resource.tokenPrefix", "cK5QfIYM"] pairs so
  // the expansion shows every stored field (keys stay raw — they're field names).
  const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
    v && typeof v === "object" && !Array.isArray(v)
      ? flat(v, prefix + k + ".")
      : [[prefix + k, Array.isArray(v) ? v.join(", ") : String(v)]]);

  const detailRows = (e) => [
    [T("Time"), fmtAt(e.at, true)],
    [T("Event"), e.event],
    [T("Status"), e.ok ? T("OK") : T("Denied")],
    [T("Member"), e.userName ? e.userName + " (" + e.userId + ")" : e.userId || T("Visitor")],
    [T("IP address"), e.ip || "—"],
    [T("User agent"), e.userAgent || "—"],
    ...flat(e.resource, "resource."),
    ...flat(e.details, "details."),
  ];

  // The API filters by prefix, so the event picker offers each namespace
  // ("share.*") ahead of the individual event names.
  const prefixes = Array.from(new Set(facets.events.map(ev => ev.split(".")[0] + ".")));
  const SEL = { fontSize: 12.5, border: "1px solid var(--line)", borderRadius: 8, padding: "5px 8px", background: "#fff", color: "var(--ink-2)", maxWidth: 230 };
  const MONO = "ui-monospace, SFMono-Regular, Menlo, monospace";

  return (
    <div className="scrim" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 760 }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <h2>{T("Audit log")}</h2>
            <p>{T("Every access event recorded in the room, newest first. Filter by member and event.")}</p>
          </div>
          <button className="xbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body scroll" style={{ minHeight: 320 }}>
          <div style={{ display: "flex", gap: 10, alignItems: "center", marginBottom: 12, flexWrap: "wrap" }}>
            <select value={user} onChange={e => setUser(e.target.value)} style={SEL} aria-label={T("Filter by member")}>
              <option value="">{T("All members")}</option>
              <option value="none">{T("Visitors (no account)")}</option>
              {facets.users.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
            </select>
            <select value={event} onChange={e => setEvent(e.target.value)} style={SEL} aria-label={T("Filter by event")}>
              <option value="">{T("All events")}</option>
              {prefixes.map(p => <option key={p} value={p}>{p}*</option>)}
              {facets.events.map(ev => <option key={ev} value={ev}>{ev}</option>)}
            </select>
            <div className="spacer" style={{ flex: 1 }} />
            {rows && <span style={{ fontSize: 12, color: "var(--ink-3)" }}>{T("{n} entries", { n: rows.length + (cursor ? "+" : "") })}</span>}
          </div>
          {!rows ? <div style={{ fontSize: 13, color: "var(--ink-3)" }}>{T("Loading…")}</div> :
            rows.length === 0 ? (
              <div className="empty"><div className="ic"><Icon name="shield" size={26} /></div><h3>{T("No entries")}</h3><p>{T("Nothing in the log matches these filters.")}</p></div>
            ) : rows.map(e => {
              const isOpen = open.has(e._id);
              return (
                <div key={e._id} className="stage-item" style={{ alignItems: "flex-start", cursor: "pointer" }} onClick={() => toggle(e._id)}>
                  <span className={"pill " + (e.ok ? "pill-green" : "pill-red")}>{e.ok ? T("OK") : T("Denied")}</span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600 }}>{sentenceFor(e)}</div>
                    <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3 }}>{fmtAt(e.at)}{e.ip ? " · " + e.ip : ""}</div>
                    {isOpen && (
                      <div style={{ display: "grid", gridTemplateColumns: "max-content 1fr", gap: "4px 14px", marginTop: 9, paddingTop: 9, borderTop: "1px solid var(--line)", fontSize: 12 }} onClick={ev => ev.stopPropagation()}>
                        {detailRows(e).map(([k, v]) => (
                          <React.Fragment key={k}>
                            <div style={{ color: "var(--ink-3)", whiteSpace: "nowrap" }}>{k}</div>
                            <div style={{ fontFamily: MONO, wordBreak: "break-all", color: "var(--ink-2)" }}>{v}</div>
                          </React.Fragment>
                        ))}
                      </div>
                    )}
                  </div>
                  <Icon name="arrowR" size={14} style={{ color: "var(--ink-4)", marginTop: 5, flexShrink: 0, transform: isOpen ? "rotate(90deg)" : "none", transition: "transform .15s" }} />
                </div>
              );
            })}
          {err && <div style={{ fontSize: 12, color: "var(--red,#EC2B3B)", marginTop: 9 }}>{err}</div>}
          {rows && cursor && (
            <div style={{ display: "flex", justifyContent: "center", marginTop: 10 }}>
              <button className="btn btn-ghost" disabled={busy} onClick={() => load(cursor)}>{busy ? T("Loading…") : T("Load more")}</button>
            </div>
          )}
        </div>
        <div className="modal-foot"><div className="spacer" /><button className="btn btn-ghost" onClick={onClose}>{T("Close")}</button></div>
      </div>
    </div>
  );
}

Object.assign(window, { NewSectionModal, EditSectionModal, AddDocsModal, EditDocModal, BrandingModal, DataModal, KeyPicker, RequestAccessModal, MembersModal, AccessRequestsModal, AuditLogModal });
