/* Datavize AI - assistant sidebar.
   A real query engine grounded in the live dataset (instant, reliable,
   clickable answers), with an optional "Live AI" toggle that calls
   window.__ai.complete. Exports: AssistantPanel.                      */

function matchEntities(q) {
  const D = window.DATA;
  const ql = " " + q.toLowerCase().replace(/[^a-z0-9 ]/g, " ") + " ";
  const stop = new Set(["the", "and", "fund", "trust", "network", "project", "institute", "foundation", "collective", "research", "studio", "alliance", "academy", "commons", "clinics", "centre", "center", "group", "lab", "media", "ground", "scholars", "sound", "data", "open", "new", "hall", "town", "well"]);
  const all = [D.funder, ...D.orgs, ...D.people, ...D.projects, ...D.events];
  const scored = [];
  all.forEach((e) => {
    const name = e.name.toLowerCase();
    let score = 0;
    if (ql.includes(" " + name + " ") || ql.includes(name)) score += 100;
    name.split(/[^a-z0-9]+/).forEach((w) => { if (w.length >= 4 && !stop.has(w) && ql.includes(w)) score += 22; });
    if (e.codename && ql.includes(e.codename.toLowerCase())) score += 70;
    if (e.title && ql.includes(e.title.toLowerCase())) score += 10;
    if (score > 0) scored.push({ e, score });
  });
  scored.sort((a, b) => b.score - a.score);
  return scored.map((s) => s.e);
}

/* the local, grounded engine - returns { text, refs, verdict } */
function localAnswer(q) {
  const D = window.DATA;
  const ql = q.toLowerCase();
  const ents = matchEntities(q);
  const orgHit = ents.find((e) => D.kindOf(e.id) === "org");

  const has = (...words) => words.some((w) => ql.includes(w));

  // 1) MILESTONES - did X hit their milestones?
  if (has("milestone", "on track", "on-track", "hit what", "supposed to", "delivered", "behind")) {
    if (orgHit) {
      const mh = D.milestoneHealth(orgHit.id);
      const verdict = mh.verdict === "behind" ? "NO" : (mh.verdict === "met" || mh.verdict === "on-track") ? "YES" : "PARTLY";
      const detail = mh.list.map((m) => `• ${m[0]} - ${m[1].toUpperCase()}`).join("\n");
      const lead = verdict === "YES" ? `Yes - ${orgHit.name} is hitting its milestones.`
        : verdict === "NO" ? `No - ${orgHit.name} is behind on its milestones.`
        : `Partly - ${orgHit.name} has met some milestones but at least one is behind.`;
      return { verdict, refs: [orgHit.id], text: `${lead}\n${mh.met}/${mh.total} met${mh.behind ? `, ${mh.behind} behind` : ""}.\n\n${detail}` };
    }
    // portfolio-wide
    const behind = D.orgs.filter((o) => D.milestoneHealth(o.id).verdict === "behind");
    return { refs: behind.map((o) => o.id), text: behind.length
      ? `${behind.length} org${behind.length > 1 ? "s are" : " is"} behind on milestones:\n` + behind.map((o) => `• ${o.name} - ${D.milestoneHealth(o.id).behind} behind`).join("\n")
      : "No organisations are currently behind on their milestones." };
  }

  // 2) FUNDING GAPS - who needs more funding?
  if (has("need", "more funding", "underfunded", "gap", "unfunded", "fund more", "who should we fund", "shortfall")) {
    const list = D.needsFunding();
    if (orgHit && D.orgGap(orgHit) >= 0 && !has("who", "which", "list", "all", "everyone")) {
      const g = D.orgGap(orgHit);
      return { verdict: g > 0 ? "YES" : "NO", refs: [orgHit.id], text: g > 0
        ? `Yes - ${orgHit.name} has an open funding gap of ${D.fmtMoney(g)} (asked ${D.fmtMoney(orgHit.funding.ask)}, awarded ${D.fmtMoney(orgHit.funding.awarded)}). Risk level: ${orgHit.risk.level}.`
        : `No - ${orgHit.name} is fully funded against its ask (${D.fmtMoney(orgHit.funding.awarded)}).` };
    }
    const top = list.slice(0, 6);
    return { refs: top.map((o) => o.id), text: `${list.length} organisations have an open funding gap, totalling ${D.fmtMoney(D.totalGap())}. Largest needs:\n`
      + top.map((o) => `• ${o.name} - ${D.fmtMoney(D.orgGap(o))}${o.priority === "need" ? "  (high need)" : ""}`).join("\n") };
  }

  // 3) RISK - who's at risk / going cold?
  if (has("risk", "at risk", "cold", "cooling", "worry", "concern", "watch", "flag")) {
    const r = D.risks();
    return { refs: r.map((x) => x.org.id), text: `${r.length} high-risk relationships flagged:\n`
      + r.map((x) => `• ${x.org.name} - ${x.note}`).join("\n") };
  }

  // 4) INTRO / PATH - how do I reach X / connection between A and B
  if (has("reach", "intro", "introduce", "connect", "path", "between", "get to", "how do i", "link")) {
    if (ents.length >= 2) {
      const path = D.shortestPath(ents[0].id, ents[1].id);
      if (path) return { refs: path, text: `Shortest path (${path.length - 1} hop${path.length - 1 === 1 ? "" : "s"}):\n` + path.map((id) => D.byId[id].name).join("  →  ") };
      return { refs: [ents[0].id, ents[1].id], text: `No connection found between ${ents[0].name} and ${ents[1].name}.` };
    }
    if (ents.length === 1) {
      const path = D.shortestPath("funder", ents[0].id);
      const handler = D.kindOf(ents[0].id) === "org" && ents[0].account ? D.byId[ents[0].account] : null;
      return { refs: path || [ents[0].id], text: `To reach ${ents[0].name}${handler ? `, go through ${handler.name} (account holder)` : ""}.` + (path ? `\nPath: ` + path.map((id) => D.byId[id].name).join("  →  ") : "") };
    }
  }

  // 5) SUMMARY / WHO IS - brief me on X
  if (ents.length) {
    const e = ents[0];
    return { refs: [e.id], text: D.brief(e.id) };
  }

  // 6) counts / overview
  if (has("how many", "overview", "summary", "portfolio", "total", "ecosystem")) {
    return { refs: ["funder"], text: `The ecosystem has ${D.counts.orgs} organisations (${D.counts.funded} funded), ${D.counts.people} people, ${D.counts.projects} projects and ${D.counts.events} events across 6 domains. Open funding gap: ${D.fmtMoney(D.totalGap())} across ${D.needsFunding().length} orgs.` };
  }

  // fallback
  return { text: "I can answer questions about funding gaps, milestones, risk, introductions/paths, and summaries. Try “who needs more funding?”, “did Haven Therapy hit their milestones?”, or “how do I reach Priya Nair?”" };
}

async function liveAnswer(q) {
  const D = window.DATA;
  // compact grounding context
  const ctx = D.orgs.map((o) => D.brief(o.id)).join("\n");
  const people = D.people.map((p) => `${p.name} - ${p.title}${p.org ? " at " + D.byId[p.org].name : p.team ? " (Wellspring team)" : ""}`).join("\n");
  const prompt = `You are NeuroAI, an assistant embedded in a grant-funder's relationship tool. Answer ONLY from the data below. Be concise (2-5 sentences). When the question is yes/no, start with "Yes", "No" or "Partly". Use £ figures where relevant.\n\n=== ORGANISATIONS ===\n${ctx}\n\n=== PEOPLE ===\n${people}\n\n=== QUESTION ===\n${q}`;
  const text = await window.__ai.complete({ messages: [{ role: "user", content: prompt }] });
  // attach refs by name-matching the answer + question
  const refs = matchEntities(q + " " + text).slice(0, 5).map((e) => e.id);
  return { text: (text || "").trim(), refs };
}

const SUGGESTED = [
  "Who needs more funding?",
  "Did Haven Therapy hit their milestones?",
  "Who's at risk?",
  "How do I reach Priya Nair?",
  "Brief me on Beacon Research Lab",
];

function Bubble({ m, onOpenRecord }) {
  const D = window.DATA;
  if (m.role === "user") {
    return <div style={{ alignSelf: "flex-end", maxWidth: "85%", background: "var(--accent)", color: "var(--accent-contrast)", padding: "9px 13px", borderRadius: "14px 14px 4px 14px", fontSize: 13.5, lineHeight: 1.45 }}>{m.text}</div>;
  }
  const vmap = { YES: ["YES", "var(--good)", "var(--good-bg)"], NO: ["NO", "var(--rose)", "var(--rose-bg)"], PARTLY: ["PARTLY", "var(--warn)", "var(--warn-bg)"] };
  const v = m.verdict && vmap[m.verdict];
  return (
    <div style={{ alignSelf: "flex-start", maxWidth: "92%", display: "flex", flexDirection: "column", gap: 8 }}>
      <div style={{ display: "flex", gap: 9 }}>
        <div style={{ width: 26, height: 26, flex: "none", borderRadius: 8, display: "grid", placeItems: "center", background: "var(--accent-soft)", color: "var(--accent)" }}><Icon name="spark" size={15} /></div>
        <div style={{ background: "var(--surface-2)", border: "1px solid var(--border)", padding: "10px 13px", borderRadius: "14px 14px 14px 4px", fontSize: 13.5, lineHeight: 1.5, whiteSpace: "pre-wrap", color: "var(--text)" }}>
          {v && <div style={{ marginBottom: 6 }}><span className="badge" style={{ color: v[1], background: v[2], borderColor: "transparent", fontWeight: 700, letterSpacing: "0.04em" }}>{v[0]}</span></div>}
          {m.text}
        </div>
      </div>
      {m.refs && m.refs.length > 0 && (
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, paddingLeft: 35 }}>
          {m.refs.map((id) => {
            const e = D.byId[id]; if (!e) return null;
            const k = D.kindOf(id);
            return <button key={id} className="chip focusable" onClick={() => onOpenRecord(id)} style={{ fontSize: 12 }}>
              {k === "org" ? <span className="dot" style={{ background: domainColor(e.domain) }} /> : <Icon name={(typeMeta(k)).icon} size={12} style={{ color: "var(--text-muted)" }} />}
              {e.name}
            </button>;
          })}
        </div>
      )}
    </div>
  );
}

function AssistantPanel({ open, onClose, onOpenRecord }) {
  const D = window.DATA;
  const [messages, setMessages] = React.useState([
    { role: "ai", text: "I'm NeuroAI - grounded in your live ecosystem data. Ask me about funding gaps, milestones, risk, or how to reach someone.", refs: [] },
  ]);
  const [input, setInput] = React.useState("");
  const [live, setLive] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const scrollRef = React.useRef(null);
  const hasAI = typeof window !== "undefined" && window.__ai && typeof window.__ai.complete === "function";

  React.useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [messages, busy]);

  const ask = async (q) => {
    const query = (q != null ? q : input).trim();
    if (!query || busy) return;
    setInput("");
    setMessages((m) => [...m, { role: "user", text: query }]);
    setBusy(true);
    try {
      let ans;
      if (live && hasAI) {
        try { ans = await liveAnswer(query); if (!ans.text) ans = localAnswer(query); }
        catch (e) { ans = localAnswer(query); }
      } else {
        await new Promise((r) => setTimeout(r, 340)); // brief "thinking"
        ans = localAnswer(query);
      }
      setMessages((m) => [...m, { role: "ai", ...ans }]);
    } finally { setBusy(false); }
  };

  if (!open) return null;
  return (
    <div style={{ position: "absolute", top: 0, right: 0, bottom: 0, width: 384, maxWidth: "94vw", zIndex: 30, display: "flex", flexDirection: "column", background: "var(--surface)", borderLeft: "1px solid var(--border)", boxShadow: "var(--shadow-lg)" }} className="fade-panel">
      {/* header */}
      <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "center", gap: 11 }}>
        <div style={{ width: 34, height: 34, borderRadius: 10, display: "grid", placeItems: "center", background: "var(--accent-soft)", color: "var(--accent)" }}><Icon name="spark" size={18} /></div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 680, letterSpacing: "-0.01em" }}>NeuroAI</div>
          <div style={{ fontSize: 11.5, color: "var(--text-muted)" }}>Grounded in your ecosystem</div>
        </div>
        {hasAI && (
          <button onClick={() => setLive((v) => !v)} className="focusable" title="Use live AI model"
            style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 9px", borderRadius: 999, cursor: "pointer", font: "inherit", fontSize: 11.5, fontWeight: 600, border: "1px solid " + (live ? "transparent" : "var(--border)"), background: live ? "var(--accent)" : "var(--surface-2)", color: live ? "var(--accent-contrast)" : "var(--text-muted)" }}>
            <Icon name="bolt" size={13} /> Live
          </button>
        )}
        <button className="btn btn-icon btn-ghost" onClick={onClose} title="Close"><Icon name="x" size={18} /></button>
      </div>

      {/* transcript */}
      <div ref={scrollRef} style={{ flex: 1, overflow: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 14 }}>
        {messages.map((m, i) => <Bubble key={i} m={m} onOpenRecord={onOpenRecord} />)}
        {busy && (
          <div style={{ alignSelf: "flex-start", display: "flex", gap: 9, alignItems: "center" }}>
            <div style={{ width: 26, height: 26, flex: "none", borderRadius: 8, display: "grid", placeItems: "center", background: "var(--accent-soft)", color: "var(--accent)" }}><Icon name="spark" size={15} /></div>
            <div style={{ background: "var(--surface-2)", border: "1px solid var(--border)", padding: "11px 14px", borderRadius: "14px 14px 14px 4px", display: "flex", gap: 4 }}>
              {[0, 1, 2].map((d) => <span key={d} style={{ width: 6, height: 6, borderRadius: 999, background: "var(--text-faint)", animation: "intel-blink 1s infinite", animationDelay: d * 0.15 + "s" }} />)}
            </div>
          </div>
        )}
      </div>

      {/* suggestions */}
      {messages.length <= 1 && (
        <div style={{ padding: "0 16px 8px", display: "flex", flexWrap: "wrap", gap: 6 }}>
          {SUGGESTED.map((s) => <button key={s} className="chip focusable" style={{ fontSize: 12 }} onClick={() => ask(s)}>{s}</button>)}
        </div>
      )}

      {/* input */}
      <div style={{ padding: 14, borderTop: "1px solid var(--border)", display: "flex", gap: 8 }}>
        <input className="input focusable" placeholder="Ask about your ecosystem…" value={input}
          onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") ask(); }} />
        <button className="btn btn-primary btn-icon" onClick={() => ask()} disabled={busy || !input.trim()} title="Send"><Icon name="arrowRight" size={17} /></button>
      </div>
    </div>
  );
}

window.AssistantPanel = AssistantPanel;
