// Hedge Fund Team overlay — opens a WS to /ws/team, streams events from N
// parallel framework analysts + a synthesizer, shows each agent's progress
// and final markdown side by side, then the Managing Director's memo.
const { useState: tUseState, useEffect: tUseEffect, useRef: tUseRef, useCallback: tUseCallback } = React;

function TeamOverlay({ open, onClose, topic, hypothesis }) {
  const [members, setMembers] = tUseState([]);
  const [agentState, setAgentState] = tUseState({});  // { role: { status, title, toolCalls, lastTool, markdown, error } }
  const [synthesis, setSynthesis] = tUseState(null);
  // Phase 9: 'idle' (just opened) | 'last-memo' (showing stored verdict)
  // | 'running' | 'synthesizing' | 'done' | 'error'
  const [phase, setPhase] = tUseState('idle');
  const [lastMemo, setLastMemo] = tUseState(null);  // {date, topic, body, model, path}
  const [error, setError] = tUseState('');
  const [expanded, setExpanded] = tUseState({});
  const wsRef = tUseRef(null);

  const startRun = tUseCallback(() => {
    if (!topic && !(hypothesis && hypothesis.question)) {
      setError('Need a hypothesis question or topic before running the team.');
      setPhase('error');
      return;
    }
    setMembers([]); setAgentState({}); setSynthesis(null); setError('');
    setPhase('running');
    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    // Phase 2C.5 — append the Supabase JWT as ?token= so the WS handshake
    // can authenticate (browsers can't set Authorization headers on WS).
    // tibebReadToken is installed by the boot gate at index.html.
    const tok = (window.tibebReadToken && window.tibebReadToken()) || '';
    const wsUrl = `${proto}://${location.host}/ws/team${tok ? `?token=${encodeURIComponent(tok)}` : ''}`;
    const ws = new WebSocket(wsUrl);
    wsRef.current = ws;
    ws.onopen = () => {
      ws.send(JSON.stringify({
        topic: topic || hypothesis.question,
        hypothesis: hypothesis || null,
      }));
    };
    ws.onmessage = (ev) => {
      let event;
      try { event = JSON.parse(ev.data); } catch { return; }
      handleEvent(event);
    };
    ws.onerror = () => {
      setError("Couldn't reach the analyst team right now. Try again in a few seconds.");
      setPhase('error');
    };
    ws.onclose = () => {
      if (phase === 'running' || phase === 'synthesizing') setPhase('done');
    };
  }, [topic, hypothesis]);

  tUseEffect(() => {
    if (!open) {
      setMembers([]); setAgentState({}); setSynthesis(null);
      setPhase('idle'); setError(''); setExpanded({}); setLastMemo(null);
      if (wsRef.current) try { wsRef.current.close(); } catch {}
      return;
    }
    // On open, fetch the most recent Managing Director memo for this topic.
    // If found, show it as the default view with a 'Run fresh' button.
    // If not found, auto-start a run (first time on this thesis).
    const t = topic || (hypothesis && hypothesis.question) || '';
    if (!t) {
      setError('Need a hypothesis question or topic before running the team.');
      setPhase('error');
      return;
    }
    setPhase('idle');
    fetch(`/api/team/last-memo?topic=${encodeURIComponent(t)}`)
      .then(r => r.json())
      .then(j => {
        if (j.found) {
          setLastMemo(j);
          setPhase('last-memo');
        } else {
          // No prior memo — kick off a fresh run immediately
          startRun();
        }
      })
      .catch(() => startRun());

    return () => { try { wsRef.current?.close(); } catch {} };
  }, [open, topic, hypothesis, startRun]);

  const handleEvent = tUseCallback((event) => {
    const t = event.type;
    if (t === 'team_started') {
      setMembers(event.members || []);
      const init = {};
      const expandedInit = {};
      (event.members || []).forEach(m => {
        init[m.role] = {
          title: m.title, model: m.model, status: 'queued',
          toolCalls: 0, lastTool: '', markdown: '', error: '',
          priorMemories: 0, memoryPath: '', memoryError: '',
        };
        // Auto-expand every agent card on team start so the user sees
        // each analyst's thinking + final memo without having to click
        // through. The user explicitly asked: 'I want each individual
        // agent's thoughts on the thesis' — auto-expand makes that the
        // default view, not a hidden detail.
        expandedInit[m.role] = true;
      });
      setExpanded(expandedInit);
      // Pre-register synthesizer slot too so its memory chip can render
      if (event.has_synthesizer) {
        init['managing-director'] = {
          title: 'Managing Director', model: event.synthesizer_model,
          status: 'queued', isSynthesizer: true,
          priorMemories: 0, memoryPath: '', memoryError: '',
        };
      }
      setAgentState(init);
    } else if (t === 'agent_memory_loaded') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), priorMemories: event.count || 0 },
      }));
    } else if (t === 'agent_memory_written') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), memoryPath: event.path || '' },
      }));
    } else if (t === 'agent_memory_error') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), memoryError: event.error || '' },
      }));
    } else if (t === 'agent_started') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), title: event.title, model: event.model, status: 'thinking' },
      }));
    } else if (t === 'agent_text') {
      // Phase 14 — live token streaming. Each delta arrives as a small
      // string fragment; we append into the agent's `streaming` buffer
      // so the UI types out the memo in real time before agent_done
      // replaces it with the final markdown.
      const delta = event.delta || '';
      if (!delta) return;
      setAgentState(s => ({
        ...s,
        [event.role]: {
          ...(s[event.role] || {}),
          status: 'thinking',
          streaming: ((s[event.role] || {}).streaming || '') + delta,
        },
      }));
    } else if (t === 'agent_tool_use') {
      setAgentState(s => ({
        ...s,
        [event.role]: {
          ...(s[event.role] || {}),
          toolCalls: ((s[event.role] || {}).toolCalls || 0) + 1,
          lastTool: `${event.tool}${event.input_summary ? ' · ' + event.input_summary : ''}`,
        },
      }));
    } else if (t === 'agent_done') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), status: 'done', markdown: event.markdown || '' },
      }));
    } else if (t === 'agent_error') {
      setAgentState(s => ({
        ...s,
        [event.role]: { ...(s[event.role] || {}), status: 'error', error: event.error || '' },
      }));
    } else if (t === 'synthesis_started') {
      setPhase('synthesizing');
      setAgentState(s => ({
        ...s,
        'managing-director': { ...(s['managing-director'] || {}), status: 'thinking', model: event.model },
      }));
    } else if (t === 'synthesis_done') {
      setSynthesis(event.memo || '');
      setAgentState(s => ({
        ...s,
        'managing-director': { ...(s['managing-director'] || {}), status: 'done' },
      }));
    } else if (t === 'synthesis_error') {
      setError(`Synthesis failed: ${event.error}`);
    } else if (t === 'team_done') {
      setPhase('done');
    } else if (t === 'error') {
      setError(event.error || 'Unknown error.');
      setPhase('error');
    }
  }, [phase]);

  if (!open) return null;

  const statusColor = (s) => ({
    queued: 'var(--text-dim)',
    thinking: 'var(--catalyst)',
    done: 'var(--accent)',
    error: 'var(--danger)',
  }[s] || 'var(--text-dim)');

  const modelChip = (model) => {
    if (!model) return null;
    const lower = model.toLowerCase();
    let label = model, tier = 'sonnet';
    if (lower.includes('opus')) { label = 'OPUS 4.7'; tier = 'opus'; }
    else if (lower.includes('sonnet')) { label = 'SONNET 4.6'; tier = 'sonnet'; }
    else if (lower.includes('haiku')) { label = 'HAIKU 4.5'; tier = 'haiku'; }
    else { label = model.replace(/^claude-/, '').toUpperCase(); }
    return <span className={`team-model-chip tier-${tier}`}>{label}</span>;
  };

  return (
    <div className="team-overlay">
      <div className="team-panel">
        <div className="team-head">
          <div>
            <div className="team-eyebrow">
              <span className="pulse-dot"/>
              Hedge Fund Team · {phase}
            </div>
            <div className="team-title">
              {topic || (hypothesis && hypothesis.question) || '(no topic)'}
            </div>
          </div>
          <button className="mm-stop" onClick={onClose}>
            {phase === 'running' || phase === 'synthesizing' ? 'Cancel' : 'Close'}
          </button>
        </div>

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

        {/* Phase 9 — last-memo view: show the stored Managing Director verdict
            with a Re-run button. Only shows when phase === 'last-memo'. */}
        {phase === 'last-memo' && lastMemo && (
          <div className="team-synthesis">
            <div className="team-synth-eyebrow">
              <span className="pulse-dot"/>
              Last verdict · {lastMemo.date}
              {lastMemo.model && modelChip(lastMemo.model)}
              <button className="brain-editor-btn primary"
                      style={{ marginLeft: 'auto' }}
                      onClick={startRun}
                      title="Re-run the team against the current brain state — picks up new notes, captures, and graphify updates since this verdict was filed.">
                ↻ Re-run team
              </button>
            </div>
            <div className="team-synth-body" dangerouslySetInnerHTML={{
              __html: window.marked ? window.marked.parse(lastMemo.body, { breaks: true, gfm: true }) : lastMemo.body,
            }}/>
            <div style={{ marginTop: 12, fontSize: 10, color: 'var(--text-dim)', fontFamily: "'JetBrains Mono', monospace" }}>
              {lastMemo.path}
            </div>
          </div>
        )}

        <div className="team-roster">
          {members.map(m => {
            const s = agentState[m.role] || {};
            const open = !!expanded[m.role];
            return (
              <div key={m.role} className={`team-member status-${s.status || 'queued'}`}>
                <div className="team-member-row" onClick={() => setExpanded(e => ({ ...e, [m.role]: !open }))}>
                  <span className="team-status-dot" style={{ background: statusColor(s.status) }}/>
                  <div className="team-member-title">{m.title}</div>
                  {modelChip(s.model || m.model)}
                  <div className="team-member-meta">
                    {s.priorMemories > 0 && (
                      <span className="team-memory-chip" title="Prior run summaries loaded into this analyst's context">
                        📘 {s.priorMemories} prior
                      </span>
                    )}
                    {s.memoryPath && (
                      <span className="team-memory-saved" title={`Saved to ${s.memoryPath}`}>
                        ✓ saved
                      </span>
                    )}
                    {s.status === 'thinking' && s.lastTool && <span className="team-tool">{s.lastTool}</span>}
                    {s.toolCalls > 0 && <span className="team-tool-count">{s.toolCalls} tool{s.toolCalls === 1 ? '' : 's'}</span>}
                    <span className="team-status-label">{(s.status || 'queued').toUpperCase()}</span>
                  </div>
                </div>
                {/* Live token stream while the analyst is thinking. Each
                    agent_text event from the backend appends a delta into
                    s.streaming — this renders progressively, so the memo
                    types out in real time. Once agent_done fires, the
                    streaming buffer is replaced with the parsed markdown
                    block below. */}
                {open && !s.markdown && !s.error && s.streaming && (
                  <div className="team-member-output team-member-streaming" dangerouslySetInnerHTML={{
                    __html: (window.marked
                      ? window.marked.parse(s.streaming, { breaks: true, gfm: true })
                      : s.streaming) + '<span class="team-cursor"></span>',
                  }}/>
                )}
                {/* Pre-stream placeholder: the agent has been kicked off
                    (status thinking) but no text has arrived yet. Or it's
                    still queued for a slot. */}
                {open && !s.markdown && !s.error && !s.streaming && (s.status === 'thinking' || s.status === 'queued') && (
                  <div className="team-member-thinking">
                    <span className="team-thinking-dot"/>
                    <span className="team-thinking-dot"/>
                    <span className="team-thinking-dot"/>
                    <span className="team-thinking-label">
                      {s.status === 'queued'
                        ? `${m.title} queued — waiting for slot…`
                        : `${m.title} reading the thesis through their lens${s.lastTool ? ` · last tool: ${s.lastTool}` : '…'}`}
                    </span>
                  </div>
                )}
                {open && s.markdown && (
                  <>
                    <div className="team-member-output" dangerouslySetInnerHTML={{
                      __html: window.marked ? window.marked.parse(s.markdown, { breaks: true, gfm: true }) : s.markdown,
                    }}/>
                    {/* Per-agent re-think button. Closes the user's "AGAINST THEIR
                        PERSONALITY AND ANY RESEARCH IF NEW NODES THEN THEY SHOULD
                        UPDATE THEIR NOTES" loop: runs JUST this analyst against the
                        latest brain + their prior memo, streams the new memo. */}
                    <div className="team-member-actions">
                      <button
                        className="team-rethink-btn"
                        disabled={!!s.rethinking}
                        title="Re-run this analyst against the latest brain state. They'll re-read the relevant notes and either confirm, refine, or contradict their prior memo."
                        onClick={() => {
                          // Open a fresh WS to /ws/team/rethink for just this agent.
                          // Reuses the same event handler so agent_text streams into
                          // s.streaming and replaces s.markdown when agent_done lands.
                          setAgentState(state => ({
                            ...state,
                            [m.role]: { ...(state[m.role] || {}), rethinking: true,
                                        streaming: '', markdown: '', error: '' },
                          }));
                          const proto = location.protocol === 'https:' ? 'wss' : 'ws';
                          const tok = (window.tibebReadToken && window.tibebReadToken()) || '';
                          const rs = new WebSocket(`${proto}://${location.host}/ws/team/rethink${tok ? `?token=${encodeURIComponent(tok)}` : ''}`);
                          rs.addEventListener('open', () => {
                            rs.send(JSON.stringify({
                              role: m.role,
                              topic: topic || (hypothesis && hypothesis.question) || '',
                              prior_memo: s.markdown || '',
                              hypothesis,
                            }));
                          });
                          rs.addEventListener('message', (e) => {
                            try { handleEvent(JSON.parse(e.data)); } catch {}
                          });
                          rs.addEventListener('close', () => {
                            setAgentState(state => ({
                              ...state,
                              [m.role]: { ...(state[m.role] || {}), rethinking: false },
                            }));
                          });
                          rs.addEventListener('error', () => {
                            setAgentState(state => ({
                              ...state,
                              [m.role]: { ...(state[m.role] || {}), rethinking: false,
                                          error: 'rethink WS error' },
                            }));
                          });
                        }}
                      >
                        {s.rethinking ? '↻ Re-thinking…' : '↻ Re-think against latest brain'}
                      </button>
                    </div>
                  </>
                )}
                {open && s.error && (
                  <div className="team-member-error">{s.error}</div>
                )}
              </div>
            );
          })}
        </div>

        {(phase === 'synthesizing' || synthesis) && (
          <div className="team-synthesis">
            <div className="team-synth-eyebrow">
              <span className="pulse-dot"/>
              {synthesis ? 'Managing Director · memo' : 'Managing Director · synthesizing…'}
              {modelChip(agentState['managing-director']?.model)}
              {agentState['managing-director']?.priorMemories > 0 && (
                <span className="team-memory-chip">📘 {agentState['managing-director'].priorMemories} prior</span>
              )}
              {agentState['managing-director']?.memoryPath && (
                <span className="team-memory-saved">✓ saved</span>
              )}
            </div>
            {synthesis && (
              <div className="team-synth-body" dangerouslySetInnerHTML={{
                __html: window.marked ? window.marked.parse(synthesis, { breaks: true, gfm: true }) : synthesis,
              }}/>
            )}
          </div>
        )}

        <div className="team-foot">
          <div>{members.filter(m => agentState[m.role]?.status === 'done').length}/{members.length} analysts complete</div>
          <div className="team-foot-model">Analyst team · debate mode</div>
        </div>
      </div>
    </div>
  );
}

window.TeamOverlay = TeamOverlay;
