/* Honey Scripts — Top navbar */

const { useState: useStateT, useEffect: useEffectT, useRef: useRefT, useMemo } = React;

function LangPicker({ lang, setLang }) {
  const [open, setOpen] = useStateT(false);
  const ref = useRefT(null);
  useEffectT(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", h);
    return () => document.removeEventListener("mousedown", h);
  }, []);
  const current = LANGUAGES.find(l => l.code === lang);
  return (
    <div ref={ref} style={{ position: "relative" }}>
      <button className="langbtn focus-honey" onClick={() => setOpen(o => !o)}>
        <span style={{ fontSize: 14 }}>{current.flag}</span>
        <span className="lang-name">{current.short}</span>
        <Icon.ChevronDown style={{ color: "var(--muted-2)" }}/>
      </button>
      {open && (
        <div className="popover" style={{ minWidth: 184 }}>
          {LANGUAGES.map(l => (
            <div
              key={l.code}
              className={"popover-item" + (l.code === lang ? " active" : "")}
              onClick={() => { setLang(l.code); setOpen(false); }}
            >
              <span style={{ fontSize: 15 }}>{l.flag}</span>
              <span>{l.name}</span>
              <span className="check"><Icon.Check/></span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ============================================================
   Fast navigation — search helpers
   ============================================================ */

// Accent + case insensitive normaliser so "instalacion" finds "instalación".
function hsNorm(s) {
  return String(s || "")
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");
}

// Strip light markdown so snippets read as plain prose.
function hsStripMD(s) {
  return String(s || "")
    .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
    .replace(/[`*_>#]/g, "")
    .replace(/\s+/g, " ")
    .trim();
}

// Split a query into normalised terms (every term must match → AND search).
function hsTerms(q) {
  return hsNorm(q).split(/\s+/).filter(Boolean);
}

// Map every page id to the script/product it belongs to (from the sidebar)
// plus its short sidebar label, so results can show "Script › Page › Section".
let _hsPages = null;
function hsPageInfo(pid) {
  if (!_hsPages) {
    _hsPages = {};
    (typeof SIDEBAR !== "undefined" ? SIDEBAR : []).forEach(g => (g.items || []).forEach(it => {
      const prod = { id: it.id, label: it.label, glyph: it.glyph };
      if (it.landingPage) _hsPages[it.landingPage] = { prod, pageLabel: it.label, isLanding: true };
      (it.pages || []).forEach(p => { _hsPages[p.id] = { prod, pageLabel: p.label, isLanding: false }; });
    }));
  }
  return _hsPages[pid] || null;
}

// Translate a sidebar label (script / page) to the active language using the
// same i18n keys the sidebar uses. Falls back to the raw label if untranslated.
const HS_LABEL_KEYS = {
  "Welcome": "navWelcome",
  "Honey Radio": "itemHoneyRadio",
  "Honey TextUI 3D": "itemHoneyTextui",
  "Honey Elevators": "itemHoneyElevators",
  "FiveM Asset Escrow": "itemEscrow",
  "Installation": "pageInstallation",
  "FAQ & Troubleshooting": "pageFAQ",
  "API & Usage": "pageApi",
  "Usage & Admin Panel": "pageUsage",
};
function hsTr(t, label) {
  const k = HS_LABEL_KEYS[label];
  if (!k) return label;
  const v = t(k);
  return (v && v !== k) ? v : label;
}

// Build a flat search index for the active language. Indexes page meta,
// every heading, AND the body text (paragraphs, lists, callouts, errors,
// dependencies, code) — each row remembers the nearest heading so we can
// scroll straight to it. Each entry is tagged with its owning script.
function hsBuildIndex(lang) {
  const out = [];
  Object.keys(PAGES.meta).forEach(pid => {
    const m = PAGES.meta[pid][lang] || PAGES.meta[pid].en;
    if (!m) return;
    const pageTitle = m.title || pid;

    // The page itself.
    out.push({
      pid, page: pageTitle, kind: "page",
      title: pageTitle, raw: m.subtitle || "", heading: null, anchor: null,
      hay: hsNorm(pageTitle + " " + (m.subtitle || "")), w: 0,
    });

    let lastHeading = null; // { id, text }
    (getPageBlocks(pid, lang) || []).forEach(b => {
      if (b.type === "h2" || b.type === "h3") {
        const text = hsStripMD(b.text);
        if (b.id) lastHeading = { id: b.id, text };
        out.push({
          pid, page: pageTitle, kind: "section",
          title: text, raw: text, heading: null, anchor: b.id || null,
          hay: hsNorm(pageTitle + " " + text), w: b.type === "h2" ? 10 : 12,
        });
        return;
      }

      // Flatten body blocks into searchable text.
      let body = "";
      let weight = 22;
      if (b.type === "p") body = b.text;
      else if (b.type === "list") body = (b.items || []).join(" • ");
      else if (b.type === "callout") body = (b.title ? b.title + ": " : "") + (b.body || "");
      else if (b.type === "errorCard") body = [b.code, b.cause, (b.solutions || []).join(" ")].filter(Boolean).join(" — ");
      else if (b.type === "deps") body = (b.items || []).map(d => d.name + " " + d.desc).join(" • ");
      else if (b.type === "code") { body = b.code; weight = 26; }
      else return;

      body = hsStripMD(body);
      if (!body) return;
      out.push({
        pid, page: pageTitle, kind: b.type === "code" ? "code" : "text",
        title: lastHeading ? lastHeading.text : pageTitle,
        raw: body, heading: lastHeading ? lastHeading.text : null,
        anchor: lastHeading ? lastHeading.id : null,
        hay: hsNorm(pageTitle + " " + (lastHeading ? lastHeading.text : "") + " " + body),
        w: weight,
      });
    });
  });
  // Tag every entry with the script/product + short page label it belongs to.
  out.forEach(e => {
    const info = hsPageInfo(e.pid);
    e.prodId = info ? info.prod.id : e.pid;
    e.prodLabel = info ? info.prod.label : e.page;
    e.prodGlyph = info ? info.prod.glyph : null;
    e.pageLabel = info ? info.pageLabel : e.page;
    e.isLanding = info ? info.isLanding : false;
  });
  return out;
}

// Does any word in `words` share a long-enough prefix with `term`?
// Lets inflected forms match (e.g. "instalacion" ~ "instalando" share "instala").
function hsSharePrefix(words, term) {
  const min = Math.max(5, Math.ceil(term.length * 0.55));
  for (const w of words) {
    if (w.length < min) continue;
    const m = Math.min(w.length, term.length);
    let k = 0;
    while (k < m && w[k] === term[k]) k++;
    if (k >= min) return true;
  }
  return false;
}

// Rank one entry against the query terms. Returns a score (lower = better)
// or null when the entry doesn't contain every term.
function hsRank(entry, terms, qnorm) {
  let fuzzy = false;
  if (!entry._words) entry._words = entry.hay.split(/[^a-z0-9]+/).filter(Boolean);
  for (const t of terms) {
    if (entry.hay.includes(t)) continue;
    if (hsSharePrefix(entry._words, t)) { fuzzy = true; continue; }
    return null;
  }
  let score = entry.w + (fuzzy ? 6 : 0);
  const primary = hsNorm(entry.title);
  if (primary === qnorm) score -= 14;
  else if (primary.startsWith(qnorm)) score -= 9;
  else if (primary.includes(qnorm)) score -= 4;
  // Word-boundary bonus on the first term.
  if (new RegExp("(^|[^a-z0-9])" + terms[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).test(entry.hay)) score -= 2;
  return score;
}

// Highlight matching words inside a string (keeps whitespace). Marks exact
// substring hits and inflected prefix hits.
function hsHighlight(text, terms) {
  if (!terms || !terms.length) return text;
  const parts = String(text).split(/(\s+)/);
  return parts.map((w, i) => {
    if (/^\s+$/.test(w) || !w) return <React.Fragment key={i}>{w}</React.Fragment>;
    const n = hsNorm(w);
    const hit = terms.some(t => n.includes(t) || hsSharePrefix([n], t));
    return hit ? <mark key={i} className="sr-mark">{w}</mark> : <React.Fragment key={i}>{w}</React.Fragment>;
  });
}

// Windowed snippet around the first matching word.
function hsSnippet(text, terms, win) {
  win = win || 16;
  const words = String(text).split(/\s+/);
  if (words.length <= win) return text;
  let idx = words.findIndex(w => { const n = hsNorm(w); return terms.some(t => n.includes(t)); });
  if (idx < 0) idx = 0;
  let start = Math.max(0, idx - Math.floor(win / 2));
  const end = Math.min(words.length, start + win);
  start = Math.max(0, end - win);
  let s = words.slice(start, end).join(" ");
  if (start > 0) s = "… " + s;
  if (end < words.length) s = s + " …";
  return s;
}

const HS_KIND_ICON = {
  page: () => <Icon.Hex/>,
  section: () => <Icon.Hash/>,
  text: () => <Icon.Text/>,
  code: () => <Icon.Terminal/>,
};

// Small UI-string table for the palette chrome (falls back to English).
const SEARCH_UI = {
  en: { open: "Search the docs…", placeholder: "Search guides, topics, errors, commands…", tip: "Type any word — we search every guide, step, error message and command.", jump: "Quick links", recent: "Suggested topics", result: "result", results: "results", none: "No matches for", noneHint: "Try a product name, an error code, or a keyword.", navigate: "navigate", select: "open", close: "close", inPage: "in" },
  es: { open: "Buscar en la documentación…", placeholder: "Buscá guías, temas, errores, comandos…", tip: "Escribí cualquier palabra — buscamos en cada guía, paso, mensaje de error y comando.", jump: "Accesos rápidos", recent: "Temas sugeridos", result: "resultado", results: "resultados", none: "Sin coincidencias para", noneHint: "Probá con el nombre de un producto, un código de error o una palabra clave.", navigate: "moverse", select: "abrir", close: "cerrar", inPage: "en" },
  pt: { open: "Pesquisar na documentação…", placeholder: "Busque guias, tópicos, erros, comandos…", tip: "Digite qualquer palavra — buscamos em cada guia, passo, mensagem de erro e comando.", jump: "Links rápidos", recent: "Tópicos sugeridos", result: "resultado", results: "resultados", none: "Nenhum resultado para", noneHint: "Tente um nome de produto, um código de erro ou uma palavra-chave.", navigate: "navegar", select: "abrir", close: "fechar", inPage: "em" },
  fr: { open: "Rechercher dans la doc…", placeholder: "Guides, sujets, erreurs, commandes…", tip: "Tapez n'importe quel mot — on cherche dans chaque guide, étape, message d'erreur et commande.", jump: "Liens rapides", recent: "Sujets suggérés", result: "résultat", results: "résultats", none: "Aucun résultat pour", noneHint: "Essayez un nom de produit, un code d'erreur ou un mot-clé.", navigate: "naviguer", select: "ouvrir", close: "fermer", inPage: "dans" },
  de: { open: "Doku durchsuchen…", placeholder: "Guides, Themen, Fehler, Befehle…", tip: "Tippe ein beliebiges Wort — wir durchsuchen jeden Guide, Schritt, Fehlertext und Befehl.", jump: "Schnellzugriff", recent: "Vorschläge", result: "Treffer", results: "Treffer", none: "Keine Treffer für", noneHint: "Versuche einen Produktnamen, einen Fehlercode oder ein Stichwort.", navigate: "navigieren", select: "öffnen", close: "schließen", inPage: "in" },
};
function hsUI(lang) { return SEARCH_UI[lang] || SEARCH_UI.en; }

// Curated quick-links shown when the field is empty (landing pages + key pages).
function hsQuickLinks(lang) {
  const wanted = ["welcome", "honey-radio", "escrow", "honey-radio-faq", "introduction"];
  const seen = new Set();
  const out = [];
  const push = (pid) => {
    if (!pid || seen.has(pid) || !PAGES.meta[pid]) return;
    seen.add(pid);
    const m = PAGES.meta[pid][lang] || PAGES.meta[pid].en;
    out.push({ pid, title: m.title, sub: m.subtitle, anchor: null, kind: "page" });
  };
  wanted.forEach(push);
  // Top up with the first few landing pages from the sidebar.
  SIDEBAR.forEach(g => g.items.forEach(it => { if (it.landingPage && !it.comingSoon) push(it.landingPage); }));
  return out.slice(0, 6);
}

/* ============================================================
   Command palette (modal)
   ============================================================ */

function CommandPalette({ open, onClose, onNavigate }) {
  const lang = window.useLang();
  const t = window.useT();
  const ui = hsUI(lang);
  const [q, setQ] = useStateT("");
  const [active, setActive] = useStateT(0);
  const inputRef = useRefT(null);
  const listRef = useRefT(null);

  const index = useMemo(() => hsBuildIndex(lang), [lang]);
  const quick = useMemo(() => hsQuickLinks(lang), [lang]);

  // Reset + focus whenever the palette opens; lock background scroll while open.
  useEffectT(() => {
    if (open) {
      setQ(""); setActive(0);
      const prevOverflow = document.body.style.overflow;
      document.body.style.overflow = "hidden";
      const id = requestAnimationFrame(() => inputRef.current && inputRef.current.focus());
      return () => { cancelAnimationFrame(id); document.body.style.overflow = prevOverflow; };
    }
  }, [open]);

  const terms = hsTerms(q);
  const qnorm = hsNorm(q.trim());

  // Ranked, de-duplicated, grouped-by-page results.
  const { flat, groups } = useMemo(() => {
    if (!terms.length) return { flat: [], groups: [] };
    const scored = [];
    for (const e of index) {
      const s = hsRank(e, terms, qnorm);
      if (s != null) scored.push({ e, s });
    }
    scored.sort((a, b) => a.s - b.s || a.e.w - b.e.w);

    // De-dupe near-identical hits (same page + anchor + kind).
    const seen = new Set();
    const picked = [];
    for (const { e } of scored) {
      const key = e.pid + "|" + (e.anchor || "") + "|" + e.kind + "|" + e.title;
      if (seen.has(key)) continue;
      seen.add(key);
      picked.push(e);
      if (picked.length >= 28) break;
    }

    // Group by script/product, preserving first-seen (best) order.
    const order = [];
    const byProd = {};
    picked.forEach(e => {
      if (!byProd[e.prodId]) {
        byProd[e.prodId] = { label: e.prodLabel, glyph: e.prodGlyph, items: [] };
        order.push(e.prodId);
      }
      byProd[e.prodId].items.push(e);
    });
    const groups = order.map(id => byProd[id]);
    const flat = groups.flatMap(g => g.items);
    return { flat, groups };
  }, [terms.join(" "), qnorm, index]);

  // Clamp active selection within range and scroll it into view.
  useEffectT(() => { if (active >= flat.length) setActive(0); }, [flat.length]);
  useEffectT(() => {
    if (!open) return;
    const el = listRef.current && listRef.current.querySelector('[data-active="true"]');
    if (el) el.scrollIntoView({ block: "nearest" });
  }, [active, flat.length, open]);

  const go = (entry) => {
    if (!entry) return;
    onNavigate(entry.pid, entry.anchor || null);
    onClose();
  };

  const onKeyDown = (e) => {
    if (e.key === "Escape") { e.preventDefault(); onClose(); return; }
    const max = flat.length;
    if (!max) return;
    if (e.key === "ArrowDown") { e.preventDefault(); setActive(a => (a + 1) % max); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setActive(a => (a - 1 + max) % max); }
    else if (e.key === "Enter") { e.preventDefault(); go(flat[active]); }
    else if (e.key === "Home") { e.preventDefault(); setActive(0); }
    else if (e.key === "End") { e.preventDefault(); setActive(max - 1); }
  };

  if (!open) return null;

  // Flat index counter so each row knows its position for keyboard nav.
  let counter = -1;

  // Portal to <body>: the topbar's backdrop-filter would otherwise become the
  // containing block for our position:fixed overlay, trapping it in the navbar.
  return ReactDOM.createPortal((
    <div className="cmdk-overlay" onMouseDown={onClose}>
      <div className="cmdk" role="dialog" aria-modal="true" onMouseDown={e => e.stopPropagation()}>
        <div className="cmdk-input">
          <Icon.Search style={{ color: "var(--honey)", flexShrink: 0 }}/>
          <input
            ref={inputRef}
            type="search"
            inputMode="search"
            enterKeyHint="go"
            placeholder={ui.placeholder}
            value={q}
            onChange={e => { setQ(e.target.value); setActive(0); }}
            onKeyDown={onKeyDown}
            spellCheck={false}
            autoComplete="off"
            autoCorrect="off"
            autoCapitalize="off"
          />
          {q && (
            <button className="cmdk-clear" onClick={() => { setQ(""); inputRef.current && inputRef.current.focus(); }} aria-label="Clear">
              <Icon.Close width={14} height={14}/>
            </button>
          )}
          <button className="cmdk-esc" onClick={onClose} aria-label="Close">
            <span className="cmdk-esc-txt">esc</span>
            <Icon.Close className="cmdk-esc-x" width={17} height={17}/>
          </button>
        </div>

        <div className="cmdk-body" ref={listRef}>
          {!terms.length ? (
            <React.Fragment>
              <div className="cmdk-tip">
                <span className="cmdk-tip-ico"><Icon.Search width={14} height={14}/></span>
                <span>{ui.tip}</span>
              </div>
              <div className="cmdk-group">
                <div className="cmdk-group-h">{ui.jump}</div>
                {quick.map((e, i) => (
                  <button key={e.pid} className="sr-row" onClick={() => go(e)}>
                    <span className="sr-ico"><Icon.Hex/></span>
                    <span className="sr-main">
                      <span className="sr-title">{e.title}</span>
                      {e.sub && <span className="sr-sub">{e.sub}</span>}
                    </span>
                    <Icon.Arrow className="sr-go"/>
                  </button>
                ))}
              </div>
            </React.Fragment>
          ) : flat.length === 0 ? (
            <div className="cmdk-empty">
              <div className="cmdk-empty-q">{ui.none} “{q.trim()}”</div>
              <div className="cmdk-empty-hint">{ui.noneHint}</div>
            </div>
          ) : (
            groups.map((g, gi) => {
              const GroupGlyph = g.glyph ? (Icon[g.glyph[0].toUpperCase() + g.glyph.slice(1)] || null) : null;
              return (
              <div className="cmdk-group" key={gi}>
                <div className="cmdk-group-h">
                  <span className="cmdk-group-ico">
                    {GroupGlyph ? <GroupGlyph width={13} height={13}/> : <BrandIcon width={13} height={13}/>}
                  </span>
                  <span className="cmdk-group-name">{hsTr(t, g.label)}</span>
                </div>
                {g.items.map((e) => {
                  counter += 1;
                  const idx = counter;
                  const isActive = idx === active;
                  const Glyph = HS_KIND_ICON[e.kind] || HS_KIND_ICON.text;
                  const showSnippet = e.kind === "text" || e.kind === "code";
                  return (
                    <button
                      key={idx}
                      data-active={isActive ? "true" : "false"}
                      className={"sr-row" + (isActive ? " active" : "")}
                      onMouseEnter={() => setActive(idx)}
                      onClick={() => go(e)}
                    >
                      <span className={"sr-ico" + (e.kind === "code" ? " mono" : "")}><Glyph/></span>
                      <span className="sr-main">
                        {showSnippet ? (
                          <React.Fragment>
                            <span className={"sr-title" + (e.kind === "code" ? " mono" : "")}>
                              {(() => {
                                const pl = hsTr(t, e.pageLabel);
                                const parts = [];
                                if (!e.isLanding) parts.push(pl);
                                if (e.heading) parts.push(e.heading);
                                return hsHighlight(parts.join("  ›  ") || pl, terms);
                              })()}
                            </span>
                            <span className={"sr-sub" + (e.kind === "code" ? " mono" : "")}>{hsHighlight(hsSnippet(e.raw, terms), terms)}</span>
                          </React.Fragment>
                        ) : e.kind === "section" ? (
                          <React.Fragment>
                            <span className="sr-title">{hsHighlight(e.title, terms)}</span>
                            {!e.isLanding && <span className="sr-sub sr-crumb">{hsTr(t, e.pageLabel)}</span>}
                          </React.Fragment>
                        ) : (
                          <React.Fragment>
                            <span className="sr-title">{e.isLanding ? hsHighlight(e.title, terms) : hsHighlight(hsTr(t, e.pageLabel), terms)}</span>
                            {e.raw && <span className="sr-sub">{hsHighlight(e.raw, terms)}</span>}
                          </React.Fragment>
                        )}
                      </span>
                      {isActive && <span className="sr-enter">↵</span>}
                    </button>
                  );
                })}
              </div>
              );
            })
          )}
        </div>

        <div className="cmdk-foot">
          <div className="cmdk-hints">
            <span><kbd className="cmdk-key">↑</kbd><kbd className="cmdk-key">↓</kbd> {ui.navigate}</span>
            <span><kbd className="cmdk-key">↵</kbd> {ui.select}</span>
            <span><kbd className="cmdk-key">esc</kbd> {ui.close}</span>
          </div>
          {terms.length > 0 && <div className="cmdk-count">{flat.length} {flat.length === 1 ? ui.result : ui.results}</div>}
        </div>
      </div>
    </div>
  ), document.body);
}

/* The topbar trigger pill — opens the palette. */
function SearchBar({ onNavigate }) {
  const t = window.useT();
  const lang = window.useLang();
  const ui = hsUI(lang);
  const [open, setOpen] = useStateT(false);

  // Ctrl/Cmd+K and "/" open it; a global event lets other UI open it too.
  useEffectT(() => {
    const k = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setOpen(true); }
      else if (e.key === "/" && !/^(input|textarea)$/i.test((e.target.tagName || "")) && !e.target.isContentEditable) {
        e.preventDefault(); setOpen(true);
      }
    };
    const openEvt = () => setOpen(true);
    document.addEventListener("keydown", k);
    window.addEventListener("hs:openSearch", openEvt);
    return () => { document.removeEventListener("keydown", k); window.removeEventListener("hs:openSearch", openEvt); };
  }, []);

  return (
    <div style={{ position: "relative", flex: 1, maxWidth: 480 }}>
      <button className="searchbar searchbar-trigger" onClick={() => setOpen(true)}>
        <Icon.Search style={{ color: "var(--muted-2)" }}/>
        <span className="searchbar-ph">{ui.open}</span>
        <span className="kbd">Ctrl <span className="kbd-sep">+</span> K</span>
      </button>
      <CommandPalette open={open} onClose={() => setOpen(false)} onNavigate={onNavigate}/>
    </div>
  );
}

function Topbar({ lang, setLang, theme, setTheme, onNavigate, onOpenMobileNav }) {
  const t = window.useT();
  return (
    <div className="topbar">
      <div className="topbar-inner">
        <div className="topbar-zone topbar-left">
          <button className="btn btn-ghost only-mobile" onClick={onOpenMobileNav} style={{ padding: 8 }}>
            <Icon.Menu/>
          </button>
          <div className="brand">
            <div className="brand-logo"><img src="assets/bee-logo.png" alt="Honey Scripts"/></div>
          </div>
          <LangPicker lang={lang} setLang={setLang}/>
        </div>

        <div className="topbar-center">
          <SearchBar onNavigate={onNavigate}/>
        </div>

        <div className="topbar-zone topbar-right">
          <a className="btn btn-primary only-desktop" href="https://store.honey-scripts.com" target="_blank" rel="noreferrer noopener">
            <Icon.Cart/> {t("tebex")}
          </a>
          <button
            className="btn btn-ghost"
            onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
            title={theme === "dark" ? t("themeLight") : t("themeDark")}
            style={{ padding: 8 }}
          >
            {theme === "dark" ? <Icon.Sun/> : <Icon.Moon/>}
          </button>
        </div>
      </div>
    </div>
  );
}

window.Topbar = Topbar;
