// chat.jsx — iMessage-style phone + live Claude-backed agent for Personal AI (telco)
// Exports: ChatPhone, PALogomark

const PALogomark = ({ color = '#fff' }) => (
  <svg viewBox="0 0 209 214" fill={color} xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
    <rect x="184.22" y="3.14" width="22.52" height="22.52" rx="3.68" ry="3.68" transform="translate(46.73 142.04) rotate(-44.85)"/>
    <path d="M156.44,87.46c.24,0,.47,0,.71,0,9.36-.18,18.09-4,24.57-10.75s9.96-15.62,9.77-24.98c-.37-18.94-16.09-34.35-35.02-34.35-.23,0-.47,0-.71,0-19.32.38-34.73,16.41-34.35,35.73.37,18.94,16.09,34.35,35.02,34.35ZM140.92,52.72c-.08-4.15,1.46-8.08,4.33-11.07,2.87-2.99,6.74-4.68,10.87-4.76h.34c8.39,0,15.34,6.83,15.51,15.22.08,4.15-1.46,8.08-4.33,11.07-2.87,2.99-6.74,4.68-10.87,4.76h-.34c-8.39,0-15.35-6.83-15.51-15.22Z"/>
    <path d="M53.03,85.34c19.32,0,35.05-15.72,35.05-35.04S72.35,15.25,53.03,15.25,17.99,30.97,17.99,50.3s15.72,35.04,35.04,35.04ZM37.5,50.3c0-8.56,6.97-15.53,15.53-15.53s15.53,6.97,15.53,15.53-6.97,15.53-15.53,15.53-15.53-6.97-15.53-15.53Z"/>
    <path d="M108.43,105.98c-1.84-3.76-5.58-6.09-9.77-6.09H10.88c-3.37,0-6.49,1.52-8.56,4.17-2.08,2.65-2.8,6.04-1.99,9.31l21.2,85.62c1.2,4.87,5.56,8.26,10.6,8.26,3.35,0,6.46-1.53,8.54-4.2l66.59-85.62c2.57-3.3,3.02-7.69,1.18-11.45ZM81.83,118.99l-45.83,58.93-14.59-58.93h60.42Z"/>
    <path d="M165.45,107.87c-1.24-4.96-5.69-8.42-10.82-8.42-3.4,0-6.57,1.55-8.69,4.25l-69.04,87.93c-2.65,3.37-3.13,7.86-1.25,11.72,1.87,3.86,5.7,6.25,9.99,6.25h91.03c3.44,0,6.64-1.56,8.75-4.27,2.12-2.71,2.86-6.19,2.02-9.53l-21.98-87.93ZM165.89,190.09h-62.97l47.76-60.83,15.21,60.83Z"/>
  </svg>
);

function fmtTime(date, ampm) {
  return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
    .replace(ampm ? '' : /\s?(AM|PM)/i, '');
}


function StatusBar() {
  const [time, setTime] = React.useState(() => fmtTime(new Date(), false));
  React.useEffect(() => {
    function tick() { setTime(fmtTime(new Date(), false)); }
    const now = new Date();
    const msToNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds();
    const t = setTimeout(() => { tick(); const id = setInterval(tick, 60000); return () => clearInterval(id); }, msToNextMinute);
    return () => clearTimeout(t);
  }, []);
  return (
    <div className="pp-status">
      <span className="pp-status__time">{time}</span>
      <div className="pp-status__island"></div>
      <div className="pp-status__right">
        <svg width="18" height="12" viewBox="0 0 18 12"><rect x="0" y="7" width="3" height="5" rx="0.7" fill="#fff"/><rect x="4.5" y="4.5" width="3" height="7.5" rx="0.7" fill="#fff"/><rect x="9" y="2.2" width="3" height="9.8" rx="0.7" fill="#fff"/><rect x="13.5" y="0" width="3" height="12" rx="0.7" fill="#fff"/></svg>
        <svg width="16" height="12" viewBox="0 0 16 12"><path d="M8 3C10.1 3 12 3.8 13.4 5.2L14.5 4.1C12.8 2.4 10.5 1.3 8 1.3 5.5 1.3 3.2 2.4 1.5 4.1L2.6 5.2C4 3.8 5.9 3 8 3Z" fill="#fff"/><path d="M8 6.4C9.3 6.4 10.4 6.9 11.2 7.7L12.3 6.6C11.2 5.5 9.7 4.8 8 4.8 6.3 4.8 4.8 5.5 3.7 6.6L4.8 7.7C5.6 6.9 6.7 6.4 8 6.4Z" fill="#fff"/><circle cx="8" cy="9.8" r="1.4" fill="#fff"/></svg>
        <svg width="26" height="13" viewBox="0 0 26 13"><rect x="0.5" y="0.5" width="22" height="12" rx="3.3" stroke="#fff" strokeOpacity="0.4" fill="none"/><rect x="2" y="2" width="19" height="9" rx="1.8" fill="#fff"/><path d="M24 4.5V8.5C24.8 8.2 25.3 7.4 25.3 6.5 25.3 5.6 24.8 4.8 24 4.5Z" fill="#fff" fillOpacity="0.5"/></svg>
      </div>
    </div>
  );
}

function splitBubbles(text) {
  return String(text).trim().split(/\n{2,}/).map(s => s.trim()).filter(Boolean);
}

function ChatPhone({ accent = '#6756FF', greeting = [], placeholder = 'iMessage', carrier = 'your carrier', bare = false, storeKey }) {
  const KEY      = 'pai-chat-' + (storeKey || carrier);
  const CONV_KEY = 'pai-conv-' + (storeKey || carrier);
  const [turns, setTurns] = React.useState(() => {
    try { const s = localStorage.getItem(KEY); return s ? JSON.parse(s) : []; } catch (e) { return []; }
  });
  const [typing, setTyping] = React.useState(false);
  const [streamText, setStreamText] = React.useState('');
  const [draft, setDraft] = React.useState('');
  const streamRef = React.useRef(null);
  const inputRef = React.useRef(null);
  const conversationIdRef = React.useRef(
    (() => { try { return localStorage.getItem(CONV_KEY) || null; } catch (e) { return null; } })()
  );
  const startedFocus = turns.some(t => t.role === 'user');

  // auto-focus the field on first run so the cursor is already waiting
  React.useEffect(() => {
    if (!startedFocus && inputRef.current) {
      const id = setTimeout(() => { try { inputRef.current.focus({ preventScroll: true }); } catch (e) {} }, 400);
      return () => clearTimeout(id);
    }
  }, [startedFocus]);

  React.useEffect(() => {
    try { localStorage.setItem(KEY, JSON.stringify(turns)); } catch (e) {}
  }, [turns]);

  React.useEffect(() => {
    const el = streamRef.current;
    if (!el) return;
    if (typing) el.scrollTop = el.scrollHeight;
  }, [typing, streamText]);

  React.useEffect(() => {
    const el = streamRef.current;
    if (!el) return;
    el.scrollTop = el.scrollHeight;
  }, [turns]);


  // Build display rows from greeting (always 'in') + turns
  const started = turns.some(t => t.role === 'user');
  const rows = [];
  greeting.forEach((g, i) => rows.push({ from: 'in', text: g, k: 'g' + i, actions: [] }));
  turns.forEach((t, i) => {
    const from = t.role === 'user' ? 'out' : 'in';
    if (from === 'in') {
      // Assistant: one bubble so streaming→final transition is seamless
      rows.push({ from, text: t.content, k: 't' + i + '-0', actions: t.actions || [], citations: t.citations || [] });
    } else {
      // User: split on double-newlines for iMessage style
      const bubbles = splitBubbles(t.content);
      bubbles.forEach((b, j) => {
        rows.push({ from, text: b, k: 't' + i + '-' + j, actions: [], citations: [] });
      });
    }
  });

  async function send(overrideText) {
    const text = overrideText !== undefined ? String(overrideText).trim() : draft.trim();
    if (!text || typing) return;
    if (overrideText === undefined) setDraft('');
    setTurns(prev => [...prev, { role: 'user', content: text }]);
    setTyping(true);
    setStreamText('');

    let fullText = '';
    let citations = [];
    try {
      if (!conversationIdRef.current) {
        const r = await fetch('/api/conversations', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({}),
        });
        if (!r.ok) throw new Error('could not create conversation');
        const d = await r.json();
        conversationIdRef.current = d.id;
        try { localStorage.setItem(CONV_KEY, d.id); } catch (e) {}
      }

      const res = await fetch(`/api/conversations/${conversationIdRef.current}/stream`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: text }),
      });

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

      const reader = res.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 });
        const lines = buf.split('\n');
        buf = lines.pop() || '';
        let changed = false;
        for (const line of lines) {
          if (!line.startsWith('data: ')) continue;
          const raw = line.slice(6).trim();
          if (!raw) continue;
          try {
            const ev = JSON.parse(raw);
            if (ev.type === 'token') {
              fullText += ev.content.replace(/\[\d+(?:,\s*\d+)*\]/g, ' ');
              changed = true;
            } else if (ev.type === 'done') {
              citations = ev.citations || [];
            } else if (ev.type === 'error') {
              throw new Error(ev.message || 'stream error');
            }
          } catch (_) {}
        }
        /* flushSync forces React to render immediately after each network chunk
           instead of batching all updates until the loop ends (React 18 behaviour) */
        if (changed) {
          ReactDOM.flushSync(() => {
            setStreamText(fullText.replace(/```json-actions[\s\S]*$/, '').replace(/\s*\[\d+(?:,\s*\d+)*\]\s*/g, ' ').trim());
          });
        }
      }
    } catch (e) {
      if (!fullText) {
        fullText = "I'm having trouble reaching the network right now.\n\nThe quick version: I'm a memory layer your carrier can embed, so support remembers every customer across every call.";
      }
    }

    const actionMatch = fullText.match(/```json-actions\n([\s\S]*?)\n```/);
    let content = fullText;
    let actions = [];
    if (actionMatch) {
      content = fullText.replace(actionMatch[0], '').trim();
      try { actions = JSON.parse(actionMatch[1]).actions || []; } catch (_) {}
    }
    content = content.replace(/\s*\[\d+(?:,\s*\d+)*\]\s*/g, ' ').trim();

    if (citations.length) console.log('[citations]', citations);

    setTyping(false);
    setStreamText('');
    setTurns(t => [...t, { role: 'assistant', content: content.trim(), actions, citations }]);
    setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
  }

  function reset(e) {
    e && e.stopPropagation();
    setTurns([]);
    setDraft('');
    setStreamText('');
    conversationIdRef.current = null;
    try { localStorage.removeItem(KEY); localStorage.removeItem(CONV_KEY); } catch (e) {}
  }

  function onKey(e) {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
  }

  const screen = (
    <div
      className={bare ? 'pp-screen--bare' : 'pp-device__screen'}
      style={{ '--accent': accent, '--pp-screen-bg': bare ? 'transparent' : undefined }}
    >
      {!bare && <StatusBar />}
      <div className="pp-head">
        <div className="pp-appicon" style={{ background: accent }}><PALogomark /></div>
        <div className="pp-head__name">Personal AI <span className="chev">›</span></div>
        <button className="pp-head__reset" onClick={reset} title="Restart conversation">
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
            <path d="M10.0938 3.9063A4.375 4.375 0 1 1 7 2.625" stroke="currentColor" strokeWidth="1.3125" strokeLinecap="round" strokeLinejoin="round"/>
            <path d="M5 1.3125L7 2.625L5 3.9375" stroke="currentColor" strokeWidth="1.3125" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </button>
      </div>

      <div className="pp-stream" ref={streamRef}>
        {rows.map((r, idx) => {
          const next = rows[idx + 1];
          const prev = rows[idx - 1];
          const lastInGroup = !next || next.from !== r.from;
          const grpGap = prev && prev.from !== r.from;
          return (
            <div key={r.k} className={`pp-row pp-row--${r.from} ${lastInGroup ? 'last-in-group' : ''} ${grpGap ? 'grp-gap' : ''}`}>
              {r.from === 'out'
                ? <div className="pp-bubble pp-bubble--out">{r.text}</div>
                : (
                  <div className="pp-bubble-col">
                    <div className="pp-bubble pp-bubble--in" dangerouslySetInnerHTML={{ __html: window.marked ? window.marked.parse(r.text) : r.text }} />
                    {/* citations hidden — uncomment to re-enable
                    {r.citations && r.citations.length > 0 && (() => {
                      const chips = r.citations.map((c, ci) => {
                        const label = c.title || c.name || c.document_title ||
                          (c.metadata && (c.metadata.title || c.metadata.source || c.metadata.name)) ||
                          c.source || c.url || null;
                        return label ? <span key={ci} className="pp-citation">{label}</span> : null;
                      }).filter(Boolean);
                      return chips.length > 0 ? (
                        <div className="pp-citations">
                          <span className="pp-citations__label">Sources</span>
                          {chips}
                        </div>
                      ) : null;
                    })()}
                    */}
                    {r.actions && r.actions.length > 0 && (
                      <div className="pp-action-chips">
                        {r.actions.map((a, ai) => (
                          <button key={ai} className="pp-action-chip" onClick={() => send(a.prompt)}>{a.label}</button>
                        ))}
                      </div>
                    )}
                  </div>
                )
              }
            </div>
          );
        })}
        {typing && !streamText && (
          <div className="pp-row pp-row--in last-in-group grp-gap">
            <div className="pp-typing"><span></span><span></span><span></span></div>
          </div>
        )}
        {typing && streamText && (
          <div className="pp-row pp-row--in last-in-group grp-gap">
            <div className="pp-bubble pp-bubble--in" dangerouslySetInnerHTML={{ __html: window.marked ? window.marked.parse(streamText) : streamText }} />
          </div>
        )}
      </div>

      {!started && !typing && (
        <div className="pp-cue" aria-hidden="true">
          <span className="pp-cue__pill">
            <span className="pp-cue__pulse"></span>
            Type a message to talk to the agent
          </span>
          <span className="pp-cue__arrow">↓</span>
        </div>
      )}
      <div className="pp-input">
        <div className={'pp-input__field' + (started ? '' : ' pp-input__field--cue')}>
          {!started && !draft && <span className="pp-caret" aria-hidden="true"></span>}
          <input
            ref={inputRef}
            value={draft}
            placeholder={started ? placeholder : 'Type your message…'}
            onChange={e => setDraft(e.target.value)}
            onKeyDown={onKey}
          />
        </div>
        <button className={'pp-input__send' + (draft.trim() && !typing ? ' pp-input__send--ready' : '')} disabled={!draft.trim() || typing} onClick={() => send()} aria-label="Send">
          <svg width="18" height="18" viewBox="0 0 16 16" fill="none"><path d="M8 13V3M8 3L3.5 7.5M8 3l4.5 4.5" stroke="#fff" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </button>
      </div>
      <div className="pp-homebar"><i></i></div>
    </div>
  );

  if (bare) return screen;
  return <div className="pp-device">{screen}</div>;
}

Object.assign(window, { ChatPhone, PALogomark });
