// ============================================
// Reliq — Sidebar + TopBar
// ============================================

// ── Hash-router helper (no need to thread `go` into TopBar from every caller)
const navTo = (path, focusToken) => {
  if (focusToken) window.__reliq_pending_focus = focusToken;
  window.location.hash = "/" + path.replace(/^\/+/, "");
  window.scrollTo({ top: 0, behavior: "instant" });
};

// ── Notification data ─────────────────────────────────────────────────────────
//
// Data model fields:
//   id          — unique notification ID
//   category    — "scan"|"report"|"api"|"webhook"|"github"|"billing"|"team"|"policy"|"security"
//   type        — icon lookup key (used by NOTIF_ICON)
//   severity    — "critical"|"warning"|"info"|"success"
//   title       — short heading
//   body        — human-readable description (no raw API keys or account credentials)
//   timestamp   — relative or absolute display string
//   read        — boolean; persisted via NOTIF_READ_KEY
//   route       — hash path without leading "/" (e.g. "scan/scn_2k8x")
//   focusToken  — optional object for window.__reliq_pending_focus
//   actionLabel — CTA label shown on hover / screen-reader
//   access      — "billing"|"authenticated"|"subscription"
//                 "billing"       → always visible to authenticated users (billing, settings notices)
//                 "authenticated" → visible unless pending_payment (existing workspace content)
//                 "subscription"  → trialing/active only (actions behind plan gate)
//   metadata    — typed contextual data for future extensions; IDs use correct prefixes
//                 scan notifications: { scanId: "scn_*" }
//                 report notifications: { reportId: "rep_*" } — never scanId for report routes
//
// The `action` function has been replaced by `route` + optional `focusToken`.
// Click handler: navTo("/" + n.route, n.focusToken)
//
// NOTIF_READ_KEY uses the canonical key from RELIQ_KEYS (reliq_notif_read, underscore).

const NOTIF_READ_KEY = window.RELIQ_KEYS?.NOTIF_READ || "reliq_notif_read";

const INITIAL_NOTIFICATIONS = [
  {
    id: "n1", category: "scan", type: "scan", severity: "critical",
    title: "Critical finding detected",
    body: "Roamly v1.4.0 — 1 critical Data Safety issue requires action before submission.",
    timestamp: "12 min ago", read: false,
    route: "scan/scn_2k8x", actionLabel: "View scan",
    access: "authenticated",
    metadata: { scanId: "scn_2k8x" },
  },
  {
    id: "n2", category: "report", type: "report", severity: "info",
    title: "Report ready",
    body: "Compliance report for Roamly v1.4.0 has been generated and is ready to view.",
    timestamp: "15 min ago", read: false,
    route: "reports", focusToken: { type: "report", id: "rep_2k8x" }, actionLabel: "View report",
    access: "authenticated",
    metadata: { reportId: "rep_2k8x" }, // report ID (rep_*) — never scan ID
  },
  {
    id: "n3", category: "github", type: "github", severity: "warning",
    title: "GitHub Action scan completed",
    body: "QuickInvoice v2.1.3 — Needs Fixes · 1 critical finding.",
    timestamp: "1 hour ago", read: false,
    route: "scan/scn_2k8w", actionLabel: "View scan",
    access: "authenticated",
    metadata: { scanId: "scn_2k8w" },
  },
  {
    id: "n4", category: "webhook", type: "webhook", severity: "warning",
    title: "Webhook delivery failed",
    body: "Linear endpoint returned 503. Check your webhook configuration.",
    timestamp: "2 hours ago", read: false,
    route: "api", focusToken: { type: "api-tab", tab: "webhooks" }, actionLabel: "View webhooks",
    access: "subscription", // webhooks is a subscription-only feature
    metadata: { endpoint: "linear", statusCode: 503 },
  },
  {
    id: "n5", category: "policy", type: "policy", severity: "info",
    title: "Policy update available",
    body: "Google Play target SDK requirement updated to API 35.",
    timestamp: "3 hours ago", read: false,
    route: "policy", actionLabel: "View policy",
    access: "authenticated",
    metadata: { policyCode: "TARGET_SDK_REQUIREMENT" },
  },
  {
    id: "n6", category: "api", type: "api", severity: "info",
    title: "API key created",
    body: "\"Production CI\" key was created. Rotate it if you didn't make this change.",
    timestamp: "Yesterday, 14:32", read: true,
    route: "api", focusToken: { type: "api-tab", tab: "api" }, actionLabel: "View API keys",
    access: "subscription", // API keys are a subscription-only feature
    metadata: { keyName: "Production CI" },
  },
  {
    id: "n7", category: "billing", type: "billing", severity: "warning",
    title: "Scan limit at 85%",
    body: "You've used 85 of 100 scans this month. Upgrade your plan to increase your limit.",
    timestamp: "Yesterday, 09:11", read: true,
    route: "billing", actionLabel: "View billing",
    access: "billing", // always visible — billing notices apply to all authenticated users
    metadata: { used: 85, limit: 100 },
  },
  {
    id: "n8", category: "scan", type: "scan", severity: "critical",
    title: "Scan failed",
    body: "MoodBoard AI v0.9.2 — upload timed out. Retry when ready.",
    timestamp: "2 days ago", read: true,
    route: "scan/scn_2k8v", actionLabel: "View scan",
    access: "authenticated",
    metadata: { scanId: "scn_2k8v" },
  },
  {
    id: "n9", category: "report", type: "report", severity: "info",
    title: "Report link shared",
    body: "QuickInvoice v2.1.3 compliance report is now publicly accessible via share link.",
    timestamp: "2 days ago", read: true,
    route: "reports", focusToken: { type: "report", id: "rep_2k8w" }, actionLabel: "View report",
    access: "authenticated",
    metadata: { reportId: "rep_2k8w" }, // report ID (rep_*) — never scan ID
  },
  {
    id: "n10", category: "github", type: "github", severity: "info",
    title: "CI scan complete — Pass",
    body: "Brewline v4.2.1 scored 95/100. No critical issues found.",
    timestamp: "3 days ago", read: true,
    route: "scan/scn_2k8s", actionLabel: "View scan",
    access: "authenticated",
    metadata: { scanId: "scn_2k8s" },
  },
];

// ── Docs / help items ─────────────────────────────────────────────────────────
const DOCS_ITEMS = [
  {
    id: "d1", icon: "scan", category: "Getting started",
    title: "Quick start",
    desc: "Run your first scan and understand your score.",
    action: () => navTo("/scan/new"),
  },
  {
    id: "d2", icon: "git-branch", category: "Integrations",
    title: "GitHub Action",
    desc: "Add Reliq to your CI/CD pipeline. One-line setup.",
    action: () => navTo("/api", { type: "api-tab", tab: "github" }),
  },
  {
    id: "d3", icon: "code", category: "Integrations",
    title: "API reference",
    desc: "Create scans, fetch reports, and manage webhooks programmatically.",
    action: () => navTo("/api", { type: "api-tab", tab: "api" }),
  },
  {
    id: "d4", icon: "webhook", category: "Integrations",
    title: "Webhooks",
    desc: "Listen for scan.completed and report.generated events.",
    action: () => navTo("/api", { type: "api-tab", tab: "webhooks" }),
  },
  {
    id: "d5", icon: "book", category: "Reference",
    title: "Policy Library",
    desc: "Browse Apple App Store and Google Play compliance checks.",
    action: () => navTo("/policy"),
  },
  {
    id: "d6", icon: "file-text", category: "Reference",
    title: "Reports guide",
    desc: "Export, share, and white-label your compliance reports.",
    action: () => navTo("/reports"),
  },
  {
    id: "d7", icon: "shield", category: "Support",
    title: "Status",
    desc: "Check Reliq service availability and recent incidents.",
    action: () => navTo("/status"),
  },
  {
    id: "d8", icon: "users", category: "Support",
    title: "Contact support",
    desc: "Get help with scans, billing, or integrations.",
    action: () => {
      const el = document.createElement("a");
      el.href = "mailto:support@reliq.dev";
      el.click();
    },
  },
];

// ── Shared nav item definitions ───────────────────────────────────────────────
// Used by both Sidebar and MobileDrawer so there's a single source of truth.
const NAV_ITEMS = [
  { id: "dashboard", icon: "home",       label: "Dashboard" },
  { id: "scan/new",  icon: "scan",       label: "New Scan" },
  { id: "scans",     icon: "folder",     label: "All Scans", count: 24 },
  { id: "reports",   icon: "file-text",  label: "Reports" },
  { id: "apps",      icon: "smartphone", label: "Apps", count: 6 },
  { id: "policy",    icon: "book",       label: "Policy Library" },
];
const NAV_SECONDARY = [
  { id: "api",      icon: "code",     label: "API & Webhooks" },
  { id: "team",     icon: "users",    label: "Team" },
  { id: "billing",  icon: "bolt",     label: "Billing" },
  { id: "settings", icon: "settings", label: "Settings" },
];

// ── Account menu (sidebar profile popover) ────────────────────────────────────
// Simplified ChatGPT-style menu: email header, a couple of quick actions, and
// Log Out. "Upgrade your plan" routes to Billing; "Settings" routes to Settings.
const AccountMenu = ({ account, onSelect, onSignOut }) => {
  const menuRef = React.useRef(null);

  // Focus the first item on open and support arrow-key navigation.
  React.useEffect(() => {
    const root = menuRef.current;
    if (!root) return;
    const items = () => Array.from(root.querySelectorAll('[role="menuitem"]'));
    const initial = items();
    if (initial[0]) initial[0].focus();
    const onKey = (e) => {
      const els = items();
      if (!els.length) return;
      const i = els.indexOf(document.activeElement);
      if (e.key === "ArrowDown")      { e.preventDefault(); (els[i + 1] || els[0]).focus(); }
      else if (e.key === "ArrowUp")   { e.preventDefault(); (els[i - 1] || els[els.length - 1]).focus(); }
      else if (e.key === "Home")      { e.preventDefault(); els[0].focus(); }
      else if (e.key === "End")       { e.preventDefault(); els[els.length - 1].focus(); }
    };
    root.addEventListener("keydown", onKey);
    return () => root.removeEventListener("keydown", onKey);
  }, []);

  const itemStyle = {
    display:"flex", alignItems:"center", justifyContent:"space-between", gap:12, width:"100%",
    padding:"9px 12px", border:"none", background:"transparent",
    borderRadius:"var(--r-sm)", cursor:"pointer", textAlign:"left",
    fontSize:13.5, color:"var(--fg-0)", fontFamily:"inherit",
  };
  const hoverOn  = (e) => { e.currentTarget.style.background = "var(--bg-2)"; };
  const hoverOff = (e) => { e.currentTarget.style.background = "transparent"; };

  return (
    <div ref={menuRef} role="menu" aria-label="Account menu" style={{
      position:"absolute", bottom:"calc(100% + 8px)", left:0, zIndex:300,
      width:240, maxWidth:"calc(100vw - 24px)",
      background:"var(--bg-1)", border:"1px solid var(--line-2)",
      borderRadius:"var(--r-md)",
      boxShadow:"0 8px 28px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.10)",
      overflow:"hidden", padding:6,
    }}>
      {/* Email header */}
      <div style={{
        padding:"8px 12px 8px", fontSize:13, color:"var(--fg-2)",
        whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis",
      }}>
        {account.email}
      </div>
      <div style={{height:1, background:"var(--line-1)", margin:"2px 0 4px"}} />

      {/* Quick actions */}
      <button role="menuitem" type="button" style={itemStyle}
        onClick={() => onSelect("/billing")}
        onMouseEnter={hoverOn} onMouseLeave={hoverOff}
        onFocus={hoverOn}      onBlur={hoverOff}>
        <span>Upgrade your plan</span>
      </button>
      <button role="menuitem" type="button" style={itemStyle}
        onClick={() => onSelect("/settings")}
        onMouseEnter={hoverOn} onMouseLeave={hoverOff}
        onFocus={hoverOn}      onBlur={hoverOff}>
        <span>Settings</span>
        <span className="mono" style={{fontSize:12.5, color:"var(--fg-3)", letterSpacing:"0.02em"}}>⌘,</span>
      </button>

      <div style={{height:1, background:"var(--line-1)", margin:"4px 0"}} />

      {/* Log out */}
      <button role="menuitem" type="button" style={itemStyle}
        onClick={onSignOut}
        onMouseEnter={hoverOn} onMouseLeave={hoverOff}
        onFocus={hoverOn}      onBlur={hoverOff}>
        <span>Log Out</span>
      </button>
    </div>
  );
};

// ── Sidebar ───────────────────────────────────────────────────────────────────
const Sidebar = ({ route, go }) => {
  const items     = NAV_ITEMS;
  const secondary = NAV_SECONDARY;
  const [menuOpen, setMenuOpen] = React.useState(false);
  const wrapRef    = React.useRef(null);
  const triggerRef = React.useRef(null);

  const isActive = (id) =>
    route === id ||
    (id === "scans"  && route.startsWith("scan/") && route !== "scan/new") ||
    (id === "apps"   && route.startsWith("app/")) ||
    (id === "policy" && route.startsWith("policy/"));

  // Account details — prefer the signed-in user; fall back to workspace defaults.
  const account = React.useMemo(() => {
    const u = (window.reliqAuth && window.reliqAuth.getUser && window.reliqAuth.getUser()) || null;
    const name  = (u && u.name)  || "Zola Lena";
    const email = (u && u.email) || "zola@holt.africa";
    const initials = (name.trim().split(/\s+/).map(w => w[0]).slice(0, 2).join("") || "ZL").toUpperCase();
    const planKey =
      (window.reliqPlans && window.reliqPlans.getCurrentPlanKey && window.reliqPlans.getCurrentPlanKey()) ||
      (window.reliqSubscription && window.reliqSubscription.getPlan && window.reliqSubscription.getPlan()) ||
      "team";

    const cleanPlanKey = String(planKey || "team").toLowerCase();
    const planName =
      (window.PLAN_META && window.PLAN_META[cleanPlanKey] && window.PLAN_META[cleanPlanKey].name) ||
      (cleanPlanKey === "team" ? "Team" : cleanPlanKey.charAt(0).toUpperCase() + cleanPlanKey.slice(1));

    const membership =
      (window.reliqAuth && window.reliqAuth.getMembership && window.reliqAuth.getMembership()) ||
      null;

    const role = ((membership && membership.role) || "OWNER").toString().toUpperCase();
    const shortName = name.trim().split(/\s+/).length > 1
      ? `${name.trim().split(/\s+/)[0]} ${name.trim().split(/\s+/)[1][0]}.`
      : name;

    return {
      name,
      shortName,
      email,
      initials,
      plan: planName,
      role,
      usage: `${planName.toUpperCase()} · ${role}`
    };
  }, [menuOpen]);

  // Close on outside click / Escape; restore focus to the trigger on Escape.
  React.useEffect(() => {
    if (!menuOpen) return;
    const onMouse = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setMenuOpen(false);
    };
    const onKey = (e) => {
      if (e.key === "Escape") {
        setMenuOpen(false);
        if (triggerRef.current) triggerRef.current.focus();
      }
    };
    document.addEventListener("mousedown", onMouse);
    window.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onMouse);
      window.removeEventListener("keydown", onKey);
    };
  }, [menuOpen]);

  const navTo = (path) => { setMenuOpen(false); go(path); };

  // Logout — POST /v1/auth/logout to end the real session, then clear ALL local
  // auth/subscription state and route to sign-in.
  const signOut = () => {
    setMenuOpen(false);
    try { window.reliqAuth && window.reliqAuth.logoutRequest && window.reliqAuth.logoutRequest(); } catch (_) {}
    try {
      if (window.reliqSubscription && window.reliqSubscription.logout) window.reliqSubscription.logout();
      else if (window.reliqAuth && window.reliqAuth.signOut)          window.reliqAuth.signOut();
    } catch (_) {}
    go("/signin");
  };

  const onTriggerKey = (e) => {
    if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setMenuOpen(o => !o); }
    else if (e.key === "ArrowUp" && !menuOpen) { e.preventDefault(); setMenuOpen(true); }
    else if (e.key === "Escape" && menuOpen)   { e.preventDefault(); setMenuOpen(false); }
  };

  return (
    <aside className="sidebar">
      <div className="brand" onClick={() => go("/")} style={{ cursor: "pointer" }}>
        <img className="brand-logo brand-logo-light" src="assets/reliq-logo-dark.png" alt="Reliq" />
        <img className="brand-logo brand-logo-dark"  src="assets/reliq-logo-light.png" alt="Reliq" />
      </div>
      <div className="nav-section">Workspace</div>
      {items.map(it => (
        <div key={it.id} className={`nav-item ${isActive(it.id) ? "active" : ""}`} onClick={() => go("/" + it.id)}>
          <Icon name={it.icon} className="ic" />
          <span>{it.label}</span>
          {it.count != null && <span className="count">{it.count}</span>}
        </div>
      ))}
      <div className="nav-section">Developer</div>
      {secondary.map(it => (
        <div key={it.id} className={`nav-item ${route === it.id ? "active" : ""}`} onClick={() => go("/" + it.id)}>
          <Icon name={it.icon} className="ic" />
          <span>{it.label}</span>
        </div>
      ))}
      <div className="footer">
        <div ref={wrapRef} style={{ position: "relative" }}>
          {menuOpen && (
            <AccountMenu account={account} onSelect={navTo} onSignOut={signOut} />
          )}
          <div className="userchip"
            ref={triggerRef}
            role="button" tabIndex={0}
            aria-haspopup="menu" aria-expanded={menuOpen} aria-label="Account menu"
            onClick={() => setMenuOpen(o => !o)}
            onKeyDown={onTriggerKey}>
            <div className="avatar">{account.initials}</div>
            <div className="col">
              <div className="uname">{account.shortName}</div>
              <div className="uplan">{account.usage}</div>
            </div>
            <Icon name="chevron" size={14} className="ic"
              style={{ transform: menuOpen ? "rotate(-90deg)" : "none", transition: "transform .15s" }} />
          </div>
        </div>
      </div>
    </aside>
  );
};

// ── MobileDrawer ──────────────────────────────────────────────────────────────
// Slide-in navigation panel shown on phones (≤480px) where the sidebar is hidden.
// Rendered inside AuthLayout; controlled via `open` + `onClose` props.
const MobileDrawer = ({ route, go, open, onClose }) => {
  // Focus trap — reuse the shared useFocusTrap hook when available
  const trapRef = (window.useFocusTrap || (() => React.useRef(null)))(open);

  // Close on Escape
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);

  // Lock body scroll while open
  React.useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  const isActive = (id) =>
    route === id ||
    (id === "scans"  && route.startsWith("scan/") && route !== "scan/new") ||
    (id === "apps"   && route.startsWith("app/")) ||
    (id === "policy" && route.startsWith("policy/"));

  const navigate = (id) => { go("/" + id); onClose(); };

  return (
    <>
      {/* Backdrop */}
      <div
        className={"mob-drawer-backdrop" + (open ? " open" : "")}
        onClick={onClose}
        aria-hidden="true"
      />

      {/* Drawer panel */}
      <nav
        id="mob-drawer"
        className={"mob-drawer" + (open ? " open" : "")}
        ref={trapRef}
        role="dialog"
        aria-modal="true"
        aria-label="Navigation menu"
      >
        {/* Brand row */}
        <div className="mob-drawer-brand">
          <div onClick={() => navigate("")} style={{ cursor: "pointer" }}>
            <img className="brand-logo brand-logo-light" src="assets/reliq-logo-dark.png"
                 alt="Reliq" style={{ height: 22 }} />
            <img className="brand-logo brand-logo-dark"  src="assets/reliq-logo-light.png"
                 alt="Reliq" style={{ height: 22 }} />
          </div>
          <button className="mob-drawer-close" onClick={onClose} aria-label="Close navigation">
            <Icon name="x" size={14} />
          </button>
        </div>

        {/* Workspace section */}
        <div className="mob-drawer-section">Workspace</div>
        {NAV_ITEMS.map(it => (
          <div key={it.id}
            className={"mob-drawer-item" + (isActive(it.id) ? " active" : "")}
            onClick={() => navigate(it.id)}
            role="link"
            tabIndex={open ? 0 : -1}
            onKeyDown={e => { if (e.key === "Enter" || e.key === " ") navigate(it.id); }}
          >
            <Icon name={it.icon} size={16} className="ic" />
            <span style={{ flex: 1 }}>{it.label}</span>
            {it.count != null && <span className="count">{it.count}</span>}
          </div>
        ))}

        {/* Developer section */}
        <div className="mob-drawer-section">Developer</div>
        {NAV_SECONDARY.map(it => (
          <div key={it.id}
            className={"mob-drawer-item" + (route === it.id ? " active" : "")}
            onClick={() => navigate(it.id)}
            role="link"
            tabIndex={open ? 0 : -1}
            onKeyDown={e => { if (e.key === "Enter" || e.key === " ") navigate(it.id); }}
          >
            <Icon name={it.icon} size={16} className="ic" />
            <span>{it.label}</span>
          </div>
        ))}

        {/* Footer / user chip */}
        <div className="mob-drawer-footer">
          <div className="mob-drawer-userchip">
            <div className="mob-drawer-avatar">ZL</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: "var(--fg-0)" }}>Zola L.</div>
              <div style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--fg-3)", marginTop: 1 }}>TEAM · OWNER</div>
            </div>
          </div>
        </div>
      </nav>
    </>
  );
};

// ── Responsive placeholder for universal search bar ───────────────────────────
const useSearchPlaceholder = () => {
  const [w, setW] = React.useState(() => window.innerWidth);
  React.useEffect(() => {
    const h = () => setW(window.innerWidth);
    window.addEventListener("resize", h);
    return () => window.removeEventListener("resize", h);
  }, []);
  if (w >= 1100) return "Search scans, apps, policies…";
  if (w >= 768)  return "Search scans…";
  return "Search";
};

// ── ThemeToggle ───────────────────────────────────────────────────────────────
// Modes:
//   "auto"   — theme follows time of day (default). Teal dot badge on button.
//   "manual" — user's explicit pick. A small "AUTO" chip appears to reset.
//
// localStorage keys:
//   reliq-theme-mode  "auto" | "manual"
//   reliq-theme       "light" | "dark"  (only used when mode = "manual")

const ThemeToggle = () => {
  // ── Helpers ────────────────────────────────────────────────────────────────
  const autoTheme = () => {
    const h = new Date().getHours();
    return (h >= 6 && h < 18) ? "light" : "dark";
  };
  const readMode  = () => { try { return localStorage.getItem("reliq-theme-mode") || "auto"; } catch (_) { return "auto"; } };
  const readTheme = () => { try { return localStorage.getItem("reliq-theme") || autoTheme(); } catch (_) { return autoTheme(); } };
  const applyTheme = (t) => {
    document.documentElement.setAttribute("data-theme", t);
    try { localStorage.setItem("reliq-theme", t); } catch (_) {}
  };

  // ── State ──────────────────────────────────────────────────────────────────
  const [isAuto, setIsAuto] = React.useState(() => readMode() === "auto");
  const [theme,  setTheme]  = React.useState(() =>
    readMode() === "auto" ? autoTheme() : readTheme()
  );

  // Re-check auto theme every 5 minutes while the page is open.
  // Only fires when mode is still "auto" — does nothing in manual mode.
  React.useEffect(() => {
    const id = setInterval(() => {
      if (readMode() === "auto") {
        const next = autoTheme();
        applyTheme(next);
        setTheme(next);
      }
    }, 5 * 60 * 1000);
    return () => clearInterval(id);
  }, []);

  // ── Manual toggle ──────────────────────────────────────────────────────────
  const flip = () => {
    const next = theme === "dark" ? "light" : "dark";
    try { localStorage.setItem("reliq-theme-mode", "manual"); } catch (_) {}
    applyTheme(next);
    setTheme(next);
    setIsAuto(false);
  };

  // ── Reset to auto ──────────────────────────────────────────────────────────
  const resetToAuto = (e) => {
    e.stopPropagation();
    try {
      localStorage.setItem("reliq-theme-mode", "auto");
      localStorage.removeItem("reliq-theme");
    } catch (_) {}
    const next = autoTheme();
    applyTheme(next);
    setTheme(next);
    setIsAuto(true);
  };

  // ── Icons ──────────────────────────────────────────────────────────────────
  const SunIcon = () => (
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="12" cy="12" r="4"/>
      <path d="M12 2v2"/><path d="M12 20v2"/>
      <path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/>
      <path d="M2 12h2"/><path d="M20 12h2"/>
      <path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>
    </svg>
  );
  const MoonIcon = () => (
    <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
    </svg>
  );

  // ── Render ─────────────────────────────────────────────────────────────────
  return (
    <div style={{ display:"flex", alignItems:"center", gap:4 }}>

      {/* Sun/moon toggle */}
      <button
        className="theme-toggle"
        onClick={flip}
        title={isAuto
          ? `Auto (time-based) · ${theme} mode until ${theme === "light" ? "6 pm" : "6 am"} · click to override`
          : `${theme === "dark" ? "Dark" : "Light"} mode (manual) · click to toggle`
        }
        style={{ position:"relative" }}
      >
        {theme === "dark" ? <SunIcon /> : <MoonIcon />}

        {/* Teal dot — visible only in auto mode */}
        {isAuto && (
          <span style={{
            position:"absolute", top:-2, right:-2,
            width:6, height:6, borderRadius:"50%",
            background:"var(--teal)",
            border:"1.5px solid var(--bg-0)",
            pointerEvents:"none",
          }} />
        )}
      </button>

      {/* "AUTO" reset chip — visible only in manual mode */}
      {!isAuto && (
        <button
          onClick={resetToAuto}
          title="Reset to auto (time-based) theme"
          style={{
            background:"none", border:"1px solid var(--line-2)",
            borderRadius:4, cursor:"pointer", lineHeight:1,
            fontFamily:"var(--font-mono)", fontSize:9.5,
            letterSpacing:"0.08em", textTransform:"uppercase",
            color:"var(--fg-3)", padding:"2px 5px",
            transition:"color .12s, border-color .12s",
          }}
          onMouseEnter={e => {
            e.currentTarget.style.color = "var(--teal)";
            e.currentTarget.style.borderColor = "var(--teal)";
          }}
          onMouseLeave={e => {
            e.currentTarget.style.color = "var(--fg-3)";
            e.currentTarget.style.borderColor = "var(--line-2)";
          }}
          aria-label="Reset to automatic time-based theme"
        >
          AUTO
        </button>
      )}

    </div>
  );
};

// ── NotificationPopover ───────────────────────────────────────────────────────
const NOTIF_ICON  = { scan:"scan", report:"file-text", webhook:"webhook", github:"git-branch", policy:"book", billing:"bolt", api:"key" };
const SEV_COLOR   = { critical:"var(--red)", warning:"var(--amber)", info:"var(--blue)", success:"var(--teal)" };
const SEV_BG      = { critical:"var(--red-bg)", warning:"var(--amber-bg)", info:"var(--blue-bg)", success:"var(--teal-bg)" };

const NotificationPopover = ({ notifications, onMarkRead, onMarkAllRead }) => {
  const unread = notifications.filter(n => !n.read).length;
  return (
    <div style={{
      position:"absolute", top:"calc(100% + 8px)", right:0, zIndex:300,
      width:360, maxHeight:480, display:"flex", flexDirection:"column",
      background:"var(--bg-1)", border:"1px solid var(--line-2)",
      borderRadius:"var(--r-md)",
      boxShadow:"0 8px 28px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.10)",
      overflow:"hidden",
    }}>
      {/* Header */}
      <div style={{
        display:"flex", alignItems:"center",
        padding:"12px 14px 10px", borderBottom:"1px solid var(--line-1)", flexShrink:0,
      }}>
        <span style={{fontFamily:"var(--font-display)", fontWeight:600, fontSize:13.5, flex:1}}>
          Notifications
        </span>
        {unread > 0 && (
          <button onClick={onMarkAllRead} style={{
            background:"none", border:"none", cursor:"pointer",
            fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)",
            padding:"2px 4px", borderRadius:4,
          }}
          onMouseEnter={e => e.currentTarget.style.color="var(--fg-0)"}
          onMouseLeave={e => e.currentTarget.style.color="var(--fg-3)"}>
            Mark all read
          </button>
        )}
      </div>

      {/* List */}
      <div style={{overflowY:"auto", flex:1}}>
        {notifications.length === 0 ? (
          <div style={{padding:"40px 20px", textAlign:"center"}}>
            <Icon name="bell" size={20} style={{color:"var(--fg-3)"}} />
            <div style={{fontSize:13.5, fontWeight:500, color:"var(--fg-2)", marginTop:10}}>No notifications</div>
            <div style={{fontSize:12.5, color:"var(--fg-3)", marginTop:4}}>All caught up.</div>
          </div>
        ) : notifications.map((n, i) => (
          <div key={n.id}
            onClick={() => { onMarkRead(n.id); navTo("/" + n.route, n.focusToken); }}
            style={{
              display:"flex", alignItems:"flex-start", gap:10,
              padding:"11px 14px",
              borderTop: i === 0 ? "none" : "1px solid var(--line-1)",
              cursor:"pointer",
              background: n.read ? "transparent" : "var(--bg-2)",
              transition:"background .1s",
            }}
            onMouseEnter={e => e.currentTarget.style.background="var(--bg-3)"}
            onMouseLeave={e => e.currentTarget.style.background = n.read ? "transparent" : "var(--bg-2)"}
          >
            {/* Type icon */}
            <div style={{
              width:30, height:30, borderRadius:7, flexShrink:0, marginTop:1,
              background: SEV_BG[n.severity] || "var(--bg-3)",
              color: SEV_COLOR[n.severity] || "var(--fg-2)",
              display:"flex", alignItems:"center", justifyContent:"center",
            }}>
              <Icon name={NOTIF_ICON[n.type] || "bell"} size={13} />
            </div>

            {/* Content */}
            <div style={{flex:1, minWidth:0}}>
              <div style={{
                display:"flex", alignItems:"baseline", gap:6, marginBottom:2,
              }}>
                <span style={{fontSize:13, fontWeight: n.read ? 500 : 600, color:"var(--fg-0)"}}>
                  {n.title}
                </span>
                <span style={{
                  fontFamily:"var(--font-mono)", fontSize:9.5, fontWeight:600,
                  letterSpacing:"0.06em", textTransform:"uppercase", flexShrink:0,
                  color: SEV_COLOR[n.severity], padding:"1px 5px",
                  background: SEV_BG[n.severity],
                  border:`1px solid ${SEV_COLOR[n.severity]}33`, borderRadius:3,
                }}>
                  {n.severity}
                </span>
              </div>
              <div style={{fontSize:12, color:"var(--fg-2)", lineHeight:1.5, marginBottom:3}}>
                {n.body}
              </div>
              <div style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)"}}>
                {n.timestamp}
              </div>
            </div>

            {/* Unread dot */}
            {!n.read && (
              <div style={{
                width:7, height:7, borderRadius:"50%",
                background:"var(--blue)", flexShrink:0, marginTop:6,
              }} />
            )}
          </div>
        ))}
      </div>

      {/* Footer */}
      <div style={{
        padding:"9px 14px", borderTop:"1px solid var(--line-1)", flexShrink:0,
      }}>
        <button onClick={() => navTo("/settings", { type:"settings-tab", tab:"notifications" })}
          style={{
            background:"none", border:"none", cursor:"pointer",
            fontFamily:"var(--font-mono)", fontSize:11.5, color:"var(--fg-3)",
            display:"flex", alignItems:"center", gap:6, padding:0, width:"100%",
          }}
          onMouseEnter={e => e.currentTarget.style.color="var(--fg-0)"}
          onMouseLeave={e => e.currentTarget.style.color="var(--fg-3)"}>
          <Icon name="settings" size={11} />
          View notification settings
        </button>
      </div>
    </div>
  );
};

// ── DocsPopover ───────────────────────────────────────────────────────────────
const DocsPopover = () => {
  const [search, setSearch] = React.useState("");
  const inputRef = React.useRef(null);

  React.useEffect(() => { setTimeout(() => inputRef.current?.focus(), 20); }, []);

  const ql = search.trim().toLowerCase();
  const filtered = ql
    ? DOCS_ITEMS.filter(d => d.title.toLowerCase().includes(ql) || d.desc.toLowerCase().includes(ql) || d.category.toLowerCase().includes(ql))
    : DOCS_ITEMS;

  return (
    <div style={{
      position:"absolute", top:"calc(100% + 8px)", right:0, zIndex:300,
      width:320, maxHeight:480, display:"flex", flexDirection:"column",
      background:"var(--bg-1)", border:"1px solid var(--line-2)",
      borderRadius:"var(--r-md)",
      boxShadow:"0 8px 28px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.10)",
      overflow:"hidden",
    }}>
      {/* Header */}
      <div style={{padding:"12px 14px 8px", borderBottom:"1px solid var(--line-1)", flexShrink:0}}>
        <div style={{fontFamily:"var(--font-display)", fontWeight:600, fontSize:13.5, marginBottom:2}}>
          Help & Docs
        </div>
        <div style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)"}}>
          Guides, API docs, and release support.
        </div>
      </div>

      {/* Search */}
      <div style={{padding:"8px 10px", borderBottom:"1px solid var(--line-1)", flexShrink:0}}>
        <div style={{position:"relative"}}>
          <Icon name="search" size={12} style={{
            position:"absolute", left:9, top:"50%", transform:"translateY(-50%)",
            color:"var(--fg-3)", pointerEvents:"none",
          }} />
          <input
            ref={inputRef}
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder="Search docs…"
            style={{
              width:"100%", boxSizing:"border-box",
              background:"var(--bg-2)", border:"1px solid var(--line-2)",
              borderRadius:"var(--r-sm)", padding:"6px 10px 6px 28px",
              fontSize:12.5, color:"var(--fg-0)", outline:"none",
              fontFamily:"var(--font-display)",
            }}
          />
        </div>
      </div>

      {/* Items */}
      <div style={{overflowY:"auto", flex:1, padding:"4px 4px 6px"}}>
        {filtered.length === 0 ? (
          <div style={{padding:"28px 16px", textAlign:"center"}}>
            <div style={{fontSize:13, color:"var(--fg-3)"}}>No docs found.</div>
          </div>
        ) : filtered.map(d => (
          <div key={d.id}
            onClick={() => d.action()}
            style={{
              display:"flex", alignItems:"flex-start", gap:9,
              padding:"8px 10px", borderRadius:6, cursor:"pointer",
              transition:"background .1s",
            }}
            onMouseEnter={e => e.currentTarget.style.background="var(--bg-3)"}
            onMouseLeave={e => e.currentTarget.style.background="transparent"}
          >
            <div style={{
              width:28, height:28, borderRadius:6, flexShrink:0,
              background:"var(--bg-3)", color:"var(--fg-2)",
              display:"flex", alignItems:"center", justifyContent:"center", marginTop:1,
            }}>
              <Icon name={d.icon} size={13} />
            </div>
            <div style={{flex:1, minWidth:0}}>
              <div style={{fontSize:13, fontWeight:500, color:"var(--fg-0)", marginBottom:2}}>
                {d.title}
              </div>
              <div style={{fontFamily:"var(--font-mono)", fontSize:11, color:"var(--fg-3)", lineHeight:1.4}}>
                {d.desc}
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

// ── TopBar ────────────────────────────────────────────────────────────────────
const TopBar = ({ crumbs = [], actions = null, onHamburger = null, mobOpen = false }) => {
  const kbdLabel    = window.KBD_LABEL || "⌘K";
  const placeholder = useSearchPlaceholder();
  const searchbarRef = React.useRef(null);

  // ── Search open ──
  const openSearch = () => {
    const rect = searchbarRef.current?.getBoundingClientRect() || null;
    window.dispatchEvent(new CustomEvent("reliq:open-search", { detail: { rect } }));
  };

  // ── Notification state ──
  // Access-aware filter: trialing/active see all; past_due/canceled omit subscription-only notices.
  // Pending-payment users never reach AuthLayout, so no special case needed for them here.
  const getVisibleNotifications = React.useCallback((all) => {
    const state = window.reliqSubscription?.getProductState?.() || "";
    const isSubscription = state === "authenticated_trialing" || state === "authenticated_active";
    if (isSubscription) return all;
    // past_due / canceled: hide subscription-only notifications (webhooks, API key notices)
    return all.filter(n => (n.access || "authenticated") !== "subscription");
  }, []);

  const [notifications, setNotifications] = React.useState(() => {
    try {
      const readIds = JSON.parse(localStorage.getItem(NOTIF_READ_KEY) || "[]");
      return INITIAL_NOTIFICATIONS.map(n => ({ ...n, read: readIds.includes(n.id) || n.read }));
    } catch { return INITIAL_NOTIFICATIONS; }
  });

  const persistRead = (updated) => {
    try {
      localStorage.setItem(NOTIF_READ_KEY,
        JSON.stringify(updated.filter(n => n.read).map(n => n.id)));
    } catch {}
  };

  const markRead = (id) => {
    setNotifications(prev => {
      const next = prev.map(n => n.id === id ? { ...n, read: true } : n);
      persistRead(next);
      return next;
    });
  };

  const markAllRead = () => {
    setNotifications(prev => {
      const next = prev.map(n => ({ ...n, read: true }));
      persistRead(next);
      return next;
    });
  };

  const visibleNotifications = getVisibleNotifications(notifications);
  const unreadCount = visibleNotifications.filter(n => !n.read).length;

  // ── Popover state ──
  const [bellOpen, setBellOpen] = React.useState(false);
  const [docsOpen, setDocsOpen] = React.useState(false);
  const bellWrapRef = React.useRef(null);
  const docsWrapRef = React.useRef(null);

  // Only one popover at a time + close on outside click / Escape
  React.useEffect(() => {
    const onMouse = (e) => {
      if (bellOpen && bellWrapRef.current && !bellWrapRef.current.contains(e.target)) setBellOpen(false);
      if (docsOpen && docsWrapRef.current && !docsWrapRef.current.contains(e.target)) setDocsOpen(false);
    };
    const onKey = (e) => {
      if (e.key === "Escape") { setBellOpen(false); setDocsOpen(false); }
    };
    document.addEventListener("mousedown", onMouse);
    window.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onMouse);
      window.removeEventListener("keydown", onKey);
    };
  }, [bellOpen, docsOpen]);

  return (
    <div className="topbar">
      {/* Hamburger — visible only at ≤480px when sidebar is hidden */}
      {onHamburger && (
        <button
          className="mob-nav-btn"
          onClick={onHamburger}
          aria-label={mobOpen ? "Close navigation menu" : "Open navigation menu"}
          aria-expanded={mobOpen}
          aria-controls="mob-drawer"
          style={{ marginRight: 8 }}
        >
          <Icon name={mobOpen ? "x" : "menu"} size={16} />
        </button>
      )}

      {/* Breadcrumbs */}
      <div className="crumbs">
        {crumbs.map((c, i) => (
          <React.Fragment key={i}>
            {i > 0 && <span className="sep">/</span>}
            <span className={i === crumbs.length - 1 ? "cur" : ""}>{c}</span>
          </React.Fragment>
        ))}
      </div>

      {/* Universal search bar */}
      <div ref={searchbarRef} className="searchbar" onClick={openSearch}
        style={{cursor:"pointer"}} role="button" aria-label="Open global search">
        <Icon name="search" size={13} />
        <span>{placeholder}</span>
        <kbd>{kbdLabel}</kbd>
      </div>

      {/* Actions */}
      <div className="actions">
        {actions}
        <ThemeToggle />

        {/* ── Bell ── */}
        <div style={{position:"relative"}} ref={bellWrapRef}>
          <button
            className="btn btn-icon btn-ghost"
            aria-label="Open notifications"
            style={{position:"relative"}}
            onClick={() => { setBellOpen(o => !o); setDocsOpen(false); }}
          >
            <Icon name="bell" size={15} />
            {unreadCount > 0 && (
              <span style={{
                position:"absolute", top:2, right:2,
                width:7, height:7, borderRadius:"50%",
                background:"var(--red)", border:"2px solid var(--bg-0)",
                pointerEvents:"none",
              }} />
            )}
          </button>
          {bellOpen && (
            <NotificationPopover
              notifications={visibleNotifications}
              onMarkRead={markRead}
              onMarkAllRead={markAllRead}
            />
          )}
        </div>

        {/* ── Docs ── */}
        <div style={{position:"relative"}} ref={docsWrapRef}>
          <button
            className="btn btn-icon btn-ghost"
            aria-label="Open help and documentation"
            onClick={() => { setDocsOpen(o => !o); setBellOpen(false); }}
          >
            <Icon name="book" size={15} />
          </button>
          {docsOpen && <DocsPopover />}
        </div>
      </div>
    </div>
  );
};

// ── AccountStatusBanner ───────────────────────────────────────────────────────
// Shown inside AuthLayout when the user's subscription is past_due or canceled.
// TRIALING and ACTIVE users see nothing. Pending-payment users are redirected
// before they ever render an AuthLayout page.
const AccountStatusBanner = ({ go }) => {
  const [state, setState] = React.useState(() => {
    if (!window.reliqSubscription) return null;
    const sub = window.reliqSubscription.getSubscriptionStatus();
    if (sub === "past_due") return "past_due";
    if (sub === "canceled") return "canceled";
    return null;
  });

  // Re-check whenever reliq-auth-change fires (sign-in / sign-out)
  React.useEffect(() => {
    const refresh = () => {
      if (!window.reliqSubscription) return;
      const sub = window.reliqSubscription.getSubscriptionStatus();
      setState(sub === "past_due" ? "past_due" : sub === "canceled" ? "canceled" : null);
    };
    window.addEventListener("reliq-auth-change", refresh);
    return () => window.removeEventListener("reliq-auth-change", refresh);
  }, []);

  if (!state) return null;

  const isPastDue  = state === "past_due";
  const bg         = isPastDue ? "var(--amber-bg)"          : "var(--red-bg)";
  const border     = isPastDue ? "rgba(245,158,11,0.3)"     : "rgba(239,68,68,0.3)";
  const iconColor  = isPastDue ? "var(--amber)"             : "var(--red)";
  const label      = isPastDue ? "Payment past due."        : "Subscription canceled.";
  const detail     = isPastDue
    ? "Update your payment method to avoid losing access."
    : "Your subscription has ended. Renew to restore full access to your scans and reports.";
  const btnLabel   = isPastDue ? "Update payment" : "Renew subscription";
  const btnStyle   = isPastDue
    ? { background:"var(--amber)", color:"#1a1206", border:0 }
    : {};

  return (
    <div role="alert" style={{
      background:bg, borderBottom:`1px solid ${border}`,
      padding:"10px 24px", display:"flex", alignItems:"center", gap:12,
      fontSize:13, color:"var(--fg-0)", flexShrink:0,
    }}>
      <Icon name="alert" size={15} style={{ color:iconColor, flexShrink:0 }} />
      <span style={{ flex:1, lineHeight:1.5 }}>
        <strong>{label}</strong> {detail}
      </span>
      <button
        className={`btn btn-sm${isPastDue ? "" : " btn-primary"}`}
        style={btnStyle}
        onClick={() => go("/billing")}
      >
        {btnLabel}
      </button>
    </div>
  );
};

// ── AuthLayout ────────────────────────────────────────────────────────────────
// Standard layout for all authenticated app pages: Sidebar + TopBar + content.
// On phones (≤480px) the sidebar is hidden and a hamburger button in the TopBar
// opens a MobileDrawer that slides in from the left.
//
// Usage:
//   <AuthLayout route="scans" go={go} crumbs={["Workspace","All Scans"]}
//               actions={<button>New Scan</button>} maxWidth={1180}>
//     {/* page content — no surrounding <div className="content"> needed */}
//   </AuthLayout>
const AuthLayout = ({ route, go, crumbs = [], actions = null, maxWidth = null, children }) => {
  const [mobOpen, setMobOpen] = React.useState(false);
  const closeMob = React.useCallback(() => setMobOpen(false), []);

  return (
    <div className="app-shell">
      <Sidebar route={route} go={go} />
      <div className="app-main">
        <AccountStatusBanner go={go} />
        <TopBar
          crumbs={crumbs}
          actions={actions}
          onHamburger={() => setMobOpen(o => !o)}
          mobOpen={mobOpen}
        />
        <div className="content" style={maxWidth ? { maxWidth } : undefined}>
          {children}
        </div>
      </div>
      {/* Mobile drawer — only interactive at ≤480px, harmless on wider screens */}
      <MobileDrawer route={route} go={go} open={mobOpen} onClose={closeMob} />
    </div>
  );
};

Object.assign(window, { Sidebar, TopBar, ThemeToggle, AuthLayout, AccountStatusBanner, MobileDrawer });
