/* 19K-RGP Assistant — smart, browser-only concierge with an optional LLM upgrade.
   - Free-text questions ALWAYS work (synonym-aware retrieval over a local knowledge base).
   - Confident match -> answer + follow-up chips; uncertain -> "did you mean"; none -> suggestions.
   - mode "concierge" (default): 100% client-side, no key, cannot hallucinate.
   - mode "llm": POSTs to RGP_CONFIG.ASSISTANT_ENDPOINT, then FALLS BACK to the local engine on any error.
   - mode "off": launcher becomes a Help link.
   Knowledge base: assets/data/assistant-intents.json */
(function () {
  let KB = { intents: [], synonyms: {}, starters: [], intro: "", notFound: "" };
  let REV = {};                 // synonym -> canonical
  let history = [];             // for llm mode
  let lastFocus = null;
  const HELP = { id: "help", q: "What can you do?" };
  const STOP = new Set(("a an the is are was were be of to in on for and or with how do does did i my me you your we " +
    "what where which when can could would should please tell show give get there this that these those it its as at by").split(" "));

  const $ = (id) => document.getElementById(id);
  const cfg = () => window.RGP_CONFIG || {};
  function escapeHtml(s) { const d = document.createElement("div"); d.textContent = s == null ? "" : String(s); return d.innerHTML; }

  // ---- retrieval engine -----------------------------------------------------
  function buildRev() {
    REV = {};
    const syn = KB.synonyms || {};
    for (const c in syn) { REV[c] = c; (syn[c] || []).forEach((s) => (REV[s] = c)); }
  }
  const canon = (t) => REV[t] || t;
  function toks(s) {
    return (s || "").toLowerCase().replace(/[^a-z0-9×]+/g, " ").split(" ")
      .filter((t) => t && t.length > 1 && !STOP.has(t));
  }
  function querySet(s) { const out = new Set(); toks(s).forEach((t) => { out.add(t); out.add(canon(t)); }); return out; }
  const weight = (t) => (t.length >= 7 ? 2.2 : t.length >= 5 ? 1.7 : t.length >= 3 ? 1.2 : 0.8);

  function search(query) {
    const q = querySet(query); if (!q.size) return [];
    const qt = toks(query);
    const out = [];
    for (const it of KB.intents || []) {
      const kwSet = new Set(); (it.keywords || []).forEach((k) => { kwSet.add(k); kwSet.add(canon(k)); });
      const qSet = new Set(); toks(it.q || "").forEach((w) => { qSet.add(w); qSet.add(canon(w)); });
      let score = 0, hits = 0;
      q.forEach((t) => {
        if (kwSet.has(t)) { score += weight(t); hits++; }          // curated keyword: full weight
        else if (qSet.has(t)) { score += 0.35; hits++; }           // incidental question-text word: weak
      });
      const qtext = (it.q || "").toLowerCase();
      qt.forEach((t) => { if (t.length >= 4 && qtext.includes(t)) score += 0.5; });   // phrase boost
      const exact = new Set(it.keywords || []);
      qt.forEach((t) => { if (exact.has(t)) score += 0.8; });                          // precise keyword hit
      if (hits) out.push({ it, score });
    }
    out.sort((a, b) => b.score - a.score);
    return out;
  }

  const byId = (id) => (KB.intents || []).find((x) => x.id === id);
  const starterChips = () => (KB.starters || []).map((id) => (id === "help" ? HELP : byId(id))).filter(Boolean);
  const followChips = (it) => (it.followups || []).map(byId).filter(Boolean).slice(0, 3);

  function classify(query) {
    const low = query.toLowerCase().trim();
    if (/^(hi|hey|hello+|yo|salam|hallo|howdy|good\s+(morning|afternoon|evening))\b/.test(low)) return { type: "text", html: KB.intro };
    if (/(thank|thanks|thx|thank you|shukran|cheers|appreciate)/.test(low)) return { type: "text", html: "You're welcome! Anything else I can help you find?" };
    if (/(^help$|^menu$|^topics?$|what can you|who are you|what do you do|how can you|capabilities)/.test(low)) return { type: "help" };
    const res = search(query);
    if (res.length && res[0].score >= 2.0) {
      const top = res[0].it;
      let chips = followChips(top);
      for (const r of res.slice(1)) { if (chips.length >= 3) break; if (r.it.id !== top.id && !chips.includes(r.it)) chips.push(r.it); }
      return { type: "answer", intent: top, chips: chips.slice(0, 3) };
    }
    if (res.length && res[0].score >= 0.8) return { type: "didyoumean", chips: res.slice(0, 4).map((r) => r.it) };
    return { type: "notfound", chips: res.slice(0, 3).map((r) => r.it) };
  }

  // ---- rendering ------------------------------------------------------------
  function bubble(html, who) {
    const m = $("assistant-msgs"); if (!m) return;
    const d = document.createElement("div");
    d.className = "amsg amsg--" + who;
    d.innerHTML = html;
    m.appendChild(d); m.scrollTop = m.scrollHeight;
  }
  const addBot = (html) => bubble(html, "bot");
  const addUser = (text) => bubble(escapeHtml(text), "user");

  function answerHtml(it) {
    let h = it.a || "";
    if (it.links && it.links.length) {
      h += '<div class="amsg-links">' + it.links.map(([t, href]) =>
        `<a class="achip" href="${escapeHtml(href)}"${/^https?:/.test(href) ? ' rel="noopener" target="_blank"' : ""}>${escapeHtml(t)}</a>`).join("") + "</div>";
    }
    return h;
  }

  function setChips(list) {
    const c = $("assistant-chips"); if (!c) return;
    c.innerHTML = "";
    (list || []).forEach((it) => {
      const b = document.createElement("button");
      b.type = "button"; b.className = "achip"; b.textContent = it.q;
      b.addEventListener("click", () => askIntent(it));
      c.appendChild(b);
    });
  }

  function showTyping() {
    const m = $("assistant-msgs"); if (!m) return () => {};
    const d = document.createElement("div");
    d.className = "amsg amsg--bot amsg--typing";
    d.innerHTML = '<span class="adot"></span><span class="adot"></span><span class="adot"></span>';
    m.appendChild(d); m.scrollTop = m.scrollHeight;
    return () => d.remove();
  }
  function withTyping(fn) { const stop = showTyping(); setTimeout(() => { stop(); fn(); }, 300); }

  function renderHelp() {
    const cats = [...new Set((KB.intents || []).map((i) => i.cat))];
    addBot("I can help with: <strong>" + cats.map(escapeHtml).join("</strong>, <strong>") + "</strong>. Pick a topic or just type your question — for example:");
    setChips(starterChips());
  }

  function renderResult(r) {
    if (r.type === "text") { addBot(r.html); setChips(starterChips()); }
    else if (r.type === "help") { renderHelp(); }
    else if (r.type === "answer") { addBot(answerHtml(r.intent)); setChips(r.chips.length ? r.chips : starterChips()); }
    else if (r.type === "didyoumean") { addBot("Did you mean one of these?"); setChips(r.chips); }
    else { addBot(KB.notFound); setChips(r.chips.length ? r.chips : starterChips()); }
  }

  // ---- conversation entry points -------------------------------------------
  function askIntent(it) {
    addUser(it.q);
    if (it.id === "help") { withTyping(renderHelp); return; }
    withTyping(() => { addBot(answerHtml(it)); setChips(followChips(it).length ? followChips(it) : starterChips()); });
  }

  async function askText(raw) {
    const text = (raw || "").trim(); if (!text) return;
    addUser(text); history.push({ role: "user", content: text });
    const mode = cfg().ASSISTANT_MODE || "concierge";
    const stop = showTyping();
    if (mode === "llm" && cfg().ASSISTANT_ENDPOINT) {
      try {
        const resp = await fetch(cfg().ASSISTANT_ENDPOINT, {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ question: text, history })
        });
        if (!resp.ok) throw new Error(resp.status);
        const data = await resp.json();
        stop(); addBot(data.answer || KB.notFound);
        history.push({ role: "assistant", content: data.answer || "" });
        setChips(starterChips()); return;
      } catch (e) { /* fall back to the local engine */ }
    }
    setTimeout(() => { stop(); renderResult(classify(text)); }, 280);
  }

  // ---- panel open/close + a11y ---------------------------------------------
  function openPanel() {
    const p = $("assistant-panel"), l = $("assistant-launcher"); if (!p) return;
    p.classList.add("is-open"); if (l) l.setAttribute("aria-expanded", "true");
    lastFocus = document.activeElement;
    const input = $("assistant-input");
    if (input && (cfg().ASSISTANT_MODE || "concierge") !== "off") input.focus();
    else { const f = p.querySelector("button, a"); if (f) f.focus(); }
  }
  function closePanel() {
    const p = $("assistant-panel"), l = $("assistant-launcher"); if (!p) return;
    p.classList.remove("is-open");
    if (l) { l.setAttribute("aria-expanded", "false"); l.focus(); }
  }

  async function init() {
    const launcher = $("assistant-launcher");
    if (!launcher || launcher.dataset.bound) return;
    launcher.dataset.bound = "1";

    const mode = cfg().ASSISTANT_MODE || "concierge";
    const panel = $("assistant-panel");
    if (mode === "off") {
      launcher.innerHTML = "Help";
      launcher.addEventListener("click", () => (location.href = "start.html"));
      if (panel) panel.remove();
      return;
    }
    if (panel) panel.setAttribute("data-mode", mode);

    try {
      const r = await fetch("assets/data/assistant-intents.json", { cache: "no-store" });
      KB = await r.json();
    } catch (e) {
      KB = { intents: [], synonyms: {}, starters: [],
        intro: "Assistant knowledge base couldn't load. See <a href='start.html'>Start here</a>.",
        notFound: "Knowledge base unavailable — see <a href='start.html'>Start here</a>." };
    }
    buildRev();
    if (KB.intro) addBot(KB.intro);
    setChips(starterChips());

    launcher.addEventListener("click", () => {
      const p = $("assistant-panel");
      p && p.classList.contains("is-open") ? closePanel() : openPanel();
    });
    const closeBtn = document.querySelector("[data-assistant-close]");
    if (closeBtn) closeBtn.addEventListener("click", closePanel);

    // size preference: expand / shrink the panel (remembered)
    const expandBtn = document.querySelector("[data-assistant-expand]");
    const applySize = () => {
      const exp = localStorage.getItem("rgp-assistant-size") === "expanded";
      if (panel) panel.classList.toggle("is-expanded", exp);
      if (expandBtn) expandBtn.setAttribute("aria-label", exp ? "Shrink assistant" : "Expand assistant");
    };
    applySize();
    if (expandBtn) expandBtn.addEventListener("click", () => {
      const exp = panel.classList.toggle("is-expanded");
      localStorage.setItem("rgp-assistant-size", exp ? "expanded" : "normal");
      expandBtn.setAttribute("aria-label", exp ? "Shrink assistant" : "Expand assistant");
      const input = $("assistant-input"); if (input) input.focus();
    });

    document.addEventListener("keydown", (e) => {
      const p = $("assistant-panel");
      if (e.key === "Escape" && p && p.classList.contains("is-open")) closePanel();
    });
    const form = $("assistant-form");
    if (form) form.addEventListener("submit", (e) => {
      e.preventDefault();
      const input = $("assistant-input");
      const v = input.value; input.value = ""; askText(v);
    });
  }

  document.addEventListener("DOMContentLoaded", init);
  document.addEventListener("partials:loaded", init);
})();
