/* admin.jsx
 *
 * Admin-only views. Currently just the cert verification review queue.
 *
 * Exposes:
 *   window.AdminCertReviewsPage   — the cert review queue
 *
 * Auth: the page itself does a client-side check on user.is_admin. The
 * backend endpoints ALSO check, so a non-admin who guesses the URL gets
 * 403 from the API even if they bypass the client check.
 *
 * Layout: a single list of pending reviews by default, with a filter to
 * see approved/rejected/all. Each row shows:
 *   - Who declared (email, username)
 *   - What they declared (cert id, issuer, verification id, custom name)
 *   - PDF view link
 *   - Approve / Reject buttons (with notes textarea on reject)
 */
(function () {
  "use strict";

  const e = React.createElement;

  function fmtDate(iso) {
    if (!iso) return "—";
    try { return new Date(iso).toLocaleString(); } catch (_) { return iso; }
  }

  function ReviewRow({ item, onAction }) {
    const [notes, setNotes] = React.useState("");
    const [showNotes, setShowNotes] = React.useState(false);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState("");

    async function viewPdf() {
      try {
        const resp = await fetch(`/api/user-certs/${encodeURIComponent(item.owned_cert_id)}/pdf`, {
          headers: { Authorization: `Bearer ${window.AuthStore.token}` },
        });
        if (!resp.ok) throw new Error(await resp.text() || resp.statusText);
        const blob = await resp.blob();
        const url = URL.createObjectURL(blob);
        window.open(url, "_blank");
        setTimeout(() => URL.revokeObjectURL(url), 60000);
      } catch (e2) {
        setErr("Could not load PDF: " + (e2.message || e2));
      }
    }

    async function decide(action) {
      if (action === "reject" && !showNotes) { setShowNotes(true); return; }
      setBusy(true);
      setErr("");
      try {
        await window.AuthStore.api(`/api/admin/cert-reviews/${encodeURIComponent(item.owned_cert_id)}`, {
          method: "POST",
          body: JSON.stringify({ action, notes }),
        });
        onAction && onAction(item.owned_cert_id);
      } catch (e2) {
        setErr(e2.message || String(e2));
      } finally {
        setBusy(false);
      }
    }

    const certLabel = (window.CERT_META?.[item.declared_cert_id]?.label) ||
                      item.declared_custom_name ||
                      item.declared_cert_id || "(unknown cert)";

    return e("div", { className: "adm-row" },
      e("div", { className: "adm-row-meta" },
        e("div", { className: "adm-row-title" }, certLabel,
          item.declared_cert_id === "other" && e("span", { className: "adm-other" }, " (custom)")
        ),
        e("div", { className: "adm-row-userline" },
          item.user_email, " · ",
          e("span", { className: "adm-mono" }, "@", item.user_username), " · ",
          "uploaded ", fmtDate(item.pdf_uploaded_at)
        )
      ),
      e("div", { className: "adm-row-declared" },
        item.declared_issuer && e("div", null, e("span", { className: "adm-key" }, "Issuer: "), item.declared_issuer),
        item.declared_verification_id && e("div", null,
          e("span", { className: "adm-key" }, "Verification ID: "),
          e("span", { className: "adm-mono" }, item.declared_verification_id)
        )
      ),
      e("div", { className: "adm-row-actions" },
        e("button", { className: "btn btn-ghost btn-sm", onClick: viewPdf }, "View PDF"),
        e("button", { className: "btn btn-primary btn-sm", onClick: () => decide("approve"), disabled: busy }, "Approve"),
        e("button", { className: "btn btn-danger btn-sm", onClick: () => decide("reject"), disabled: busy },
          showNotes ? "Confirm reject" : "Reject"),
      ),
      showNotes && e("div", { className: "adm-row-notes" },
        e("textarea", {
          className: "adm-notes-input",
          placeholder: "Reason for rejection (visible to user)…",
          value: notes,
          onChange: ev => setNotes(ev.target.value),
          maxLength: 2000,
          rows: 3,
        }),
        e("div", { className: "adm-notes-hint" },
          "Click Confirm reject above to submit. Cancel:",
          " ",
          e("button", { className: "pf-link-btn", onClick: () => { setShowNotes(false); setNotes(""); } }, "discard")
        )
      ),
      err && e("div", { className: "adm-row-err" }, err)
    );
  }

  // ──────────────────────────────────────────────────────────
  // ReviewQueueTab — the existing review queue, extracted into a tab
  // ──────────────────────────────────────────────────────────
  function ReviewQueueTab() {
    const [status, setStatus] = React.useState("pending");
    const [items, setItems]   = React.useState([]);
    const [loading, setLoading] = React.useState(true);
    const [err, setErr]       = React.useState("");

    async function load() {
      setLoading(true);
      setErr("");
      try {
        const data = await window.AuthStore.api(`/api/admin/cert-reviews?status=${encodeURIComponent(status)}`);
        setItems(Array.isArray(data) ? data : []);
      } catch (e2) {
        setErr(e2.message || String(e2));
      } finally {
        setLoading(false);
      }
    }

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

    function handleAction(ownedCertId) {
      setItems(items.filter(i => i.owned_cert_id !== ownedCertId));
    }

    return e("div", null,
      e("div", { className: "adm-filter" },
        ["pending", "approved", "rejected", "all"].map(s =>
          e("button", {
            key: s,
            className: `adm-filter-btn ${status === s ? "active" : ""}`,
            onClick: () => setStatus(s),
          }, s[0].toUpperCase() + s.slice(1))
        )
      ),
      err && e("div", { className: "pf-banner-error" }, err),
      loading && e("div", { className: "adm-loading" }, "Loading…"),
      !loading && items.length === 0 && e("div", { className: "pf-empty" },
        e("div", { className: "pf-empty-title" }, "No ", status, " reviews"),
        e("p", { className: "pf-empty-body" }, status === "pending"
          ? "Inbox zero. Nothing waiting for review."
          : "Nothing in this bucket yet.")
      ),
      !loading && items.length > 0 && e("div", { className: "adm-list" },
        items.map(it => e(ReviewRow, {
          key: it.owned_cert_id,
          item: it,
          onAction: handleAction,
        }))
      )
    );
  }

  // ──────────────────────────────────────────────────────────
  // CustomCertsTab — aggregated demand for certs we don't yet teach
  // ──────────────────────────────────────────────────────────
  function CustomCertsTab() {
    const [items, setItems] = React.useState([]);
    const [loading, setLoading] = React.useState(true);
    const [err, setErr] = React.useState("");

    async function load() {
      setLoading(true);
      setErr("");
      try {
        const data = await window.AuthStore.api("/api/admin/custom-certs");
        setItems(Array.isArray(data) ? data : []);
      } catch (e2) {
        setErr(e2.message || String(e2));
      } finally {
        setLoading(false);
      }
    }
    React.useEffect(() => { load(); }, []);

    const total = items.reduce((acc, r) => acc + r.count, 0);

    return e("div", null,
      e("p", { className: "adm-tab-blurb" },
        "Names users entered when picking \"Other\" in the cert picker. Sorted by frequency — high-count entries are good candidates for new study tracks."
      ),
      err && e("div", { className: "pf-banner-error" }, err),
      loading && e("div", { className: "adm-loading" }, "Loading…"),
      !loading && items.length === 0 && e("div", { className: "pf-empty" },
        e("div", { className: "pf-empty-title" }, "No custom certs yet"),
        e("p", { className: "pf-empty-body" },
          "When users add a cert that isn't in our picker (using the \"Other\" option), it'll show up here so you can see what to build next.")
      ),
      !loading && items.length > 0 && e("div", { className: "adm-custom-wrap" },
        e("div", { className: "adm-custom-summary" },
          e("span", null, total, " total \"Other\" entries across ", items.length, " unique cert name", items.length === 1 ? "" : "s")
        ),
        e("table", { className: "adm-custom-table" },
          e("thead", null,
            e("tr", null,
              e("th", null, "Cert name"),
              e("th", { className: "adm-tnum" }, "Count"),
              e("th", null, "Issuers users entered"),
              e("th", null, "Sample users")
            )
          ),
          e("tbody", null,
            items.map((r, i) => e("tr", { key: i },
              e("td", { className: "adm-custom-name" }, r.name),
              e("td", { className: "adm-tnum" }, r.count),
              e("td", { className: "adm-custom-issuers" },
                r.sample_issuers.length > 0
                  ? r.sample_issuers.join(", ")
                  : e("span", { className: "adm-muted" }, "—")
              ),
              e("td", { className: "adm-custom-users" },
                r.sample_users.map((u, j) =>
                  e("span", { key: j, className: "adm-mono adm-pill" }, "@", u)
                )
              )
            ))
          )
        )
      )
    );
  }

  // ──────────────────────────────────────────────────────────
  // AdminCertReviewsPage — tab container
  // ──────────────────────────────────────────────────────────
  function AdminCertReviewsBody() {
    // The actual cert-review UI (tabs + tab content). No .adm-page wrapper
    // — caller is responsible for that. Auth check still here so the
    // standalone /admin/cert-reviews page and the dispatcher-wrapped one
    // both behave identically.
    const user = window.AuthStore && window.AuthStore.user;
    const [tab, setTab] = React.useState("queue");

    if (!user) {
      return e("h1", { className: "adm-page-title" }, "Sign in required");
    }
    if (!user.is_admin) {
      return e(React.Fragment, null,
        e("h1", { className: "adm-page-title" }, "Forbidden"),
        e("p", null, "Admin access is required to view this page.")
      );
    }

    return e(React.Fragment, null,
      e("div", { className: "adm-page-head" },
        e("h1", { className: "adm-page-title" }, "Cert Admin"),
        e("p", { className: "adm-page-sub" },
          tab === "queue"
            ? "Review uploaded certifications. Approving marks a user's cert as ✓ Verified."
            : "See what certs users want that aren't in our picker yet.")
      ),
      e("div", { className: "adm-tabs" },
        e("button", { className: `adm-tabbtn ${tab === "queue" ? "active" : ""}`, onClick: () => setTab("queue") }, "Review queue"),
        e("button", { className: `adm-tabbtn ${tab === "custom" ? "active" : ""}`, onClick: () => setTab("custom") }, "Custom certs"),
      ),
      tab === "queue" && e(ReviewQueueTab),
      tab === "custom" && e(CustomCertsTab)
    );
  }

  // Standalone version, kept exported as window.AdminCertReviewsPage for
  // any callers that still bypass the admin dispatcher. This is the
  // original public API; behavior unchanged.
  function AdminCertReviewsPage() {
    return e("div", { className: "adm-page" },
      e(AdminCertReviewsBody)
    );
  }

  // ═══════════════════════════════════════════════════════════════════════
  // V1 ADMIN PANEL
  // ═══════════════════════════════════════════════════════════════════════
  // Five pages: dashboard, users, user detail, requests, audit log.
  // Each is a function component below; AdminPanel dispatches based on the
  // window.Router.currentAdminRoute() return value.
  //
  // Shared concerns:
  //   - All routes do a client-side is_admin check up top (UX guard only).
  //   - All fetches go through window.AuthStore.api so the bearer token
  //     gets attached automatically.
  //   - Toast errors via setError local state per page; no global error bus.

  function adminApi(path, opts) {
    return window.AuthStore.api(path, opts || {});
  }

  function fmtRelative(iso) {
    if (!iso) return "—";
    try {
      const d = new Date(iso);
      const diff = (Date.now() - d.getTime()) / 1000;
      if (diff < 60) return Math.round(diff) + "s ago";
      if (diff < 3600) return Math.round(diff / 60) + "m ago";
      if (diff < 86400) return Math.round(diff / 3600) + "h ago";
      if (diff < 604800) return Math.round(diff / 86400) + "d ago";
      return d.toLocaleDateString();
    } catch (_) { return iso; }
  }

  // Premium expiry helpers — kept module-scope so the user-detail render
  // can use them inline without re-defining them per render.
  function _isPremiumExpired(iso) {
    if (!iso) return false;
    try { return new Date(iso).getTime() < Date.now(); } catch (_) { return false; }
  }
  function _expiryLabel(iso) {
    if (!iso) return "";
    try {
      const d = new Date(iso);
      const diff = (d.getTime() - Date.now()) / 1000;  // seconds until expiry; negative = past
      if (diff < 0) {
        const past = -diff;
        if (past < 3600) return "Expired " + Math.round(past / 60) + "m ago";
        if (past < 86400) return "Expired " + Math.round(past / 3600) + "h ago";
        return "Expired " + Math.round(past / 86400) + "d ago";
      }
      if (diff < 3600) return "in " + Math.round(diff / 60) + "m";
      if (diff < 86400) return "in " + Math.round(diff / 3600) + "h";
      if (diff < 86400 * 60) return "in " + Math.round(diff / 86400) + " days";
      return "in " + Math.round(diff / 86400 / 30) + " months";
    } catch (_) { return ""; }
  }

  // Modal: prompts the admin to choose "Until revoked" or "N days" before
  // granting premium. Used for both initial grant AND extending/changing
  // expiration on an already-premium user (the backend allows re-grant).
  // Quick-pick chips for common durations + a custom input for arbitrary
  // day counts. Keyboard-friendly: Enter submits, Esc closes.
  function GrantPremiumModal({ user, busy, onClose, onConfirm }) {
    const isExtending = !!user.is_premium;
    // mode: "perpetual" | "duration"
    const [mode, setMode] = React.useState(isExtending && user.premium_expires_at ? "duration" : "perpetual");
    const [days, setDays] = React.useState(30);
    const [err, setErr] = React.useState("");

    const QUICK_PRESETS = [
      { label: "7 days", days: 7 },
      { label: "30 days", days: 30 },
      { label: "90 days", days: 90 },
      { label: "1 year", days: 365 },
    ];

    // Esc to close. Single binding effect.
    React.useEffect(() => {
      const onKey = (ev) => { if (ev.key === "Escape" && !busy) onClose(); };
      window.addEventListener("keydown", onKey);
      return () => window.removeEventListener("keydown", onKey);
    }, [busy, onClose]);

    function submit() {
      setErr("");
      if (mode === "perpetual") {
        return onConfirm(null);
      }
      const n = Number(days);
      if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
        setErr("Enter a positive whole number of days.");
        return;
      }
      onConfirm(n);
    }

    // Friendly preview of when the premium would expire.
    const preview = (() => {
      if (mode === "perpetual") return "No expiration. Premium lasts until manually revoked.";
      const n = Number(days);
      if (!Number.isFinite(n) || n < 1) return "Enter a number of days.";
      const expires = new Date(Date.now() + n * 86400000);
      return "Premium will expire on " + expires.toLocaleString() + ".";
    })();

    return e("div", { className: "adm-modal-backdrop", onClick: (ev) => { if (ev.target.classList.contains("adm-modal-backdrop") && !busy) onClose(); } },
      e("div", { className: "adm-modal", role: "dialog", "aria-labelledby": "gpm-title" },
        e("div", { className: "adm-modal-head" },
          e("h2", { id: "gpm-title", className: "adm-modal-title" },
            isExtending ? "Change premium expiration" : "Grant premium"
          ),
          e("button", { className: "adm-modal-close", onClick: onClose, "aria-label": "Close", disabled: busy }, "×")
        ),
        e("div", { className: "adm-modal-body" },
          e("p", { className: "adm-modal-help" }, "Granting to ", e("strong", null, user.email), "."),
          // Mode selector — two big radio-like buttons.
          e("div", { className: "adm-mode-row" },
            e("button", {
              className: "adm-mode-btn " + (mode === "perpetual" ? "active" : ""),
              onClick: () => setMode("perpetual"),
              disabled: busy,
            },
              e("div", { className: "adm-mode-btn-title" }, "Until revoked"),
              e("div", { className: "adm-mode-btn-sub" }, "Perpetual. No auto-expiry."),
            ),
            e("button", {
              className: "adm-mode-btn " + (mode === "duration" ? "active" : ""),
              onClick: () => setMode("duration"),
              disabled: busy,
            },
              e("div", { className: "adm-mode-btn-title" }, "For a duration"),
              e("div", { className: "adm-mode-btn-sub" }, "Choose number of days."),
            ),
          ),
          mode === "duration" && e("div", { className: "adm-duration-block" },
            e("div", { className: "adm-preset-row" },
              QUICK_PRESETS.map(p => e("button", {
                key: p.days,
                className: "btn btn-ghost btn-sm",
                onClick: () => setDays(p.days),
                disabled: busy,
              }, p.label))
            ),
            e("div", { className: "adm-days-input-row" },
              e("input", {
                type: "number", min: 1, step: 1,
                className: "adm-days-input",
                value: days,
                onChange: (ev) => setDays(ev.target.value),
                onKeyDown: (ev) => { if (ev.key === "Enter") submit(); },
                disabled: busy,
                "aria-label": "Number of days",
              }),
              e("span", { className: "adm-days-suffix" }, "days"),
            ),
          ),
          e("p", { className: "adm-modal-preview" }, preview),
          err && e("div", { className: "adm-toast error" }, err),
        ),
        e("div", { className: "adm-modal-foot" },
          e("button", { className: "btn btn-ghost", onClick: onClose, disabled: busy }, "Cancel"),
          e("button", { className: "btn btn-primary", onClick: submit, disabled: busy },
            busy ? "Granting…" : (isExtending ? "Update expiration" : "Grant premium")
          ),
        )
      )
    );
  }

  // ─── Shared admin layout: nav strip across the top of each page ────────
  function AdminNav({ active }) {
    const items = [
      ["dashboard", "Dashboard", "/admin"],
      ["users", "Users", "/admin/users"],
      ["requests", "Requests", "/admin/requests"],
      ["cert-reviews", "Cert Reviews", "/admin/cert-reviews"],
      ["spotlights", "Spotlights", "/admin/spotlights"],
      ["audit", "Audit Log", "/admin/audit"],
    ];
    return e("nav", { className: "adm-nav" },
      items.map(([k, label, url]) =>
        e("a", {
          key: k,
          className: "adm-nav-link " + (active === k ? "active" : ""),
          href: url,
          onClick: (ev) => {
            ev.preventDefault();
            // Use history pushState directly to avoid full page reload
            window.history.pushState({}, "", url);
            window.dispatchEvent(new PopStateEvent("popstate"));
          },
        }, label)
      )
    );
  }

  // ─── Dashboard ─────────────────────────────────────────────────────────
  function AdminDashboard() {
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState("");

    React.useEffect(() => {
      let cancelled = false;
      adminApi("/api/admin/dashboard")
        .then(d => { if (!cancelled) setData(d); })
        .catch(ex => { if (!cancelled) setErr(ex.message || "Failed to load."); });
      return () => { cancelled = true; };
    }, []);

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "dashboard" }),
      e("h1", { className: "adm-page-title" }, "Admin Dashboard"),
      err && e("div", { className: "adm-error" }, err),
      !data && !err && e("div", { className: "adm-loading" }, "Loading…"),
      data && e("div", { className: "adm-tile-grid" },
        e(StatTile, { label: "Total active users", value: data.total_users }),
        e(StatTile, { label: "Soft-deleted users", value: data.deleted_users }),
        e(StatTile, { label: "Admins", value: data.admins }),
        e(StatTile, { label: "Premium users", value: data.premium_users }),
        e(StatTile, { label: "Signups, last 7d", value: data.signups_7d }),
        e(StatTile, { label: "Signups, last 30d", value: data.signups_30d }),
        e(StatTile, { label: "Pending cert reviews", value: data.pending_cert_reviews,
          href: data.pending_cert_reviews > 0 ? "/admin/cert-reviews" : null }),
        e(StatTile, { label: "Cert content requests (total)", value: data.cert_request_total,
          sub: data.cert_request_unique_certs + " unique certs requested",
          href: data.cert_request_total > 0 ? "/admin/requests" : null }),
      )
    );
  }

  function StatTile({ label, value, sub, href }) {
    const inner = e("div", { className: "adm-tile" },
      e("div", { className: "adm-tile-label" }, label),
      e("div", { className: "adm-tile-value" }, value),
      sub && e("div", { className: "adm-tile-sub" }, sub)
    );
    if (!href) return inner;
    return e("a", { className: "adm-tile-link", href, onClick: (ev) => {
      ev.preventDefault();
      window.history.pushState({}, "", href);
      window.dispatchEvent(new PopStateEvent("popstate"));
    } }, inner);
  }

  // ─── Users list ────────────────────────────────────────────────────────
  function AdminUsers() {
    const [q, setQ] = React.useState("");
    const [page, setPage] = React.useState(1);
    const [filterKind, setFilterKind] = React.useState("active");
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState("");
    const [loading, setLoading] = React.useState(true);

    // Debounced search — wait 300ms after typing stops before firing the query.
    // Stops us from hammering the API while user is mid-type.
    React.useEffect(() => {
      let cancelled = false;
      setLoading(true);
      const t = setTimeout(() => {
        const params = new URLSearchParams();
        if (q) params.set("q", q);
        params.set("page", String(page));
        params.set("filter_kind", filterKind);
        adminApi("/api/admin/users?" + params.toString())
          .then(d => { if (!cancelled) { setData(d); setLoading(false); } })
          .catch(ex => { if (!cancelled) { setErr(ex.message || "Failed."); setLoading(false); } });
      }, 300);
      return () => { cancelled = true; clearTimeout(t); };
    }, [q, page, filterKind]);

    function openUser(id) {
      window.history.pushState({}, "", "/admin/user/" + id);
      window.dispatchEvent(new PopStateEvent("popstate"));
    }

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "users" }),
      e("h1", { className: "adm-page-title" }, "Users"),
      e("div", { className: "adm-filters" },
        e("input", {
          className: "adm-search", type: "search", placeholder: "Search by email, username, or name…",
          value: q, onChange: (ev) => { setQ(ev.target.value); setPage(1); },
        }),
        e("select", {
          className: "adm-filter-select", value: filterKind,
          onChange: (ev) => { setFilterKind(ev.target.value); setPage(1); },
        },
          e("option", { value: "active" }, "Active only"),
          e("option", { value: "deleted" }, "Soft-deleted only"),
          e("option", { value: "admin" }, "Admins only"),
          e("option", { value: "premium" }, "Premium only"),
          e("option", { value: "all" }, "All (incl. deleted)"),
        )
      ),
      err && e("div", { className: "adm-error" }, err),
      data && e(React.Fragment, null,
        e("div", { className: "adm-table-meta" },
          loading ? "Loading…" :
          `${data.total} user${data.total === 1 ? "" : "s"} • page ${data.page}`
        ),
        e("div", { className: "adm-table-wrap" },
          e("table", { className: "adm-table" },
            e("thead", null, e("tr", null,
              e("th", null, "Email"),
              e("th", null, "Name"),
              e("th", null, "Tier"),
              e("th", null, "Created"),
              e("th", null, "Last login"),
              e("th", null, "Status"),
            )),
            e("tbody", null,
              data.users.map(u => e("tr", {
                key: u.id, className: "adm-table-row" + (u.deleted_at ? " deleted" : ""),
                onClick: () => openUser(u.id),
              },
                e("td", null, u.email),
                e("td", null, u.name),
                e("td", null,
                  u.is_superuser && e("span", { className: "adm-tier adm-tier-su" }, "Superuser"),
                  !u.is_superuser && u.is_admin && e("span", { className: "adm-tier adm-tier-admin" }, "Admin"),
                  u.is_premium && e("span", { className: "adm-tier adm-tier-premium" }, "Premium"),
                  !u.is_admin && !u.is_premium && !u.is_superuser && e("span", { className: "adm-tier-none" }, "—"),
                ),
                e("td", null, fmtRelative(u.created_at)),
                e("td", null, fmtRelative(u.last_login)),
                e("td", null, u.deleted_at ? e("span", { className: "adm-status-del" }, "Deleted") : e("span", { className: "adm-status-ok" }, "Active")),
              ))
            )
          )
        ),
        // Simple pagination
        e("div", { className: "adm-pager" },
          e("button", { className: "btn btn-ghost btn-sm", disabled: page <= 1, onClick: () => setPage(page - 1) }, "← Prev"),
          e("span", { className: "adm-pager-info" }, `Page ${page} of ${Math.max(1, Math.ceil(data.total / data.page_size))}`),
          e("button", { className: "btn btn-ghost btn-sm",
            disabled: page * data.page_size >= data.total,
            onClick: () => setPage(page + 1) }, "Next →"),
        )
      )
    );
  }

  // ─── User detail ───────────────────────────────────────────────────────
  function AdminUserDetail({ userId }) {
    const [user, setUser] = React.useState(null);
    const [err, setErr] = React.useState("");
    const [busy, setBusy] = React.useState(false);
    const [toast, setToast] = React.useState("");
    const me = window.AuthStore && window.AuthStore.user;

    function refresh() {
      adminApi("/api/admin/users/" + userId)
        .then(setUser)
        .catch(ex => setErr(ex.message || "Failed."));
    }
    React.useEffect(() => { refresh(); }, [userId]);

    // Grant-premium modal state. Null = closed; { defaultMode } = open.
    // Two modes the user can choose: "perpetual" (until revoked) and
    // "duration" (N days). When already-premium, the modal is also used
    // to extend/change the expiration without revoking first.
    const [grantPremiumOpen, setGrantPremiumOpen] = React.useState(false);

    async function doAction(path, confirmMsg, body) {
      if (confirmMsg && !window.confirm(confirmMsg)) return;
      setBusy(true);
      try {
        // POST with body if provided. Server endpoints that take no body
        // tolerate a null body via FastAPI/pydantic optional models, so
        // we always JSON.stringify when body is supplied and let the
        // server parse.
        const opts = { method: "POST" };
        if (body !== undefined) {
          opts.body = JSON.stringify(body);
        }
        await adminApi(path, opts);
        setToast("Done.");
        setTimeout(() => setToast(""), 2500);
        refresh();
      } catch (ex) {
        setErr(ex.message || "Action failed.");
        setTimeout(() => setErr(""), 4000);
      } finally {
        setBusy(false);
      }
    }

    if (err && !user) {
      return e("div", { className: "adm-page" }, e(AdminNav, { active: "users" }), e("div", { className: "adm-error" }, err));
    }
    if (!user) {
      return e("div", { className: "adm-page" }, e(AdminNav, { active: "users" }), e("div", { className: "adm-loading" }, "Loading…"));
    }

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "users" }),
      e("button", { className: "btn btn-ghost btn-sm adm-back",
        onClick: () => { window.history.pushState({}, "", "/admin/users"); window.dispatchEvent(new PopStateEvent("popstate")); },
      }, "← Back to users"),
      toast && e("div", { className: "adm-toast ok" }, toast),
      err && e("div", { className: "adm-toast error" }, err),

      e("div", { className: "adm-user-head" },
        e("h1", { className: "adm-page-title" }, user.name),
        e("div", { className: "adm-user-meta" },
          e("div", null, e("strong", null, "Email: "), user.email),
          e("div", null, e("strong", null, "Username: "), user.username),
          e("div", null, e("strong", null, "ID: "), user.id),
          e("div", null, e("strong", null, "Created: "), fmtDate(user.created_at)),
          e("div", null, e("strong", null, "Last login: "), fmtDate(user.last_login)),
          user.deleted_at && e("div", { className: "adm-deleted-warn" },
            e("strong", null, "⚠ Soft-deleted: "), fmtDate(user.deleted_at)
          ),
        ),
        e("div", { className: "adm-tier-badges" },
          user.is_superuser && e("span", { className: "adm-tier adm-tier-su" }, "Superuser"),
          user.is_admin && e("span", { className: "adm-tier adm-tier-admin" }, "Admin"),
          user.is_premium && e("span", { className: "adm-tier adm-tier-premium" }, "Premium"),
          user.has_password && e("span", { className: "adm-tier-auth" }, "Password"),
          user.has_google && e("span", { className: "adm-tier-auth" }, "Google"),
        )
      ),

      // Action panel
      e("section", { className: "adm-section" },
        e("h2", { className: "adm-h2" }, "Actions"),
        e("div", { className: "adm-actions" },
          // Premium grant/revoke. When NOT premium → green "Grant" opens
          // the modal that asks for duration. When ALREADY premium → grey
          // "Extend / change" reopens the same modal to set a new expiry
          // (server allows re-grant on an already-premium user). Revoke
          // is a separate destructive button.
          !user.is_premium && e("button", {
            className: "btn btn-primary btn-sm", disabled: busy,
            onClick: () => setGrantPremiumOpen(true),
          }, "Grant premium"),
          user.is_premium && e(React.Fragment, null,
            e("button", {
              className: "btn btn-ghost btn-sm", disabled: busy,
              onClick: () => setGrantPremiumOpen(true),
            }, "Extend / change expiry"),
            e("button", {
              className: "btn btn-ghost btn-sm adm-danger", disabled: busy,
              onClick: () => doAction(`/api/admin/users/${user.id}/revoke-premium`, "Revoke premium for " + user.email + "?"),
            }, "Revoke premium"),
          ),
          user.deleted_at
            ? e("button", { className: "btn btn-primary btn-sm", disabled: busy,
                onClick: () => doAction(`/api/admin/users/${user.id}/restore`, "Restore " + user.email + "? A password reset email will be sent.") }, "Restore account")
            : user.id !== (me && me.id) && !user.is_superuser && e("button", { className: "btn btn-ghost btn-sm adm-danger", disabled: busy,
                onClick: () => doAction(`/api/admin/users/${user.id}/soft-delete`, "Soft-delete " + user.email + "? They'll have 30 days before purge.") }, "Soft-delete"),
          !user.deleted_at && user.has_password && e("button", { className: "btn btn-ghost btn-sm", disabled: busy,
            onClick: () => doAction(`/api/admin/users/${user.id}/send-password-reset`, "Send password reset email to " + user.email + "?") }, "Send password reset"),
          // Superuser-only admin grant/revoke
          me && me.is_superuser && !user.deleted_at && (
            user.is_admin
              ? !user.is_superuser && user.id !== me.id && e("button", { className: "btn btn-ghost btn-sm adm-danger", disabled: busy,
                  onClick: () => doAction(`/api/admin/users/${user.id}/revoke-admin`, "Revoke admin from " + user.email + "?") }, "Revoke admin")
              : e("button", { className: "btn btn-primary btn-sm", disabled: busy,
                  onClick: () => doAction(`/api/admin/users/${user.id}/grant-admin`, "Grant admin to " + user.email + "?") }, "Grant admin")
          ),
        )
      ),

      // Grant-premium modal. Open when grantPremiumOpen=true. On submit,
      // sends { days: N | null } to the grant-premium endpoint and closes.
      grantPremiumOpen && e(GrantPremiumModal, {
        user,
        busy,
        onClose: () => setGrantPremiumOpen(false),
        onConfirm: async (days) => {
          await doAction(
            `/api/admin/users/${user.id}/grant-premium`,
            null,                            // skip native confirm — modal IS the confirm
            { days: days }                   // null = perpetual, number = N days
          );
          setGrantPremiumOpen(false);
        },
      }),

      // Premium grant metadata. Now also shows expiry info — with a
      // computed "expires in X days" relative timer for at-a-glance
      // sense of how much time is left. If past expiry, shows "Expired".
      user.is_premium && e("section", { className: "adm-section" },
        e("h2", { className: "adm-h2" }, "Premium info"),
        e("div", { className: "adm-info-grid" },
          e("div", null, e("strong", null, "Granted at: "), fmtDate(user.premium_granted_at)),
          e("div", null, e("strong", null, "Granted by (admin id): "), user.premium_granted_by || "—"),
          e("div", null,
            e("strong", null, "Expires: "),
            user.premium_expires_at
              ? e(React.Fragment, null,
                  fmtDate(user.premium_expires_at),
                  " · ",
                  e("span", {
                    className: _isPremiumExpired(user.premium_expires_at) ? "adm-status-del" : "adm-status-ok"
                  }, _expiryLabel(user.premium_expires_at))
                )
              : e("span", { className: "adm-status-ok" }, "Until revoked"),
          ),
        )
      ),

      // Owned certs
      user.owned_certs && user.owned_certs.length > 0 && e("section", { className: "adm-section" },
        e("h2", { className: "adm-h2" }, "Owned certifications (" + user.owned_certs.length + ")"),
        e("table", { className: "adm-table compact" },
          e("thead", null, e("tr", null,
            e("th", null, "Cert"), e("th", null, "Issuer"), e("th", null, "Earned"), e("th", null, "Expires"), e("th", null, "Status"))),
          e("tbody", null, user.owned_certs.map(c => e("tr", { key: c.id },
            e("td", null, c.cert_id),
            e("td", null, c.issuer || "—"),
            e("td", null, c.earned_date || "—"),
            e("td", null, c.expiration_date || "—"),
            e("td", null,
              c.review_status === "approved" ? e("span", { className: "adm-status-ok" }, "✓ Verified") :
              c.review_status === "pending" ? e("span", { className: "adm-status-pending" }, "Pending review") :
              c.review_status === "rejected" ? e("span", { className: "adm-status-del" }, "Rejected") :
              "—"
            ),
          )))
        )
      ),

      // Data JSON peek — collapsed by default since it's noisy
      e("section", { className: "adm-section" },
        e("h2", { className: "adm-h2" }, "Account data (data_json)"),
        e("details", { className: "adm-data-details" },
          e("summary", null, "Show raw user data"),
          e("pre", { className: "adm-data-pre" }, JSON.stringify(user.data, null, 2))
        )
      )
    );
  }

  // ─── Requests view ─────────────────────────────────────────────────────
  function AdminRequests() {
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState("");
    React.useEffect(() => {
      adminApi("/api/admin/requests")
        .then(setData)
        .catch(ex => setErr(ex.message || "Failed."));
    }, []);

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "requests" }),
      e("h1", { className: "adm-page-title" }, "User Requests"),
      err && e("div", { className: "adm-error" }, err),
      !data && !err && e("div", { className: "adm-loading" }, "Loading…"),
      data && e(React.Fragment, null,
        e("section", { className: "adm-section" },
          e("h2", { className: "adm-h2" }, "Cert content requests (build priority)"),
          e("p", { className: "adm-help" }, "Ranked by number of users who clicked Request on a cert. Build the top-ranked next."),
          data.cert_requests.length === 0
            ? e("p", { className: "adm-help" }, "No requests yet.")
            : e("table", { className: "adm-table" },
                e("thead", null, e("tr", null,
                  e("th", null, "Cert"), e("th", null, "Requests"))),
                e("tbody", null, data.cert_requests.map((r, i) => e("tr", { key: i },
                  e("td", null, e("strong", null, r.cert)),
                  e("td", null, r.count),
                )))
              )
        ),
        data.new_career_requests.length > 0 && e("section", { className: "adm-section" },
          e("h2", { className: "adm-h2" }, "Career path requests"),
          e("p", { className: "adm-help" }, "User-submitted suggestions for new career paths."),
          e("table", { className: "adm-table" },
            e("thead", null, e("tr", null,
              e("th", null, "Description"), e("th", null, "Count"), e("th", null, "Last requested"))),
            e("tbody", null, data.new_career_requests.map((r, i) => e("tr", { key: i },
              e("td", null, r.description),
              e("td", null, r.count),
              e("td", null, fmtRelative(r.last_at)),
            )))
          )
        ),
        data.career_unlock_requests.length > 0 && e("section", { className: "adm-section" },
          e("h2", { className: "adm-h2" }, "Career unlock requests (\"Tell us this matters\")"),
          e("p", { className: "adm-help" }, "Free-text feedback from users on locked careers."),
          e("table", { className: "adm-table" },
            e("thead", null, e("tr", null,
              e("th", null, "Description"), e("th", null, "Count"))),
            e("tbody", null, data.career_unlock_requests.map((r, i) => e("tr", { key: i },
              e("td", { style: { whiteSpace: "pre-wrap" } }, r.description),
              e("td", null, r.count),
            )))
          )
        )
      )
    );
  }

  // ─── Audit log ─────────────────────────────────────────────────────────
  function AdminAuditLogPage() {
    const [data, setData] = React.useState(null);
    const [page, setPage] = React.useState(1);
    const [actionFilter, setActionFilter] = React.useState("");
    const [err, setErr] = React.useState("");

    React.useEffect(() => {
      const params = new URLSearchParams();
      params.set("page", String(page));
      if (actionFilter) params.set("action", actionFilter);
      adminApi("/api/admin/audit-log?" + params.toString())
        .then(setData)
        .catch(ex => setErr(ex.message || "Failed."));
    }, [page, actionFilter]);

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "audit" }),
      e("h1", { className: "adm-page-title" }, "Audit Log"),
      err && e("div", { className: "adm-error" }, err),
      e("div", { className: "adm-filters" },
        e("select", { className: "adm-filter-select", value: actionFilter,
          onChange: (ev) => { setActionFilter(ev.target.value); setPage(1); } },
          e("option", { value: "" }, "All actions"),
          e("option", { value: "grant_premium" }, "Grant premium"),
          e("option", { value: "revoke_premium" }, "Revoke premium"),
          e("option", { value: "soft_delete_user" }, "Soft delete"),
          e("option", { value: "restore_user" }, "Restore"),
          e("option", { value: "send_password_reset" }, "Send password reset"),
          e("option", { value: "grant_admin" }, "Grant admin"),
          e("option", { value: "revoke_admin" }, "Revoke admin"),
        ),
      ),
      data && e(React.Fragment, null,
        e("div", { className: "adm-table-meta" }, `${data.total} entries`),
        e("table", { className: "adm-table" },
          e("thead", null, e("tr", null,
            e("th", null, "When"), e("th", null, "Admin"), e("th", null, "Action"), e("th", null, "Target"), e("th", null, "Details"))),
          e("tbody", null, data.entries.map(r => e("tr", { key: r.id },
            e("td", null, fmtRelative(r.created_at)),
            e("td", null, r.admin_email),
            e("td", null, e("code", null, r.action)),
            e("td", null, r.target_type ? `${r.target_type}#${r.target_id}` : "—"),
            e("td", { className: "adm-audit-details" }, r.details ? e("code", null, JSON.stringify(r.details)) : "—"),
          )))
        ),
        e("div", { className: "adm-pager" },
          e("button", { className: "btn btn-ghost btn-sm", disabled: page <= 1, onClick: () => setPage(page - 1) }, "← Prev"),
          e("span", { className: "adm-pager-info" }, `Page ${page} of ${Math.max(1, Math.ceil(data.total / data.page_size))}`),
          e("button", { className: "btn btn-ghost btn-sm",
            disabled: page * data.page_size >= data.total, onClick: () => setPage(page + 1) }, "Next →"),
        )
      )
    );
  }

  // ─── Spotlights: choose featured cert + career for the home page ────────
  function AdminSpotlights() {
    const [data, setData] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [cert, setCert] = React.useState("");
    const [career, setCareer] = React.useState("");
    const [saveStatus, setSaveStatus] = React.useState(null); // 'saving' | 'saved' | 'error'
    const [saveMsg, setSaveMsg] = React.useState("");

    React.useEffect(() => {
      adminApi("/api/spotlights")
        .then(d => {
          setData(d || {cert: null, career: null});
          setCert(d && d.cert ? d.cert : "");
          setCareer(d && d.career ? d.career : "");
        })
        .catch(e => setErr(e.message || String(e)));
    }, []);

    async function save() {
      setSaveStatus("saving");
      setSaveMsg("");
      try {
        const result = await adminApi("/api/admin/spotlights", {
          method: "PUT",
          body: JSON.stringify({cert: cert || null, career: career || null}),
        });
        setData(result);
        setCert(result.cert || "");
        setCareer(result.career || "");
        setSaveStatus("saved");
        setSaveMsg("Saved.");
        setTimeout(() => { setSaveStatus(null); setSaveMsg(""); }, 2500);
      } catch (e) {
        setSaveStatus("error");
        setSaveMsg(e.message || "Save failed.");
      }
    }

    const exams = window.EXAMS || [];
    const certMeta = window.CERT_META || {};
    const careers = window.CAREER_PATHS || [];
    // Cert dropdown: live EXAMS first, then comingSoon entries from CERT_META.
    const liveCertIds = new Set(exams.map(x => x.id));
    const certOptions = [
      ...exams.map(x => ({id: x.id, label: x.label})),
      ...Object.entries(certMeta)
        .filter(([id, m]) => m && m.comingSoon && !liveCertIds.has(id))
        .map(([id, m]) => ({id, label: (m.label || id) + " (coming soon)"})),
    ];

    if (err) return e("div", { className: "adm-page" },
      e(AdminNav, { active: "spotlights" }), e("div", { className: "adm-error" }, err));
    if (!data) return e("div", { className: "adm-page" },
      e(AdminNav, { active: "spotlights" }), e("div", { className: "adm-loading" }, "Loading…"));

    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "spotlights" }),
      e("h1", { className: "adm-page-title" }, "Home Spotlights"),
      e("p", { style: {color: "var(--fg-dim)", marginTop: "-8px", marginBottom: "20px"} },
        "Pick one cert and one career to feature on the home page (below the Careers / Certifications buttons). Leave either blank to hide that slot."),
      e("div", { style: {display: "grid", gap: "20px", maxWidth: "560px"} },
        e("div", null,
          e("label", { style: {display: "block", fontSize: "12px", color: "var(--fg-dim)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "6px"} }, "Featured Certification"),
          e("select", {
            value: cert,
            onChange: ev => setCert(ev.target.value),
            style: {width: "100%", padding: "8px 10px", background: "var(--input)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", color: "var(--fg)", font: "inherit"},
          },
            e("option", { value: "" }, "— None —"),
            certOptions.map(o => e("option", { key: o.id, value: o.id }, o.label))
          )
        ),
        e("div", null,
          e("label", { style: {display: "block", fontSize: "12px", color: "var(--fg-dim)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "6px"} }, "Featured Career"),
          e("select", {
            value: career,
            onChange: ev => setCareer(ev.target.value),
            style: {width: "100%", padding: "8px 10px", background: "var(--input)", border: "1px solid var(--border)", borderRadius: "var(--r-md)", color: "var(--fg)", font: "inherit"},
          },
            e("option", { value: "" }, "— None —"),
            careers.map(c => e("option", { key: c.id, value: c.id }, c.name || c.id))
          )
        ),
        e("div", { style: {display: "flex", gap: "12px", alignItems: "center"} },
          e("button", {
            className: "btn",
            onClick: save,
            disabled: saveStatus === "saving",
          }, saveStatus === "saving" ? "Saving…" : "Save spotlights"),
          saveMsg && e("span", {
            style: {color: saveStatus === "error" ? "var(--red, #ff6b6b)" : "var(--green)", fontSize: "13px"},
          }, saveMsg),
        ),
        e("div", { style: {marginTop: "12px", fontSize: "12px", color: "var(--fg-mute)"} },
          "Current values: cert = ",
          e("code", null, data.cert || "(none)"),
          ", career = ",
          e("code", null, data.career || "(none)"),
          ". Changes are audit-logged."
        )
      )
    );
  }

  // ─── Dispatcher ────────────────────────────────────────────────────────
  // Router-driven: re-renders on URL change. Returns the right component
  // for the current /admin/* path. Falls back to a generic forbidden if
  // user isn't admin (server still enforces).
  function AdminPanel() {
    const user = window.AuthStore && window.AuthStore.user;
    const [routeBump, setRouteBump] = React.useState(0);
    React.useEffect(() => {
      const refresh = () => setRouteBump(n => n + 1);
      window.addEventListener("popstate", refresh);
      return () => window.removeEventListener("popstate", refresh);
    }, []);

    if (!user) {
      return e("div", { className: "adm-page" },
        e("h1", { className: "adm-page-title" }, "Sign in required"));
    }
    if (!user.is_admin) {
      // Mirror the "page not found" treatment — don't reveal admin routes exist
      return e("div", { className: "adm-page" },
        e("h1", { className: "adm-page-title" }, "Page not found"),
        e("p", null, "The page you're looking for doesn't exist."));
    }

    const path = window.location.pathname;
    if (path === "/admin" || path === "/admin/") return e(AdminDashboard);
    if (path === "/admin/users") return e(AdminUsers);
    if (path === "/admin/requests") return e(AdminRequests);
    if (path === "/admin/spotlights" || path === "/admin/spotlights/") return e(AdminSpotlights);
    if (path === "/admin/audit") return e(AdminAuditLogPage);
    // Cert reviews — renders the legacy cert-review UI with the admin nav
    // strip on top, all inside ONE .adm-page wrapper so padding is correct.
    // Uses AdminCertReviewsBody (the un-wrapped inner content) so we don't
    // get double-nested .adm-page divs.
    if (path === "/admin/cert-reviews" || path === "/admin/cert-reviews/") {
      return e("div", { className: "adm-page" },
        e(AdminNav, { active: "cert-reviews" }),
        e(AdminCertReviewsBody)
      );
    }
    const userMatch = path.match(/^\/admin\/user\/(\d+)$/);
    if (userMatch) return e(AdminUserDetail, { userId: parseInt(userMatch[1], 10) });
    // Fall through — unknown /admin/* path
    return e("div", { className: "adm-page" },
      e(AdminNav, { active: "" }),
      e("h1", { className: "adm-page-title" }, "Admin page not found"),
      e("p", null, "No admin page exists at " + path));
  }

  window.AdminPanel = AdminPanel;

  window.AdminCertReviewsPage = AdminCertReviewsPage;
})();
