// ============================================
// Reliq — Global Search / Command Palette
// Anchored dropdown below the top search bar
// ============================================

// ── Platform detection ────────────────────────────────────────────────────────
const detectMac = () => {
  try {
    const p = (navigator.userAgentData?.platform || navigator.platform || "").toLowerCase();
    return p.startsWith("mac") || p === "iphone" || p === "ipad";
  } catch (_) { return false; }
};
const IS_MAC = detectMac();
const KBD_LABEL = IS_MAC ? "⌘K" : "Ctrl K";

// ── Search index ──────────────────────────────────────────────────────────────
//
// access field — controls which product states can see each item:
//   "public"        — no auth required (landing, docs, free-scan, github-action)
//   "billing"       — any authed user, including pending_payment and past_due
//                     (billing, settings — always accessible)
//   "authenticated" — authenticated users except pending_payment
//                     (existing workspace data — scans, reports, apps, policy, dashboard)
//   "subscription"  — trialing or active subscription only
//                     (new scan, API, team, gated workspace actions)
//
// Use gsGetUserTier() + gsFilterByTier() to filter before searching.

const GS_ACTIONS = [
  { type:"action", id:"act_free_scan",     title:"Free Scan Preview",   subtitle:"Try Reliq without an account — no signup needed",   icon:"scan",       path:"/free-scan",    access:"public",        keywords:"free scan preview demo try no account no signup visitor" },
  { type:"action", id:"act_new_scan",      title:"New Scan",            subtitle:"Upload and scan a new build",                       icon:"scan",       path:"/scan/new",     access:"subscription" },
  { type:"action", id:"act_dashboard",     title:"Dashboard",           subtitle:"Go to workspace overview",                          icon:"home",       path:"/dashboard",    access:"authenticated" },
  { type:"action", id:"act_docs",                   title:"Documentation",               subtitle:"Docs — scan, results, reports, CI/CD, API",           icon:"book",       path:"/docs",          access:"public",  keywords:"docs documentation guide help quick start uploading builds scan results findings fix plan permissions sdk privacy reports pdf export share links github action api keys webhooks team roles billing troubleshooting" },
  { type:"action", id:"act_github_action",          title:"GitHub Action",               subtitle:"Automate scans in your CI pipeline",                  icon:"git-branch", path:"/github-action", access:"public",  keywords:"github action ci cd pipeline reliq scan yaml workflow cicd automated install setup reliq_key secret" },
  { type:"action", id:"act_docs_ci",                title:"GitHub Action docs",          subtitle:"Docs · CI/CD · reliq/scan@v1",                        icon:"git-branch", path:"/docs",          access:"public",  scrollAnchor:"docs-github-action",   keywords:"github action ci cd pipeline reliq scan yaml workflow cicd automated reliq_key secret" },
  { type:"action", id:"act_docs_webhooks",          title:"Webhook docs",                subtitle:"Docs · scan.completed · signature verification",       icon:"webhook",    path:"/docs",          access:"public",  scrollAnchor:"docs-webhooks",        keywords:"webhooks webhook events scan completed report generated signature hmac x-reliq-signature" },
  { type:"action", id:"act_docs_api",               title:"API docs",                    subtitle:"Docs · API keys · programmatic scans",                icon:"code",       path:"/docs",          access:"public",  scrollAnchor:"docs-api-keys",        keywords:"api keys programmatic rest endpoint curl scan trigger rlq_ bearer authorization" },
  { type:"action", id:"act_docs_reports",           title:"Reports & Share Links docs",  subtitle:"Docs · PDF export · share links · white-label",        icon:"file-text",  path:"/docs",          access:"public",  scrollAnchor:"docs-reports",         keywords:"reports pdf export share links white-label rep_ report guide read-only" },
  { type:"action", id:"act_docs_billing",           title:"Billing & Trials docs",       subtitle:"Docs · plans · billing · trial · scan limits",         icon:"bolt",       path:"/docs",          access:"public",  scrollAnchor:"docs-billing",         keywords:"billing trial plans starter pro team scan limits usage invoice subscription renewal cancel" },
  { type:"action", id:"act_docs_troubleshooting",   title:"Troubleshooting",             subtitle:"Docs · scan errors · common issues · support",         icon:"info",       path:"/docs",          access:"public",  scrollAnchor:"docs-troubleshooting", keywords:"troubleshooting scan failed error file type too large missing metadata webhook github action score low" },
  { type:"action", id:"act_apps",          title:"Apps",                subtitle:"Browse all connected apps",                        icon:"smartphone", path:"/apps",         access:"authenticated" },
  { type:"action", id:"act_scans",         title:"All Scans",           subtitle:"View full scan history",                           icon:"folder",     path:"/scans",        access:"authenticated" },
  { type:"action", id:"act_reports",       title:"Reports",             subtitle:"View and share compliance reports",                 icon:"file-text",  path:"/reports",      access:"authenticated" },
  { type:"action", id:"act_policy",        title:"Policy Library",      subtitle:"Browse store review policies",                     icon:"book",       path:"/policy",       access:"authenticated" },
  { type:"action", id:"act_api",           title:"API & Webhooks",      subtitle:"Keys and CI/CD integration",                       icon:"code",       path:"/api",          access:"subscription" },
  { type:"action", id:"act_team",          title:"Team",                subtitle:"Manage seats and roles",                           icon:"users",      path:"/team",         access:"subscription" },
  { type:"action", id:"act_team_sso",      title:"SSO & Security",      subtitle:"Configure SAML SSO for your workspace",            icon:"lock",       path:"/team",         access:"subscription", keywords:"sso saml single sign on okta azure google workspace security idp enforce domain" },
  { type:"action", id:"act_team_audit",    title:"Audit Log",           subtitle:"Workspace activity and member actions",            icon:"activity",   path:"/team",         access:"subscription", keywords:"audit log activity team members actions history events workspace" },
  { type:"action", id:"act_billing",       title:"Billing",             subtitle:"Plan, usage, and invoices",                        icon:"bolt",       path:"/billing",      access:"billing" },
  { type:"action", id:"act_settings",                  title:"Settings",                    subtitle:"Profile, workspace, notifications, security",       icon:"settings",   path:"/settings",     access:"billing",        keywords:"settings profile workspace notifications scan defaults security danger zone" },
  { type:"action", id:"act_settings_profile",          title:"Profile settings",            subtitle:"Settings · name, email, timezone, role",            icon:"users",      path:"/settings",     access:"billing",        focusToken:{ type:"settings-tab", tab:"profile" },       keywords:"profile name email avatar timezone role personal details account" },
  { type:"action", id:"act_settings_notifications",    title:"Notification preferences",    subtitle:"Settings · scan alerts, billing, security, digest",  icon:"bell",       path:"/settings",     access:"billing",        focusToken:{ type:"settings-tab", tab:"notifications" }, keywords:"notifications email digest scan completed high risk billing alerts security alerts product updates in-app" },
  { type:"action", id:"act_settings_security",         title:"Security settings",           subtitle:"Settings · password, 2FA, sessions, activity",      icon:"lock",       path:"/settings",     access:"billing",        focusToken:{ type:"settings-tab", tab:"security" },      keywords:"security password 2fa two factor authentication sessions revoke events activity github connected" },
  { type:"action", id:"act_settings_danger",           title:"Danger zone",                 subtitle:"Settings · delete account, workspace, clear data",   icon:"alert",      path:"/settings",     access:"billing",        focusToken:{ type:"settings-tab", tab:"danger" },        keywords:"danger zone delete account workspace clear data export reset history" },
];

const GS_APPS = [
  { type:"app", id:"app_roamly",       title:"Roamly",       subtitle:"com.roamly.app",           letters:"R", grad:"linear-gradient(135deg,#5B9DFF,#A78BFA)", verdict:"needs_fixes", score:78, path:"/scan/scn_2k8x", platforms:["play","app"], primaryRisk:"Privacy",         source:"github.com/zolalena/roamly",      access:"authenticated", keywords:"travel google play app store github needs fixes" },
  { type:"app", id:"app_quickinvoice", title:"QuickInvoice", subtitle:"com.quickinvoice.android", letters:"Q", grad:"linear-gradient(135deg,#F59E0B,#EF4444)", verdict:"will_reject", score:41, path:"/scan/scn_2k8w", platforms:["play"],        primaryRisk:"Security",        source:"github.com/zolalena/quickinvoice", access:"authenticated", keywords:"finance invoicing google play will reject github permissions security" },
  { type:"app", id:"app_moodboard",    title:"MoodBoard AI", subtitle:"com.moodboard.ai",         letters:"M", grad:"linear-gradient(135deg,#A78BFA,#EF4444)", verdict:"needs_fixes", score:64, path:"/scan/scn_2k8v", platforms:["play","app"], primaryRisk:"Permissions",      source:"github.com/zolalena/moodboard",   access:"authenticated", keywords:"social ai google play app store needs fixes github privacy manifest permissions" },
  { type:"app", id:"app_tidalflow",    title:"TidalFlow",    subtitle:"com.tidalflow.app",        letters:"T", grad:"linear-gradient(135deg,#00D4AA,#5B9DFF)", verdict:"pass",        score:88, path:"/scan/scn_2k8u", platforms:["app"],         primaryRisk:"None",            source:null,                              access:"authenticated", keywords:"health fitness app store pass" },
  { type:"app", id:"app_pocketcoach",  title:"Pocket Coach", subtitle:"com.pocket.coach",         letters:"P", grad:"linear-gradient(135deg,#5B9DFF,#00D4AA)", verdict:"likely_pass", score:76, path:"/scan/scn_2k8t", platforms:["play","app"], primaryRisk:"Privacy Manifest", source:null,                              access:"authenticated", keywords:"health fitness google play app store likely pass privacy manifest" },
  { type:"app", id:"app_brewline",     title:"Brewline",     subtitle:"com.brewline.app",         letters:"B", grad:"linear-gradient(135deg,#F59E0B,#A78BFA)", verdict:"pass",        score:95, path:"/scan/scn_2k8s", platforms:["play"],        primaryRisk:"None",            source:"bitrise.io/app/brewline",         access:"authenticated", keywords:"lifestyle brewing google play pass bitrise" },
];

const GS_SCANS = [
  { type:"scan", id:"scn_2k8x", title:"Roamly v1.4.0 · build 142",       subtitle:"scn_2k8x · 2026-05-22 · GitHub",  verdict:"needs_fixes", score:78, crit:1, warn:4, path:"/scan/scn_2k8x", access:"authenticated" },
  { type:"scan", id:"scn_2k8r", title:"Roamly v1.4.0 · build 141",       subtitle:"scn_2k8r · 2026-05-22 · CI/CD",   verdict:"needs_fixes", score:74, crit:1, warn:6, path:"/scan/scn_2k8r", access:"authenticated" },
  { type:"scan", id:"scn_2k8w", title:"QuickInvoice v2.1.3 · build 87",  subtitle:"scn_2k8w · 2026-05-22 · Manual",  verdict:"will_reject", score:41, crit:3, warn:8, path:"/scan/scn_2k8w", access:"authenticated" },
  { type:"scan", id:"scn_2k8v", title:"MoodBoard AI v0.9.2 · build 28",  subtitle:"scn_2k8v · 2026-05-22 · GitHub",  verdict:"needs_fixes", score:64, crit:1, warn:5, path:"/scan/scn_2k8v", access:"authenticated" },
  { type:"scan", id:"scn_2k8u", title:"TidalFlow v3.0.1 · build 201",    subtitle:"scn_2k8u · 2026-05-21 · API",     verdict:"pass",        score:88, crit:0, warn:2, path:"/scan/scn_2k8u", access:"authenticated" },
  { type:"scan", id:"scn_2k8t", title:"Pocket Coach v1.0.0 · build 12",  subtitle:"scn_2k8t · 2026-05-21 · Manual",  verdict:"likely_pass", score:76, crit:0, warn:3, path:"/scan/scn_2k8t", access:"authenticated" },
  { type:"scan", id:"scn_2k8s", title:"Brewline v4.2.1 · build 312",     subtitle:"scn_2k8s · 2026-05-20 · CI/CD",   verdict:"pass",        score:95, crit:0, warn:0, path:"/scan/scn_2k8s", access:"authenticated" },
  { type:"scan", id:"scn_2k8q", title:"QuickInvoice v2.0.8 · build 80",  subtitle:"scn_2k8q · 2026-05-18 · Manual",  verdict:"needs_fixes", score:68, crit:1, warn:4, path:"/scan/scn_2k8q", access:"authenticated" },
  { type:"scan", id:"scn_2k8p", title:"MoodBoard AI v0.9.1 · build 25",  subtitle:"scn_2k8p · 2026-05-17 · GitHub",  verdict:"will_reject", score:58, crit:2, warn:7, path:"/scan/scn_2k8p", access:"authenticated" },
  { type:"scan", id:"scn_2k8o", title:"TidalFlow v2.9.8 · build 196",    subtitle:"scn_2k8o · 2026-05-15 · API",     verdict:"likely_pass", score:81, crit:0, warn:3, path:"/scan/scn_2k8o", access:"authenticated" },
];

const GS_REPORTS = [
  { type:"report", id:"rep_2k8x", title:"Roamly · v1.4.0 build 142",      subtitle:"rep_2k8x · Private",  verdict:"needs_fixes", visibility:"private",  shareUrl:"reliq.dev/r/rep_2k8x", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8r", title:"Roamly · v1.4.0 build 141",      subtitle:"rep_2k8r · Private",  verdict:"needs_fixes", visibility:"private",  shareUrl:"reliq.dev/r/rep_2k8r", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8w", title:"QuickInvoice · v2.1.3 build 87", subtitle:"rep_2k8w · Shared",   verdict:"will_reject", visibility:"shared",   shareUrl:"reliq.dev/r/rep_2k8w", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8v", title:"MoodBoard AI · v0.9.2 build 28", subtitle:"rep_2k8v · Shared",   verdict:"needs_fixes", visibility:"shared",   shareUrl:"reliq.dev/r/rep_2k8v", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8u", title:"TidalFlow · v3.0.1 build 201",   subtitle:"rep_2k8u · Expired",  verdict:"pass",        visibility:"expired",  shareUrl:"reliq.dev/r/rep_2k8u", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8t", title:"Pocket Coach · v1.0.0 build 12", subtitle:"rep_2k8t · Private",  verdict:"likely_pass", visibility:"private",  shareUrl:"reliq.dev/r/rep_2k8t", path:"/reports", access:"authenticated" },
  { type:"report", id:"rep_2k8s", title:"Brewline · v4.2.1 build 312",    subtitle:"rep_2k8s · Private",  verdict:"pass",        visibility:"private",  shareUrl:"reliq.dev/r/rep_2k8s", path:"/reports", access:"authenticated" },
];

const GS_POLICIES = [
  { type:"policy", id:"pol_ds",      title:"Data Safety Completeness",  subtitle:"Google Play · Critical", severity:"critical", platform:["play"],       path:"/policy", policyCode:"DATA_SAFETY_COMPLETENESS", policyCategory:"play",        access:"authenticated", keywords:"data safety section android completeness privacy" },
  { type:"policy", id:"pol_sdk",     title:"Target SDK Requirement",    subtitle:"Google Play · Critical", severity:"critical", platform:["play"],       path:"/policy", policyCode:"TARGET_SDK_REQUIREMENT",  policyCategory:"play",        access:"authenticated", keywords:"target sdk api level android outdated version sdk inventory" },
  { type:"policy", id:"pol_pm",      title:"Privacy Manifest Required", subtitle:"App Store · Critical",   severity:"critical", platform:["app"],        path:"/policy", policyCode:"PRIVACY_MANIFEST_REQUIRED",policyCategory:"app",         access:"authenticated", keywords:"privacy manifest apple ios required nsprivacy" },
  { type:"policy", id:"pol_nl",      title:"Nutrition Label Accuracy",  subtitle:"App Store · Warning",    severity:"warning",  platform:["app"],        path:"/policy", policyCode:"NUTRITION_LABEL_ACCURACY", policyCategory:"app",         access:"authenticated", keywords:"nutrition label app privacy questionnaire accuracy" },
  { type:"policy", id:"pol_tracker", title:"Tracker SDK Disclosure",    subtitle:"Both Stores · Warning",  severity:"warning",  platform:["play","app"], path:"/policy", policyCode:"TRACKER_SDK_DISCLOSURE",  policyCategory:"sdks",        access:"authenticated", keywords:"tracker sdk firebase mixpanel disclosure tracking appsflyer adjust facebook" },
  { type:"policy", id:"pol_clear",   title:"Cleartext Traffic Blocked", subtitle:"Google Play · Critical", severity:"critical", platform:["play"],       path:"/policy", policyCode:"CLEARTEXT_TRAFFIC",        policyCategory:"security",    access:"authenticated", keywords:"cleartext http traffic network security config" },
  { type:"policy", id:"pol_secrets", title:"Hardcoded Secrets",         subtitle:"Both Stores · Critical", severity:"critical", platform:["play","app"], path:"/policy", policyCode:"HARDCODED_SECRETS",        policyCategory:"security",    access:"authenticated", keywords:"hardcoded api key secret token credential" },
  { type:"policy", id:"pol_meta",    title:"App Review Metadata",       subtitle:"Both Stores · Warning",  severity:"warning",  platform:["play","app"], path:"/policy", policyCode:"MISLEADING_METADATA",      policyCategory:"play",        access:"authenticated", keywords:"metadata misleading description screenshots review" },
  { type:"policy", id:"pol_perm",    title:"Dangerous Permissions",     subtitle:"Both Stores · Warning",  severity:"warning",  platform:["play","app"], path:"/policy", policyCode:"CONTACTS_POLICY",          policyCategory:"permissions", access:"authenticated", keywords:"permissions dangerous contacts location camera read_contacts perm" },
];

const GS_FINDINGS = [
  { type:"finding", id:"DATA_SAFETY_COMPLETENESS",           title:"DATA_SAFETY_COMPLETENESS",           subtitle:"Google Play · Data Safety · Critical",   severity:"critical", path:"/scan/scn_2k8x", access:"authenticated", keywords:"data safety privacy" },
  { type:"finding", id:"TARGET_SDK_OUTDATED",                title:"TARGET_SDK_OUTDATED",                subtitle:"Google Play · Target SDK · Critical",    severity:"critical", path:"/scan/scn_2k8w", access:"authenticated", keywords:"sdk api level version outdated" },
  { type:"finding", id:"PRIVACY_MANIFEST_MISSING",           title:"PRIVACY_MANIFEST_MISSING",           subtitle:"App Store · Privacy · Critical",          severity:"critical", path:"/scan/scn_2k8v", access:"authenticated", keywords:"privacy manifest nsprivacy apple" },
  { type:"finding", id:"TRACKER_SDK_UNDISCLOSED",            title:"TRACKER_SDK_UNDISCLOSED",            subtitle:"Both · Tracking · Warning",               severity:"warning",  path:"/scan/scn_2k8x", access:"authenticated", keywords:"tracker sdk firebase appsflyer adjust facebook mixpanel" },
  { type:"finding", id:"READ_CONTACTS",                      title:"READ_CONTACTS",                      subtitle:"Google Play · Permissions · Warning",     severity:"warning",  path:"/scan/scn_2k8w", access:"authenticated", keywords:"contacts permission perm read dangerous" },
  { type:"finding", id:"CLEARTEXT_TRAFFIC",                  title:"CLEARTEXT_TRAFFIC",                  subtitle:"Google Play · Security · Critical",       severity:"critical", path:"/scan/scn_2k8v", access:"authenticated", keywords:"cleartext http traffic insecure" },
  { type:"finding", id:"HARDCODED_SECRET",                   title:"HARDCODED_SECRET",                   subtitle:"Both · Security · Critical",              severity:"critical", path:"/scan/scn_2k8v", access:"authenticated", keywords:"hardcoded api key secret token credential" },
  { type:"finding", id:"ADVERTISING_ID_WITHOUT_DECLARATION", title:"ADVERTISING_ID_WITHOUT_DECLARATION", subtitle:"Google Play · Tracking · Warning",        severity:"warning",  path:"/scan/scn_2k8x", access:"authenticated", keywords:"advertising id idfa gaid declaration tracking" },
  { type:"finding", id:"SCREENSHOT_WRONG_SIZE",              title:"SCREENSHOT_WRONG_SIZE",              subtitle:"App Store · Listing · Info",              severity:"info",     path:"/scan/scn_2k8t", access:"authenticated", keywords:"screenshot size listing metadata" },
  { type:"finding", id:"METADATA_MISLEADING",                title:"METADATA_MISLEADING",                subtitle:"Both · Metadata · Warning",               severity:"warning",  path:"/scan/scn_2k8w", access:"authenticated", keywords:"metadata description misleading listing review" },
  { type:"finding", id:"LOCATION_ALWAYS_PERMISSION",         title:"LOCATION_ALWAYS_PERMISSION",         subtitle:"Both · Permissions · Warning",            severity:"warning",  path:"/scan/scn_2k8v", access:"authenticated", keywords:"location permission gps always perm" },
  { type:"finding", id:"NUTRITION_LABEL_MISMATCH",           title:"NUTRITION_LABEL_MISMATCH",           subtitle:"App Store · Privacy · Warning",           severity:"warning",  path:"/scan/scn_2k8x", access:"authenticated", keywords:"nutrition label privacy mismatch apple" },
  { type:"finding", id:"FACEBOOK_SDK_UNDISCLOSED",           title:"FACEBOOK_SDK_UNDISCLOSED",           subtitle:"Both · SDK · Warning",                   severity:"warning",  path:"/scan/scn_2k8v", access:"authenticated", keywords:"facebook sdk inventory disclosure tracker" },
  { type:"finding", id:"APPSFLYER_SDK_UNDISCLOSED",          title:"APPSFLYER_SDK_UNDISCLOSED",          subtitle:"Both · SDK · Warning",                   severity:"warning",  path:"/scan/scn_2k8x", access:"authenticated", keywords:"appsflyer sdk inventory disclosure tracker" },
];

// ── Access tier helpers ───────────────────────────────────────────────────────
//
// Numeric tiers — higher = more access:
//   0  visitor / visitor_preview  → public only
//   1  authenticated_pending_payment → billing + settings only
//   2  authenticated_past_due / authenticated_canceled → existing data + billing
//   3  authenticated_trialing / authenticated_active → full index
//
// Item access → minimum tier required:
//   "public"        → 0   "billing"       → 1 (billing, settings)
//   "authenticated" → 2   (existing workspace data)
//   "subscription"  → 3   (new scan, API, team)

const GS_ACCESS_TIER = { public:0, billing:1, authenticated:2, subscription:3 };

const gsGetUserTier = () => {
  const isAuthed = window.reliqAuth?.isAuthed?.() || false;
  if (!isAuthed) return 0; // visitor / visitor_preview
  const state = window.reliqSubscription?.getProductState?.() || "";
  if (state === "authenticated_pending_payment") return 1;
  if (state === "authenticated_past_due" || state === "authenticated_canceled") return 2;
  return 3; // trialing / active
};

const gsFilterByTier = (items, tier) =>
  items.filter(item => (GS_ACCESS_TIER[item.access || "subscription"] || 3) <= tier);

// ── Recent search helpers ─────────────────────────────────────────────────────
const GS_RECENT_KEY = (window.RELIQ_KEYS?.RECENT_SEARCH) || "reliq_recent_search";
const GS_RECENT_MAX = 5;

const gsReadRecent = () => {
  try { return JSON.parse(localStorage.getItem(GS_RECENT_KEY) || "[]"); }
  catch (_) { return []; }
};

const gsWriteRecent = (item) => {
  try {
    const slim = {
      id: item.id, type: item.type, title: item.title, subtitle: item.subtitle,
      icon: item.icon, path: item.path, letters: item.letters, grad: item.grad,
      verdict: item.verdict, severity: item.severity, access: item.access || "subscription",
      scrollAnchor: item.scrollAnchor || null,  // preserve scroll target for doc items
    };
    const prev = gsReadRecent();
    const next = [slim, ...prev.filter(r => r.id !== item.id)].slice(0, GS_RECENT_MAX);
    localStorage.setItem(GS_RECENT_KEY, JSON.stringify(next));
  } catch (_) {}
};

// ── Scoring ───────────────────────────────────────────────────────────────────
const GS_GROUP_ORDER  = ["action","app","scan","report","policy","finding"];
const GS_GROUP_LABELS = { action:"Pages", app:"Apps", scan:"Scans", report:"Reports", policy:"Policies", finding:"Findings" };
const GS_ALL = [...GS_ACTIONS, ...GS_APPS, ...GS_SCANS, ...GS_REPORTS, ...GS_POLICIES, ...GS_FINDINGS];

const GS_TYPE_SOFT_CAPS = { action:2, app:3, scan:3, report:2, policy:3, finding:3 };

const gsScore = (item, ql) => {
  const t  = item.title.toLowerCase();
  const s  = (item.subtitle  || "").toLowerCase();
  const id = (item.id        || "").toLowerCase();
  const kw = (item.keywords  || "").toLowerCase();
  const vd = (item.verdict   || "").replace(/_/g," ").toLowerCase();
  const sr = (item.source    || "").toLowerCase();
  const su = (item.shareUrl  || "").toLowerCase();

  if (id === ql || t === ql)               return 100;
  if (id.startsWith(ql))                   return 90;
  if (t.startsWith(ql))                    return 85;
  if (id.includes(ql))                     return 75;
  if (t.includes(ql))                      return 65;
  if (s.includes(ql))                      return 45;
  if (kw.includes(ql))                     return 35;
  if (vd.includes(ql) || sr.includes(ql) || su.includes(ql)) return 25;
  return 0;
};

const gsSevBonus  = (sev)  => ({ critical:5, warning:2, info:0 }[sev] || 0);
const gsTypePrio  = (type) => ({ action:6, app:5, scan:4, report:3, policy:2, finding:1 }[type] || 0);

const gsSearchFlat = (query) => {
  const ql = query.trim().toLowerCase();
  if (!ql) return [];

  // Filter the index to only items the current user can access
  const tier  = gsGetUserTier();
  const index = gsFilterByTier(GS_ALL, tier);

  const scored = index
    .map(item => {
      const sc = gsScore(item, ql) + gsSevBonus(item.severity);
      return sc > 0 ? { item, sc } : null;
    })
    .filter(Boolean)
    .sort((a, b) => b.sc - a.sc || gsTypePrio(b.item.type) - gsTypePrio(a.item.type));

  // Per-type soft caps, total 8
  const tCounts = {};
  const out = [];
  for (const { item } of scored) {
    if (out.length >= 8) break;
    const cap = GS_TYPE_SOFT_CAPS[item.type] || 2;
    tCounts[item.type] = (tCounts[item.type] || 0) + 1;
    if (tCounts[item.type] <= cap) out.push(item);
  }
  return out;
};

const gsGroup = (items) => {
  const map = {};
  GS_GROUP_ORDER.forEach(k => { map[k] = []; });
  items.forEach(item => { if (map[item.type]) map[item.type].push(item); });
  return GS_GROUP_ORDER.filter(k => map[k].length > 0).map(k => ({ type:k, items:map[k] }));
};

// ── Micro-components ──────────────────────────────────────────────────────────
const GsVerdictBadge = ({ verdict }) => {
  const M = {
    pass:        { label:"PASS",    color:"var(--teal)",  bg:"var(--teal-bg)" },
    likely_pass: { label:"LIKELY",  color:"var(--blue)",  bg:"var(--blue-bg)" },
    needs_fixes: { label:"FIXES",   color:"var(--amber)", bg:"var(--amber-bg)" },
    will_reject: { label:"REJECT",  color:"var(--red)",   bg:"var(--red-bg)" },
  }[verdict];
  if (!M) return null;
  return (
    <span style={{ fontFamily:"var(--font-mono)", fontSize:9, fontWeight:600, letterSpacing:"0.06em",
      padding:"2px 5px", borderRadius:3, whiteSpace:"nowrap", flexShrink:0,
      color:M.color, background:M.bg, border:`1px solid ${M.color}33` }}>
      {M.label}
    </span>
  );
};

const GsSevBadge = ({ severity }) => {
  const M = {
    critical: { label:"CRIT", color:"var(--red)",   bg:"var(--red-bg)" },
    warning:  { label:"WARN", color:"var(--amber)", bg:"var(--amber-bg)" },
    info:     { label:"INFO", color:"var(--blue)",  bg:"var(--blue-bg)" },
  }[severity];
  if (!M) return null;
  return (
    <span style={{ fontFamily:"var(--font-mono)", fontSize:9, fontWeight:600, letterSpacing:"0.06em",
      padding:"2px 5px", borderRadius:3, whiteSpace:"nowrap", flexShrink:0,
      color:M.color, background:M.bg, border:`1px solid ${M.color}33` }}>
      {M.label}
    </span>
  );
};

const GsItemIcon = ({ item }) => {
  if (item.type === "app" && item.letters) {
    return (
      <div style={{ width:26, height:26, borderRadius:6, flexShrink:0,
        background:item.grad, color:"#fff",
        fontFamily:"var(--font-display)", fontSize:11, fontWeight:700,
        display:"flex", alignItems:"center", justifyContent:"center" }}>
        {item.letters}
      </div>
    );
  }
  const ICONS = { action:"bolt", scan:"scan", report:"file-text", policy:"book", finding:"alert" };
  return (
    <div style={{ width:26, height:26, borderRadius:6, flexShrink:0,
      background:"var(--bg-3)", color:"var(--fg-2)",
      display:"flex", alignItems:"center", justifyContent:"center" }}>
      <Icon name={item.icon || ICONS[item.type] || "search"} size={13} />
    </div>
  );
};

const GsRow = ({ item, isActive, idx, onClick }) => (
  <div
    data-gs-idx={idx}
    onClick={onClick}
    style={{ display:"flex", alignItems:"center", gap:9,
      padding:"6px 10px", borderRadius:6, cursor:"pointer",
      background: isActive ? "var(--bg-3)" : "transparent" }}
    onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = "var(--bg-3)"; }}
    onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
  >
    <GsItemIcon item={item} />
    <div style={{ flex:1, minWidth:0 }}>
      <div style={{ fontSize:13, fontWeight:500, color:"var(--fg-0)",
        overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>
        {item.title}
      </div>
      {item.subtitle && (
        <div style={{ fontSize:10.5, color:"var(--fg-3)", fontFamily:"var(--font-mono)",
          overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", marginTop:1 }}>
          {item.subtitle}
        </div>
      )}
    </div>
    <div style={{ display:"flex", gap:4, alignItems:"center", flexShrink:0 }}>
      {item.verdict   && <GsVerdictBadge verdict={item.verdict} />}
      {item.severity  && !item.verdict && <GsSevBadge severity={item.severity} />}
      {isActive && (
        <span style={{ display:"flex", alignItems:"center", justifyContent:"center",
          width:17, height:17, borderRadius:3,
          background:"var(--bg-4)", border:"1px solid var(--line-2)",
          color:"var(--fg-3)", fontSize:10, flexShrink:0 }}>↵</span>
      )}
    </div>
  </div>
);

// ── Position helper ───────────────────────────────────────────────────────────
const calcDropPos = (rect) => {
  const MIN_W = 440;
  const w     = Math.max(rect.width, MIN_W);
  // keep inside viewport
  const left  = Math.min(rect.left, window.innerWidth - w - 12);
  return { top: rect.bottom + 5, left: Math.max(8, left), width: w };
};

// ── Main component ────────────────────────────────────────────────────────────
const GlobalSearch = ({ go }) => {
  const [open,      setOpen]      = React.useState(false);
  const [query,     setQuery]     = React.useState("");
  const [activeIdx, setActiveIdx] = React.useState(0);
  const [dropPos,   setDropPos]   = React.useState({ top:60, left:200, width:460 });

  const inputRef = React.useRef(null);
  const dropRef  = React.useRef(null);

  const closeModal = React.useCallback(() => { setOpen(false); setQuery(""); }, []);

  const openModal = React.useCallback((rect) => {
    if (rect) setDropPos(calcDropPos(rect));
    setQuery("");
    setActiveIdx(0);
    setOpen(true);
  }, []);

  // ── Custom event from searchbar click ──
  React.useEffect(() => {
    const h = (e) => openModal(e.detail?.rect);
    window.addEventListener("reliq:open-search", h);
    return () => window.removeEventListener("reliq:open-search", h);
  }, [openModal]);

  // ── Re-anchor on window resize ──
  React.useEffect(() => {
    if (!open) return;
    const h = () => {
      const el = document.querySelector(".searchbar");
      if (el) setDropPos(calcDropPos(el.getBoundingClientRect()));
    };
    window.addEventListener("resize", h);
    return () => window.removeEventListener("resize", h);
  }, [open]);

  // ── Global keyboard shortcut ──
  React.useEffect(() => {
    const h = (e) => {
      const tag      = (document.activeElement?.tagName || "").toLowerCase();
      const editable = document.activeElement?.isContentEditable;
      const isOurs   = inputRef.current && document.activeElement === inputRef.current;

      const isShortcut = IS_MAC
        ? (e.metaKey && !e.ctrlKey && e.key === "k")
        : (e.ctrlKey && !e.metaKey && e.key === "k");

      // Block shortcut when typing in a foreign field
      if (!open && !isOurs && (tag === "input" || tag === "textarea" || tag === "select" || editable)) return;

      if (isShortcut) {
        e.preventDefault();
        if (open) {
          closeModal();
        } else {
          const el = document.querySelector(".searchbar");
          openModal(el ? el.getBoundingClientRect() : null);
        }
        return;
      }

      if (!open) return;
      if (e.key === "Escape")    { e.preventDefault(); closeModal(); }
      else if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx(i => i + 1); }
      else if (e.key === "ArrowUp")   { e.preventDefault(); setActiveIdx(i => Math.max(0, i - 1)); }
      else if (e.key === "Enter") {
        e.preventDefault();
        dropRef.current?.querySelector(`[data-gs-idx="${activeIdx}"]`)?.click();
      }
    };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [open, activeIdx, openModal, closeModal]);

  // ── Auto-focus input ──
  React.useEffect(() => {
    if (open) setTimeout(() => inputRef.current?.focus(), 20);
  }, [open]);

  // ── Click-outside to close (no overlay needed) ──
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => {
      if (dropRef.current?.contains(e.target)) return;
      // Don't close if clicking the searchbar trigger itself
      if (document.querySelector(".searchbar")?.contains(e.target)) return;
      closeModal();
    };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, [open, closeModal]);

  // ── Reset active on query change ──
  React.useEffect(() => { setActiveIdx(0); }, [query]);

  // ── Recent searches (shown in empty state) ──
  const recentItems = React.useMemo(() => {
    const tier = gsGetUserTier();
    return gsFilterByTier(gsReadRecent(), tier);
  }, [open]); // recompute when search opens (tier may have changed)

  // ── Build result sets ──
  const flatResults  = gsSearchFlat(query);
  const groups       = gsGroup(flatResults);
  const flatOrdered  = groups.flatMap(g => g.items);
  const flatLen      = flatOrdered.length;

  // Clamp active idx
  React.useEffect(() => {
    if (flatLen > 0) setActiveIdx(i => Math.min(i, flatLen - 1));
  }, [flatLen]);

  // Scroll active into view
  React.useEffect(() => {
    dropRef.current?.querySelector(`[data-gs-idx="${activeIdx}"]`)
      ?.scrollIntoView({ block:"nearest" });
  }, [activeIdx]);

  const selectItem = (item) => {
    closeModal();
    gsWriteRecent(item); // persist to recent searches
    // Doc items with a scrollAnchor: tell the destination page which section to scroll to.
    // DocsPage reads reliq_scroll_to from sessionStorage on mount and removes it.
    if (item.scrollAnchor) {
      try { sessionStorage.setItem("reliq_scroll_to", item.scrollAnchor); } catch (_) {}
    }
    // Drop a pending-focus token so the destination page can scroll to / open the exact item
    if (item.type === "report") {
      window.__reliq_pending_focus = { type: "report", id: item.id };
    } else if (item.type === "policy" && item.policyCode) {
      window.__reliq_pending_focus = { type: "policy", code: item.policyCode, category: item.policyCategory };
    } else if (item.id === "act_team_sso") {
      window.__reliq_pending_focus = { type: "team-tab", tab: "sso" };
    } else if (item.id === "act_team_audit") {
      window.__reliq_pending_focus = { type: "team-tab", tab: "audit" };
    } else if (item.focusToken) {
      // Generic focusToken — covers settings tabs and any future destination tokens
      window.__reliq_pending_focus = item.focusToken;
    }
    if (item.path) go(item.path);
  };

  if (!open) return null;

  // ── Render body ──
  const renderBody = () => {
    if (!query.trim()) {
      // Show recent searches when available; fall back to hint text
      if (recentItems.length > 0) {
        return (
          <React.Fragment>
            <div style={{ padding:"5px 10px 2px", fontSize:9.5, fontFamily:"var(--font-mono)",
              fontWeight:600, letterSpacing:"0.14em", textTransform:"uppercase", color:"var(--fg-3)" }}>
              Recent
            </div>
            {recentItems.map(item => (
              // idx={-1} keeps the ↵ indicator hidden; items are still clickable
              <GsRow key={item.id} item={item} idx={-1} isActive={false}
                onClick={() => selectItem(item)} />
            ))}
          </React.Fragment>
        );
      }
      return (
        <div style={{ display:"flex", alignItems:"center", gap:8,
          padding:"12px 12px", color:"var(--fg-3)", fontSize:12,
          fontFamily:"var(--font-mono)" }}>
          <Icon name="search" size={12} style={{ flexShrink:0 }} />
          <span>Search apps, scans, reports, findings, and policies.</span>
        </div>
      );
    }

    if (groups.length === 0) {
      return (
        <div style={{ padding:"18px 12px", textAlign:"center" }}>
          <div style={{ fontSize:13, fontWeight:500, color:"var(--fg-2)" }}>
            No results for "{query}"
          </div>
          <div style={{ fontSize:11.5, color:"var(--fg-3)", marginTop:4, lineHeight:1.5 }}>
            Try an app name, scan ID, report ID, policy, or finding code.
          </div>
        </div>
      );
    }

    let gIdx = 0;
    return groups.map(({ type, items }, gi) => (
      <React.Fragment key={type}>
        {gi > 0 && <div style={{ height:1, background:"var(--line-1)", margin:"3px 0" }} />}
        <div style={{ padding:"5px 10px 2px", fontSize:9.5, fontFamily:"var(--font-mono)",
          fontWeight:600, letterSpacing:"0.14em", textTransform:"uppercase", color:"var(--fg-3)" }}>
          {GS_GROUP_LABELS[type]}
        </div>
        {items.map(item => {
          const fi = gIdx++;
          return <GsRow key={item.id} item={item} idx={fi}
            isActive={activeIdx === fi} onClick={() => selectItem(item)} />;
        })}
      </React.Fragment>
    ));
  };

  return (
    <div
      ref={dropRef}
      style={{
        position:"fixed",
        top:    dropPos.top,
        left:   dropPos.left,
        width:  dropPos.width,
        zIndex: 9999,
        background:   "var(--bg-1)",
        border:       "1px solid var(--line-2)",
        borderRadius: 10,
        boxShadow:    "0 8px 32px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.12)",
        overflow:     "hidden",
        display:      "flex",
        flexDirection:"column",
        maxHeight:    420,
      }}
    >
      {/* ── Input row ── */}
      <div style={{ display:"flex", alignItems:"center", gap:8,
        padding:"9px 12px", borderBottom:"1px solid var(--line-1)", flexShrink:0 }}>
        <Icon name="search" size={14} style={{ color:"var(--fg-3)", flexShrink:0 }} />
        <input
          ref={inputRef}
          value={query}
          onChange={e => setQuery(e.target.value)}
          placeholder="Search apps, scans, reports, findings, policies…"
          style={{ flex:1, background:"transparent", border:"none", outline:"none",
            fontSize:13.5, color:"var(--fg-0)", fontFamily:"var(--font-display)",
            caretColor:"var(--blue)" }}
        />
        {query && (
          <button onClick={() => { setQuery(""); setActiveIdx(0); inputRef.current?.focus(); }}
            style={{ background:"none", border:"none", cursor:"pointer", padding:"2px 4px",
              borderRadius:4, color:"var(--fg-3)", display:"flex", alignItems:"center" }}>
            <Icon name="x" size={13} />
          </button>
        )}
      </div>

      {/* ── Results ── */}
      <div style={{ overflowY:"auto", flex:1, padding:"4px 4px 6px" }}>
        {renderBody()}
      </div>

      {/* ── Footer (only when there are typed results) ── */}
      {query.trim() && flatLen > 0 && (
        <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between",
          padding:"5px 12px", borderTop:"1px solid var(--line-1)",
          fontSize:10.5, color:"var(--fg-3)", fontFamily:"var(--font-mono)", flexShrink:0 }}>
          <span>
            {[["↑↓","nav"],["↵","open"],["Esc","close"]].map(([k,l]) => (
              <span key={k} style={{ marginRight:10 }}>
                <kbd style={{ background:"var(--bg-3)", border:"1px solid var(--line-2)",
                  borderRadius:3, padding:"1px 4px", marginRight:3, fontSize:9.5,
                  fontFamily:"var(--font-mono)" }}>{k}</kbd>{l}
              </span>
            ))}
          </span>
          <span>{flatLen} result{flatLen !== 1 ? "s" : ""}</span>
        </div>
      )}
    </div>
  );
};

Object.assign(window, { GlobalSearch, IS_MAC, KBD_LABEL });
