/* ===========================================================
   Reiktable — AI Loremaster (Claude built-in; OpenWebUI optional)
   Exposes (window): AIView
   =========================================================== */
(function () {
  const { useState, useRef, useEffect } = React;

  // tiny markdown -> elements
  function fmt(text) {
    const blocks = text.split(/\n{2,}/);
    return blocks.map((b, i) => {
      const lines = b.split("\n");
      if (lines.every(l => /^\s*[-*]\s+/.test(l))) {
        return <ul key={i}>{lines.map((l, j) => <li key={j} dangerouslySetInnerHTML={{ __html: inline(l.replace(/^\s*[-*]\s+/, "")) }} />)}</ul>;
      }
      return <p key={i} dangerouslySetInnerHTML={{ __html: inline(b) }} />;
    });
  }
  function inline(s) {
    return s
      .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
      .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
      .replace(/\*(.+?)\*/g, "<em>$1</em>")
      .replace(/`(.+?)`/g, "<code>$1</code>");
  }

  function campaignContext(s) {
    const party = s.characters.map(c => `${c.name} (${c.career} T${c.careerTier}, ${c.species}${c.god ? ", devotee of " + c.god : ""})`).join("; ");
    const quests = (s.quests || []).filter(q => q.status !== "done").map(q => q.title).join("; ");
    const npcs = (s.npcs || []).slice(0, 8).map(n => `${n.name} (${n.role})`).join("; ");
    const recent = (s.journal || []).slice(0, 2).map(j => `S${j.session}: ${j.title} — ${j.body}`).join(" / ");
    return `CAMPAIGN: "${s.campaign?.name}" — ${s.campaign?.system}.
PARTY: ${party}.
ACTIVE QUESTS: ${quests || "none recorded"}.
KEY NPCS: ${npcs || "none"}.
RECENT EVENTS: ${recent || "none recorded"}.`;
  }

  async function searchBooks(query) {
    try {
      const recs = await window.PDFDB.all();
      const terms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
      if (!terms.length || !recs.length) return [];
      const hits = [];
      for (const b of recs) for (const pg of b.pages) {
        const low = pg.text.toLowerCase();
        if (terms.every(t => low.includes(t))) {
          let sc = 0; for (const t of terms) sc += low.split(t).length - 1;
          hits.push({ book: b.name, page: pg.n, text: pg.text, sc });
        }
      }
      hits.sort((a, b) => b.sc - a.sc);
      return hits.slice(0, 3).map(h => `[${h.book} p.${h.page}] ${h.text.replace(/\s+/g, " ").slice(0, 700)}`);
    } catch (e) { return []; }
  }

  // Default LLM call: built-in Claude here; /api/ai (server proxy) when self-hosted.
  async function llm(messages) {
    if (window.claude && window.claude.complete) return await window.claude.complete({ messages });
    const r = await fetch("/api/ai", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ messages }) });
    if (!r.ok) throw new Error("ai backend error");
    const j = await r.json();
    return j.text || j.content || "(no response)";
  }

  async function callAI(s, history, userMsg, grounding) {
    const sys = `You are ${s.settings?.aiName || "the Loremaster"}, an expert Game Master's assistant for Warhammer Fantasy Roleplay 4th Edition. You know WFRP 4e rules (tests, Success Levels, advantage, conditions, careers, magic, corruption) and the grim, perilous tone of the Old World. Be concise, practical and evocative. Use **bold** for key terms and short bullet lists for options. ${s.settings?.gmMode ? "GM MODE ON: you may improvise NPCs, scenes, complications and read-aloud text for the Game Master." : "Assist the table with rules, ideas and lore; do not railroad."}

${campaignContext(s)}`;

    let grounded = "";
    if (grounding) {
      const snips = await searchBooks(userMsg);
      if (snips.length) grounded = `\n\nRELEVANT EXCERPTS FROM THE GROUP'S OWN RULEBOOKS (cite the book & page if you use them):\n${snips.join("\n---\n")}`;
    }

    const messages = [
      { role: "user", content: `SYSTEM BRIEF:\n${sys}${grounded}\n\nWhen you are ready, just answer the user's messages in character as the Loremaster.` },
      { role: "assistant", content: "Understood. I'm ready — ask me anything about the rules, the world, or this campaign." },
      ...history.map(m => ({ role: m.role === "me" ? "user" : "assistant", content: m.text })),
      { role: "user", content: userMsg },
    ];

    const provider = s.settings?.aiProvider || "claude";
    if (provider === "openwebui" && s.settings?.openwebui?.url) {
      const cfg = s.settings.openwebui;
      const res = await fetch(cfg.url.replace(/\/$/, "") + "/api/chat/completions", {
        method: "POST",
        headers: { "Content-Type": "application/json", ...(cfg.key ? { Authorization: "Bearer " + cfg.key } : {}) },
        body: JSON.stringify({ model: cfg.model || "llama3", messages: [{ role: "system", content: sys + grounded }, ...messages.slice(2)] }),
      });
      const j = await res.json();
      return j.choices?.[0]?.message?.content || "(no response)";
    }
    // default: built-in Claude here, or server /api/ai proxy when self-hosted
    return await llm(messages);
  }

  const PROMPTS_PLAYER = [
    "How do opposed tests work in WFRP 4e?",
    "Explain Advantage and how it builds in combat.",
    "What happens when I gain a Critical Wound?",
    "Summarise where the party is right now.",
  ];
  const PROMPTS_GM = [
    "Give me 3 Reiklander NPC names with a quirk each.",
    "Improvise a tense scene as the party enters Grünwald.",
    "A complication for the hunt for Fabergus Heinsdork.",
    "Roll up a random roadside encounter in the Schwarzwald.",
  ];

  window.AIView = function () {
    const [s, update] = window.useStore();
    const [msgs, setMsgs] = useState([]);
    const [input, setInput] = useState("");
    const [busy, setBusy] = useState(false);
    const [grounding, setGrounding] = useState(true);
    const [showSettings, setShowSettings] = useState(false);
    const scrollRef = useRef();

    useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [msgs, busy]);

    async function send(text) {
      const t = (text ?? input).trim();
      if (!t || busy) return;
      const history = msgs;
      setMsgs(m => [...m, { role: "me", text: t }]);
      setInput(""); setBusy(true);
      try {
        const reply = await callAI(s, history, t, grounding);
        setMsgs(m => [...m, { role: "ai", text: reply }]);
      } catch (e) {
        setMsgs(m => [...m, { role: "ai", text: "*The Loremaster is silent — the connection failed. Check the AI settings, or try again.*" }]);
      }
      setBusy(false);
    }

    const gmMode = s.settings?.gmMode;
    const prompts = gmMode ? PROMPTS_GM : PROMPTS_PLAYER;

    return (
      <div className="content narrow fade-in" style={{ display: "flex", flexDirection: "column", height: "calc(100vh - 56px)", paddingBottom: 16 }}>
        <div className="page-h" style={{ marginBottom: 12 }}>
          <div className="center g12">
            <div className="ava md" style={{ background: "linear-gradient(140deg,var(--viol),#6b46c1)" }}><Icon name="sparkles" size={20} /></div>
            <div><h1 style={{ fontSize: 22 }}>{s.settings?.aiName || "The Loremaster"}</h1><p>AI assistant · {s.settings?.aiProvider === "openwebui" ? "OpenWebUI" : "Claude"} {gmMode && "· GM Mode"}</p></div>
          </div>
          <div className="sp"></div>
          <label className="switch" title="Ground answers in your uploaded rulebooks"><input type="checkbox" checked={grounding} onChange={e => setGrounding(e.target.checked)} /><span className="track"></span><span className="tx2" style={{ fontSize: 12.5 }}>Cite books</span></label>
          <label className="switch" title="Let the Loremaster run scenes & NPCs as GM"><input type="checkbox" checked={!!gmMode} onChange={e => update(st => { st.settings.gmMode = e.target.checked; })} /><span className="track"></span><span className="tx2" style={{ fontSize: 12.5 }}>GM mode</span></label>
          <Btn className="icon ghost" onClick={() => setShowSettings(true)}><Icon name="settings" size={16} /></Btn>
        </div>

        <div className="card grow" style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div ref={scrollRef} className="card-b scroll-y grow" style={{ minHeight: 0 }}>
            {msgs.length === 0 ? (
              <div className="empty" style={{ paddingTop: 30 }}>
                <div className="ava lg" style={{ margin: "0 auto 14px", background: "linear-gradient(140deg,var(--viol),#6b46c1)" }}><Icon name="sparkles" size={26} /></div>
                <h3 style={{ color: "var(--tx)", fontSize: 17 }}>Ask the Loremaster</h3>
                <p style={{ maxWidth: 440, margin: "6px auto 18px" }}>Rules questions, lore, NPCs, plot hooks, recaps — grounded in <b className="tx2">this campaign</b>{grounding ? " and your uploaded rulebooks" : ""}.</p>
                <div className="suggest" style={{ justifyContent: "center", maxWidth: 560, margin: "0 auto" }}>
                  {prompts.map(p => <button key={p} onClick={() => send(p)}>{p}</button>)}
                </div>
              </div>
            ) : (
              <div className="chat">
                {msgs.map((m, i) => (
                  <div key={i} className={cls("msg", m.role)}>
                    <div className="av">{m.role === "ai" ? <Icon name="sparkles" size={15} /> : "You"}</div>
                    <div className="bub">{m.role === "ai" ? fmt(m.text) : m.text}</div>
                  </div>
                ))}
                {busy && <div className="msg ai"><div className="av"><Icon name="sparkles" size={15} /></div><div className="bub"><span className="typing"><i></i><i></i><i></i></span></div></div>}
              </div>
            )}
          </div>
          <div style={{ borderTop: "1px solid var(--line)", padding: 12 }}>
            {msgs.length > 0 && <div className="suggest mb10">{prompts.slice(0, 3).map(p => <button key={p} onClick={() => send(p)}>{p}</button>)}</div>}
            <div className="searchbar" style={{ padding: "8px 10px 8px 14px" }}>
              <input placeholder={`Message ${s.settings?.aiName || "the Loremaster"}…`} value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }} disabled={busy} />
              <Btn className="pri icon" onClick={() => send()} disabled={busy || !input.trim()}><Icon name="send" size={16} /></Btn>
            </div>
            <div className="tx3" style={{ fontSize: 11, marginTop: 7, textAlign: "center" }}>AI can be wrong — verify rulings against your books. Powered by your Claude plan.</div>
          </div>
        </div>

        {showSettings && <AISettings s={s} update={update} onClose={() => setShowSettings(false)} />}
      </div>
    );
  };

  function AISettings({ s, update, onClose }) {
    const cfg = s.settings || {};
    const ow = cfg.openwebui || { url: "", key: "", model: "" };
    return (
      <Modal title="AI Settings" icon="settings" onClose={onClose} footer={<Btn className="pri" onClick={onClose}>Done</Btn>}>
        <Field label="Loremaster name"><input className="input" value={cfg.aiName || ""} onChange={e => update(st => { st.settings.aiName = e.target.value; })} placeholder="The Loremaster" /></Field>
        <div className="mt14"><span className="lbl" style={{ display: "block", marginBottom: 6, fontFamily: "var(--mono)", fontSize: 10.5, letterSpacing: ".06em", textTransform: "uppercase", color: "var(--tx-3)" }}>Provider</span>
          <PillTabs tabs={[{ k: "claude", label: "Claude (built-in)" }, { k: "openwebui", label: "OpenWebUI" }]} active={cfg.aiProvider || "claude"} onChange={k => update(st => { st.settings.aiProvider = k; })} />
        </div>
        {(cfg.aiProvider || "claude") === "claude" ? (
          <p className="tx3 mt14" style={{ fontSize: 13, lineHeight: 1.6 }}>Uses the built-in Claude connection (your Claude plan, via the host). No key needed. Best for quick rules help and ideas.</p>
        ) : (
          <div className="col g10 mt14">
            <Field label="OpenWebUI URL"><input className="input mono" value={ow.url} onChange={e => update(st => { st.settings.openwebui = { ...ow, url: e.target.value }; })} placeholder="http://localhost:3000" /></Field>
            <Field label="API key (optional)"><input className="input mono" type="password" value={ow.key} onChange={e => update(st => { st.settings.openwebui = { ...ow, key: e.target.value }; })} placeholder="sk-…" /></Field>
            <Field label="Model"><input className="input mono" value={ow.model} onChange={e => update(st => { st.settings.openwebui = { ...ow, model: e.target.value }; })} placeholder="llama3, mistral…" /></Field>
            <p className="tx3" style={{ fontSize: 12.5, lineHeight: 1.6 }}>Your OpenWebUI must allow requests from this page (CORS). Calls go straight from your browser to your server — credentials stay local.</p>
          </div>
        )}
      </Modal>
    );
  }
})();
