/* global React */

// Shared client for docket pages that are gaining corpus chat. Existing pages
// may migrate to this component over time; the API and corpora remain owned by
// the personal-site deployment.
(function attachCommentFileChat() {
  const { useEffect, useRef, useState } = React;
  const CHAT_ENDPOINT = '/api/comment-chat';
  const CHAT_USER_TURN_LIMIT = 12;

  function renderInline(text, citationTargets, onSelectLetter, keyPrefix) {
    const parts = [];
    const pattern = /\*\*([^*]+)\*\*|\[#(\d{1,5})(?:\s+([^\]]*?))?\]/g;
    let last = 0;
    let match;
    let index = 0;

    while ((match = pattern.exec(text)) !== null) {
      if (match.index > last) parts.push(text.slice(last, match.index));
      if (match[1] !== undefined) {
        parts.push(<strong key={`${keyPrefix}-b${index}`}>{match[1]}</strong>);
      } else {
        const ref = parseInt(match[2], 10);
        const label = `#${match[2]}${match[3] ? ` ${match[3]}` : ''}`;
        const hasTarget = Object.prototype.hasOwnProperty.call(citationTargets, ref);
        if (hasTarget) {
          parts.push(
            <button
              key={`${keyPrefix}-c${index}`}
              type="button"
              className="chat-cite"
              title="Open this letter"
              aria-label={`Open cited letter ${label}`}
              onClick={() => onSelectLetter(citationTargets[ref])}
            >
              {label}
            </button>,
          );
        } else {
          parts.push(`[${label}]`);
        }
      }
      last = pattern.lastIndex;
      index += 1;
    }

    if (last < text.length) parts.push(text.slice(last));
    return parts;
  }

  function Message({ message, busy, citationTargets, onSelectLetter, keyPrefix }) {
    if (message.role === 'user') {
      return <div className="chat-msg chat-msg-user">{message.text}</div>;
    }
    if (!message.text) {
      return busy ? (
        <div className="chat-msg chat-msg-model">
          <span className="chat-typing" role="status" aria-label="Preparing an answer">
            <span></span><span></span><span></span>
          </span>
        </div>
      ) : null;
    }

    return (
      <div className="chat-msg chat-msg-model">
        {message.text.split(/\n{2,}/).map((block, blockIndex) => {
          const lines = block.split('\n');
          const isList = lines.length > 0 && lines.every((line) => /^\s*-\s+/.test(line) || !line.trim());
          if (isList) {
            return (
              <ul key={blockIndex}>
                {lines.filter((line) => line.trim()).map((line, lineIndex) => (
                  <li key={lineIndex}>
                    {renderInline(
                      line.replace(/^\s*-\s+/, ''),
                      citationTargets,
                      onSelectLetter,
                      `${keyPrefix}-${blockIndex}-${lineIndex}`,
                    )}
                  </li>
                ))}
              </ul>
            );
          }
          return (
            <p key={blockIndex}>
              {renderInline(block, citationTargets, onSelectLetter, `${keyPrefix}-${blockIndex}`)}
            </p>
          );
        })}
      </div>
    );
  }

  function errorCopy(status, code) {
    if (code === 'conversation_limit') return 'This conversation reached its length limit. Start a new one to keep going.';
    if (status === 429 || code === 'rate_limited') return 'The chat is busy right now. Please try again in a little while.';
    if (status === 404 || status === 405 || status === 501) return 'Chat is unavailable in this static preview (it needs the Pages Function).';
    if (code === 'chat_not_configured') return 'Chat is not configured on this deployment.';
    if (code === 'blocked') return 'The model’s safety filters blocked that response. Try rephrasing the question.';
    return 'Something went wrong. Please try again.';
  }

  function CommentFileCorpusChat({
    docketId,
    letterCount,
    starterPrompts,
    citationTargets,
    citationDemo,
    placeholder,
    ariaLabel,
    sourceName,
    onSelectLetter,
  }) {
    const [messages, setMessages] = useState([]);
    const [input, setInput] = useState('');
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);
    const logRef = useRef(null);
    const userTurns = messages.filter((message) => message.role === 'user').length;
    const atLimit = userTurns >= CHAT_USER_TURN_LIMIT;

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

    async function send(text) {
      const question = text.trim();
      if (!question || busy || atLimit) return;
      const history = [...messages, { role: 'user', text: question }];
      setMessages([...history, { role: 'model', text: '' }]);
      setInput('');
      setError(null);
      setBusy(true);

      try {
        const response = await fetch(CHAT_ENDPOINT, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ docket: docketId, messages: history }),
        });
        if (!response.ok) {
          let code = null;
          try { code = (await response.json()).error; } catch (parseError) { /* non-JSON response */ }
          setError(errorCopy(response.status, code));
          setMessages(history);
          return;
        }
        if (!response.body) throw new Error('Streaming response body unavailable');

        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';
        let gotText = false;
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          const events = buffer.split('\n\n');
          buffer = events.pop();
          for (const event of events) {
            const dataLine = event.split('\n').find((line) => line.startsWith('data:'));
            if (!dataLine) continue;
            const payload = dataLine.slice(5).trim();
            if (payload === '[DONE]') continue;
            let object;
            try { object = JSON.parse(payload); } catch (parseError) { continue; }
            if (object.t) {
              gotText = true;
              setMessages((previous) => {
                const next = previous.slice();
                const lastMessage = next[next.length - 1];
                next[next.length - 1] = { role: 'model', text: lastMessage.text + object.t };
                return next;
              });
            } else if (object.error) {
              setError(errorCopy(response.status, object.error));
            }
          }
        }
        if (!gotText) setMessages(history);
      } catch (requestError) {
        setError(errorCopy(0, null));
        setMessages(history);
      } finally {
        setBusy(false);
      }
    }

    return (
      <section className="chat-section" id="ask-comment-file" aria-labelledby={`${docketId}-chat-title`}>
        <div className="chat-shell">
          <div className="section-eyebrow">Ask the comment file</div>
          <h2 className="section-h" id={`${docketId}-chat-title`}>Start with a question</h2>
          <p className="section-lede">
            Search the full text of all {letterCount} posted items. Answers cite sources such as{' '}
            <span className="chat-cite chat-cite-demo">{citationDemo}</span>; select a citation to open the letter record.
          </p>
          <div className="chat-panel">
            <div
              className="chat-log"
              ref={logRef}
              role="log"
              aria-live="polite"
              aria-relevant="additions text"
              aria-busy={busy}
            >
              {messages.length === 0 && (
                <div className="chat-starters">
                  <div className="chat-starters-label">Try one of these, or ask your own:</div>
                  {starterPrompts.map((starter) => (
                    <button
                      key={starter}
                      type="button"
                      className="chat-chip"
                      disabled={busy}
                      onClick={() => send(starter)}
                    >
                      {starter}
                    </button>
                  ))}
                </div>
              )}
              {messages.map((message, index) => (
                <Message
                  key={index}
                  message={message}
                  busy={busy && index === messages.length - 1}
                  citationTargets={citationTargets}
                  onSelectLetter={onSelectLetter}
                  keyPrefix={`m${index}`}
                />
              ))}
            </div>
            {error && <div className="chat-error" role="alert">{error}</div>}
            {atLimit && !error && (
              <div className="chat-error" role="status">
                This conversation reached its length limit. Start a new one to keep going.
              </div>
            )}
            <form className="chat-input-row" onSubmit={(event) => { event.preventDefault(); send(input); }}>
              <input
                type="text"
                value={input}
                maxLength={2000}
                placeholder={placeholder}
                aria-label={ariaLabel}
                disabled={busy || atLimit}
                onChange={(event) => setInput(event.target.value)}
              />
              <button type="submit" disabled={busy || atLimit || !input.trim()}>Ask</button>
            </form>
            <div className="chat-disclosure">
              <span>
                Answers are AI-generated from the filings&rsquo; full text and this site&rsquo;s editorial classifications.
                Questions are sent to Google Gemini; do not enter confidential or personal information.
                Verify against the linked letters on {sourceName}.
              </span>
              {messages.length > 0 && (
                <button type="button" className="chat-reset" onClick={() => { setMessages([]); setError(null); }}>
                  Start over
                </button>
              )}
            </div>
          </div>
        </div>
      </section>
    );
  }

  window.CommentFileCorpusChat = CommentFileCorpusChat;
}());
