/* MonthEndIQ — Q&A Copilot  (localStorage-persisted chat history)
 *
 * Storage design:
 *   Primary key:  monthendiq_chat_f_<encoded-filename>   (stable across session IDs)
 *   Legacy key:   monthendiq_chat_<sessionId>            (migrated on first load)
 *
 *   Keying by filename (not sessionId) means chat history survives backend
 *   restarts (Render free tier) and re-uploads of the same file — the session
 *   ID changes but the filename key stays the same.
 *
 * Lifecycle:
 *   LOAD  — useState lazy initialiser reads localStorage on every mount.
 *            Tries the filename key first; if empty, checks the legacy session
 *            key and migrates it across.
 *
 *   SAVE  — useEffect([msgs]) writes after every message update (filename key).
 *            Also fires a best-effort POST /api/chat/{sessionId} for in-session
 *            server persistence (silent failure — localStorage is the source of truth).
 *
 *   CLEAR — clearChat() removes both keys and resets to the welcome message.
 */
const { useState: useStateQna, useRef: useRefQna, useEffect: useEffectQna, useCallback: useCallbackQna } = React;

/* ── Suggested questions ─────────────────────────────────────────────────── */
const SUGGESTIONS_MOM = [
  "Why did operating profit change this month?",
  "What were the largest positive profit drivers?",
  "What were the largest negative profit drivers?",
  "Which revenue accounts moved the most?",
  "Which cost categories changed the most?",
  "Summarise this month for a Finance Director.",
  "What should I investigate further?",
];

const SUGGESTIONS_BVA = [
  "Why did profit differ from budget?",
  "Which areas are most over budget?",
  "What were the largest favourable budget variances?",
  "What were the largest adverse budget variances?",
  "Which cost categories exceeded budget?",
  "Summarise budget performance for a Finance Director.",
  "What should I investigate first?",
];

const SUGGESTIONS_NHS_GP = [
  "What is our income per patient this month?",
  "What is our surplus per patient and how does it compare to the £5–15 target?",
  "How is our ARRS utilisation — are we getting full value from our allocation?",
  "What is our QOF achievement and is there a gap to entitlement?",
  "How does locum spend compare to our permanent clinical staff costs?",
  "What is the surplus per WTE GP partner?",
  "Summarise this month's performance for a GP partner meeting.",
];

/* ── Storage helpers ─────────────────────────────────────────────────────── */
const CHAT_KEY_BY_FILE    = (name) => `monthendiq_chat_f_${encodeURIComponent((name || "").trim())}`;
const CHAT_KEY_BY_SESSION = (sid)  => `monthendiq_chat_${sid}`;  // legacy

function _tryReadKey(key) {
  try {
    const raw = key && localStorage.getItem(key);
    if (!raw) return null;
    const turns = JSON.parse(raw);
    return Array.isArray(turns) ? turns : null;
  } catch { return null; }
}

function chatLoad(sessionId, fileName) {
  const fileKey    = fileName ? CHAT_KEY_BY_FILE(fileName) : null;
  const sessionKey = CHAT_KEY_BY_SESSION(sessionId);

  // Prefer filename-keyed (stable across sessions)
  const fromFile = fileKey ? _tryReadKey(fileKey) : null;
  if (fromFile && fromFile.length > 0) return fromFile;

  // Fall back to legacy session key and migrate
  const fromSession = _tryReadKey(sessionKey);
  if (fromSession && fromSession.length > 0) {
    if (fileKey) {
      try { localStorage.setItem(fileKey, JSON.stringify(fromSession)); } catch {}
    }
    try { localStorage.removeItem(sessionKey); } catch {}
    return fromSession;
  }

  return [];
}

function chatSave(sessionId, fileName, msgs) {
  const turns = msgs.slice(1);  // index 0 is always the welcome message — never stored
  const key   = fileName ? CHAT_KEY_BY_FILE(fileName) : CHAT_KEY_BY_SESSION(sessionId);
  try {
    localStorage.setItem(key, JSON.stringify(turns));
  } catch (err) {
    console.warn("[Chat] localStorage quota exceeded:", err);
  }
}

function chatClear(sessionId, fileName) {
  try {
    if (fileName) localStorage.removeItem(CHAT_KEY_BY_FILE(fileName));
    localStorage.removeItem(CHAT_KEY_BY_SESSION(sessionId));
  } catch {}
}

/* ── Misc helpers ────────────────────────────────────────────────────────── */
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function stripHtml(html) {
  return html
    .replace(/<br\s*\/?>/gi, "\n")
    .replace(/<[^>]+>/g, "")
    .replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"').replace(/&#39;/g, "'")
    .trim();
}

function makeWelcome(periodLabel, analysisType, sector) {
  const isBvA   = analysisType === "budget_vs_actual";
  const isNhsGp = sector === "nhs_gp";
  const period  = periodLabel ? "<b>" + periodLabel + "</b>" : "your P&amp;L";
  const contextLine = isBvA
    ? "I have your budget vs actual data loaded for " + period + "."
    : isNhsGp
      ? "I have your NHS GP practice P&amp;L loaded for " + period + ". I understand GMS contracts, QOF, ARRS, and NHS GP benchmarks."
      : "I have your month-on-month P&amp;L loaded for " + period + ".";
  const bullets = isBvA
    ? "<li>Why did profit differ from budget?</li><li>Which areas are most over budget?</li><li>Summarise budget performance for a Finance Director</li>"
    : isNhsGp
      ? "<li>What is our income per patient this month?</li><li>How is our ARRS utilisation?</li><li>What is our surplus per WTE partner?</li>"
      : "<li>Why did operating profit change this month?</li><li>What were the biggest cost movements?</li><li>Summarise this period for a Finance Director</li>";
  return {
    who: "ai",
    html:
      contextLine +
      " Here are some things you can ask me:" +
      "<ul style=\"margin:8px 0 0;padding-left:18px;line-height:1.8\">" +
      bullets +
      "<li>What should I investigate further?</li>" +
      "</ul>",
  };
}

/* ── Component ───────────────────────────────────────────────────────────── */
function QnaCopilot({ sessionId, fileName, period, periodMode, selectedPeriod, analysisType, sector, prefillQuestion, onPrefillConsumed }) {
  const { Icon } = window;

  const userInitials = (() => {
    try {
      const n = localStorage.getItem("meiq_firm_name") || "";
      return n.trim() ? n.trim().split(/\s+/).slice(0, 2).map(w => w[0].toUpperCase()).join("") : "FP";
    } catch { return "FP"; }
  })();

  /*
   * LOAD on mount — lazy useState initialiser.
   *
   * React calls this function exactly once per component instance, synchronously,
   * before the first render. Because QnaCopilot is conditionally rendered in App
   * (it unmounts when the user navigates away), this runs fresh on every return,
   * reliably reading whatever was saved to localStorage.
   *
   * NOTE: There is intentionally NO useEffect([sessionId]) that also calls setMsgs.
   * That pattern (present in the previous version) caused a second setMsgs call on
   * every mount which, combined with the saveTurns auto-delete, could wipe history.
   */
  const [msgs, setMsgs] = useStateQna(() => {
    const saved = chatLoad(sessionId, fileName);
    return [makeWelcome(period?.label, analysisType, sector), ...saved];
  });

  const [text, setText]       = useStateQna("");
  const [loading, setLoading] = useStateQna(false);
  const scrollRef             = useRefQna(null);
  const mountedRef            = useRefQna(false);

  /*
   * SAVE on every message update — but skip the very first render.
   * On mount, msgs is initialised from localStorage (via the lazy init above).
   * Saving immediately would write that same data back (harmless) OR, if the
   * session was just restored and the component mounts with only the welcome
   * message before chat loads, would overwrite real history with [].
   * After the first render, every change is user-driven and should be saved.
   */
  useEffectQna(() => {
    if (!sessionId) return;
    if (!mountedRef.current) { mountedRef.current = true; return; }
    chatSave(sessionId, fileName, msgs);
  }, [msgs, sessionId]);

  /* Scroll to bottom after each message */
  useEffectQna(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [msgs]);

  /* ── Clear chat ──────────────────────────────────────────────────────── */
  const clearChat = () => {
    chatClear(sessionId, fileName);
    setMsgs([makeWelcome(period?.label, analysisType, sector)]);
    // Clear server-side history too (best-effort)
    fetch(apiUrl("/api/chat/" + sessionId), {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ turns: [] }),
    }).catch(() => {});
  };

  /* ── Markdown → HTML (applied once stream is complete) ──────────────── */
  const mdToHtml = (raw) =>
    raw
      .replace(/\*\*(.+?)\*\*/gs, "<b>$1</b>")
      .replace(/\*(.+?)\*/gs,     "<i>$1</i>")
      .replace(/\n\n/g, "<br><br>")
      .replace(/\n/g,   "<br>");

  /* ── Send question (streaming via SSE) ──────────────────────────────── */
  const ask = async (q) => {
    if (!q.trim() || loading) return;
    setMsgs((m) => [...m, { who: "user", html: escapeHtml(q) }]);
    setText("");
    setLoading(true);

    /* Last 6 exchanges (12 messages) as AI context; skip welcome message */
    const history = msgs
      .slice(1)
      .slice(-12)
      .map((m) => ({
        role:    m.who === "user" ? "user" : "assistant",
        content: stripHtml(m.html),
      }));

    /* Add an empty streaming placeholder immediately */
    setMsgs((m) => [...m, { who: "ai", html: "", streaming: true }]);

    try {
      const res = await fetch(apiUrl("/api/ask-stream/" + sessionId), {
        method:  "POST",
        headers: { "Content-Type": "application/json" },
        body:    JSON.stringify({
          question:     q,
          period:       period?.label || null,
          mode:         periodMode    || "monthly",
          history,
          currency_sym: (() => { try { return localStorage.getItem("meiq_currency_sym") || "£"; } catch { return "£"; } })(),
        }),
      });

      if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);

      const reader  = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "", rawText = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";

        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          const raw = line.slice(6).trim();
          if (raw === "[DONE]") break;
          try {
            const { delta } = JSON.parse(raw);
            if (delta) {
              rawText += delta;
              const partialHtml = rawText
                .replace(/\n\n/g, "<br><br>")
                .replace(/\n/g,   "<br>");
              setMsgs((m) => {
                const next = [...m];
                next[next.length - 1] = { who: "ai", html: partialHtml, streaming: true };
                return next;
              });
            }
          } catch { /* skip malformed SSE line */ }
        }
      }

      /* Apply full markdown conversion once stream ends */
      const finalHtml = mdToHtml(rawText) ||
        "I couldn't generate an answer — please try again.";
      const aiMsg = { who: "ai", html: finalHtml, streaming: false };
      setMsgs((m) => {
        const next = [...m];
        next[next.length - 1] = aiMsg;
        return next;
      });

      /* Best-effort server sync */
      const allTurns = [...msgs.slice(1), { who: "user", html: escapeHtml(q) }, aiMsg];
      fetch(apiUrl("/api/chat/" + sessionId), {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ turns: allTurns }),
      }).catch(() => {});
    } catch {
      setMsgs((m) => {
        const next = [...m];
        next[next.length - 1] = {
          who: "ai",
          html: "Sorry, there was a network error. Please check the server is running and try again.",
          streaming: false,
        };
        return next;
      });
    } finally {
      setLoading(false);
    }
  };

  useEffectQna(() => {
    if (prefillQuestion) {
      ask(prefillQuestion);
      onPrefillConsumed && onPrefillConsumed();
    }
  }, [prefillQuestion]);

  const [copiedIdx, setCopiedIdx] = useStateQna(null);
  const hasHistory = msgs.length > 1;

  const copyMsg = (html, idx) => {
    const div = document.createElement("div");
    div.innerHTML = html;
    const text = (div.innerText || div.textContent || "").trim();
    navigator.clipboard.writeText(text).catch(() => {});
    setCopiedIdx(idx);
    setTimeout(() => setCopiedIdx(null), 2000);
  };

  /* ── Render ──────────────────────────────────────────────────────────── */
  return (
    <div className="content-inner" style={{ height: "calc(100vh - 64px)", display: "flex", flexDirection: "column" }}>
      <div className="qna" style={{ flex: 1 }}>

        {/* Clear chat — only visible once conversation has started */}
        {hasHistory && (
          <div style={{
            display: "flex", justifyContent: "flex-end", alignItems: "center",
            padding: "8px 4px 6px",
            borderBottom: "1px solid var(--border)",
          }}>
            <button
              onClick={clearChat}
              disabled={loading}
              style={{
                display: "inline-flex", alignItems: "center", gap: 6,
                font: "var(--text-body-strong)", fontSize: 12,
                color: "var(--fg-3)", background: "transparent", border: "none",
                cursor: "pointer", padding: "4px 8px", borderRadius: "var(--radius-sm)",
              }}
              onMouseOver={(e) => { e.currentTarget.style.background = "var(--surface-2)"; e.currentTarget.style.color = "var(--ink)"; }}
              onMouseOut ={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--fg-3)"; }}
              title="Clear conversation — history will be deleted from this browser"
            >
              <Icon name="trash-2" size={13} />
              Clear chat
            </button>
          </div>
        )}

        {/* Message thread */}
        <div className="qna-scroll" ref={scrollRef}>
          {msgs.map((m, i) => {
            const isLiveStream = m.streaming && i === msgs.length - 1;
            const isCopied = copiedIdx === i;
            return (
              <div key={i} className={"msg " + m.who} style={{ position: "relative" }}>
                <div className="av">
                  {m.who === "ai" ? <Icon name="sparkles" size={16} /> : userInitials}
                </div>
                <div style={{ flex: 1, minWidth: 0, position: "relative" }}>
                  <div
                    className="bubble"
                    dangerouslySetInnerHTML={{
                      __html: m.html + (isLiveStream
                        ? '<span class="stream-cursor">▍</span>'
                        : ""),
                    }}
                  />
                  {/* Copy button — AI messages only, not while streaming */}
                  {m.who === "ai" && !m.streaming && m.html && (
                    <button
                      onClick={() => copyMsg(m.html, i)}
                      title="Copy to clipboard"
                      className="msg-copy-btn"
                      style={{
                        position: "absolute", top: 6, right: 2,
                        display: "inline-flex", alignItems: "center", gap: 4,
                        padding: "3px 7px", borderRadius: "var(--radius-xs)",
                        border: "1px solid var(--border-strong)",
                        background: "var(--surface)", color: isCopied ? "var(--favourable-text)" : "var(--fg-3)",
                        font: "var(--text-label)", fontSize: 10.5, cursor: "pointer",
                        opacity: 0, transition: "opacity .15s, color .15s",
                      }}
                    >
                      <Icon name={isCopied ? "check" : "copy"} size={11} />
                      {isCopied ? "Copied" : "Copy"}
                    </button>
                  )}
                </div>
              </div>
            );
          })}

          {/* Spinner only shown while waiting for the first token */}
          {loading && !msgs[msgs.length - 1]?.streaming && (
            <div className="msg ai">
              <div className="av"><Icon name="sparkles" size={16} /></div>
              <div className="bubble" style={{ display: "flex", gap: 8, alignItems: "center", color: "var(--fg-3)" }}>
                <div className="spinner" style={{ width: 14, height: 14 }} />
                Analysing your P&amp;L…
              </div>
            </div>
          )}
        </div>

        {/* Suggested questions */}
        <div className="qna-suggest">
          {(analysisType === "budget_vs_actual" ? SUGGESTIONS_BVA : sector === "nhs_gp" ? SUGGESTIONS_NHS_GP : SUGGESTIONS_MOM).map((s) => (
            <button key={s} className="suggest" onClick={() => ask(s)} disabled={loading}>
              {s}
            </button>
          ))}
        </div>

        {/* Composer */}
        <div className="composer">
          <input
            value={text}
            placeholder="Ask MonthEndIQ about your P&amp;L…"
            onChange={(e) => setText(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); ask(text); }
            }}
            disabled={loading}
          />
          <button className="send" aria-label="Send question" title="Send question"
            onClick={() => ask(text)} disabled={loading || !text.trim()}>
            <Icon name="arrow-up" size={18} color="#fff" />
          </button>
        </div>

      </div>
    </div>
  );
}
Object.assign(window, { QnaCopilot });
