// Explorer · Assistant — a docked, cross-page AI chat surface.
//
// A persistent bottom-right launcher opens a right-edge drawer that
// stays out of the data's way: on wide viewports the page yields room
// (body[data-asst-docked]) so nothing is occluded; below 1024px it
// overlays with a scrim. The transcript persists to localStorage, so
// the conversation survives navigation between Explorer pages and a
// refresh — the same store any page that mounts <ExplorerAssistant/>
// would read, which is how "accessible everywhere, one memory" works.
//
// Three jobs, in priority order (mirrors the empty-state prompt groups):
//   1. Educate   — explain the system + investing concepts.
//   2. Ask       — open Q&A / support.
//   3. Look up   — quick facts about the vault / portfolio.
//
// Answers are LIVE (window.claude.complete) and may return simple HTML
// with in-app deep links (portfolio.html, loans.html, …). All chrome is
// the Assistant primitive family in lib/onrail-ui.jsx (.asst-* in
// components.css) — this screen owns only state, persistence, and the
// model call (the orchestration the system intentionally does not own).

// ─────────────────────────── Suggested prompts ───────────────────────────
// Tweakable sets. "balanced" leads with one of each job; "learn" leans
// educational; "vault" leans live data lookup.

const ASST_PROMPT_SETS = {
  balanced: [
    { label: "What is the Onrail index, in plain terms?", q: "What is the Onrail index, in plain terms? Keep it to a couple of sentences." },
    { label: "Explain APY vs. the coupon on a loan", q: "Explain the difference between the portfolio APY and the coupon rate on an individual loan." },
    { label: "How is the book diversified right now?", q: "How is the portfolio diversified across loan types right now?" }
  ],
  learn: [
    { label: "What's a fix & flip loan?", q: "What is a fix & flip loan, and why does it carry a higher rate?" },
    { label: "What does DSCR mean?", q: "What does DSCR mean for a loan, and why does it matter to investors?" },
    { label: "How does on-chain settlement protect me?", q: "How does settling these loans on-chain in USDC protect or benefit an investor?" }
  ],
  vault: [
    { label: "What's the current portfolio APY?", q: "What is the current portfolio APY and how has it moved recently?" },
    { label: "Show me the newest originations", q: "What are the most recent loan originations on the book?" },
    { label: "How much principal is outstanding?", q: "How much principal is outstanding across the book, and across how many active loans?" }
  ]
};

// ─────────────────────────── Grounding primer ───────────────────────────
// Facts mirror the Overview aggregates 1:1 so live answers stay internally
// consistent with the rest of the app. Behavior + formatting contract for
// the model: concise, educational, simple HTML, in-app deep links.

const ASST_PRIMER = `You are the Onrail Explorer assistant — a calm, precise guide embedded in Onrail's on-chain index of real-estate-backed private credit loans. Onrail is live on Ethereum and settles in USDC.

YOUR JOB, in priority order:
1. Educate — explain how the system works and general investing concepts (APY, coupon, DSCR, bridge / fix & flip / ground-up / construction loans, overcollateralization, on-chain settlement, reserves).
2. Answer questions and provide support.
3. Look up quick facts about the vault / portfolio from the data below.

CURRENT BOOK (as shown on the Overview — use these exact figures):
- Portfolio APY: 9.12% (+0.05pp vs last month)
- Outstanding principal: $17.84M (+2.1% vs last month)
- Active loans: 33 (+3 new this month), across 16 markets
- Average loan size: $540K
- Composition by loan type: Fix & Flip 42%, Bridge 28%, DSCR 18%, Ground-Up 8%, Construction 4%
- Newest originations: CB-NY-AA01 (Bridge, $154K, 8.22%); PL-WA-AA02 (Fix & Flip, $263K, 11.93%); SF-CA-AA01 (DSCR, $335K, 9.66%)

EXPLORER PAGES you can deep-link to with a relative anchor:
- overview.html (home), portfolio.html (composition + breakdowns), loans.html (every active loan), transactions.html (the on-chain ledger)
When a page would help, link to it inline, e.g. <a href="portfolio.html">the portfolio breakdown</a>.

RULES:
- Be concise — usually 2–4 short sentences or a short list. This is a side panel, not an essay.
- Respond in MINIMAL HTML only: <p>, <strong>, <ul>, <li>, <a href="...">, <code>. No headings, no markdown, no inline styles.
- Educational only — never give personalized financial advice or predict returns. If asked, gently decline and explain the concept instead.
- If something isn't in the data above, say you don't have that figure and point to the page where it lives.`;

// Conversation seed — primer + an ack, so haiku adopts the role.
const ASST_SEED = [
  { role: "user", content: ASST_PRIMER },
  { role: "assistant", content: "<p>Understood — I'm the Onrail Explorer assistant. Ask me how the index works, what a metric means, or for a quick figure from the book.</p>" }
];

const ASST_STORE_KEY = "onrail-explorer-assistant-v1";

// ─────────────────────────── HTML sanitizer ───────────────────────────
// The model returns simple HTML; we whitelist tags + scrub hrefs before
// rendering. Unknown tags unwrap to their text. Links: relative *.html /
// in-page anchors stay same-tab; absolute http(s) opens a new tab.

const ASST_ALLOWED = { P: 1, STRONG: 1, EM: 1, B: 1, I: 1, UL: 1, OL: 1, LI: 1, CODE: 1, BR: 1, A: 1 };
const ASST_PAGES = /^(overview|portfolio|loans|transactions)\.html(#[\w-]+)?$/i;

function asstSanitize(raw) {
  let html = String(raw || "").trim();
  // Plain-text fallback: no tags → wrap paragraphs on blank lines.
  if (!/<[a-z][\s\S]*>/i.test(html)) {
    html = html
      .split(/\n{2,}/)
      .map((p) => `<p>${p.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/\n/g, "<br>")}</p>`)
      .join("");
  }
  const doc = new DOMParser().parseFromString(`<div>${html}</div>`, "text/html");
  const root = doc.body.firstElementChild;
  const walk = (node) => {
    [...node.childNodes].forEach((child) => {
      if (child.nodeType === 3) return; // text
      if (child.nodeType !== 1) { child.remove(); return; }
      const tag = child.tagName;
      if (!ASST_ALLOWED[tag]) {
        // Unwrap unknown element: replace with its (sanitized) children.
        walk(child);
        while (child.firstChild) node.insertBefore(child.firstChild, child);
        child.remove();
        return;
      }
      // Scrub attributes.
      if (tag === "A") {
        const href = child.getAttribute("href") || "";
        [...child.attributes].forEach((a) => child.removeAttribute(a.name));
        if (ASST_PAGES.test(href) || /^#[\w-]+$/.test(href)) {
          child.setAttribute("href", href);
        } else if (/^https?:\/\//i.test(href)) {
          child.setAttribute("href", href);
          child.setAttribute("target", "_blank");
          child.setAttribute("rel", "noopener noreferrer");
        } else {
          child.setAttribute("href", "#");
        }
      } else {
        [...child.attributes].forEach((a) => child.removeAttribute(a.name));
      }
      walk(child);
    });
  };
  walk(root);
  return root.innerHTML;
}

// ─────────────────────────── The assistant ───────────────────────────
// State + persistence + the live model call. Presentation is entirely
// the Assistant primitive family.

function ExplorerAssistant({ surface = "solid", promptSet = "balanced", launcher = "labeled" }) {
  const [open, setOpen] = React.useState(false);
  const [messages, setMessages] = React.useState([]); // visible turns only
  const [input, setInput] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const bodyRef = React.useRef(null);

  // ── Restore from store (cross-navigation memory) ──
  React.useEffect(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(ASST_STORE_KEY) || "null");
      if (saved && Array.isArray(saved.messages)) setMessages(saved.messages);
      if (saved && saved.open) setOpen(true);
    } catch (e) { /* ignore */ }
  }, []);

  // ── Persist ──
  React.useEffect(() => {
    try { localStorage.setItem(ASST_STORE_KEY, JSON.stringify({ messages, open })); } catch (e) {}
  }, [messages, open]);

  // ── Wide-viewport: yield page room so data isn't covered ──
  React.useEffect(() => {
    const apply = () => {
      const docked = open && window.innerWidth >= 1024;
      document.body.toggleAttribute("data-asst-docked", docked);
    };
    apply();
    window.addEventListener("resize", apply);
    return () => { window.removeEventListener("resize", apply); document.body.removeAttribute("data-asst-docked"); };
  }, [open]);

  // ── Auto-scroll transcript to the latest turn ──
  React.useEffect(() => {
    const el = bodyRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages, busy, open]);

  // ── Esc closes ──
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open]);

  // ── Live turn ──
  const send = React.useCallback(async (text) => {
    const content = String(text || "").trim();
    if (!content || busy) return;
    const next = [...messages, { role: "user", content }];
    setMessages(next);
    setInput("");
    setBusy(true);
    try {
      const history = next.map((m) => ({ role: m.role, content: m.content }));
      let reply;
      if (window.claude && window.claude.complete) {
        reply = await window.claude.complete({ messages: [...ASST_SEED, ...history] });
      } else {
        reply = "<p>I'm offline in this preview, but here's the idea: ask me to explain a metric (try <strong>“what does DSCR mean?”</strong>) or for a figure from <a href=\"portfolio.html\">the portfolio</a>.</p>";
      }
      setMessages((cur) => [...cur, { role: "assistant", content: asstSanitize(reply) }]);
    } catch (e) {
      setMessages((cur) => [...cur, { role: "assistant", content: "<p>Something went wrong reaching the assistant. Try again in a moment.</p>" }]);
    } finally {
      setBusy(false);
    }
  }, [messages, busy]);

  // ── Contextual asks: any page element can open + submit a prompt ──
  React.useEffect(() => {
    const onAsk = (e) => {
      const q = e.detail && e.detail.prompt;
      setOpen(true);
      if (q) setTimeout(() => send(q), 280); // let the drawer open first
    };
    window.addEventListener("onrail:ask", onAsk);
    return () => window.removeEventListener("onrail:ask", onAsk);
  }, [send]);

  const newChat = () => { setMessages([]); setInput(""); };

  const prompts = ASST_PROMPT_SETS[promptSet] || ASST_PROMPT_SETS.balanced;
  const empty = messages.length === 0;

  return (
    <>
      <AssistantLauncher
        label="Ask Onrail"
        compact={launcher === "compact"}
        hidden={open}
        aria-expanded={open}
        onClick={() => setOpen(true)}
      />

      <AssistantScrim open={open} onClick={() => setOpen(false)} />

      <AssistantPanel open={open} surface={surface} label="Onrail assistant">
        <AssistantHead
          title="Onrail Assistant"
          subtitle="Explore · learn · ask"
          onNew={newChat}
          onClose={() => setOpen(false)}
        />

        <AssistantBody ref={bodyRef}>
          {empty ? (
            <AssistantIntro lede="I'm here to help you understand the Onrail index — how it works, what the numbers mean, and anything you want to ask about the book.">
              <AssistantPrompts label="Try asking">
                {prompts.map((p) => (
                  <AssistantPrompt key={p.label} onClick={() => send(p.q)}>{p.label}</AssistantPrompt>
                ))}
              </AssistantPrompts>
            </AssistantIntro>
          ) : (
            messages.map((m, i) =>
              m.role === "user"
                ? <AssistantMessage key={i} from="user">{m.content}</AssistantMessage>
                : <AssistantMessage key={i} from="assistant" html={m.content} />
            )
          )}
          {busy && <AssistantTyping />}
        </AssistantBody>

        <AssistantComposer
          value={input}
          onChange={setInput}
          onSubmit={send}
          placeholder="Ask about the index, a metric, or a loan…"
          disclaimer="Educational information, not financial advice."
          busy={busy}
        />
      </AssistantPanel>
    </>
  );
}

window.ExplorerAssistant = ExplorerAssistant;
