// chat.jsx — coach chat pane. Streams Server-Sent Events from /api/chat.
//
// We can't use the browser's EventSource because EventSource has no way to
// attach an Authorization header. Instead we POST with fetch, then read the
// response.body as a ReadableStream and parse the SSE frames ourselves.
//
// SSE frame format:
//   event: <type>\n
//   data: <json>\n
//   \n
//
// Event types we handle: meta, delta, tool_use, tool_result, done, error.

const { useEffect, useState, useRef, useCallback } = React;

const _auth = window.CoachAuth;

async function* parseSseStream(response) {
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buf = '';
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buf += decoder.decode(value, { stream: true });
    let idx;
    while ((idx = buf.indexOf('\n\n')) !== -1) {
      const frame = buf.slice(0, idx);
      buf = buf.slice(idx + 2);
      if (!frame.trim()) continue;
      let event = 'message';
      let data = '';
      for (const line of frame.split('\n')) {
        if (line.startsWith('event: ')) event = line.slice(7).trim();
        else if (line.startsWith('data: ')) data += line.slice(6);
      }
      let parsed = null;
      try { parsed = data ? JSON.parse(data) : null; }
      catch (_e) { parsed = { _raw: data }; }
      yield { event, data: parsed };
    }
  }
}

function ToolCard({ tool, args, result }) {
  // Render the tool call inline with the conversation. Args + result are
  // both shown small + collapsed-ish so they don't dominate the chat.
  let resultObj = null;
  try { resultObj = result ? JSON.parse(result) : null; } catch (_e) { /* not json */ }
  return (
    <div className="chat-tool">
      <div className="chat-tool-hd">
        <span className="chat-tool-tag">tool</span>
        <span className="chat-tool-name">{tool}</span>
        {args && Object.keys(args).length > 0 && (
          <span className="chat-tool-args">{JSON.stringify(args)}</span>
        )}
      </div>
      {result != null && (
        <pre className="chat-tool-result">
          {resultObj ? JSON.stringify(resultObj, null, 2) : result}
        </pre>
      )}
    </div>
  );
}

function MessageBubble({ message }) {
  // `message.content` is always an array of content blocks (matches what
  // the backend persists). Render text blocks as markdown; tool_use /
  // tool_result blocks become ToolCards interleaved in order.
  const blocks = Array.isArray(message.content)
    ? message.content
    : [{ type: 'text', text: typeof message.content === 'string' ? message.content : JSON.stringify(message.content) }];

  // Pair tool_use with the matching tool_result by tool_use_id.
  const resultByUseId = {};
  for (const b of blocks) {
    if (b.type === 'tool_result' && b.tool_use_id) {
      resultByUseId[b.tool_use_id] = b.content;
    }
  }

  return (
    <div className={`chat-msg chat-msg-${message.role}`}>
      <div className="chat-msg-role">{message.role}</div>
      <div className="chat-msg-body">
        {blocks.map((b, i) => {
          if (b.type === 'text') {
            const html = window.marked ? window.marked.parse(b.text || '') : (b.text || '');
            return <div key={i} className="chat-prose" dangerouslySetInnerHTML={{ __html: html }} />;
          }
          if (b.type === 'tool_use') {
            return <ToolCard key={i}
              tool={b.tool || b.name}
              args={b.args || b.input}
              result={resultByUseId[b.id] || null} />;
          }
          if (b.type === 'tool_result' && !blocks.some((x) => x.type === 'tool_use' && x.id === b.tool_use_id)) {
            return <ToolCard key={i} tool="result" args={null} result={b.content} />;
          }
          return null;
        })}
      </div>
    </div>
  );
}

function ChatCard() {
  const [messages, setMessages] = useState([]);
  const [streaming, setStreaming] = useState(null);
  const [input, setInput] = useState('');
  const [pending, setPending] = useState(false);
  const [error, setError] = useState(null);
  const scrollRef = useRef(null);

  const refresh = useCallback(async () => {
    try {
      const r = await _auth.authedFetch('/api/chat/messages');
      setMessages(r.messages || []);
    } catch (e) {
      setError(e.message);
    }
  }, []);

  useEffect(() => { refresh(); }, [refresh]);

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

  const send = async (e) => {
    e.preventDefault();
    if (!input.trim() || pending) return;
    setError(null);
    setPending(true);

    const userMsg = { role: 'user', content: [{ type: 'text', text: input }] };
    setMessages((prev) => [...prev, userMsg]);
    const text = input;
    setInput('');

    const live = { role: 'assistant', content: [] };
    setStreaming(live);

    try {
      const client = _auth.getClient();
      const { data } = await client.auth.getSession();
      const token = data.session?.access_token;
      const resp = await fetch('/api/chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(token ? { Authorization: 'Bearer ' + token } : {}),
        },
        body: JSON.stringify({ message: text }),
      });
      if (!resp.ok) {
        let detail = 'http_' + resp.status;
        try { const j = await resp.json(); detail = j.detail || detail; } catch (_e) {}
        throw new Error(detail);
      }

      let currentText = '';
      for await (const { event, data: evt } of parseSseStream(resp)) {
        if (!evt) continue;
        if (event === 'delta') {
          currentText += evt.text || '';
          const blocks = [...live.content];
          const last = blocks[blocks.length - 1];
          if (last && last.type === 'text') {
            blocks[blocks.length - 1] = { type: 'text', text: currentText };
          } else {
            blocks.push({ type: 'text', text: currentText });
          }
          live.content = blocks;
          setStreaming({ ...live });
        } else if (event === 'tool_use') {
          live.content = [
            ...live.content,
            { type: 'tool_use', id: evt.tool_use_id || ('use_' + Date.now()),
              tool: evt.tool, args: evt.args },
          ];
          currentText = '';
          setStreaming({ ...live });
        } else if (event === 'tool_result') {
          live.content = [
            ...live.content,
            { type: 'tool_result', tool_use_id: evt.tool_use_id, content: evt.content },
          ];
          setStreaming({ ...live });
        } else if (event === 'error') {
          throw new Error((evt.error || 'error') + ': ' + (evt.detail || ''));
        }
      }

      setMessages((prev) => [...prev, live]);
      setStreaming(null);
    } catch (e) {
      setError(e.message);
      setStreaming(null);
    } finally {
      setPending(false);
    }
  };

  const newConversation = async () => {
    if (!confirm('Start a new conversation? The current thread will be archived.')) return;
    try {
      await _auth.authedFetch('/api/chat/archive', { method: 'POST' });
      setMessages([]);
      await refresh();
    } catch (e) {
      setError(e.message);
    }
  };

  return (
    <section className="card chat-card">
      <div className="card-hd">
        <span>coach chat</span>
        <button className="btn-ghost small" onClick={newConversation}
                disabled={pending || messages.length === 0}>
          new thread
        </button>
      </div>
      <div className="chat-scroll" ref={scrollRef}>
        {messages.length === 0 && !streaming && (
          <div className="chat-empty muted small">
            Ask anything — try "how should I train today given my recent metrics?"
          </div>
        )}
        {messages.map((m, i) => <MessageBubble key={i} message={m} />)}
        {streaming && <MessageBubble message={streaming} />}
      </div>
      {error && <div className="card-err chat-err">{error}</div>}
      <form onSubmit={send} className="chat-compose">
        <textarea
          rows="2"
          placeholder="ask the coach… (⌘/Ctrl+Enter to send)"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) send(e);
          }}
          disabled={pending}
        />
        <button className="btn-primary chat-send" type="submit"
                disabled={pending || !input.trim()}>
          {pending ? '…' : 'send'}
        </button>
      </form>
    </section>
  );
}

window.CoachChat = { ChatCard };
