/* admin.jsx — Last Rose admin panel. Cognito user administration (the ARES panel,
   ported): list / create / reset-password / reset-authenticator / promote-demote /
   set demo access / enable-disable / delete. Admin-only (self-installing overlay).
   Talks to window.lrAdmin. Also hosts the per-user DEMO-ACCESS grid + the deep-link
   AccessBlocked gate, so entitlements are enforced entirely from this overlay layer
   (the regenerated hub.jsx / app.jsx stay pristine). */

function _fmtDate(iso) {
  if (!iso) return "—";
  try { return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }); }
  catch (e) { return "—"; }
}

// Toggle helper for the pillar grid. "__all__"/"__none__" are select-all / clear sentinels.
function _togglePillar(list, id) {
  if (id === "__none__") return [];
  if (id === "__all__") return ((window.lrAllPillars && window.lrAllPillars()) || []).map((p) => p.id);
  return list.indexOf(id) >= 0 ? list.filter((x) => x !== id) : list.concat(id);
}

function StatusPill({ u, th }) {
  const map = {
    CONFIRMED: { label: "Active", bg: "rgba(63,174,106,0.16)", fg: "#3fae6a" },
    FORCE_CHANGE_PASSWORD: { label: "Invited", bg: "rgba(214,158,46,0.16)", fg: "#d69e2e" },
    RESET_REQUIRED: { label: "Reset", bg: "rgba(214,158,46,0.16)", fg: "#d69e2e" },
  };
  const s = !u.enabled ? { label: "Disabled", bg: "rgba(216,59,84,0.15)", fg: "#d83b54" }
    : (map[u.status] || { label: u.status || "—", bg: th.chip, fg: th.sub });
  return <span className="rounded-full px-2.5 py-1 text-[11px] font-semibold" style={{ background: s.bg, color: s.fg }}>{s.label}</span>;
}

/* The 11-card access grid. Data-driven from window.lrAllPillars() (the UNFILTERED pillar
   list), so it always matches whatever the hub actually ships — add a 12th pillar and the
   grid grows with zero changes here. `selected` is an array of granted pillar ids. */
function PillarGrid({ th, selected, onToggle, disabled, note }) {
  const all = (window.lrAllPillars && window.lrAllPillars()) || [];
  return (
    <div>
      <div className="mb-2.5 flex items-center justify-between">
        <label className="text-[11px] font-semibold uppercase tracking-[0.18em]" style={{ color: th.sub }}>Demo access</label>
        {!disabled && (
          <div className="flex items-center gap-2 text-[11px] font-semibold">
            <button type="button" onClick={() => onToggle("__all__")} className="hover:underline" style={{ color: th.accentText || th.accent }}>Select all</button>
            <span style={{ color: th.faint }}>·</span>
            <button type="button" onClick={() => onToggle("__none__")} className="hover:underline" style={{ color: th.sub }}>None</button>
          </div>
        )}
      </div>
      <div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
        {all.map((p) => {
          const on = selected.indexOf(p.id) >= 0;
          const soon = p.status && p.status !== "live";
          return (
            <button
              key={p.id} type="button" disabled={disabled} onClick={() => onToggle(p.id)}
              className="flex items-center justify-between gap-2 rounded-xl border px-3 py-2 text-left text-[13px] transition-colors disabled:cursor-not-allowed disabled:opacity-50"
              style={{
                borderColor: on ? th.accent : th.cardBorder,
                background: on ? "color-mix(in oklab, " + th.accent + " 13%, transparent)" : th.chip,
                color: th.inkStrong,
              }}
            >
              <span className="min-w-0 truncate">
                {p.title}
                {soon && <span className="ml-1.5 text-[10px] uppercase tracking-[0.12em]" style={{ color: th.faint }}>soon</span>}
              </span>
              <span className="flex h-4 w-4 shrink-0 items-center justify-center rounded" style={{ border: `1px solid ${on ? th.accent : th.cardBorder}`, background: on ? th.accent : "transparent", color: "#fff" }}>
                {on && <Icon name="Check" size={11} />}
              </span>
            </button>
          );
        })}
      </div>
      {note && <div className="mt-2.5 text-[12px] leading-relaxed" style={{ color: th.faint }}>{note}</div>}
    </div>
  );
}

/* Modal to edit an existing user's demo access. Local draft state; commits via onSave. */
function AccessModal({ th, user, onClose, onSave }) {
  const [sel, setSel] = React.useState(Array.isArray(user.pillars) ? user.pillars.slice() : []);
  const [saving, setSaving] = React.useState(false);
  const total = ((window.lrAllPillars && window.lrAllPillars()) || []).length;
  async function save() {
    setSaving(true);
    const ok = await onSave(sel);
    setSaving(false);
    if (ok) onClose();
  }
  return (
    <div className="fixed inset-0 z-[10000] flex items-center justify-center p-6" style={{ background: "rgba(5,5,8,0.62)", backdropFilter: "blur(3px)" }} onClick={onClose}>
      <div className="w-full max-w-lg rounded-2xl p-6" style={{ background: th.card, border: `1px solid ${th.cardBorder}`, boxShadow: "0 30px 80px -30px rgba(0,0,0,0.7)" }} onClick={(e) => e.stopPropagation()}>
        <div className="mb-1 flex items-start justify-between gap-4">
          <div className="min-w-0">
            <div className="text-[11px] font-semibold uppercase tracking-[0.16em]" style={{ color: th.sub }}>Demo access</div>
            <div className="mt-0.5 truncate text-[17px] font-semibold" style={{ color: th.inkStrong, fontFamily: "'Spectral', serif" }}>{user.email}</div>
          </div>
          <button onClick={onClose} className="shrink-0 rounded-lg p-1.5" style={{ color: th.sub, background: th.chip, border: `1px solid ${th.cardBorder}` }} title="Close"><Icon name="X" size={16} /></button>
        </div>
        <div className="mt-4">
          <PillarGrid th={th} selected={sel} onToggle={(id) => setSel((s) => _togglePillar(s, id))}
            note={`${sel.length} of ${total} demos enabled. Changes apply the next time this user signs in.`} />
        </div>
        <div className="mt-6 flex items-center justify-end gap-2.5">
          <button onClick={onClose} className="rounded-xl px-4 py-2.5 text-[13px] font-semibold" style={{ color: th.ink, background: th.chip, border: `1px solid ${th.cardBorder}` }}>Cancel</button>
          <button onClick={save} disabled={saving}
            className="inline-flex items-center gap-2 rounded-xl px-4 py-2.5 text-[13px] font-semibold text-white transition-transform active:scale-[0.98] disabled:opacity-70"
            style={{ background: th.accent, boxShadow: "0 10px 24px -12px rgba(216,59,84,0.7)" }}>
            {saving ? <Icon name="LoaderCircle" size={15} style={{ animation: "lr-spin 1s linear infinite" }} /> : <Icon name="Check" size={15} />} Save access
          </button>
        </div>
      </div>
    </div>
  );
}

function AdminPanel({ mode, onBack, onLogout }) {
  const th = window.SHELL_THEMES[mode] || window.SHELL_THEMES.dark;
  const [users, setUsers] = React.useState([]);
  const [me, setMe] = React.useState("");
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState("");
  const [busy, setBusy] = React.useState("");
  const [notice, setNotice] = React.useState(null); // { title, email, password }
  const [copied, setCopied] = React.useState(false);
  const [newEmail, setNewEmail] = React.useState("");
  const [newRole, setNewRole] = React.useState("lastrose-users");
  const [newPillars, setNewPillars] = React.useState([]); // ids granted to the NEW user (default none)
  const [editUser, setEditUser] = React.useState(null);   // row whose access is being edited
  const totalPillars = ((window.lrAllPillars && window.lrAllPillars()) || []).length;

  async function refresh() {
    setLoading(true); setError("");
    try { const r = await window.lrAdmin.call("list"); setUsers(r.users || []); setMe(r.me || ""); }
    catch (e) {
      setError(e.message || "Could not load users.");
      if (e && (e.status === 401 || e.status === 403)) {
        // Backend says we're not (or no longer) an admin — drop the flag so the overlay hides itself.
        try { localStorage.setItem("lr-isadmin", "0"); } catch (x) {}
        try { window.dispatchEvent(new Event("lr-auth-changed")); } catch (x) {}
      }
    }
    setLoading(false);
  }
  React.useEffect(() => { refresh(); }, []);

  // opts: { confirm?: string, email?: string }. Returns true only on success.
  async function act(action, payload, opts) {
    opts = opts || {};
    if (opts.confirm && !window.confirm(opts.confirm)) return false;
    setBusy(payload.username || payload.email || action); setError("");
    let ok = false;
    try {
      const r = await window.lrAdmin.call(action, payload);
      if (r && r.password) {
        const title = r.reprovisioned ? "Authenticator reset — new temporary password"
          : action === "create" ? "User created — temporary password" : "Password reset — temporary password";
        // Reset actions return only the (UUID) username — prefer the row's email for the handoff.
        setNotice({ title, email: opts.email || r.email || r.username, password: r.password }); setCopied(false);
      }
      await refresh();
      ok = true;
    } catch (e) { setError(e.message || "That action failed."); }
    setBusy(""); return ok;
  }

  async function addUser(e) {
    e.preventDefault();
    const email = newEmail.trim().toLowerCase();
    if (!email.includes("@")) { setError("Enter a valid email address."); return; }
    const isAdmin = newRole === "lastrose-admins";
    const ok = await act("create", { email, group: newRole, pillars: isAdmin ? [] : newPillars }, { email });
    if (ok) { setNewEmail(""); setNewPillars([]); } // keep the typed address on failure (e.g. "already exists")
  }

  function copyPw() {
    if (!notice) return;
    try { navigator.clipboard.writeText(notice.password); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch (e) {}
  }

  const cardStyle = { background: th.card, border: `1px solid ${th.cardBorder}` };
  const btn = (extra) => ({ color: th.ink, background: th.chip, border: `1px solid ${th.cardBorder}`, ...extra });
  const newIsAdmin = newRole === "lastrose-admins";

  return (
    <div className="h-full w-full overflow-y-auto lr-scroll" style={{ background: th.page, fontFamily: "'Manrope', sans-serif" }}>
      {/* top bar */}
      <header className="sticky top-0 z-20 border-b" style={{ borderColor: th.headerBorder, background: th.header, backdropFilter: "blur(14px)" }}>
        <div className="mx-auto flex max-w-[1280px] items-center justify-between px-10 py-4">
          <div className="flex items-center gap-3">
            <button onClick={onBack} className="inline-flex items-center gap-2 rounded-xl px-3 py-2 text-[13px] font-medium transition-colors" style={btn()}>
              <Icon name="ArrowLeft" size={15} /> Hub
            </button>
            <div className="flex items-center gap-2.5">
              <RoseMark size={30} color={th.accent} petals={6} />
              <span style={{ fontFamily: "'Spectral', serif", fontSize: 22, color: th.inkStrong }}>User administration</span>
            </div>
          </div>
          <div className="flex items-center gap-2.5">
            <button onClick={onLogout} className="inline-flex items-center gap-2 rounded-xl px-3.5 py-2 text-[13px] font-medium transition-colors" style={btn()}>
              <Icon name="LogOut" size={15} /> Log out
            </button>
          </div>
        </div>
      </header>

      <main className="mx-auto max-w-[1280px] px-10 pb-24 pt-10">
        <p className="mb-6 max-w-2xl text-[14px] leading-relaxed" style={{ color: th.ink }}>
          The pool is <b>invite-only</b> — it is the allowlist. New users get a temporary password (shown once here);
          on first sign-in they set their own and enroll an authenticator app. Each user only sees the demos you grant
          them below. Signed in as <b style={{ color: th.inkStrong }}>{me || "—"}</b>.
        </p>

        {/* credential notice */}
        {notice && (
          <div className="mb-6 rounded-2xl p-4" style={{ background: "rgba(63,174,106,0.10)", border: "1px solid rgba(63,174,106,0.35)" }}>
            <div className="flex items-start justify-between gap-4">
              <div className="min-w-0">
                <div className="text-[12px] font-semibold uppercase tracking-[0.14em]" style={{ color: "#3fae6a" }}>{notice.title}</div>
                <div className="mt-1 text-[13px]" style={{ color: th.ink }}>{notice.email} — hand this to the user; it won't be shown again.</div>
                <div className="mt-2 select-all font-mono text-[15px]" style={{ color: th.inkStrong }}>{notice.password}</div>
              </div>
              <div className="flex flex-col items-end gap-2">
                <button onClick={copyPw} className="inline-flex items-center gap-2 rounded-xl px-3 py-2 text-[13px] font-semibold" style={btn()}>
                  <Icon name={copied ? "Check" : "Copy"} size={14} /> {copied ? "Copied" : "Copy"}
                </button>
                <button onClick={() => setNotice(null)} className="text-[12px] font-semibold" style={{ color: th.sub }}>Dismiss</button>
              </div>
            </div>
          </div>
        )}

        {error && (
          <div className="mb-5 inline-flex items-center gap-2 rounded-xl px-3.5 py-2 text-[13px]" style={{ background: "rgba(216,59,84,0.12)", color: "#d83b54" }}>
            <Icon name="CircleAlert" size={15} /> {error}
          </div>
        )}

        {/* add user */}
        <form onSubmit={addUser} className="mb-8 rounded-2xl p-4" style={cardStyle}>
          <div className="flex flex-wrap items-end gap-3">
            <div className="flex-1 min-w-[220px]">
              <label className="mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.18em]" style={{ color: th.sub }}>Invite a user</label>
              <input value={newEmail} onChange={(e) => setNewEmail(e.target.value)} placeholder="person@company.com" type="email" autoComplete="off"
                className="h-11 w-full rounded-xl border bg-transparent px-3.5 text-[15px] outline-none" style={{ borderColor: th.cardBorder, color: th.inkStrong }} />
            </div>
            <div>
              <label className="mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.18em]" style={{ color: th.sub }}>Role</label>
              <select value={newRole} onChange={(e) => setNewRole(e.target.value)}
                className="h-11 rounded-xl border bg-transparent px-3 text-[14px] outline-none" style={{ borderColor: th.cardBorder, color: th.inkStrong }}>
                <option value="lastrose-users" style={{ color: "#111" }}>User</option>
                <option value="lastrose-admins" style={{ color: "#111" }}>Administrator</option>
              </select>
            </div>
            <button type="submit" disabled={busy === (newEmail.trim().toLowerCase())}
              className="inline-flex h-11 items-center gap-2 rounded-xl px-4 text-[14px] font-semibold text-white transition-transform active:scale-[0.98]"
              style={{ background: th.accent, boxShadow: "0 10px 24px -12px rgba(216,59,84,0.7)" }}>
              <Icon name="UserPlus" size={16} /> Add user
            </button>
          </div>

          {/* demo-access picker for the new user (disabled for admins — they see all) */}
          <div className="mt-4 border-t pt-4" style={{ borderColor: th.cardBorder }}>
            <PillarGrid
              th={th}
              selected={newIsAdmin ? [] : newPillars}
              disabled={newIsAdmin}
              onToggle={(id) => setNewPillars((s) => _togglePillar(s, id))}
              note={newIsAdmin
                ? "Administrators can see every demo and manage users — no need to pick."
                : "New users start with no access. Pick the demos this person should be able to open."}
            />
          </div>
        </form>

        {/* table */}
        <div className="overflow-hidden rounded-2xl" style={cardStyle}>
          <div className="grid grid-cols-[1.5fr_0.7fr_0.7fr_0.9fr_auto] items-center gap-3 border-b px-5 py-3 text-[11px] font-semibold uppercase tracking-[0.16em]"
            style={{ borderColor: th.cardBorder, color: th.sub }}>
            <span>User</span><span>Status</span><span>Role</span><span>Access</span><span className="text-right">Actions</span>
          </div>

          {loading ? (
            <div className="flex items-center gap-2 px-5 py-8 text-[14px]" style={{ color: th.sub }}>
              <Icon name="LoaderCircle" size={16} style={{ animation: "lr-spin 1s linear infinite" }} /> Loading users…
            </div>
          ) : users.length === 0 ? (
            <div className="px-5 py-8 text-[14px]" style={{ color: th.sub }}>No users yet.</div>
          ) : users.map((u) => {
            const isMe = (u.email || "").toLowerCase() === (me || "").toLowerCase();  // me = caller email; u.username is a UUID
            const acting = busy === u.username;
            const grants = Array.isArray(u.pillars) ? u.pillars.length : 0;
            return (
              <div key={u.username} className="grid grid-cols-[1.5fr_0.7fr_0.7fr_0.9fr_auto] items-center gap-3 border-b px-5 py-3.5 text-[14px]"
                style={{ borderColor: th.cardBorder }}>
                <div className="min-w-0">
                  <div className="truncate font-medium" style={{ color: th.inkStrong }}>{u.email}{isMe && <span className="ml-2 text-[11px]" style={{ color: th.faint }}>(you)</span>}</div>
                  <div className="mt-0.5 text-[11px]" style={{ color: th.faint }}>Added {_fmtDate(u.created)}</div>
                </div>
                <div><StatusPill u={u} th={th} /></div>
                <div>
                  <span className="rounded-full px-2.5 py-1 text-[11px] font-semibold"
                    style={{ background: u.is_admin ? "rgba(139,92,246,0.16)" : th.chip, color: u.is_admin ? "#8b5cf6" : th.sub }}>
                    {u.is_admin ? "Admin" : "User"}
                  </span>
                </div>
                <div>
                  {u.is_admin ? (
                    <span className="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-semibold" style={{ background: "rgba(139,92,246,0.14)", color: "#8b5cf6" }}>
                      <Icon name="Sparkles" size={12} /> All demos
                    </span>
                  ) : (
                    <button onClick={() => setEditUser(u)} title="Edit demo access"
                      className="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-semibold transition-colors hover:brightness-110"
                      style={{ background: th.chip, color: grants ? th.ink : "#d69e2e", border: `1px solid ${th.cardBorder}` }}>
                      <Icon name="LayoutGrid" size={12} /> {grants ? (grants + "/" + totalPillars) : "No access"}
                    </button>
                  )}
                </div>
                <div className="flex items-center justify-end gap-1.5">
                  {acting && <Icon name="LoaderCircle" size={15} style={{ color: th.sub, animation: "lr-spin 1s linear infinite" }} />}
                  <IconBtn th={th} icon="KeyRound" title="Reset password"
                    onClick={() => act("reset-password", { username: u.username }, { email: u.email, confirm: `Reset the password for ${u.email}? They'll receive a new temporary password.` })} />
                  <IconBtn th={th} icon="Smartphone" title="Reset authenticator"
                    onClick={() => act("reset-totp", { username: u.username }, { email: u.email, confirm: `Reset the authenticator for ${u.email}? This re-provisions the account — their old authenticator stops working and a new temporary password is issued.` })} />
                  {u.is_admin
                    ? <IconBtn th={th} icon="ShieldOff" title="Demote to user" disabled={isMe}
                        onClick={() => act("set-group", { username: u.username, group: "lastrose-admins", add: false }, { email: u.email, confirm: `Remove administrator rights from ${u.email}?` })} />
                    : <IconBtn th={th} icon="ShieldCheck" title="Promote to admin"
                        onClick={() => act("set-group", { username: u.username, group: "lastrose-admins", add: true }, { email: u.email })} />}
                  {u.enabled
                    ? <IconBtn th={th} icon="Ban" title="Disable" disabled={isMe}
                        onClick={() => act("set-enabled", { username: u.username, enabled: false }, { email: u.email, confirm: `Disable sign-in for ${u.email}?` })} />
                    : <IconBtn th={th} icon="CircleCheck" title="Enable"
                        onClick={() => act("set-enabled", { username: u.username, enabled: true }, { email: u.email })} />}
                  <IconBtn th={th} icon="Trash2" title="Delete" danger disabled={isMe}
                    onClick={() => act("delete", { username: u.username }, { email: u.email, confirm: `Permanently delete ${u.email}? This cannot be undone.` })} />
                </div>
              </div>
            );
          })}
        </div>
      </main>

      {editUser && (
        <AccessModal th={th} user={editUser} onClose={() => setEditUser(null)}
          onSave={(pillars) => act("set-pillars", { username: editUser.username, pillars }, { email: editUser.email })} />
      )}
    </div>
  );
}

function IconBtn({ th, icon, title, onClick, disabled, danger }) {
  return (
    <button onClick={onClick} title={title} disabled={disabled}
      className="flex h-8 w-8 items-center justify-center rounded-lg transition-colors disabled:opacity-30"
      style={{ color: danger ? "#d83b54" : th.ink, background: th.chip, border: `1px solid ${th.cardBorder}` }}>
      <Icon name={icon} size={15} />
    </button>
  );
}

window.AdminPanel = AdminPanel;

/* ---- Floating launcher (admins only, shown ONLY on the hub so it never sits over a demo) ---- */
function AdminLauncher({ th, onOpen }) {
  return (
    <button onClick={onOpen} title="User administration"
      className="fixed bottom-6 right-6 z-[9998] inline-flex items-center gap-2 rounded-full px-4 py-3 text-[13px] font-semibold transition-transform active:scale-[0.97]"
      style={{ color: "#fff", background: th.accent, boxShadow: "0 14px 34px -12px rgba(216,59,84,0.8)" }}>
      <Icon name="Users" size={16} /> Admin
    </button>
  );
}

/* ---- Deep-link access gate ----
   A signed-in NON-admin who opens a #/deep-link for a demo they weren't granted sees this
   full-screen block instead of the demo (which is rendered underneath by the app's own
   root — this simply covers it). Admins and unrestricted/mock sessions never see it. */
function AccessBlocked({ th, pillarTitle }) {
  return (
    <div className="fixed inset-0 z-[9999] flex items-center justify-center p-6" style={{ background: th.page, fontFamily: "'Manrope', sans-serif" }}>
      <div className="pointer-events-none absolute inset-0">
        <div className="absolute -top-40 left-1/2 h-[520px] w-[820px] -translate-x-1/2 rounded-full" style={{ background: `radial-gradient(circle, ${th.glow1}, transparent 66%)` }} />
      </div>
      <div className="relative max-w-md text-center a-fade-up">
        <div className="mx-auto mb-5 flex h-16 w-16 items-center justify-center rounded-2xl" style={{ background: th.chip, color: th.accent, border: `1px solid ${th.cardBorder}` }}>
          <Icon name="Lock" size={28} />
        </div>
        <h1 style={{ fontFamily: "'Spectral', serif", color: th.inkStrong }} className="text-[28px] font-medium leading-tight">This demo isn't in your access.</h1>
        <p className="mt-3 text-[14.5px] leading-relaxed" style={{ color: th.ink }}>
          {pillarTitle ? <>You haven't been granted access to <b style={{ color: th.inkStrong }}>{pillarTitle}</b> yet. </> : "You haven't been granted access to this demo yet. "}
          Ask an administrator to enable it for your account.
        </p>
        <button onClick={() => { window.location.hash = "#/hub"; }}
          className="mt-7 inline-flex items-center gap-2 rounded-xl px-4 py-2.5 text-[14px] font-semibold text-white transition-transform active:scale-[0.98]"
          style={{ background: th.accent, boxShadow: "0 12px 30px -12px rgba(216,59,84,0.75)" }}>
          <Icon name="ArrowLeft" size={16} /> Back to the hub
        </button>
      </div>
    </div>
  );
}

/* ---- Self-installing overlay controller (RELEASE-MANAGED) ----
   Renders in its OWN React root, independent of the app. Reads localStorage + the URL
   hash and renders TWO things: (1) the deep-link AccessBlocked gate for signed-in
   non-admins on a demo they lack; (2) the admin panel (#/admin) + hub launcher for
   admins. Couples to the app ONLY through globals + the hash, so a fresh Claude Design
   export needs ZERO patching to keep entitlements + admin working. */
function lrTopRoute() {
  return (window.location.hash || "").replace(/^#\/?/, "").split("?")[0].split("/")[0];
}
// Portal token still valid? (b64url {exp,...}.sig). Defensive: unparseable → assume valid so a
// parsing quirk never locks a real admin out; the backend is the real gate regardless.
function lrTokenValid() {
  try {
    const t = localStorage.getItem("lr-token");
    if (!t) return false;
    if (t === "mock-session") return true;
    const p = t.split(".")[0];
    if (!p) return false;
    const b64 = p.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((p.length + 3) % 4);
    const exp = (JSON.parse(atob(b64)) || {}).exp;
    return !exp || exp * 1000 > Date.now();
  } catch (e) { return true; }
}

function LrOverlay() {
  const [, force] = React.useReducer((n) => n + 1, 0);
  React.useEffect(() => {
    const on = () => force();
    // On login the app sets lr-authed ~0.5s AFTER auth-seam fires lr-auth-changed
    // (LoginGate delays onAuthed for its "Welcome" animation) — so re-check shortly after too.
    const onAuth = () => { force(); setTimeout(force, 700); };
    const simple = ["hashchange", "popstate", "storage"];
    simple.forEach((e) => window.addEventListener(e, on));
    window.addEventListener("lr-auth-changed", onAuth);
    // Smart poll: catches SAME-TAB changes that fire NO event — logout via the hub button
    // (removeItem fires no 'storage'; navigating to the current hash fires no 'hashchange'),
    // theme toggles, token expiry, and hash changes into/out of a gated route. Only
    // re-renders when the observed signature changes.
    let last = null;
    const sig = () => [localStorage.getItem("lr-authed"), localStorage.getItem("lr-isadmin"),
                       localStorage.getItem("lr-shell-theme"), !!localStorage.getItem("lr-token"),
                       (window.location.hash || "")].join("|");
    last = sig();
    const iv = setInterval(() => { const s = sig(); if (s !== last) { last = s; force(); } }, 600);
    return () => {
      simple.forEach((e) => window.removeEventListener(e, on));
      window.removeEventListener("lr-auth-changed", onAuth);
      clearInterval(iv);
    };
  }, []);

  const ls = (k) => { try { return localStorage.getItem(k); } catch (e) { return null; } };
  const authed = ls("lr-authed") === "1" && lrTokenValid();
  if (!authed) return null;
  const isAdmin = ls("lr-isadmin") === "1";
  const mode = ls("lr-shell-theme") || "dark";
  const th = window.SHELL_THEMES[mode] || window.SHELL_THEMES.dark;

  // (1) Deep-link entitlement gate — signed-in non-admin on a demo route they lack.
  let gate = null;
  if (!isAdmin) {
    const pill = window.lrPillarForRoute ? window.lrPillarForRoute(window.location.hash) : null;
    if (pill && window.lrEntitled && !window.lrEntitled(pill)) {
      const p = ((window.lrAllPillars && window.lrAllPillars()) || []).find((x) => x.id === pill);
      gate = <AccessBlocked th={th} pillarTitle={p && p.title} />;
    }
  }

  // (2) Admin UI (admins only).
  let admin = null;
  if (isAdmin) {
    const logout = () => {
      try { ["lr-authed", "lr-token", "lr-isadmin", "lr-email"].forEach((k) => localStorage.removeItem(k)); } catch (e) {}
      window.LR_SESSION_TOKEN = null;
      // Full reload so the app (a separate React root) re-reads auth and shows the login gate.
      window.location.hash = "#/"; window.location.reload();
    };
    const top = lrTopRoute();
    if (top === "admin") {
      admin = (
        <div className="fixed inset-0 z-[9999]" style={{ background: th.page }}>
          <AdminPanel mode={mode} onBack={() => { window.location.hash = "#/hub"; }} onLogout={logout} />
        </div>
      );
    } else if (top === "" || top === "hub") {
      admin = <AdminLauncher th={th} onOpen={() => { window.location.hash = "#/admin"; }} />;
    }
  }

  return <>{gate}{admin}</>;
}

/* mount into a dedicated root, independent of the app's #root */
(function mountLrOverlay() {
  if (window.__lrAdminOverlayMounted) return;
  window.__lrAdminOverlayMounted = true;
  var el = document.getElementById("lr-admin-overlay");
  if (!el) { el = document.createElement("div"); el.id = "lr-admin-overlay"; document.body.appendChild(el); }
  try { ReactDOM.createRoot(el).render(<LrOverlay />); } catch (e) { /* React not ready — no overlay */ }
})();
