/* ============================================================
   Admin — /admin login, /board dashboard, and Edit Mode
   (warning banner + per-window settings panels).
   ============================================================ */
const { useState: uSa, useEffect: uEa, useRef: uRa, useMemo: uMa } = React;

/* ---------------- reusable: drag-reorder list ---------------- */
function DragList({ items, onReorder, renderRow }) {
  const [drag, setDrag] = uSa(null);
  const [over, setOver] = uSa(null);
  const commit = () => {
    if (drag == null || over == null || drag === over) {setDrag(null);setOver(null);return;}
    const next = items.slice();
    const [m] = next.splice(drag, 1);
    next.splice(over, 0, m);
    onReorder(next);
    setDrag(null);setOver(null);
  };
  return (
    <div className="draglist">
      {items.map((it, i) =>
      <div key={it.id || i}
      className={"dragrow" + (drag === i ? " dragging" : "") + (over === i && drag !== null ? " over" : "")}
      draggable
      onDragStart={() => setDrag(i)}
      onDragOver={(e) => {e.preventDefault();setOver(i);}}
      onDragEnd={commit}
      onDrop={commit}>
          <span className="grip"><Icon name="grip" size={18} /></span>
          {renderRow(it, i)}
        </div>
      )}
    </div>);

}

/* ---------------- reusable: modal (portaled to body so the scrim always covers the whole page) ---------------- */
function Modal({ title, onClose, children, footer, size }) {
  return ReactDOM.createPortal(
    <div className="modal-scrim" onClick={onClose}>
      <div className={"modal" + (size ? " " + size : "")} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <h3>{title}</h3>
          <button className="iconbtn" onClick={onClose}><Icon name="close" size={18} /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>,
    document.body
  );
}

/* ===================== /admin LOGIN (role-aware) ===================== */
function AdminLogin() {
  const { db } = useStore();
  const { go, login } = useNav();
  const [sel, setSel] = uSa(null);
  const [pw, setPw] = uSa("");
  const [stay, setStay] = uSa(true);
  const [err, setErr] = uSa(false);
  const users = db.users || [];
  const selUser = users.find((u) => u.id === sel);
  const A = window.Avatar,RB = window.RoleBadge;
  const submit = (e) => {
    e && e.preventDefault();
    if (!selUser) {setErr(true);return;}
    if (String(pw).trim().toLowerCase() !== String(selUser.pass).toLowerCase()) {setErr(true);return;}
    login(selUser, stay);
    go(selUser.role === "admin" ? "/board" : db.profile.defaultRoute);
  };
  return (
    <div className="login-screen">
      <form className="login-card wide" onSubmit={submit}>
        <div className="center col gap-8" style={{ marginBottom: 20 }}>
          <span className="iconbtn" style={{ width: 48, height: 48, borderRadius: 14, color: "var(--accent)", borderColor: "var(--accent)" }}><Icon name="lock" size={24} /></span>
          <h2 style={{ fontSize: 30, lineHeight: 1, whiteSpace: "nowrap", marginTop: 4 }}>Sign in</h2>
          <span className="mono dim" style={{ fontSize: 12 }}>choose an account · secure session</span>
        </div>
        <div className="col gap-8">
          {users.map((u) =>
          <button type="button" key={u.id} className={"lp-acct" + (sel === u.id ? " on" : "")} onClick={() => {setSel(u.id);setPw("");setErr(false);}}>
              {A && <A user={u} size={36} />}
              <span className="lp-acct-main">
                <span className="lp-acct-name">{u.name}</span>
                <span className="lp-acct-mail">{u.email}</span>
              </span>
              {RB && <RB role={u.role} />}
            </button>
          )}
        </div>
        {selUser &&
        <div className="lp-auth" style={{ marginTop: 14 }}>
            <label className="lp-lbl">Password <span className="lp-hint mono">demo · “{selUser.pass}”</span></label>
            <input type="password" autoFocus value={pw} onChange={(e) => {setPw(e.target.value);setErr(false);}} placeholder="password" />
            {err && <div className="lp-err">Try “{selUser.pass}”.</div>}
            <label className="lp-stay"><input type="checkbox" checked={stay} onChange={(e) => setStay(e.target.checked)} /> Stay connected</label>
            <button className="btn accent" type="submit" style={{ width: "100%", justifyContent: "center", marginTop: 14 }}>Enter</button>
          </div>
        }
        {!selUser && err && <div className="lp-err" style={{ marginTop: 10 }}>Pick an account to continue.</div>}
      </form>
    </div>);

}

/* ===================== /board ===================== */
function Board() {
  const { db, update } = useStore();
  const { go, setEdit, session, role, logout } = useNav();
  const [adminMenu, setAdminMenu] = uSa(false);
  const [filterOn, setFilterOn] = uSa(false);
  const [selTile, setSelTile] = uSa(null);
  const [tileModal, setTileModal] = uSa(null); // {mode:'add'|'edit', tile}
  const [userModal, setUserModal] = uSa(null);
  const p = db.profile;
  const me = session || { first: p.first, name: `${p.first} ${p.last}`, color: "#2f6bdb", role: "admin" };
  const A = window.Avatar,RB = window.RoleBadge;

  const notifs = filterOn && selTile ? db.notifications.filter((n) => n.tile === selTile) : db.notifications;
  const tileColor = (id) => db.personalBoard.find((t) => t.id === id)?.color || "var(--muted)";
  const a = db.analytics;

  const saveTile = (tile) => {
    update((d) => {
      if (tileModal.mode === "add") d.personalBoard.push({ ...tile, id: uid("t") });else
      {const i = d.personalBoard.findIndex((t) => t.id === tile.id);if (i >= 0) d.personalBoard[i] = tile;}
    });
    setTileModal(null);
  };
  const delTile = (id) => update((d) => {d.personalBoard = d.personalBoard.filter((t) => t.id !== id);});

  const saveUser = (u) => {
    update((d) => {
      d.users = d.users || [];
      if (userModal.mode === "add") d.users.push({ ...u, id: uid("u") });else
      {const i = d.users.findIndex((x) => x.id === u.id);if (i >= 0) d.users[i] = u;}
    });
    setUserModal(null);
  };
  const delUser = (id) => update((d) => {d.users = (d.users || []).filter((u) => u.id !== id);});
  // every grantable target (content + other pages + sections)
  const grantTargets = [
  ...db.content.filter((c) => c.ref !== "template-toolkit").map((c) => ({ id: c.id, label: c.title, group: c.type === "video" ? "Videos" : "Articles", private: !!c.private, kind: c.type, status: statusOf(c) })),
  ...(db.otherPages || []).map((pg) => ({ id: pg.id, label: pg.label, group: "Other pages", kind: pg.kind, private: pg.published === false })),
  ...db.sections.map((s) => ({ id: s.id, label: s.label, group: "Sections", hidden: !s.inMenu }))];


  return (
    <div className="board-shell" style={{ "--accent": "#2f6bdb", "--accent-ink": "#1f4fb0", "--accent-wash": "#eaf0fc" }}>
      <div className="board-top">
        <a className="brand" onClick={() => go(p.defaultRoute)} style={{ cursor: "pointer" }}>
          <span className="b1">{p.first}</span><span className="b2">{p.last}<span className="dot">.</span></span>
        </a>
        <div className="board-edit-cta">
          <button className="btn" style={{ fontSize: 16, padding: "13px 24px", background: "var(--c-amber)", color: "#2a1a02", borderColor: "#b56b08", boxShadow: "2px 2px 0 #b56b08" }} onClick={() => {setEdit(true);go(p.defaultRoute);}}>
            <Icon name="edit" size={19} /> Access to edit mode
          </button>
        </div>
        <div className="admin-pill">
          <button className="btn" style={{ background: "var(--c-amber)", color: "#2a1a02", borderColor: "#b56b08", boxShadow: "2px 2px 0 #b56b08" }} onClick={() => setAdminMenu((v) => !v)}>{A ? <A user={me} size={20} /> : <Icon name="user" size={18} />} {me.first} <Icon name="down" size={16} /></button>
          {adminMenu &&
          <div className="admin-menu" onMouseLeave={() => setAdminMenu(false)}>
              <div className="am-id">{me.name} · {RB && <RB role={role || "admin"} />}</div>
              <button onClick={() => {setAdminMenu(false);setEdit(true);go(p.defaultRoute);}}><Icon name="edit" size={16} /> Open settings</button>
              <button onClick={() => {setAdminMenu(false);go("/board");}}><Icon name="chart" size={16} /> Board</button>
              <hr className="sep" />
              <button onClick={() => {logout();go(p.defaultRoute);}} style={{ color: "var(--c-coral)" }}><Icon name="lock" size={16} /> Log out</button>
            </div>
          }
        </div>
      </div>

      {/* socials — vertical, bottom-left, with a discreet line */}
      <div className="board-socials">
        <span className="rail-line" aria-hidden="true" />
        {db.socials.map((s) => <SocialChip key={s.id} s={s} size={36} />)}
      </div>

      <div className="board-body">
        <div className="board-grid">
          {/* PERSONAL BOARD */}
          <section className="panel">
            <div className="panel-head">
              <h3>Personal board</h3>
              <button className="btn sm accent" onClick={() => setTileModal({ mode: "add", tile: { title: "", color: "#2f6bdb", links: [] } })}><Icon name="plus" size={16} /> Tile</button>
            </div>
            <div className="panel-body">
              <div className="tiles">
                {db.personalBoard.map((t) =>
                <div key={t.id} className={"tile" + (selTile === t.id ? " sel" : "")}>
                    <div className="tile-head" style={{ background: t.color }} onClick={() => {setSelTile(selTile === t.id ? null : t.id);if (!filterOn) setFilterOn(true);}}>
                      {t.title}
                      <span className="grow" />
                      <span className="iconbtn" style={{ width: 24, height: 24, background: "rgba(255,255,255,.2)", border: "none", color: "#fff" }}
                    onClick={(e) => {e.stopPropagation();setTileModal({ mode: "edit", tile: JSON.parse(JSON.stringify(t)) });}}><Icon name="edit" size={13} /></span>
                    </div>
                    <div className="tile-links">
                      {t.links.map((l, i) => <a key={i} href={l.url} target="_blank" rel="noreferrer">{l.title}</a>)}
                      {t.links.length === 0 && <span className="lk dim">no links yet</span>}
                    </div>
                  </div>
                )}
              </div>
            </div>
          </section>

          {/* NOTIFICATIONS */}
          <section className="panel">
            <div className="panel-head">
              <h3><Icon name="bell" size={17} style={{ verticalAlign: "-3px", marginRight: 6 }} />Notifications</h3>
              <button className={"btn sm" + (filterOn ? " accent" : " ghost")} onClick={() => setFilterOn((v) => !v)}><Icon name="filter" size={15} /> Filter</button>
            </div>
            <div className="panel-body" style={{ paddingTop: 6 }}>
              {filterOn &&
              <div className="mono dim" style={{ fontSize: 12, marginBottom: 8 }}>
                  {selTile ? <>Showing <b style={{ color: tileColor(selTile) }}>{db.personalBoard.find((t) => t.id === selTile)?.title}</b> · <a style={{ cursor: "pointer", textDecoration: "underline" }} onClick={() => setSelTile(null)}>clear</a></> : "Click a tile on the left to filter"}
                </div>
              }
              {notifs.map((n) =>
              <div className="notif" key={n.id}>
                  <span className="dotc" style={{ background: tileColor(n.tile) }} />
                  <div>
                    <div style={{ fontSize: 14 }}>{n.text}</div>
                    <div className="nt">{n.time}</div>
                  </div>
                </div>
              )}
            </div>
          </section>
        </div>

        {/* ANALYTICS + FEEDBACK */}
        <section className="panel" style={{ marginTop: 22 }}>
          <div className="panel-head"><h3><Icon name="chart" size={17} style={{ verticalAlign: "-3px", marginRight: 6 }} />Analytics &amp; feedback</h3>
            <span className="mono dim" style={{ fontSize: 12 }}>last 30 days · via API</span></div>
          <div className="analytics-grid">
            <div className="panel-body" style={{ borderRight: "1.5px solid var(--line)" }}>
              <div className="tiles" style={{ gridTemplateColumns: "repeat(auto-fill,minmax(120px,1fr))", marginBottom: 18 }}>
                <div className="stat"><span className="v">{a.visitors30d.toLocaleString()}</span><span className="l">Visitors</span></div>
                <div className="stat"><span className="v">{a.pageviews30d.toLocaleString()}</span><span className="l">Pageviews</span></div>
                <div className="stat"><span className="v">{a.avgMin}m</span><span className="l">Avg. time</span></div>
                <div className="stat"><span className="v">{a.bounce}%</span><span className="l">Bounce</span></div>
              </div>
              <div className="spark" style={{ marginBottom: 18 }}>
                {a.spark.map((v, i) => <span key={i} style={{ height: `${v / Math.max(...a.spark) * 100}%` }} />)}
              </div>
              <div className="col gap-8">
                {a.topPages.map((tp) =>
                <div key={tp.path} className="row between" style={{ fontSize: 13 }}>
                    <span className="mono">{tp.path}</span>
                    <span className="row gap-8" style={{ flex: 1, marginLeft: 12 }}>
                      <span style={{ flex: 1, height: 7, background: "var(--paper-2)", borderRadius: 4, overflow: "hidden" }}>
                        <span style={{ display: "block", height: "100%", width: `${tp.views / a.topPages[0].views * 100}%`, background: "var(--accent)" }} /></span>
                      <b style={{ width: 48, textAlign: "right" }}>{tp.views.toLocaleString()}</b>
                    </span>
                  </div>
                )}
              </div>
            </div>
            <div className="panel-body">
              <div className="mono dim" style={{ fontSize: 12, marginBottom: 10, letterSpacing: ".1em" }}>FEEDBACKS</div>
              <div className="col gap-12">
                {db.feedbacks.map((f) =>
                <div key={f.id} style={{ borderLeft: "3px solid var(--accent)", paddingLeft: 12 }}>
                    <div className="row between"><b style={{ fontFamily: "var(--font-display)" }}>{f.name}</b><Stars n={f.rating} size={12} /></div>
                    <div style={{ fontSize: 13, color: "var(--ink-soft)", margin: "3px 0" }}>{f.text}</div>
                    <div className="nt mono dim" style={{ fontSize: 11 }}>{fmtDate(f.date)}</div>
                  </div>
                )}
              </div>
            </div>
          </div>
        </section>

        {/* USERS & ACCESS */}
        <section className="panel" style={{ marginTop: 22 }}>
          <div className="panel-head">
            <h3><Icon name="user" size={17} style={{ verticalAlign: "-3px", marginRight: 6 }} />Users &amp; access</h3>
            <button className="btn sm accent" onClick={() => setUserModal({ mode: "add", user: { name: "", first: "", email: "", role: "reviewer", color: "#7a4fe0", pass: "", grants: [], editGrants: [] } })}><Icon name="plus" size={14} /> Invite</button>
          </div>
          <div className="panel-body">
            <div className="user-rows">
              {(db.users || []).map((u) => {
                const grants = userGrants(db, u);
                return (
                  <div key={u.id} className="user-row">
                    {A && <A user={u} size={38} />}
                    <div className="ur-id">
                      <div className="ur-name">{u.name}</div>
                      <div className="ur-mail mono">{u.email}</div>
                    </div>
                    {RB && <RB role={u.role} />}
                    <div className="ur-grants">
                      {u.role === "admin" ?
                      <span className="ur-all">All content &amp; settings</span> :
                      grants.length ? grants.map((g) => <span key={g.id} className="ur-grant">{g.label}</span>) : <span className="dim" style={{ fontSize: 12 }}>no pages shared</span>}
                    </div>
                    <button className="btn ghost sm" onClick={() => setUserModal({ mode: "edit", user: JSON.parse(JSON.stringify(u)) })}><Icon name="edit" size={14} /> Manage</button>
                  </div>);

              })}
            </div>
          </div>
          {(db.contentFeedback || []).length > 0 &&
          <div className="panel-body" style={{ borderTop: "1.5px solid var(--line)" }}>
              <div className="mono dim" style={{ fontSize: 12, marginBottom: 10, letterSpacing: ".1em" }}>REVIEWER &amp; GUEST FEEDBACK</div>
              <div className="col gap-12">
                {(db.contentFeedback || []).map((cf) =>
              <div key={cf.id} style={{ borderLeft: `3px solid ${roleMeta(cf.role).color}`, paddingLeft: 12 }}>
                    <div className="row gap-8" style={{ alignItems: "center" }}><b style={{ fontFamily: "var(--font-display)" }}>{cf.by}</b>{RB && <RB role={cf.role} />}<span className="grow" /><span className="mono dim" style={{ fontSize: 11 }}>on {cf.targetLabel}</span></div>
                    <div style={{ fontSize: 13, color: "var(--ink-soft)", margin: "3px 0" }}>{cf.text}</div>
                    <div className="nt mono dim" style={{ fontSize: 11 }}>{fmtDate(cf.date)}</div>
                  </div>
              )}
              </div>
            </div>
          }
        </section>
      </div>

      {tileModal && <TileModal data={tileModal} onClose={() => setTileModal(null)} onSave={saveTile} onDelete={delTile} />}
      {userModal && <UserModal data={userModal} targets={grantTargets} onClose={() => setUserModal(null)} onSave={saveUser} onDelete={delUser} />}
    </div>);

}

/* ---- tile editor modal ---- */
function TileModal({ data, onClose, onSave, onDelete }) {
  const [tile, setTile] = uSa(data.tile);
  const COLORS = ["#2f6bdb", "#e08a1e", "#1f9e72", "#e1543f", "#7a4fe0", "#211e1a"];
  const setLink = (i, k, v) => setTile((t) => {const links = t.links.slice();links[i] = { ...links[i], [k]: v };return { ...t, links };});
  return (
    <Modal title={data.mode === "add" ? "New tile" : "Edit tile"} onClose={onClose}
    footer={<>
        {data.mode === "edit" && <button className="btn ghost" style={{ color: "var(--c-coral)", marginRight: "auto" }} onClick={() => {onDelete(tile.id);onClose();}}><Icon name="trash" size={16} /> Delete</button>}
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn accent" onClick={() => onSave(tile)}><Icon name="save" size={16} /> Save</button>
      </>}>
      <div><label>Title</label><input value={tile.title} onChange={(e) => setTile((t) => ({ ...t, title: e.target.value }))} placeholder="e.g. Job & Career" /></div>
      <div><label>Colour</label>
        <div className="row gap-8">{COLORS.map((c) => <button key={c} onClick={() => setTile((t) => ({ ...t, color: c }))} style={{ width: 28, height: 28, borderRadius: 8, background: c, border: tile.color === c ? "3px solid var(--ink)" : "1.5px solid var(--line)" }} />)}</div>
      </div>
      <div>
        <label>Links</label>
        <div className="col gap-8">
          {tile.links.map((l, i) =>
          <div className="field-row" key={i}>
              <input placeholder="Title" value={l.title} onChange={(e) => setLink(i, "title", e.target.value)} />
              <input placeholder="https://…" value={l.url} onChange={(e) => setLink(i, "url", e.target.value)} />
              <button className="iconbtn danger" style={{ flex: "none" }} onClick={() => setTile((t) => ({ ...t, links: t.links.filter((_, j) => j !== i) }))}><Icon name="trash" size={16} /></button>
            </div>
          )}
          <button className="btn ghost sm" style={{ alignSelf: "flex-start" }} onClick={() => setTile((t) => ({ ...t, links: [...t.links, { title: "", url: "" }] }))}><Icon name="plus" size={15} /> Add link</button>
        </div>
      </div>
    </Modal>);

}

/* ---- per-language workflow status pills (EN published · FR validated …) ---- */
function ApStatus({ status }) {
  if (!status) return null;
  return (
    <span className="ap-status">
      {status.map((s) => (
        <span key={s.lang} className={"ap-pill " + (s.exists ? "s-" + s.status : "s-none")}
          title={s.exists ? s.lang.toUpperCase() + " · " + window.wfMeta(s.status).label : s.lang.toUpperCase() + " · not authored"}>
          {s.lang.toUpperCase()} {s.exists ? window.wfMeta(s.status).short : "—"}
        </span>
      ))}
    </span>
  );
}

/* ---- searchable dropdown access picker (dynamic-tag style) ----
   Two modes: GROUPED (sections + their content — tick the whole section or single
   items) and FLAT (a plain searchable item list). Selected items show as removable
   tags under the field; the dropdown carries a search box + checkboxes. */
function AccessPicker({ title, hint, icon = "compass", groups, items, isOn, childOn, groupState, onToggleItem, onToggleGroup, locked, lockMsg }) {
  const [open, setOpen] = uSa(false);
  const [q, setQ] = uSa("");
  const ref = uRa(null);
  uEa(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, [open]);
  const ql = q.trim().toLowerCase();
  const match = (s) => !ql || (s || "").toLowerCase().includes(ql);

  // selected → removable chips
  const chips = [];
  if (groups) groups.forEach((g) => {
    const st = groupState(g);
    if (st === "all" && g.children.length) chips.push({ id: g.id, label: "All " + g.label, whole: true, onRemove: () => onToggleGroup(g) });
    else g.children.forEach((c) => { if (childOn(g, c)) chips.push({ id: c.id, label: c.label, onRemove: () => onToggleItem(c.id) }); });
  });
  if (items) items.forEach((it) => { if (isOn(it.id)) chips.push({ id: it.id, label: it.label, onRemove: () => onToggleItem(it.id) }); });

  const fGroups = groups ? groups.map((g) => ({ ...g, kids: g.children.filter((c) => match(c.label) || match(g.label)) })).filter((g) => g.kids.length) : [];
  const fItems = items ? items.filter((it) => match(it.label)) : [];

  return (
    <div className={"ap" + (locked ? " locked" : "") + (open ? " is-open" : "")} ref={ref}>
      <button type="button" className={"ap-head" + (open ? " open" : "")} onClick={() => !locked && setOpen((o) => !o)}>
        <span className="ap-ic"><Icon name={icon} size={15} /></span>
        <span className="ap-t">{title}{hint && <span className="ap-hint">— {hint}</span>}</span>
        <span className="grow" />
        {chips.length > 0 && <span className="ap-count">{chips.length}</span>}
        {!locked && <span className="ap-chev"><Icon name={open ? "up" : "down"} size={16} /></span>}
      </button>
      {locked ?
        <div className="ap-locked-note"><Icon name="lock" size={12} /> {lockMsg}</div> :
        <>
          {chips.length > 0 &&
            <div className="ap-chips">
              {chips.map((c) => (
                <span key={c.id} className={"ap-chip" + (c.whole ? " whole" : "")}>{c.label}<button type="button" onClick={(e) => { e.stopPropagation(); c.onRemove(); }}><Icon name="close" size={11} /></button></span>
              ))}
            </div>
          }
          {open &&
            <div className="ap-pop">
              <div className="ap-search">
                <Icon name="search" size={15} />
                <input autoFocus placeholder={"Search…"} value={q} onChange={(e) => setQ(e.target.value)} />
              </div>
              <div className="ap-list">
                {fGroups.map((g) => {
                  const st = groupState(g);
                  return (
                    <div key={g.id} className="ap-grp">
                      <div className="ap-row parent" onClick={() => onToggleGroup(g)}>
                        <span className={"ap-box " + st}>{st === "all" ? <Icon name="check" size={12} /> : st === "some" ? <span className="ap-dash" /> : null}</span>
                        <span className="ap-row-l">{g.label}</span>
                        <span className="ap-row-tag">whole section</span>
                      </div>
                      {g.kids.map((c) => {
                        const on = childOn(g, c);
                        return (
                          <div key={c.id} className="ap-row child" onClick={() => onToggleItem(c.id)}>
                            <span className={"ap-box " + (on ? "all" : "none")}>{on ? <Icon name="check" size={12} /> : null}</span>
                            <span className="ap-row-l">{c.label}</span>
                            <ApStatus status={c.status} />
                          </div>
                        );
                      })}
                    </div>
                  );
                })}
                {fItems.map((it) => {
                  const on = isOn(it.id);
                  return (
                    <div key={it.id} className="ap-row flat" onClick={() => onToggleItem(it.id)}>
                      <span className={"ap-box " + (on ? "all" : "none")}>{on ? <Icon name="check" size={12} /> : null}</span>
                      <span className="ap-row-l">{it.label}{it.private && <span className="ap-row-tag violet">private</span>}</span>
                      <ApStatus status={it.status} />
                    </div>
                  );
                })}
                {fGroups.length === 0 && fItems.length === 0 && <div className="ap-empty">No matches.</div>}
              </div>
            </div>
          }
        </>
      }
    </div>
  );
}

/* ---- user / access editor modal ---- */
function UserModal({ data, targets, onClose, onSave, onDelete }) {
  const [u, setU] = uSa(data.user);
  const COLORS = ["#2f6bdb", "#1f9e72", "#7a4fe0", "#e08a1e", "#e1543f", "#211e1a"];
  const set = (k, v) => setU((x) => ({ ...x, [k]: v }));
  // two distinct access systems (see spec): SHARED = view-only pages that show in the
  // person's menu under “Shared pages”; EDITORIAL = which hidden / private / other
  // targets this person's ROLE may act on (edit / review / publish).
  const toggle = (id, kind) => setU((x) => { const key = kind === "edit" ? "editGrants" : "grants"; const g = (x[key] || []).slice(); const i = g.indexOf(id); if (i >= 0) g.splice(i, 1); else g.push(id); return { ...x, [key]: g }; });
  const roleList = ["admin", "editor", "reviewer", "publisher", "tester", "guest"];
  const isAdmin = u.role === "admin";
  const isPublisher = u.role === "publisher";
  const isEditorial = u.role === "editor" || u.role === "reviewer" || u.role === "publisher";
  const verb = u.role === "editor" ? "edit" : u.role === "reviewer" ? "review" : u.role === "publisher" ? "publish" : "open";
  // grant categories — the meaningful shares are HIDDEN sections & PRIVATE pages;
  // public sections are reachable by everyone already, so they're shown disabled.
  const T = targets || { sections: [], content: [], pages: [] };
  // categories
  const hiddenSecs = T.sections.filter((s) => s.hidden);
  const publicSecs = T.sections.filter((s) => !s.hidden);
  const privateItems = T.content.filter((c) => c.private);
  const otherItems = T.pages;
  const contentBySection = (secId) => T.content.filter((c) => c.sectionId === secId && !c.private);
  // build a {section + children} group, children filtered by a predicate
  const buildGroups = (secs, childFilter) => secs.map((s) => ({
    id: s.id, label: s.label, kind: s.kind,
    children: contentBySection(s.id).filter(childFilter),
  }));
  const hiddenGroups = buildGroups(hiddenSecs, () => true);
  const pubAllGroups = buildGroups(publicSecs, (c) => c.published);
  const pubUnpubGroups = buildGroups(publicSecs, (c) => !c.published);
  // Private contents — grouped by their (displayed) section, so the sections show in the
  // picker and only private pieces from menu-visible sections are listed.
  const privGroups = publicSecs.map((s) => ({
    id: s.id, label: s.label, kind: s.kind,
    children: T.content.filter((c) => c.sectionId === s.id && c.private),
  })).filter((g) => g.children.length);

  // membership: a grant array holds section ids (= whole section) and/or item ids
  const arr = (kind) => (kind === "edit" ? u.editGrants : u.grants) || [];
  const off = (kind) => (u[kind === "edit" ? "editGrantsOff" : "grantsOff"] || []);
  const has = (id, kind) => arr(kind).includes(id);
  // default-all pickers (By-role public sections): everything ON unless explicitly removed (off-list)
  const isOn = (id, kind, dflt) => dflt ? !off(kind).includes(id) : has(id, kind);
  const setOff = (id, kind, removed) => setU((x) => {
    const key = kind === "edit" ? "editGrantsOff" : "grantsOff";
    const g = (x[key] || []).slice(); const i = g.indexOf(id);
    if (removed && i < 0) g.push(id); if (!removed && i >= 0) g.splice(i, 1);
    return { ...x, [key]: g };
  });
  const toggleItem = (id, kind, dflt) => { if (dflt) setOff(id, kind, isOn(id, kind, true)); else toggle(id, kind); };
  // section-level: parent checkbox represents the SECTION id (= the whole section)
  const childOn = (g, c, kind, dflt) => dflt ? isOn(c.id, kind, true) : (has(g.id, kind) || has(c.id, kind));
  const groupState = (g, kind, dflt) => {
    const kids = g.children;
    const onCount = kids.filter((c) => childOn(g, c, kind, dflt)).length;
    const parent = dflt ? isOn(g.id, kind, true) : has(g.id, kind);
    if (dflt) return onCount === kids.length && kids.length ? "all" : onCount ? "some" : "none";
    if (parent) return "all";
    return onCount === 0 ? "none" : onCount === kids.length ? "all" : "some";
  };
  const toggleGroup = (g, kind, dflt) => {
    if (dflt) { const allOn = groupState(g, kind, dflt) === "all"; setU((x) => { const key = kind === "edit" ? "editGrantsOff" : "grantsOff"; let o = new Set(x[key] || []); o.delete(g.id); g.children.forEach((c) => allOn ? o.add(c.id) : o.delete(c.id)); return { ...x, [key]: [...o] }; }); return; }
    const st = groupState(g, kind, dflt);
    setU((x) => { const key = kind === "edit" ? "editGrants" : "grants"; let a = new Set(x[key] || []); if (st === "all") { a.delete(g.id); g.children.forEach((c) => a.delete(c.id)); } else { g.children.forEach((c) => a.delete(c.id)); a.add(g.id); } return { ...x, [key]: [...a] }; });
  };

  const valid = u.name.trim() && u.email.trim();
  return (
    <Modal size="lg" title={data.mode === "add" ? "Invite a user" : "Manage access"} onClose={onClose}
    footer={<>
        {data.mode === "edit" && u.role !== "admin" && <button className="btn ghost" style={{ color: "var(--c-coral)", marginRight: "auto" }} onClick={() => {onDelete(u.id);onClose();}}><Icon name="trash" size={16} /> Remove</button>}
        <button className="btn ghost" onClick={onClose}>Cancel</button>
        <button className="btn accent" disabled={!valid} onClick={() => valid && onSave(u)}><Icon name="save" size={16} /> Save</button>
      </>}>
      <div className="field-row">
        <div style={{ flex: 1 }}><label>Full name</label><input value={u.name} onChange={(e) => {const v = e.target.value;setU((x) => ({ ...x, name: v, first: v.trim().split(/\s+/)[0] || v }));}} placeholder="Tom Beaumont" /></div>
        <div style={{ flex: 1 }}><label>Email</label><input value={u.email} onChange={(e) => set("email", e.target.value)} placeholder="tom@studio.fr" /></div>
      </div>
      <div className="field-row">
        <div style={{ flex: 1 }}><label>Access password <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>demo</span></label><input value={u.pass} onChange={(e) => set("pass", e.target.value)} placeholder="a word they'll type" /></div>
        <div><label>Colour</label><div className="row gap-8">{COLORS.map((c) => <button key={c} type="button" onClick={() => set("color", c)} style={{ width: 26, height: 26, borderRadius: 8, background: c, border: u.color === c ? "3px solid var(--ink)" : "1.5px solid var(--line)" }} />)}</div></div>
      </div>
      <div>
        <label>Role</label>
        <div className="role-pick">
          {roleList.map((r) => {
            const m = roleMeta(r);
            return (
              <button key={r} type="button" className={"role-opt" + (u.role === r ? " on" : "")} style={{ "--rc": m.color }} onClick={() => set("role", r)}>
                <span className="ro-top"><span className="rb-dot" /> {m.label}</span>
                <span className="ro-blurb">{m.blurb}</span>
              </button>);

          })}
        </div>
      </div>
      <div>
        <label>Access {isAdmin && <span className="dim mono" style={{ fontWeight: 400, fontSize: 11 }}>· admins see everything</span>}</label>
        {isAdmin ?
        <div className="dim" style={{ fontSize: 13 }}>Admins have full access to all content, settings, users and permissions.</div> :

        <div className="access-wrap">
            {/* SYSTEM 1 — BY ROLE (editorial): what this person's ROLE may act on. */}
            {isEditorial &&
          <div className="access-sys edit-sys">
              <div className="as-cap"><Icon name="edit" size={13} /> By role — editorial access <span className="as-sub">— what this <b>{roleMeta(u.role).label}</b> can <b>{verb}</b></span></div>
              <p className="as-blurb">Public content is included <b>by default</b> (all ticked below — untick to narrow). Add the hidden sections, private or Other content this {roleMeta(u.role).label.toLowerCase()} should also be able to {verb}.</p>
              {hiddenGroups.length > 0 &&
                <AccessPicker title="Hidden sections" hint="not in the public menu" icon="compass"
                  groups={hiddenGroups} isOn={(id) => isOn(id, "edit")} childOn={(g, c) => childOn(g, c, "edit")}
                  groupState={(g) => groupState(g, "edit")} onToggleItem={(id) => toggleItem(id, "edit")} onToggleGroup={(g) => toggleGroup(g, "edit")} />}
              {privGroups.length > 0 && (isPublisher ?
                <AccessPicker title="Private contents" hint="a publisher can't act on private content" icon="lock" locked lockMsg="A publisher can't publish private content — no editorial access is possible." /> :
                <AccessPicker title="Private contents" hint="from your displayed sections" icon="lock"
                  groups={privGroups} isOn={(id) => isOn(id, "edit")} childOn={(g, c) => childOn(g, c, "edit")}
                  groupState={(g) => groupState(g, "edit")} onToggleItem={(id) => toggleItem(id, "edit")} onToggleGroup={(g) => toggleGroup(g, "edit")} />)}
              {otherItems.length > 0 &&
                <AccessPicker title="Other content" hint="CV, survey, game…" icon="compass"
                  items={otherItems} isOn={(id) => isOn(id, "edit")} onToggleItem={(id) => toggleItem(id, "edit")} />}
              {pubAllGroups.length > 0 &&
                <AccessPicker title="Public sections" hint="published content — all included by default" icon="check"
                  groups={pubAllGroups} isOn={(id) => isOn(id, "edit", true)} childOn={(g, c) => childOn(g, c, "edit", true)}
                  groupState={(g) => groupState(g, "edit", true)} onToggleItem={(id) => toggleItem(id, "edit", true)} onToggleGroup={(g) => toggleGroup(g, "edit", true)} />}
            </div>
          }

            {/* SYSTEM 2 — SHARED WITH (view-only): content this person sees in their menu. */}
            <div className="access-sys">
              <div className="as-cap"><Icon name="compass" size={13} /> Shared with this person <span className="as-sub">— view-only; appears in their menu under “Shared content”</span></div>
              {hiddenGroups.length > 0 &&
                <AccessPicker title="Hidden sections" hint="not in the public menu" icon="compass"
                  groups={hiddenGroups} isOn={(id) => isOn(id, "shared")} childOn={(g, c) => childOn(g, c, "shared")}
                  groupState={(g) => groupState(g, "shared")} onToggleItem={(id) => toggleItem(id, "shared")} onToggleGroup={(g) => toggleGroup(g, "shared")} />}
              {privGroups.length > 0 &&
                <AccessPicker title="Private contents" hint="from your displayed sections" icon="lock"
                  groups={privGroups} isOn={(id) => isOn(id, "shared")} childOn={(g, c) => childOn(g, c, "shared")}
                  groupState={(g) => groupState(g, "shared")} onToggleItem={(id) => toggleItem(id, "shared")} onToggleGroup={(g) => toggleGroup(g, "shared")} />}
              {otherItems.length > 0 &&
                <AccessPicker title="Other content" hint="CV, survey, game…" icon="compass"
                  items={otherItems} isOn={(id) => isOn(id, "shared")} onToggleItem={(id) => toggleItem(id, "shared")} />}
              {pubUnpubGroups.length > 0 &&
                <AccessPicker title="Public unpublished content" hint="drafts · in review · validated — not yet live" icon="edit"
                  groups={pubUnpubGroups} isOn={(id) => isOn(id, "shared")} childOn={(g, c) => childOn(g, c, "shared")}
                  groupState={(g) => groupState(g, "shared")} onToggleItem={(id) => toggleItem(id, "shared")} onToggleGroup={(g) => toggleGroup(g, "shared")} />}
            </div>
          </div>
        }
      </div>
    </Modal>);

}

Object.assign(window, { DragList, Modal, AdminLogin, Board, TileModal, UserModal });