// AI chat panel — opens from the header trigger. Stateless on the server: the
// full conversation lives here in localStorage and is sent on every turn.
const { useState: useStateAI, useEffect: useEffectAI, useRef: useRefAI,
        useCallback: useCallbackAI, useMemo: useMemoAI } = React;

const AI_STORAGE_KEY = "companyBrain.ai.chat";

function loadConversation() {
  try {
    const raw = localStorage.getItem(AI_STORAGE_KEY);
    if (!raw) return [];
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed : [];
  } catch { return []; }
}

function saveConversation(messages) {
  try { localStorage.setItem(AI_STORAGE_KEY, JSON.stringify(messages)); }
  catch { /* quota / private mode — soft-fail */ }
}

function clearConversation() {
  try { localStorage.removeItem(AI_STORAGE_KEY); } catch { /* ignore */ }
}

// Render a tiny markdown subset: bold, italic, inline code, and [text](url)
// links that navigate within the SPA via pushState + popstate. We deliberately
// do NOT render arbitrary HTML — Claude responses are plaintext-with-links.
function renderInlineMarkdown(text) {
  // Tokenise by patterns; preserve order and surrounding text.
  const out = [];
  let i = 0;
  const patterns = [
    { re: /\[([^\]]+)\]\(([^)]+)\)/g, kind: "link" },
    { re: /`([^`]+)`/g, kind: "code" },
    { re: /\*\*([^*]+)\*\*/g, kind: "bold" },
    { re: /\*([^*]+)\*/g, kind: "italic" },
  ];
  // Greedy scan: at each position pick the earliest-matching pattern.
  while (i < text.length) {
    let bestStart = -1, bestMatch = null, bestKind = null;
    for (const p of patterns) {
      p.re.lastIndex = i;
      const m = p.re.exec(text);
      if (m && (bestStart === -1 || m.index < bestStart)) {
        bestStart = m.index; bestMatch = m; bestKind = p.kind;
      }
    }
    if (!bestMatch) {
      out.push({ type: "text", text: text.slice(i) });
      break;
    }
    if (bestStart > i) out.push({ type: "text", text: text.slice(i, bestStart) });
    if (bestKind === "link") out.push({ type: "link", text: bestMatch[1], href: bestMatch[2] });
    else if (bestKind === "code") out.push({ type: "code", text: bestMatch[1] });
    else if (bestKind === "bold") out.push({ type: "bold", text: bestMatch[1] });
    else if (bestKind === "italic") out.push({ type: "italic", text: bestMatch[1] });
    i = bestStart + bestMatch[0].length;
  }
  return out;
}

function navigateInApp(href) {
  // Same-origin paths only — never let a relative model output escape.
  if (!href || /^[a-z]+:\/\//i.test(href) || href.startsWith("//")) return;
  if (!href.startsWith("/")) href = "/" + href;
  if (window.location.pathname !== href) window.history.pushState(null, "", href);
  window.dispatchEvent(new PopStateEvent("popstate"));
}

function MessageText({ text }) {
  // Split paragraphs on blank lines; render each as a <p>.
  const paragraphs = (text || "").split(/\n{2,}/);
  return (
    <>
      {paragraphs.map((para, pi) => (
        <p key={pi} className="ai-msg-p">
          {para.split("\n").map((line, li, arr) => (
            <React.Fragment key={li}>
              {renderInlineMarkdown(line).map((tok, ti) => {
                if (tok.type === "link") {
                  return (
                    <a key={ti} href={tok.href} className="ai-link"
                       onClick={(e) => { e.preventDefault(); navigateInApp(tok.href); }}>
                      {tok.text}
                    </a>
                  );
                }
                if (tok.type === "code")   return <code key={ti} className="ai-code">{tok.text}</code>;
                if (tok.type === "bold")   return <strong key={ti}>{tok.text}</strong>;
                if (tok.type === "italic") return <em key={ti}>{tok.text}</em>;
                return <React.Fragment key={ti}>{tok.text}</React.Fragment>;
              })}
              {li < arr.length - 1 && <br />}
            </React.Fragment>
          ))}
        </p>
      ))}
    </>
  );
}

function ToolChip({ call, t }) {
  const Icons = window.Icons;
  const [open, setOpen] = useStateAI(false);
  const status = call.error ? "error" : (call.done ? "done" : "running");
  const label = `${call.name}(${formatArgsBrief(call.args)})`;
  return (
    <div className={`ai-tool-chip ai-tool-${status}`}>
      <button type="button" className="ai-tool-summary" onClick={() => setOpen(o => !o)}>
        <Icons.Settings />
        <span className="ai-tool-name">{label}</span>
        <span className="ai-tool-status">
          {status === "running" && (t("ai_running") || "running…")}
          {status === "done" && (t("ai_done") || "done")}
          {status === "error" && (t("ai_failed") || "failed")}
        </span>
      </button>
      {open && (
        <pre className="ai-tool-detail">{JSON.stringify(call.result ?? call.args, null, 2)}</pre>
      )}
    </div>
  );
}

function formatArgsBrief(args) {
  if (!args || typeof args !== "object") return "";
  const keys = Object.keys(args).slice(0, 3);
  return keys.map(k => {
    const v = args[k];
    if (v == null) return `${k}=null`;
    if (typeof v === "string") return `${k}=${v.length > 24 ? v.slice(0, 24) + "…" : v}`;
    if (typeof v === "number" || typeof v === "boolean") return `${k}=${v}`;
    return `${k}=…`;
  }).join(", ");
}

window.AiChat = function AiChat({ open, onClose, t, lang }) {
  const Icons = window.Icons;
  const [messages, setMessages] = useStateAI(() => loadConversation());
  // Streaming: the in-flight assistant turn is held outside `messages` until
  // it finishes, so we don't persist partial state if the user navigates away.
  const [streaming, setStreaming] = useStateAI(null);
  // Shape: { text: string, toolCalls: [{ id, name, args, result, done, error }] }
  const [input, setInput] = useStateAI("");
  const [busy, setBusy] = useStateAI(false);
  const [error, setError] = useStateAI(null);
  const scrollRef = useRefAI(null);
  const textareaRef = useRefAI(null);
  const abortRef = useRefAI(null);

  // Persist the committed history; ignore the in-flight stream until it lands.
  useEffectAI(() => { saveConversation(messages); }, [messages]);

  // Auto-scroll to bottom whenever new content arrives.
  useEffectAI(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages, streaming, open]);

  // Focus the input when the panel opens.
  useEffectAI(() => {
    if (open && textareaRef.current) {
      setTimeout(() => textareaRef.current?.focus(), 50);
    }
  }, [open]);

  // Esc closes the panel; blocked while streaming so users don't interrupt mid-tool.
  useEffectAI(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape" && !busy) onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, busy, onClose]);

  const onClear = () => {
    if (busy) return;
    setMessages([]);
    clearConversation();
    setError(null);
  };

  const onSend = useCallbackAI(async () => {
    const text = input.trim();
    if (!text || busy) return;
    setError(null);

    const userMsg = { role: "user", content: text };
    const turn = [...messages, userMsg];
    setMessages(turn);
    setInput("");
    setBusy(true);

    let inflight = { text: "", toolCalls: [] };
    setStreaming(inflight);

    try {
      const stream = window.api.ai.ask(turn, lang);
      for await (const ev of stream) {
        if (ev.type === "text") {
          inflight = { ...inflight, text: inflight.text + (ev.text || "") };
          setStreaming(inflight);
        } else if (ev.type === "tool_start") {
          inflight = {
            ...inflight,
            toolCalls: [...inflight.toolCalls, {
              id: ev.id, name: ev.name, args: ev.args, result: null, done: false, error: false,
            }],
          };
          setStreaming(inflight);
        } else if (ev.type === "tool_result") {
          inflight = {
            ...inflight,
            toolCalls: inflight.toolCalls.map(tc =>
              tc.id === ev.id ? { ...tc, result: ev.result, done: true, error: !!ev.isError } : tc),
          };
          setStreaming(inflight);
        } else if (ev.type === "assistant_message") {
          // Server has finalized one assistant turn — the tool loop may continue
          // with more tool calls, so we don't commit yet. We DO need to record
          // the assistant content + paired tool_results so the next round of
          // tool calls passes Anthropic's history validation when the user
          // sends a follow-up later. Store on the in-flight object.
          inflight = { ...inflight, lastAssistantContent: ev.content };
        } else if (ev.type === "done") {
          // Commit final assistant message into the history. We assemble a
          // single assistant entry whose `content` is the LAST raw block array
          // emitted by Anthropic — this is what we replay to the API on next
          // turn. The local rendering uses inflight.text + toolCalls.
          break;
        } else if (ev.type === "error") {
          setError(ev.message || "Error");
          break;
        }
      }
    } catch (e) {
      setError(e.message || "Network error");
    } finally {
      // Commit the assistant turn. We store both:
      //   - the rendered text/toolCalls (UI state)
      //   - the raw Anthropic content array (sent to the server next turn)
      //
      // If the model produced multiple assistant_message events in this round
      // (one per tool-use cycle), only the LAST one — i.e. the final non-tool
      // assistant turn — is what Anthropic expects on replay. Any tool_use
      // blocks before that have already been "consumed" by tool_result blocks
      // and don't need to live in client history; the next user turn opens a
      // fresh round.
      setStreaming(null);
      setBusy(false);
      const finalContent = inflight.lastAssistantContent;
      // Defensive: if no assistant_message arrived (network failure mid-stream)
      // skip committing — the user can resend.
      if (finalContent) {
        setMessages(m => [...m, {
          role: "assistant",
          content: finalContent,
          // UI-only mirrors:
          _displayText: inflight.text,
          _toolCalls: inflight.toolCalls,
        }]);
      }
    }
  }, [input, busy, messages, lang]);

  const onKeyDown = (e) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      onSend();
    }
  };

  if (!open) return null;

  return (
    <div className="ai-panel-wrap">
      <div className="ai-panel-backdrop" onClick={() => !busy && onClose()} />
      <div className="ai-panel" role="dialog" aria-label={lang === "es" ? "Pregúntale a la IA" : "Ask AI"}>
        <div className="ai-panel-head">
          <div className="ai-panel-title">
            <Icons.Sparkles />
            <span>{lang === "es" ? "Pregúntale a la IA" : "Ask AI"}</span>
          </div>
          <div className="ai-panel-actions">
            <button type="button" className="ai-icon-btn" onClick={onClear} disabled={busy || messages.length === 0}
                    title={t("ai_clear") || "Clear and close"}>
              <Icons.Trash />
            </button>
            <button type="button" className="ai-icon-btn" onClick={() => !busy && onClose()} disabled={busy}
                    title={t("ai_close") || "Close"}>
              <Icons.X />
            </button>
          </div>
        </div>

        <div className="ai-panel-body" ref={scrollRef}>
          {messages.length === 0 && !streaming && (
            <div className="ai-empty">
              <Icons.Sparkles />
              <h4>{lang === "es" ? "Pregúntale lo que sea sobre tu workspace" : "Ask anything about your workspace"}</h4>
              <ul>
                <li>{lang === "es" ? "¿Cuántas facturas tengo pendientes?" : "How many invoices are pending?"}</li>
                <li>{lang === "es" ? "¿Qué tareas tengo en In Progress?" : "Which work items do I have in In Progress?"}</li>
                <li>{lang === "es" ? "Crea una factura para Acme por 1.500€" : "Create an invoice for Acme for €1,500"}</li>
              </ul>
            </div>
          )}

          {messages.map((m, i) => (
            <Message key={i} m={m} t={t} />
          ))}

          {streaming && (
            <div className="ai-msg ai-msg-assistant">
              {streaming.toolCalls.map((tc) => <ToolChip key={tc.id} call={tc} t={t} />)}
              {streaming.text && <MessageText text={streaming.text} />}
              {!streaming.text && streaming.toolCalls.length === 0 && (
                <span className="ai-typing">…</span>
              )}
            </div>
          )}

          {error && <div className="ai-error">{error}</div>}
        </div>

        <div className="ai-panel-foot">
          <textarea
            ref={textareaRef}
            className="ai-input"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={onKeyDown}
            placeholder={lang === "es"
              ? "Pregúntale algo a tu workspace…"
              : "Ask your workspace anything…"}
            rows={2}
            disabled={busy}
          />
          <button type="button" className="ai-send-btn" onClick={onSend} disabled={busy || !input.trim()}>
            <Icons.ArrowRight />
          </button>
        </div>
      </div>
    </div>
  );
};

function Message({ m, t }) {
  if (m.role === "user") {
    const text = typeof m.content === "string" ? m.content : "";
    return (
      <div className="ai-msg ai-msg-user">
        <MessageText text={text} />
      </div>
    );
  }
  // Assistant: prefer pre-rendered display text; fall back to extracting text
  // from the Anthropic content array.
  const displayText = m._displayText
    ?? (Array.isArray(m.content) ? m.content.filter(b => b?.type === "text").map(b => b.text).join("") : "");
  const toolCalls = m._toolCalls || [];
  return (
    <div className="ai-msg ai-msg-assistant">
      {toolCalls.map((tc) => <ToolChip key={tc.id} call={tc} t={t} />)}
      {displayText && <MessageText text={displayText} />}
    </div>
  );
}

Object.assign(window, { AiChat: window.AiChat });
