// Workspace — Claude Code remote view.
// Single active session model: at most one session in idle|running at a time.
// User starts a session, sends a prompt, sees the live event stream from the
// `claude` CLI running on the VPS.
//
// Events come from two paths:
//   - Initial load: GET /api/code/sessions/:id (returns session + events[])
//   - Live stream: WS messages of type 'code.event' / 'code.session.changed'
//
// Event kinds we render:
//   user        — what we sent (prompt or /compact)
//   system      — CLI init / config
//   assistant   — Claude's text and tool_use blocks
//   result      — final usage + cost summary
//   error       — synthetic error event with stderr tail
//   tool_use    — emitted as part of assistant.message.content[*]
//   tool_result — emitted as part of user.message.content[*]

// Context window for the [1M] models the Workspace runs against. The CLI
// computes its actual budget per turn; this number is just the meter
// denominator — keep it aligned with the model so the bar reflects real
// usage. If you ever wire up smaller-window models (e.g. claude-haiku-3),
// derive this from session.model instead of hardcoding.
const CONTEXT_WINDOW = 1_000_000;

const MODEL_OPTIONS = [
  { value: 'sonnet', label: 'Sonnet 4.6', hint: 'Balanced — default' },
  { value: 'opus',   label: 'Opus 4.7',   hint: 'Smartest, slowest' },
  { value: 'haiku',  label: 'Haiku 4.5',  hint: 'Fastest, cheapest' },
];

const EFFORT_OPTIONS = [
  { value: null,     label: 'Default',   hint: 'CLI picks per turn' },
  { value: 'low',    label: 'Low',       hint: 'Quick & shallow' },
  { value: 'medium', label: 'Medium',    hint: 'Standard depth' },
  { value: 'high',   label: 'High',      hint: 'Extended thinking' },
  { value: 'xhigh',  label: 'Extra high', hint: 'Deep reasoning' },
  { value: 'max',    label: 'Max',       hint: 'Very deep, slow' },
];

// Generic small dropdown used for model + effort selectors. Each option
// has { value, label, hint? }. Disabled while a turn is running so we
// don't change the model mid-spawn.
function Dropdown({ label, value, options, disabled, onChange, renderValue }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ position: 'relative' }}>
      <button className="btn-reset" onClick={() => !disabled && setOpen((o) => !o)}
        title={disabled ? 'No disponible mientras hay un turno en vuelo' : `Cambiar ${label.toLowerCase()}`}
        style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '3px 8px', height: 22,
          background: open ? 'var(--bg-2)' : 'var(--bg-2)',
          border: '1px solid var(--border)', borderRadius: 5,
          fontSize: 11, color: disabled ? 'var(--text-3)' : 'var(--text-1)',
          cursor: disabled ? 'not-allowed' : 'pointer',
          opacity: disabled ? 0.55 : 1,
        }}>
        <span style={{ fontSize: 9.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5 }}>{label}</span>
        <span className="mono" style={{ color: 'var(--text-0)' }}>{renderValue ? renderValue(value) : value}</span>
        <Icon name="chevron-down" size={10} color="var(--text-3)" />
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
          <div style={{
            position: 'absolute', top: '100%', left: 0, marginTop: 4, minWidth: 200, padding: 4,
            background: 'var(--bg-1)', border: '1px solid var(--border)',
            borderRadius: 'var(--r-sm)', boxShadow: '0 12px 32px rgba(0,0,0,.5)', zIndex: 50,
          }}>
            {options.map((o) => {
              const active = o.value === value;
              return (
                <button key={String(o.value)} className="btn-reset"
                  onClick={() => { onChange(o.value); setOpen(false); }}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8, width: '100%',
                    padding: '7px 9px', borderRadius: 4, textAlign: 'left',
                    background: active ? 'var(--bg-2)' : 'transparent',
                  }}
                  onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--bg-2)'; }}
                  onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, color: 'var(--text-0)', fontWeight: active ? 600 : 500 }}>{o.label}</div>
                    {o.hint && <div style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{o.hint}</div>}
                  </div>
                  {active && <Icon name="check" size={11} color="var(--accent)" />}
                </button>
              );
            })}
          </div>
        </>
      )}
    </div>
  );
}

function ClaudeCodeView() {
  const [sessions, setSessions] = React.useState([]);   // list of sessions for the current project
  const [session, setSession] = React.useState(null);    // selected session (full row + its events)
  const [events, setEvents] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [draft, setDraft] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [showRaw, setShowRaw] = React.useState(false);
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const eventsRef = React.useRef(null);

  // Sessions are scoped to the current project. Switching project triggers
  // a reload via projectId in the deps array of refresh().
  const currentProject = window.getCurrentProject ? window.getCurrentProject() : null;
  const projectId = currentProject ? currentProject.id : null;

  // Fetch the session list + select one. Prefers idle/running, falls back
  // to the most recent. Returns the full session row (with events) for the
  // selection, so first paint already has data.
  const refresh = React.useCallback(async (preferId) => {
    if (!projectId) { setSessions([]); setSession(null); setEvents([]); setLoading(false); return; }
    try {
      const list = await window.api('GET', `/api/code/sessions?limit=50&projectId=${encodeURIComponent(projectId)}`);
      setSessions(list);
      const target =
        (preferId && list.find((s) => s.id === preferId)) ||
        list.find((s) => s.status === 'idle' || s.status === 'running') ||
        list[0] || null;
      if (target) {
        const full = await window.api('GET', `/api/code/sessions/${target.id}`);
        setSession(full.session);
        setEvents(full.events);
      } else {
        setSession(null);
        setEvents([]);
      }
    } catch (e) { console.error(e); }
    setLoading(false);
  }, [projectId]);

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

  // Switch the visible session without spawning anything new.
  const selectSession = async (id) => {
    if (!id || (session && session.id === id)) { setPickerOpen(false); return; }
    try {
      const full = await window.api('GET', `/api/code/sessions/${id}`);
      setSession(full.session);
      setEvents(full.events);
    } catch (e) { console.error(e); }
    setPickerOpen(false);
  };

  // Live updates via WS. We refresh both:
  //   - the selected session's events/status (drives the open pane)
  //   - the picker's session list (so badges reflect new statuses across
  //     all sessions for this project, even when this session isn't the
  //     selected one)
  React.useEffect(() => {
    return window.onRealtime((msg) => {
      if (msg.type === 'code.event' && session && msg.sessionId === session.id) {
        setEvents((prev) => {
          if (prev.find((e) => e.idx === msg.idx)) return prev;
          const merged = [...prev, { idx: msg.idx, kind: msg.kind, payload: msg.payload, ts: msg.ts }];
          merged.sort((a, b) => a.idx - b.idx);
          return merged;
        });
      } else if (msg.type === 'code.session.changed') {
        // Re-fetch the selected session to get updated tokens/status/cost.
        if (session && msg.sessionId === session.id) {
          window.api('GET', `/api/code/sessions/${session.id}`)
            .then((res) => setSession(res.session)).catch(() => {});
        }
        // Refresh the list so the picker reflects status changes across
        // every session for this project (without losing the current
        // selection).
        if (projectId) {
          window.api('GET', `/api/code/sessions?limit=50&projectId=${encodeURIComponent(projectId)}`)
            .then((list) => setSessions(list)).catch(() => {});
        }
      }
    });
  }, [session, projectId]);

  // Auto-scroll to bottom on new events.
  React.useEffect(() => {
    if (eventsRef.current) {
      eventsRef.current.scrollTop = eventsRef.current.scrollHeight;
    }
  }, [events.length]);

  // Always creates a new session — the backend's get-or-create idempotency
  // is opt-in via reuseActive (see services/code.ts). Multiple sessions per
  // project coexist; the selector lets the user switch between them.
  const startSession = async (titleOverride) => {
    if (!projectId) { console.error('no project selected'); return; }
    const defaultTitle = `Claude Code · ${currentProject?.name || projectId}`;
    const title = (titleOverride || '').trim() || defaultTitle;
    setLoading(true);
    try {
      const s = await window.api('POST', '/api/code/sessions', {
        title,
        model: 'sonnet',
        projectId,
      });
      await refresh(s.id);
      setPickerOpen(false);
    } catch (e) { console.error(e); }
    setLoading(false);
  };

  // Prompt for a title, then start. Empty input → uses the default title.
  const startNewSession = () => {
    const t = prompt('Título de la nueva sesión (vacío = default):', '');
    if (t === null) return; // user cancelled
    startSession(t);
  };

  const sendMessage = async () => {
    if (!draft.trim() || !session) return;
    setSending(true);
    const prompt = draft;
    setDraft('');
    try {
      await window.api('POST', `/api/code/sessions/${session.id}/messages`, { prompt });
    } catch (e) {
      console.error(e);
      setDraft(prompt); // restore on failure
    }
    setSending(false);
  };

  const stopTurn = async () => {
    if (!session) return;
    try { await window.api('POST', `/api/code/sessions/${session.id}/stop`); }
    catch (e) { console.error(e); }
  };

  const compact = async () => {
    if (!session) return;
    try { await window.api('POST', `/api/code/sessions/${session.id}/messages`, { prompt: '/compact' }); }
    catch (e) { console.error(e); }
  };

  const resetSession = async () => {
    if (!session) return;
    if (!confirm('Borrar la sesión actual y empezar una nueva?')) return;
    try {
      await window.api('DELETE', `/api/code/sessions/${session.id}`);
      // Clear local state, refresh list, then start fresh.
      setSession(null); setEvents([]);
      await startSession();
    } catch (e) { console.error(e); }
  };

  const deleteSession = async (id) => {
    if (!confirm('Borrar esta sesión? Se perderá su historial.')) return;
    try {
      await window.api('DELETE', `/api/code/sessions/${id}`);
      // If the deleted one was selected, refresh picks the next; otherwise
      // just refresh to drop the row from the list.
      await refresh(id === session?.id ? undefined : session?.id);
    } catch (e) { console.error(e); }
  };

  // Patch model/effort/title on the current session. Refuses if the session
  // is running (the backend enforces this too — duplicate the check here so
  // the dropdowns are visibly disabled).
  const patchSession = async (fields) => {
    if (!session) return;
    try {
      const updated = await window.api('PATCH', `/api/code/sessions/${session.id}`, fields);
      setSession(updated);
      // Reflect in the picker list too without a full reload.
      setSessions((prev) => prev.map((s) => (s.id === updated.id ? { ...s, ...updated } : s)));
    } catch (e) { console.error(e); }
  };

  const renameSession = () => {
    if (!session) return;
    const next = prompt('Nuevo título:', session.title);
    if (next === null || next.trim() === '' || next === session.title) return;
    patchSession({ title: next.trim() });
  };

  // "Last activity" relative-time tag, refreshed every 30s so a sitting tab
  // still ticks. Falls back to startedAt for sessions that never received
  // a turn.
  const [, forceTick] = React.useState(0);
  React.useEffect(() => {
    const i = setInterval(() => forceTick((n) => n + 1), 30_000);
    return () => clearInterval(i);
  }, []);
  const fmtRelative = (iso) => {
    if (!iso) return '—';
    const ms = Date.now() - new Date(iso).getTime();
    if (ms < 60_000) return 'hace segundos';
    const m = Math.floor(ms / 60_000);
    if (m < 60) return `hace ${m}m`;
    const h = Math.floor(m / 60);
    if (h < 24) return `hace ${h}h`;
    const d = Math.floor(h / 24);
    return `hace ${d}d`;
  };
  const fmtTokens = (n) => {
    const x = Number(n || 0);
    if (x < 1000) return String(x);
    if (x < 1_000_000) return `${(x / 1000).toFixed(1)}k`;
    return `${(x / 1_000_000).toFixed(2)}M`;
  };

  const copySessionId = async () => {
    if (!session) return;
    try { await navigator.clipboard.writeText(session.id); } catch {}
  };

  if (loading) {
    return <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-3)', fontSize: 13 }}>Cargando…</div>;
  }

  if (!session) {
    return (
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
        <div style={{ maxWidth: 520, textAlign: 'center' }}>
          <div style={{ width: 56, height: 56, margin: '0 auto 18px', borderRadius: 14, background: 'rgba(168,85,247,.10)', border: '1px solid rgba(168,85,247,.25)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon name="terminal" size={24} color="var(--code)" />
          </div>
          <h2 style={{ fontSize: 22, fontWeight: 600, color: 'var(--text-0)', margin: 0, letterSpacing: -0.4 }}>Claude Code remoto</h2>
          <p style={{ fontSize: 13.5, color: 'var(--text-2)', marginTop: 10, lineHeight: 1.6 }}>
            Lanza una sesión que corre el CLI <code className="mono" style={{ background: 'var(--bg-2)', padding: '1px 5px', borderRadius: 3 }}>claude</code> en el VPS,
            sobre <code className="mono" style={{ background: 'var(--bg-2)', padding: '1px 5px', borderRadius: 3 }}>{currentProject?.rootDir || '—'}</code>.
            El consumo va contra tu plan Max. Podés tener varias sesiones en paralelo dentro del mismo proyecto.
          </p>
          <div style={{ marginTop: 22 }}>
            <Button variant="primary" size="lg" icon="play"
              onClick={() => startSession()}
              disabled={!projectId}>
              {projectId ? 'Iniciar sesión' : 'Seleccioná un proyecto'}
            </Button>
          </div>
        </div>
      </div>
    );
  }

  const tokensTotal = (session.tokensIn || 0) + (session.tokensOut || 0);
  const ctxPct = Math.min(100, (tokensTotal / CONTEXT_WINDOW) * 100);
  const isRunning = session.status === 'running';
  const statusColor = isRunning ? 'var(--accent)' : session.status === 'errored' ? 'var(--danger)' : session.status === 'stopped' ? 'var(--warn)' : 'var(--success)';

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
      {/* Header — two rows: title+actions, then meta strip */}
      <div style={{ padding: '18px 28px 12px', flexShrink: 0, borderBottom: '1px solid var(--border)' }}>
        {/* row 1: project chip · session selector · status · actions */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
          {/* Project chip — quick reminder of where this session runs */}
          {currentProject && (
            <div style={{
              display: 'flex', alignItems: 'center', gap: 6,
              padding: '3px 10px', height: 24,
              background: 'rgba(79,142,255,.08)', border: '1px solid rgba(79,142,255,.25)',
              borderRadius: 999, fontSize: 11, color: '#9DBDF5',
            }}>
              <Icon name="folder" size={11} />
              {currentProject.name}
            </div>
          )}

          <div style={{ width: 8, height: 8, borderRadius: '50%', background: statusColor, boxShadow: isRunning ? `0 0 0 3px color-mix(in srgb, ${statusColor} 30%, transparent)` : 'none', animation: isRunning ? 'pulse-soft 1.6s ease-in-out infinite' : 'none' }} />

          {/* Session selector */}
          <div style={{ position: 'relative', minWidth: 0, flex: '0 1 auto' }}>
            <button className="btn-reset" onClick={() => setPickerOpen((o) => !o)}
              style={{
                display: 'flex', alignItems: 'center', gap: 8,
                padding: '4px 10px', borderRadius: 6, maxWidth: 360,
                background: pickerOpen ? 'var(--bg-2)' : 'transparent',
              }}>
              <h1 style={{ fontSize: 17, fontWeight: 600, margin: 0, letterSpacing: -0.2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{session.title}</h1>
              <Icon name="chevron-down" size={13} color="var(--text-2)" />
              {sessions.length > 1 && (
                <span style={{ fontSize: 10.5, color: 'var(--text-3)', padding: '1px 6px', background: 'var(--bg-2)', borderRadius: 999 }}>
                  {sessions.length}
                </span>
              )}
            </button>
            {pickerOpen && (
              <>
                <div onClick={() => setPickerOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
                <div style={{
                  position: 'absolute', top: '100%', left: 0, marginTop: 6,
                  width: 380, maxHeight: 440, overflowY: 'auto', padding: 4,
                  background: 'var(--bg-1)', border: '1px solid var(--border)',
                  borderRadius: 'var(--r-md)', boxShadow: '0 12px 32px rgba(0,0,0,.5)',
                  zIndex: 50,
                }}>
                  <div style={{ padding: '8px 10px 6px', fontSize: 10.5, color: 'var(--text-3)', letterSpacing: 0.6, textTransform: 'uppercase' }}>
                    Sesiones · {currentProject?.name || projectId}
                  </div>
                  {sessions.map((s) => {
                    const active = session && s.id === session.id;
                    const c = s.status === 'running' ? 'var(--accent)'
                      : s.status === 'errored' ? 'var(--danger)'
                      : s.status === 'stopped' ? 'var(--warn)'
                      : s.status === 'idle' ? 'var(--success)'
                      : 'var(--text-3)';
                    return (
                      <div key={s.id}
                        style={{
                          display: 'flex', alignItems: 'center', gap: 10,
                          padding: '8px 10px', borderRadius: 6,
                          background: active ? 'var(--bg-2)' : 'transparent',
                        }}
                        onMouseEnter={(e) => { if (!active) e.currentTarget.style.background = 'var(--bg-2)'; }}
                        onMouseLeave={(e) => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                        <button className="btn-reset" onClick={() => selectSession(s.id)}
                          style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 8, textAlign: 'left' }}>
                          <span style={{ width: 6, height: 6, borderRadius: '50%', background: c, flexShrink: 0 }} />
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-0)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.title}</div>
                            <div style={{ fontSize: 10.5, color: 'var(--text-2)' }}>
                              <span className="mono" style={{ textTransform: 'uppercase', letterSpacing: 0.4, color: c }}>{s.status}</span>
                              <span style={{ margin: '0 6px', color: 'var(--text-3)' }}>·</span>
                              <span className="mono">{s.model}</span>
                              <span style={{ margin: '0 6px', color: 'var(--text-3)' }}>·</span>
                              <span className="mono">{fmtTokens((s.tokensIn || 0) + (s.tokensOut || 0))}</span>
                            </div>
                          </div>
                          {active && <Icon name="check" size={13} color="var(--accent)" />}
                        </button>
                        <button className="btn-reset"
                          onClick={(e) => { e.stopPropagation(); deleteSession(s.id); }}
                          title="Borrar sesión"
                          disabled={s.status === 'running'}
                          style={{ width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 4, color: 'var(--text-3)', opacity: s.status === 'running' ? 0.3 : 1 }}>
                          <Icon name="trash-2" size={11} />
                        </button>
                      </div>
                    );
                  })}
                  <div style={{ height: 1, background: 'var(--border)', margin: '4px 0' }} />
                  <button className="btn-reset" onClick={startNewSession}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '10px',
                      borderRadius: 6, color: 'var(--text-1)', fontSize: 12.5, fontWeight: 500,
                    }}
                    onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
                    onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                    <Icon name="plus" size={13} />
                    Nueva sesión
                  </button>
                </div>
              </>
            )}
          </div>

          <button className="btn-reset" onClick={renameSession} disabled={isRunning}
            title="Renombrar sesión"
            style={{ width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 4, color: 'var(--text-3)', opacity: isRunning ? 0.3 : 1 }}>
            <Icon name="pencil" size={11} />
          </button>

          <span style={{ fontSize: 11, color: statusColor, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>{session.status}</span>

          <div style={{ flex: 1 }} />

          <Button variant="ghost" size="sm" icon="plus" onClick={startNewSession}>Nueva</Button>
          <Button variant="ghost" size="sm" icon="archive-restore" onClick={compact} disabled={isRunning} title="Pide al CLI que compacte el contexto">Compact</Button>
          {isRunning && <Button variant="danger" size="sm" icon="square" onClick={stopTurn}>Stop</Button>}
          <Button variant="ghost" size="sm" icon="rotate-ccw" onClick={resetSession} disabled={isRunning} title="Borrar y empezar nueva">Reset</Button>
        </div>

        {/* row 2: model · effort · context meter · tokens · cost · last activity · session id */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, fontSize: 11.5, color: 'var(--text-2)', flexWrap: 'wrap' }}>
          <Dropdown
            label="Modelo"
            value={session.model}
            options={MODEL_OPTIONS}
            disabled={isRunning}
            onChange={(v) => patchSession({ model: v })}
            renderValue={(v) => MODEL_OPTIONS.find((o) => o.value === v)?.label || v}
          />
          <Dropdown
            label="Effort"
            value={session.effort || null}
            options={EFFORT_OPTIONS}
            disabled={isRunning}
            onChange={(v) => patchSession({ effort: v })}
            renderValue={(v) => EFFORT_OPTIONS.find((o) => o.value === v)?.label || 'Default'}
          />

          <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 240 }}>
            <span style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5 }}>contexto</span>
            <div style={{ flex: 1, height: 6, background: 'var(--bg-2)', borderRadius: 3, overflow: 'hidden' }}>
              <div style={{ width: `${ctxPct}%`, height: '100%', background: ctxPct > 80 ? 'var(--warn)' : 'var(--accent)', transition: 'width 200ms' }} />
            </div>
            <span className="mono" style={{ color: 'var(--text-1)', fontSize: 10.5 }}>
              {fmtTokens(tokensTotal)} / 1M
            </span>
          </div>

          <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.5 }}>tokens</span>
            <span className="mono" style={{ color: 'var(--text-1)' }}>↓ {fmtTokens(session.tokensIn)} · ↑ {fmtTokens(session.tokensOut)}</span>
          </span>

          <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <Icon name="clock" size={11} color="var(--text-3)" />
            <span>{fmtRelative(session.lastMessageAt)}</span>
          </span>

          <button className="btn-reset" onClick={copySessionId}
            title={`Copiar id (${session.id})`}
            style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 10.5, color: 'var(--text-3)', padding: '2px 6px', borderRadius: 4 }}>
            <Icon name="copy" size={10} />
            <span className="mono">{session.id.slice(0, 8)}</span>
          </button>

          <div style={{ flex: 1 }} />

          <button className="btn-reset" onClick={() => setShowRaw((r) => !r)}
            style={{ fontSize: 11, color: 'var(--text-3)', padding: '2px 6px' }}
            title="Toggle raw event payloads">
            {showRaw ? 'Ocultar JSON' : 'Ver JSON'}
          </button>
        </div>
      </div>

      {/* Event stream */}
      <div ref={eventsRef} style={{ flex: 1, overflowY: 'auto', padding: '14px 32px', background: 'var(--bg-0)' }}>
        {events.length === 0 && (
          <div style={{ padding: 40, textAlign: 'center', color: 'var(--text-3)', fontSize: 12.5, fontStyle: 'italic' }}>
            La sesión aún no tiene mensajes. Escribe abajo y presiona ⌘+Enter para enviar.
          </div>
        )}
        {events.map((ev) => <CodeEvent key={ev.idx} event={ev} showRaw={showRaw} />)}
        {isRunning && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '12px 4px', color: 'var(--text-3)', fontSize: 12 }}>
            <Icon name="loader-2" size={12} className="spin" /> Procesando…
          </div>
        )}
      </div>

      {/* Composer */}
      <div style={{ flexShrink: 0, padding: '14px 32px 18px', borderTop: '1px solid var(--border)', background: 'var(--bg-1)' }}>
        <div style={{ position: 'relative', background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 10 }}>
          <textarea
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            placeholder={isRunning ? 'Espera a que termine la respuesta…' : 'Escribe lo que Claude Code debe hacer…'}
            disabled={isRunning || sending}
            onKeyDown={(e) => {
              if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); sendMessage(); }
            }}
            rows={3}
            style={{
              width: '100%', padding: '12px 14px 38px',
              background: 'transparent', border: 'none', resize: 'none',
              color: 'var(--text-0)', fontSize: 13.5, fontFamily: 'inherit', outline: 'none',
            }}
          />
          <div style={{ position: 'absolute', bottom: 6, left: 12, right: 6, display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 11, color: 'var(--text-3)' }}>⌘ + Enter para enviar</span>
            <div style={{ flex: 1 }} />
            <Button variant="primary" size="sm" icon="send" onClick={sendMessage} disabled={isRunning || sending || !draft.trim()}>
              {sending ? 'Enviando…' : 'Enviar'}
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Per-event renderer ───────────────────────────────────────────────
function CodeEvent({ event, showRaw }) {
  const { kind, payload } = event;

  // user prompts (our send) and tool_results inside user.message.content
  if (kind === 'user') {
    const content = payload?.message?.content ?? payload?.content;
    const by = payload?.by; // persisted by sendCodeMessage; null on legacy events
    // If content is a string, it's our outgoing prompt
    if (typeof content === 'string') {
      return <UserPrompt text={content} by={by} />;
    }
    if (Array.isArray(content)) {
      return (<>
        {content.map((c, i) => {
          if (c.type === 'tool_result') return <ToolResult key={i} block={c} showRaw={showRaw} />;
          if (c.type === 'text') return <UserPrompt key={i} text={c.text} by={by} />;
          return null;
        })}
      </>);
    }
    return showRaw ? <RawEvent event={event} /> : null;
  }

  if (kind === 'assistant') {
    const content = payload?.message?.content ?? [];
    return (<>
      {content.map((c, i) => {
        if (c.type === 'text') return <AssistantText key={i} text={c.text} />;
        if (c.type === 'tool_use') return <ToolUse key={i} block={c} showRaw={showRaw} />;
        return null;
      })}
    </>);
  }

  if (kind === 'system') {
    const sub = payload?.subtype;
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', color: 'var(--text-3)', fontSize: 11 }}>
        <Icon name="settings" size={11} />
        <span className="mono">system · {sub || 'event'}</span>
        {payload?.session_id && <span className="mono" style={{ color: 'var(--text-3)' }}>· {payload.session_id.slice(0, 8)}</span>}
      </div>
    );
  }

  if (kind === 'result') {
    const u = payload?.usage || {};
    return (
      <div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(16,185,129,.06)', border: '1px solid rgba(16,185,129,.20)', borderRadius: 8, fontSize: 12, color: 'var(--text-1)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <Icon name="check-circle-2" size={13} color="var(--success)" />
          <span style={{ color: 'var(--success)', fontWeight: 600 }}>Turn complete</span>
          <span className="mono" style={{ color: 'var(--text-3)', fontSize: 11 }}>
            in {u.input_tokens || 0} · out {u.output_tokens || 0} · cache_r {u.cache_read_input_tokens || 0}
          </span>
        </div>
        {payload?.is_error && <div style={{ color: 'var(--danger)' }}>Result: error</div>}
      </div>
    );
  }

  if (kind === 'error') {
    return (
      <div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.25)', borderRadius: 8, fontSize: 12, color: 'var(--danger)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
          <Icon name="alert-circle" size={13} />
          <span style={{ fontWeight: 600 }}>Error · exit {payload?.exit_code}</span>
        </div>
        {payload?.stderr_tail && (
          <pre style={{ margin: 0, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-1)', whiteSpace: 'pre-wrap', maxHeight: 200, overflow: 'auto' }}>{payload.stderr_tail}</pre>
        )}
      </div>
    );
  }

  return showRaw ? <RawEvent event={event} /> : null;
}

function UserPrompt({ text, by }) {
  // Source of truth for the sender:
  //   1. payload.by (set by sendCodeMessage on every new message)
  //   2. window.ME.id  — fallback for legacy events that predate the field;
  //      shows the viewer's own avatar, which is the least misleading
  //      option since at the time we didn't know who sent it.
  const senderId = by || (window.ME && window.ME.id) || 'founder';
  const isMe = window.ME && senderId === window.ME.id;
  const senderName = isMe ? 'Tú' : (window.ACTORS?.[senderId]?.name || senderId);
  return (
    <div style={{ display: 'flex', gap: 10, padding: '10px 0', borderBottom: '1px solid var(--border)' }}>
      <Avatar actor={senderId} size={26} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-0)', marginBottom: 4 }}>{senderName}</div>
        <div style={{ fontSize: 13, color: 'var(--text-1)', lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>{text}</div>
      </div>
    </div>
  );
}

function AssistantText({ text }) {
  // Reuse the Vault's markdown renderer (window.RenderedMd, defined in
  // vault-editor.jsx). Falls back to plain pre-wrap if it isn't loaded yet
  // (very early during boot) so the message at least shows up.
  const Md = typeof window !== 'undefined' ? window.RenderedMd : null;
  return (
    <div style={{ display: 'flex', gap: 10, padding: '10px 0', borderBottom: '1px solid var(--border)' }}>
      <Avatar actor={'code'} size={26} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-0)', marginBottom: 4 }}>Claude Code</div>
        <div className="md-output" style={{ fontSize: 13, color: 'var(--text-1)', lineHeight: 1.6 }}>
          {Md ? <Md md={text} /> : <div style={{ whiteSpace: 'pre-wrap' }}>{text}</div>}
        </div>
      </div>
    </div>
  );
}

function ToolUse({ block, showRaw }) {
  const [open, setOpen] = React.useState(false);
  const argsStr = block.input ? JSON.stringify(block.input, null, 2) : '';
  const oneLine = argsStr.replace(/\s+/g, ' ').slice(0, 120);
  return (
    <div style={{ marginLeft: 36, marginTop: 4, marginBottom: 4 }}>
      <button className="btn-reset" onClick={() => setOpen(o => !o)}
        style={{
          display: 'flex', alignItems: 'center', gap: 8, width: '100%',
          padding: '6px 10px', borderRadius: 6,
          background: 'var(--bg-1)', border: '1px solid var(--border)',
          fontSize: 11.5, color: 'var(--text-1)', textAlign: 'left',
        }}>
        <Icon name={open ? 'chevron-down' : 'chevron-right'} size={11} color="var(--text-3)" />
        <Icon name="wrench" size={12} color="var(--accent)" />
        <span className="mono" style={{ color: 'var(--accent)', fontWeight: 600 }}>{block.name}</span>
        {!open && oneLine && <span className="mono" style={{ color: 'var(--text-3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{oneLine}</span>}
      </button>
      {open && (
        <pre style={{ margin: '4px 0 0 22px', padding: '8px 10px', background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-1)', whiteSpace: 'pre-wrap', maxHeight: 320, overflow: 'auto' }}>{argsStr || '{}'}</pre>
      )}
    </div>
  );
}

function ToolResult({ block, showRaw }) {
  const [open, setOpen] = React.useState(false);
  let preview = '';
  if (Array.isArray(block.content)) {
    preview = block.content.map(c => c.text || '').join('\n');
  } else if (typeof block.content === 'string') {
    preview = block.content;
  }
  const oneLine = preview.replace(/\s+/g, ' ').slice(0, 120);
  return (
    <div style={{ marginLeft: 36, marginBottom: 4 }}>
      <button className="btn-reset" onClick={() => setOpen(o => !o)}
        style={{
          display: 'flex', alignItems: 'center', gap: 8, width: '100%',
          padding: '6px 10px', borderRadius: 6,
          background: 'var(--bg-2)', border: '1px solid var(--border)',
          fontSize: 11.5, color: 'var(--text-2)', textAlign: 'left',
        }}>
        <Icon name={open ? 'chevron-down' : 'chevron-right'} size={11} color="var(--text-3)" />
        <Icon name="corner-down-right" size={12} color="var(--text-3)" />
        <span className="mono" style={{ color: 'var(--text-2)' }}>tool_result</span>
        {block.is_error && <span style={{ color: 'var(--danger)', fontSize: 11 }}>· error</span>}
        {!open && oneLine && <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--text-3)' }}>{oneLine}</span>}
      </button>
      {open && preview && (
        <pre style={{ margin: '4px 0 0 22px', padding: '8px 10px', background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-1)', whiteSpace: 'pre-wrap', maxHeight: 320, overflow: 'auto' }}>{preview}</pre>
      )}
    </div>
  );
}

function RawEvent({ event }) {
  return (
    <details style={{ marginLeft: 36, marginBottom: 4 }}>
      <summary style={{ fontSize: 11, color: 'var(--text-3)', cursor: 'pointer' }} className="mono">
        {event.kind} · idx {event.idx}
      </summary>
      <pre style={{ margin: 4, padding: '6px 8px', background: 'var(--bg-0)', border: '1px solid var(--border)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--text-2)', whiteSpace: 'pre-wrap', maxHeight: 240, overflow: 'auto' }}>
        {JSON.stringify(event.payload, null, 2)}
      </pre>
    </details>
  );
}

Object.assign(window, { ClaudeCodeView });
