// The always-on qualifying chat. Objective is not "converse" — it's to fill the
// bug schema (see NS.SCHEMA_FIELDS) with the fewest turns, grade severity, then
// hand a drafted ticket to a one-tap confirm card. Live path talks to
// /api/support-agent (Claude tool-calling); creds-free path walks NS.offline.
const { useState, useEffect, useRef, useCallback } = React;

function Dots() {
  return (
    <span className="brand-dots" aria-hidden="true"><i></i><i></i><i></i></span>
  );
}

// Inline confirm card — the vendor-side structure surfaced for one-tap approval.
function ConfirmCard({ draft, dupe, onConfirm, onEdit, onSubscribe, busy }) {
  const [title, setTitle] = useState(draft.title || "");
  const s = window.NS.sev(draft.severity);

  if (dupe) {
    return (
      <div className="confirm-card">
        <div className="confirm-head">
          <div className="eyebrow">Looks familiar</div>
          <h3 className="confirm-title">We may already be on this</h3>
        </div>
        <div className="confirm-body">
          <div className="confirm-dupe">
            <strong>{dupe.key} · {dupe.title}</strong>
            <span style={{ color: "var(--text-2)", fontSize: 13 }}>{dupe.reason || "A very similar issue is already open."}</span>
          </div>
        </div>
        <div className="confirm-actions">
          <button className="btn btn-primary btn-sm" disabled={busy} onClick={() => onSubscribe(dupe)}>
            Follow the existing ticket
          </button>
          <button className="btn btn-ghost btn-sm" disabled={busy} onClick={() => onConfirm({ ...draft, title })}>
            No, mine is different — file it
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="confirm-card">
      <div className="confirm-head">
        <div className="eyebrow">Almost done</div>
        <h3 className="confirm-title">Here's what I'll log — sound right?</h3>
      </div>
      <div className="confirm-body">
        <div className="confirm-field">
          <label>Summary</label>
          <input value={title} onChange={(e) => setTitle(e.target.value)} />
        </div>
        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          <span className={"sev-chip sev-" + s.rank}><span className="sev-dot" />{s.label} impact</span>
          {draft.component && <span className="status-pill">{draft.component}</span>}
        </div>
        {draft.severityRationale && (
          <p style={{ margin: 0, fontSize: 12, color: "var(--text-3)" }}>{draft.severityRationale}</p>
        )}
        {draft.kbVerdict && draft.kbVerdict !== "new-bug" && draft.kbVerdict !== "unclear" && (
          <div className="confirm-dupe">
            <strong>{window.NS.kbVerdictLabel(draft.kbVerdict)}</strong>
            <span style={{ color: "var(--text-2)", fontSize: 13 }}>
              {draft.kbCitation ? `Matches: ${draft.kbCitation}` : "Cross-checked against the knowledge base."}
            </span>
          </div>
        )}
      </div>
      <div className="confirm-actions">
        <button className="btn btn-primary btn-sm" disabled={busy} onClick={() => onConfirm({ ...draft, title })}>
          {busy ? "Logging…" : "Yep, log it"}
        </button>
        <button className="btn btn-ghost btn-sm" disabled={busy} onClick={onEdit}>
          Add a detail
        </button>
      </div>
    </div>
  );
}

function AgentChat({ compact }) {
  const me = (window.NS.session && window.NS.session.identity()) || window.NS.ME;
  const [messages, setMessages] = useState([
    { role: "assistant", content: `Hey ${me.name.split(" ")[0]} — what's going on? Just tell me in your own words and I'll take care of it.` },
  ]);
  const [input, setInput] = useState("");
  const [typing, setTyping] = useState(false);
  const [fields, setFields] = useState({});
  const [draft, setDraft] = useState(null);
  const [dupe, setDupe] = useState(null);
  const [filing, setFiling] = useState(false);
  const [done, setDone] = useState(null);
  const [offline, setOffline] = useState(false);       // sticky once we detect no API
  const offlineStep = useRef(0);
  const bodyRef = useRef(null);

  useEffect(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
  }, [messages, typing, draft, done]);

  const push = (role, content) => setMessages((m) => [...m, { role, content }]);

  // ---- offline scripted intake -------------------------------------------
  // offlineStep is the index of the step the incoming userText ANSWERS. We
  // store that answer, advance, then ask the next step's question (or draft).
  function offlineTurn(userText, accum) {
    const steps = window.NS.offline.STEPS;
    const i = offlineStep.current;
    const next = { ...accum };
    if (userText != null && steps[i]) next[steps[i].key] = userText;
    const ni = i + 1;
    offlineStep.current = ni;
    setFields(next);
    if (ni < steps.length) {
      return { message: steps[ni].q, fields: next };
    }
    // enough — grade + draft. The first message doubles as the description.
    const sevCode = window.NS.offline.severity(next);
    const d = buildDraft({ ...next, actual: next.actual || next.title }, sevCode, "Graded from how much it's blocking.");
    return { draft: d, fields: next };
  }

  function buildDraft(f, severity, rationale) {
    return {
      title: f.title || "Issue reported via support agent",
      reproSteps: f.reproSteps || "",
      expected: f.expected || "",
      actual: f.actual || "",
      component: f.component || "",
      environment: f.environment || "",
      errorText: f.errorText || "",
      blastRadius: f.blastRadius || "",
      firstSeen: f.firstSeen || "",
      severity, severityRationale: rationale,
      kbVerdict: f.kbVerdict || "", kbCitation: f.kbCitation || "",
      reporter: me.email, account: me.account,
    };
  }

  const send = useCallback(async () => {
    const text = input.trim();
    if (!text || typing || filing || draft || done) return;
    setInput("");
    push("user", text);
    setTyping(true);

    // offline mode is sticky for the session once triggered
    if (offline) {
      const res = offlineTurn(text, fields);
      await wait(500);
      setTyping(false);
      if (res.draft) { setDraft(res.draft); }
      else push("assistant", res.message);
      return;
    }

    try {
      const nextMsgs = [...messages, { role: "user", content: text }];
      const r = await fetch("/api/v1/chat", {
        method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({
          messages: nextMsgs, fields,
          context: { product: "Nudge", reporter: me.email, account: me.account },
        }),
      });
      if (!r.ok) throw new Error("http " + r.status);
      const data = await r.json();
      setTyping(false);
      if (data.fields) setFields((prev) => ({ ...prev, ...data.fields }));
      if (data.draft) {
        setDraft(buildDraft({ ...fields, ...(data.fields || {}), ...data.draft },
          data.draft.severity, data.draft.severityRationale));
      } else if (data.message) {
        push("assistant", data.message);
      } else {
        throw new Error("empty");
      }
    } catch (e) {
      // No API — switch to offline scripted intake for the rest of the session.
      // The user's first line answers step 0 (the summary); we then ask step 1.
      setOffline(true);
      offlineStep.current = 0;
      const res = offlineTurn(text, {});
      await wait(300);
      setTyping(false);
      if (res.draft) setDraft(res.draft);
      else push("assistant", res.message);
    }
  }, [input, typing, filing, draft, done, offline, fields, messages]);

  async function handleConfirm(finalDraft) {
    setFiling(true);
    const res = await window.NS.tickets.createTicket(finalDraft);
    setFiling(false);
    if (res.duplicateOf) {
      setDupe({ key: res.duplicateOf.key, title: res.duplicateOf.title, reason: res.duplicateOf.reason });
      return;
    }
    setDraft(null);
    setDupe(null);
    const t = res.ticket;
    const s = window.NS.sev(t.severity);
    setDone(t);
    push("assistant",
      `Done — I've logged this as *${t.key}* at ${s.label.toLowerCase()} impact${res.live ? "" : " (demo)"}. ` +
      `Your team's been pinged in Slack. You'll hear back ${window.NS.nextUpdateLabel(t.severity)} — track it any time under "My tickets."`);
    if (window.fireToast) window.fireToast(`${t.key} filed · ${s.label} impact`);
  }

  function handleSubscribe(d) {
    setDupe(null); setDraft(null);
    setDone({ key: d.key, title: d.title, severity: "SEV3", subscribed: true });
    push("assistant", `You're now following *${d.key}*. I won't open a duplicate — you'll get the same updates under "My tickets."`);
    if (window.fireToast) window.fireToast(`Following ${d.key}`);
  }

  function startAnother() {
    setMessages([{ role: "assistant", content: "Sure — what else is going wrong?" }]);
    setFields({}); setDraft(null); setDupe(null); setDone(null);
    offlineStep.current = offline ? 0 : 0;
  }

  return (
    <div className="chat">
      <div className="chat-head">
        <span className="chat-avatar"><Dots /></span>
        <div className="chat-head-txt">
          <strong>Nudge support</strong>
          <span>{offline ? "Assistant · demo mode" : "Always on · replies in seconds"}</span>
        </div>
        {compact && window.__nswClose && (
          <button className="icon-btn nsw-close" onClick={window.__nswClose} aria-label="Close">✕</button>
        )}
      </div>

      <div className="chat-body" ref={bodyRef}>
        {messages.map((m, i) => (
          <div key={i} className={"msg " + (m.role === "user" ? "msg-user" : "msg-agent")}>
            <div className="msg-bubble" dangerouslySetInnerHTML={{ __html: mdLite(m.content) }} />
          </div>
        ))}
        {typing && (
          <div className="msg msg-agent"><div className="msg-bubble"><span className="msg-typing"><i /><i /><i /></span></div></div>
        )}
        {draft && (
          <div className="msg msg-agent" style={{ maxWidth: "100%" }}>
            <ConfirmCard draft={draft} dupe={dupe} busy={filing}
              onConfirm={handleConfirm}
              onSubscribe={handleSubscribe}
              onEdit={() => { setDraft(null); push("assistant", "No problem — what else should I add?"); }} />
          </div>
        )}
        {done && (
          <div className="msg msg-agent" style={{ maxWidth: "100%" }}>
            <button className="btn btn-ghost btn-sm" onClick={startAnother}>Report something else</button>
          </div>
        )}
      </div>

      <div className="chat-composer">
        <textarea className="chat-input" rows={1} value={input} placeholder="Describe the bug…"
          disabled={!!draft || !!done}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }} />
        <button className="btn btn-primary chat-send" onClick={send} disabled={!input.trim() || !!draft || !!done}>Send</button>
      </div>
    </div>
  );
}

function wait(ms) { return new Promise((r) => setTimeout(r, ms)); }

// tiny markdown: [links](url), *bold*, newlines. Escapes HTML first.
function mdLite(s) {
  const esc = String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
  return esc
    .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noreferrer">$1</a>')
    .replace(/\*([^*]+)\*/g, "<strong>$1</strong>")
    .replace(/\n/g, "<br/>");
}

window.AgentChat = AgentChat;
