// VPS manager (founder-only). Two modes:
//   - List: muestra los VPS conectados, permite borrar y abrir el modal
//     de "Conectar nuevo VPS".
//   - Connect: form con IP/password/etc., y luego una pantalla de progreso
//     que stream-ea cada paso del bootstrap por WebSocket.
//
// Se monta como un overlay full-screen. window.openVpsManager() lo abre.

function VpsManager({ onClose }) {
  const [vps, setVps] = React.useState([]);
  const [mode, setMode] = React.useState('list'); // 'list' | 'connect'
  const [loading, setLoading] = React.useState(true);

  const refresh = React.useCallback(async () => {
    try {
      const rows = await window.api('GET', '/api/vps');
      setVps(rows);
    } finally {
      setLoading(false);
    }
  }, []);

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

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.55)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 100,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: 640, maxHeight: '80vh', overflow: 'auto',
        background: 'var(--bg-1)', border: '1px solid var(--border)',
        borderRadius: 'var(--r-md)', boxShadow: '0 24px 48px rgba(0,0,0,.6)',
        padding: 20, display: 'flex', flexDirection: 'column', gap: 14,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600, letterSpacing: -0.2 }}>
              {mode === 'list' ? 'Gestionar VPS' : 'Conectar VPS'}
            </div>
            <div style={{ fontSize: 12, color: 'var(--text-2)', marginTop: 2 }}>
              {mode === 'list'
                ? 'Cada VPS conectado puede ejecutar Claude Code para los proyectos asignados.'
                : 'Datos del VPS donde quieres correr Claude Code remotamente.'}
            </div>
          </div>
          <button className="btn-reset" onClick={onClose}
            style={{ padding: 6, color: 'var(--text-2)' }}>
            <Icon name="x" size={16} />
          </button>
        </div>

        {mode === 'list' && (
          <VpsList vps={vps} loading={loading} refresh={refresh} onAdd={() => setMode('connect')} />
        )}
        {mode === 'connect' && (
          <ConnectVpsForm onCancel={() => setMode('list')}
            onConnected={async () => { await refresh(); setMode('list'); }} />
        )}
      </div>
    </div>
  );
}

function VpsList({ vps, loading, refresh, onAdd }) {
  const [repairing, setRepairing] = React.useState(null); // { id, logs, phase }

  const remove = async (id) => {
    if (!window.confirm(`¿Desconectar y eliminar el VPS "${id}"?`)) return;
    try {
      await window.api('DELETE', `/api/vps/${encodeURIComponent(id)}`);
      await refresh();
    } catch (err) {
      alert(err.body?.error || err.message);
    }
  };

  const repair = async (id) => {
    setRepairing({ id, logs: [], phase: 'progress', error: null });
    try {
      const res = await window.api('POST', `/api/vps/${encodeURIComponent(id)}/repair`);
      const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
      const ws = new WebSocket(`${proto}//${location.host}/api/vps/connect/${res.connectId}/progress`);
      ws.onmessage = (ev) => {
        try {
          const m = JSON.parse(ev.data);
          setRepairing((r) => {
            if (!r) return r;
            if (m.type === 'step' || m.type === 'log') {
              return { ...r, logs: [...r.logs, m] };
            }
            if (m.type === 'done') return { ...r, phase: 'done' };
            if (m.type === 'error') return { ...r, phase: 'error', error: m.message };
            return r;
          });
        } catch {}
      };
      ws.onerror = () => {
        setRepairing((r) => r ? { ...r, phase: 'error', error: 'conexión cortada' } : r);
      };
    } catch (err) {
      setRepairing((r) => r ? { ...r, phase: 'error', error: err.body?.error || err.message } : r);
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {loading && <div style={{ fontSize: 12, color: 'var(--text-2)' }}>Cargando…</div>}
      {!loading && vps.length === 0 && (
        <div style={{
          padding: 16, textAlign: 'center', color: 'var(--text-2)',
          background: 'var(--bg-0)', border: '1px dashed var(--border)', borderRadius: 'var(--r-sm)',
          fontSize: 13,
        }}>
          Todavía no conectaste ningún VPS. Pulsa <b>Conectar VPS</b> para empezar.
        </div>
      )}
      {vps.map((v) => (
        <VpsCard key={v.id} v={v} refresh={refresh} onRepair={repair} onRemove={remove} />
      ))}
      <button className="btn-reset" onClick={onAdd}
        style={{
          marginTop: 4, padding: '10px 14px', fontSize: 13, fontWeight: 500,
          background: 'var(--accent)', color: 'white',
          borderRadius: 'var(--r-sm)', alignSelf: 'flex-start',
        }}>
        Conectar VPS
      </button>

      {repairing && (
        <div style={{
          marginTop: 8, padding: 12,
          background: 'var(--bg-0)', border: '1px solid var(--border)',
          borderRadius: 'var(--r-sm)',
        }}>
          <div style={{ fontSize: 12, fontWeight: 500, marginBottom: 6 }}>
            Reparando permisos en {repairing.id}…
          </div>
          <div style={{
            fontFamily: 'var(--font-mono)', fontSize: 11, lineHeight: 1.5,
            maxHeight: 140, overflow: 'auto', color: 'var(--text-1)',
          }}>
            {repairing.logs.map((l, i) => (
              <div key={i} style={{ color: l.kind === 'step' || l.type === 'step' ? '#9DBDF5' : 'var(--text-1)' }}>
                {(l.type === 'step' ? '▸ ' : '  ') + (l.message || l.text)}
              </div>
            ))}
          </div>
          {repairing.phase === 'done' && (
            <div style={{ marginTop: 6, fontSize: 12, color: '#34D399' }}>OK. Ya puedes crear proyectos en ese VPS.</div>
          )}
          {repairing.phase === 'error' && (
            <div style={{ marginTop: 6, fontSize: 12, color: '#F87171' }}>Falló: {repairing.error}</div>
          )}
          <div style={{ marginTop: 8, textAlign: 'right' }}>
            <button className="btn-reset" onClick={() => setRepairing(null)}
              style={{ fontSize: 12, color: 'var(--text-2)' }}>
              Cerrar
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// Card per VPS row. Composes the connection indicator + the Claude CLI
// status panel underneath. The Claude panel fetches its initial state from
// the persisted `claudeStatus` JSON in the DB; the user can re-check live
// from the agent or trigger an `npm install` via the action buttons.
function VpsCard({ v, refresh, onRepair, onRemove }) {
  const [claude, setClaude] = React.useState(undefined); // undefined = unloaded
  const [busy, setBusy] = React.useState(null); // 'check' | 'install' | null
  // showTerminal: null | 'login' | 'shell'. 'login' pre-types `claude login`,
  // 'shell' opens a plain root shell for diagnostics.
  const [showTerminal, setShowTerminal] = React.useState(null);

  // Lazy-load the persisted snapshot once the row mounts. Live refresh is
  // gated behind the explicit "Verificar" button so we don't poll the agent
  // on every render.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const r = await window.api('GET', `/api/vps/${encodeURIComponent(v.id)}/claude-status`);
        if (!cancelled) setClaude(r.status ?? null);
      } catch {
        if (!cancelled) setClaude(null);
      }
    })();
    return () => { cancelled = true; };
  }, [v.id]);

  const liveCheck = async () => {
    if (!v.agentConnected) {
      alert('El agente no está conectado. Reconecta el VPS primero.');
      return;
    }
    setBusy('check');
    try {
      const r = await window.api('POST', `/api/vps/${encodeURIComponent(v.id)}/claude-status`);
      setClaude(r.status);
    } catch (err) {
      alert(err?.body?.error || err?.message || 'Falló la verificación');
    } finally {
      setBusy(null);
    }
  };

  const install = async () => {
    if (!v.agentConnected) {
      alert('El agente no está conectado. Reconecta el VPS primero.');
      return;
    }
    if (!confirm('¿Instalar el CLI de Claude Code en el agente? Esto corre `npm install -g @anthropic-ai/claude-code` adentro del container del agente.')) return;
    setBusy('install');
    try {
      const r = await window.api('POST', `/api/vps/${encodeURIComponent(v.id)}/claude-install`);
      setClaude(r.status);
    } catch (err) {
      alert(err?.body?.error || err?.message || 'Falló la instalación');
    } finally {
      setBusy(null);
    }
  };

  const importCreds = async () => {
    setBusy('import');
    try {
      const r = await window.api('POST', `/api/vps/${encodeURIComponent(v.id)}/claude-import-credentials`);
      setClaude(r.status);
      if (!r.status?.loggedIn) {
        alert('No se encontraron credenciales de Claude existentes en el VPS, o no se pudieron leer. Prueba con "Abrir terminal y hacer login".');
      }
    } catch (err) {
      alert(err?.body?.error || err?.message || 'Falló la importación');
    } finally {
      setBusy(null);
    }
  };

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 8,
      padding: 12, border: '1px solid var(--border)', borderRadius: 'var(--r-sm)',
      background: 'var(--bg-0)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <div style={{
          width: 8, height: 8, borderRadius: '50%',
          background: v.agentConnected ? '#10B981' : '#EF4444',
        }} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 500 }}>{v.name}</div>
          <div className="mono" style={{ fontSize: 11, color: 'var(--text-2)' }}>
            {v.id} — {v.ipAddress}:{v.sshPort}
            {v.agentVersion && ` • agent ${v.agentVersion}`}
          </div>
          {v.errorMessage && (
            <div style={{ fontSize: 11, color: '#F87171', marginTop: 4 }}>{v.errorMessage}</div>
          )}
        </div>
        <button className="btn-reset" onClick={() => setShowTerminal('shell')}
          title="Abrir terminal SSH"
          style={{ padding: 6, color: 'var(--text-2)' }}>
          <Icon name="terminal" size={14} />
        </button>
        <button className="btn-reset" onClick={() => onRepair(v.id)}
          title="Reparar permisos /opt"
          style={{ padding: 6, color: 'var(--text-2)' }}>
          <Icon name="wrench" size={14} />
        </button>
        <button className="btn-reset" onClick={() => onRemove(v.id)}
          title="Desconectar"
          style={{ padding: 6, color: 'var(--text-2)' }}>
          <Icon name="trash-2" size={14} />
        </button>
      </div>

      <ClaudeStatusPanel
        v={v}
        claude={claude}
        busy={busy}
        onCheck={liveCheck}
        onInstall={install}
        onImportCreds={importCreds}
        onShowLoginHelp={() => setShowTerminal('login')}
      />

      {showTerminal && (
        <ClaudeLoginHelp
          v={v}
          autoLogin={showTerminal === 'login'}
          onClose={() => { setShowTerminal(null); if (showTerminal === 'login') liveCheck(); }}
        />
      )}
    </div>
  );
}

// Three-state UI for the Claude CLI on this VPS:
//   - claude=undefined → loading
//   - claude=null      → never checked
//   - claude.installed=false → "instalar"
//   - claude.installed=true && !loggedIn → "completar login" (modal con
//                                          instrucciones SSH)
//   - claude.loggedIn=true → mostrar email + expiración
function ClaudeStatusPanel({ v, claude, busy, onCheck, onInstall, onImportCreds, onShowLoginHelp }) {
  const wrap = (children, color) => (
    <div style={{
      padding: '8px 10px', fontSize: 12, lineHeight: 1.5,
      background: 'var(--bg-1)', borderRadius: 'var(--r-sm)',
      borderLeft: `3px solid ${color}`,
      display: 'flex', alignItems: 'center', gap: 10,
    }}>{children}</div>
  );

  if (claude === undefined) {
    return wrap(<span style={{ color: 'var(--text-2)' }}>Consultando estado de Claude…</span>, 'var(--border)');
  }
  if (claude === null) {
    return wrap(
      <>
        <span style={{ flex: 1, color: 'var(--text-2)' }}>Estado del CLI de Claude no detectado todavía.</span>
        <button className="btn-reset" onClick={onCheck} disabled={busy !== null || !v.agentConnected}
          style={{ fontSize: 12, color: 'var(--accent)' }}>
          {busy === 'check' ? 'Verificando…' : 'Verificar'}
        </button>
      </>,
      'var(--border)',
    );
  }

  if (!claude.installed) {
    return wrap(
      <>
        <Icon name="alert-triangle" size={14} style={{ color: '#F59E0B' }} />
        <div style={{ flex: 1 }}>
          <div style={{ color: 'var(--text-1)', fontWeight: 500 }}>CLI de Claude no instalado</div>
          <div style={{ color: 'var(--text-2)', fontSize: 11 }}>
            Instálalo con un clic. Corre <span className="mono">npm install -g @anthropic-ai/claude-code</span> dentro del agente.
          </div>
        </div>
        <button className="btn-reset" onClick={onCheck} disabled={busy !== null}
          style={{ fontSize: 12, color: 'var(--text-2)', padding: '4px 8px' }}>
          {busy === 'check' ? '…' : <Icon name="refresh-cw" size={12} />}
        </button>
        <button className="btn-reset" onClick={onInstall} disabled={busy !== null || !v.agentConnected}
          style={{ fontSize: 12, fontWeight: 500, color: 'white', background: 'var(--accent)', padding: '6px 10px', borderRadius: 4 }}>
          {busy === 'install' ? 'Instalando…' : 'Instalar Claude CLI'}
        </button>
      </>,
      '#F59E0B',
    );
  }

  if (!claude.loggedIn) {
    // Detect the case where the agent saw an EACCES on the credentials
    // file — that means a `claude login` already ran on this VPS but the
    // file is unreadable to uid 1000. Importing credentials fixes ownership
    // without needing a fresh login.
    const looksLikeExistingCreds = !!(claude.error && /eacces|permission/i.test(claude.error));
    const sub = looksLikeExistingCreds
      ? <>Hay credenciales en el VPS pero el agente no puede leerlas. Click en <strong>Importar credenciales</strong>.</>
      : <>Si ya hiciste <span className="mono">claude login</span> en otro lugar del VPS, click en <strong>Importar credenciales</strong>. Si no, abre la terminal y haz login.</>;
    return wrap(
      <>
        <Icon name="alert-triangle" size={14} style={{ color: '#F59E0B' }} />
        <div style={{ flex: 1 }}>
          <div style={{ color: 'var(--text-1)', fontWeight: 500 }}>
            Claude CLI instalado{claude.version ? ` (${claude.version})` : ''}, falta login
          </div>
          <div style={{ color: 'var(--text-2)', fontSize: 11 }}>{sub}</div>
        </div>
        <button className="btn-reset" onClick={onCheck} disabled={busy !== null}
          style={{ fontSize: 12, color: 'var(--text-2)', padding: '4px 8px' }}>
          {busy === 'check' ? '…' : <Icon name="refresh-cw" size={12} />}
        </button>
        <button className="btn-reset" onClick={onImportCreds} disabled={busy !== null}
          title="Busca cualquier .credentials.json existente en el VPS y lo copia con permisos correctos."
          style={{ fontSize: 12, fontWeight: 500, color: 'white', background: '#10B981', padding: '6px 10px', borderRadius: 4 }}>
          {busy === 'import' ? 'Importando…' : 'Importar credenciales'}
        </button>
        <button className="btn-reset" onClick={onShowLoginHelp} disabled={busy !== null}
          style={{ fontSize: 12, fontWeight: 500, color: 'white', background: 'var(--accent)', padding: '6px 10px', borderRadius: 4 }}>
          Abrir terminal y hacer login
        </button>
      </>,
      '#F59E0B',
    );
  }

  // Logged in.
  const expiresAt = claude.oauthExpiresAt;
  const expiresIn = expiresAt ? Math.round((expiresAt - Date.now()) / (1000 * 60 * 60 * 24)) : null;
  const expiredSoon = expiresIn !== null && expiresIn < 7;
  return wrap(
    <>
      <Icon name="check-circle" size={14} style={{ color: '#10B981' }} />
      <div style={{ flex: 1 }}>
        <div style={{ color: 'var(--text-1)', fontWeight: 500 }}>
          Claude CLI logueado{claude.account ? <> como <span className="mono">{claude.account}</span></> : ''}
        </div>
        <div style={{ color: 'var(--text-2)', fontSize: 11 }}>
          {claude.version ? `${claude.version}` : 'versión desconocida'}
          {expiresAt && (
            <span style={{ color: expiredSoon ? '#F59E0B' : 'var(--text-2)' }}>
              {' '}· token expira en {expiresIn} día{expiresIn === 1 ? '' : 's'}
            </span>
          )}
        </div>
      </div>
      <button className="btn-reset" onClick={onCheck} disabled={busy !== null}
        style={{ fontSize: 12, color: 'var(--text-2)', padding: '4px 8px' }}>
        {busy === 'check' ? '…' : <Icon name="refresh-cw" size={12} />}
      </button>
    </>,
    '#10B981',
  );
}

// Modal con el web SSH terminal embebido (Fase 6). Se abre desde el panel
// de claude-status cuando el CLI está instalado pero falta login. El
// terminal se conecta vía /api/vps/:id/terminal y, si pasamos cmd=claude_login,
// el backend pre-tipea el comando docker exec ... claude login.
function ClaudeLoginHelp({ v, onClose, autoLogin = true }) {
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,.55)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 200,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: 920, maxWidth: 'calc(100vw - 32px)', height: 600, maxHeight: 'calc(100vh - 64px)',
        background: 'var(--bg-1)', border: '1px solid var(--border)',
        borderRadius: 'var(--r-md)', padding: 20,
        display: 'flex', flexDirection: 'column', gap: 12,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>Terminal del VPS · {v.name}</div>
            <div style={{ fontSize: 12, color: 'var(--text-2)', marginTop: 2 }}>
              {autoLogin
                ? 'Pre-cargamos `claude login`. Sigue las instrucciones del CLI; cuando termines, cierra y pulsa Verificar.'
                : `Conexión SSH directa a root@${v.ipAddress}.`}
            </div>
          </div>
          <button className="btn-reset" onClick={onClose}
            style={{ padding: 6, color: 'var(--text-2)' }}>
            <Icon name="x" size={16} />
          </button>
        </div>
        <VpsTerminal vpsId={v.id} cmd={autoLogin ? 'claude_login' : null} />
        <div style={{ fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.5 }}>
          El terminal se conecta como root vía la clave SSH que se desplegó al registrar el VPS.
          Cierra esta ventana cuando termines — la sesión se desconecta automáticamente.
        </div>
      </div>
    </div>
  );
}

// xterm.js terminal connected to the backend SSH proxy via WebSocket.
// xterm + the fit addon are loaded as globals from Workspace.html so we
// can use them synchronously on first render.
function VpsTerminal({ vpsId, cmd = null }) {
  const containerRef = React.useRef(null);
  const termRef = React.useRef(null);
  const wsRef = React.useRef(null);
  const fitRef = React.useRef(null);
  const [status, setStatus] = React.useState('connecting'); // 'connecting' | 'open' | 'closed'
  const [closeReason, setCloseReason] = React.useState(null);

  React.useEffect(() => {
    if (!containerRef.current) return;
    if (typeof window.Terminal !== 'function') {
      setStatus('closed');
      setCloseReason('xterm.js no se cargó (revisa la conexión a CDN).');
      return;
    }

    const term = new window.Terminal({
      fontFamily: 'JetBrains Mono, ui-monospace, monospace',
      fontSize: 13,
      cursorBlink: true,
      theme: {
        background: '#0a0a0a',
        foreground: '#e5e5e5',
        cursor: '#4f8eff',
        selectionBackground: 'rgba(79,142,255,.35)',
      },
    });
    const FitAddon = window.FitAddon?.FitAddon;
    const fit = FitAddon ? new FitAddon() : null;
    if (fit) term.loadAddon(fit);
    term.open(containerRef.current);
    if (fit) {
      try { fit.fit(); } catch { /* container size 0 in some edge cases */ }
    }
    termRef.current = term;
    fitRef.current = fit;

    const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
    const qs = cmd ? `?cmd=${encodeURIComponent(cmd)}` : '';
    const ws = new WebSocket(`${proto}//${location.host}/api/vps/${encodeURIComponent(vpsId)}/terminal${qs}`);
    wsRef.current = ws;

    ws.onopen = () => {
      setStatus('open');
      // Send initial size so the remote PTY matches the visible viewport.
      const cols = term.cols;
      const rows = term.rows;
      try { ws.send(JSON.stringify({ type: 'resize', cols, rows })); } catch { /* ignore */ }
    };
    ws.onmessage = (ev) => {
      let msg;
      try { msg = JSON.parse(ev.data); } catch { return; }
      if (msg.type === 'data' && typeof msg.payload === 'string') {
        term.write(msg.payload);
      } else if (msg.type === 'closed') {
        setCloseReason(msg.reason || 'cerrado');
        setStatus('closed');
      }
    };
    ws.onerror = () => {
      setCloseReason('conexión interrumpida');
      setStatus('closed');
    };
    ws.onclose = () => {
      setStatus('closed');
    };

    // Forward stdin from the terminal to the WebSocket.
    const dataDisp = term.onData((data) => {
      if (ws.readyState === WebSocket.OPEN) {
        try { ws.send(JSON.stringify({ type: 'data', payload: data })); } catch { /* ignore */ }
      }
    });

    // On viewport resize, fit + tell backend the new geometry. ResizeObserver
    // catches container resizes from CSS layout shifts, not just window resize.
    const ro = (typeof ResizeObserver !== 'undefined') ? new ResizeObserver(() => {
      try {
        fit?.fit();
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
        }
      } catch { /* ignore */ }
    }) : null;
    if (ro && containerRef.current) ro.observe(containerRef.current);

    return () => {
      try { dataDisp.dispose(); } catch { /* ignore */ }
      try { ro?.disconnect(); } catch { /* ignore */ }
      try { ws.close(); } catch { /* ignore */ }
      try { term.dispose(); } catch { /* ignore */ }
    };
  }, [vpsId, cmd]);

  return (
    <div style={{ flex: 1, position: 'relative', minHeight: 0 }}>
      <div ref={containerRef} style={{
        width: '100%', height: '100%',
        background: '#0a0a0a',
        border: '1px solid var(--border)', borderRadius: 'var(--r-sm)',
        padding: 8,
      }} />
      {status === 'connecting' && (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex',
          alignItems: 'center', justifyContent: 'center',
          background: 'rgba(10,10,10,.85)', borderRadius: 'var(--r-sm)',
          color: 'var(--text-2)', fontSize: 13,
        }}>
          Conectando al VPS…
        </div>
      )}
      {status === 'closed' && (
        <div style={{
          position: 'absolute', inset: 0, display: 'flex',
          flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 6,
          background: 'rgba(10,10,10,.85)', borderRadius: 'var(--r-sm)',
        }}>
          <div style={{ color: 'var(--text-1)', fontSize: 13 }}>Sesión cerrada</div>
          <div style={{ color: 'var(--text-2)', fontSize: 12 }}>{closeReason || ''}</div>
        </div>
      )}
    </div>
  );
}

function ConnectVpsForm({ onCancel, onConnected }) {
  const [id, setId] = React.useState('');
  const [name, setName] = React.useState('');
  const [ipAddress, setIpAddress] = React.useState('');
  const [sshPort, setSshPort] = React.useState(22);
  const [rootPassword, setRootPassword] = React.useState('');
  const [phase, setPhase] = React.useState('form'); // 'form' | 'progress' | 'done' | 'error'
  const [logs, setLogs] = React.useState([]);
  const [currentStep, setCurrentStep] = React.useState(null);
  const [errorMsg, setErrorMsg] = React.useState(null);

  const slugValid = /^[a-z0-9](?:[a-z0-9-]{0,28}[a-z0-9])?$/.test(id);
  const ipValid = /^[0-9.:a-fA-F]{3,}$/.test(ipAddress);
  const canSubmit = slugValid && name.trim() && ipValid && rootPassword;

  const submit = async (e) => {
    e.preventDefault();
    if (!canSubmit) return;
    setPhase('progress');
    setLogs([]);
    setErrorMsg(null);
    try {
      const res = await window.api('POST', '/api/vps', {
        id, name, ipAddress, sshPort: Number(sshPort), rootPassword,
      });
      // Open WS to stream bootstrap progress.
      const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
      const ws = new WebSocket(`${proto}//${location.host}/api/vps/connect/${res.connectId}/progress`);
      ws.onmessage = (ev) => {
        try {
          const m = JSON.parse(ev.data);
          if (m.type === 'step') {
            setCurrentStep(m.step);
            setLogs((prev) => [...prev, { kind: 'step', text: m.message }]);
          } else if (m.type === 'log') {
            setLogs((prev) => [...prev, { kind: 'log', text: m.message }]);
          } else if (m.type === 'done') {
            setPhase('done');
            setTimeout(() => onConnected(), 1500);
          } else if (m.type === 'error') {
            setPhase('error');
            setErrorMsg(m.message);
          }
        } catch {}
      };
      ws.onerror = () => {
        setPhase('error');
        setErrorMsg('La conexión de progreso se cortó.');
      };
    } catch (err) {
      setPhase('error');
      setErrorMsg(err.body?.error || err.message);
    }
  };

  if (phase === 'progress' || phase === 'done' || phase === 'error') {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{
          padding: 12, background: 'var(--bg-0)', border: '1px solid var(--border)',
          borderRadius: 'var(--r-sm)', height: 280, overflow: 'auto',
          fontFamily: 'var(--font-mono)', fontSize: 11.5, lineHeight: 1.5,
        }}>
          {logs.map((l, i) => (
            <div key={i} style={{
              color: l.kind === 'step' ? '#9DBDF5' : 'var(--text-1)',
              fontWeight: l.kind === 'step' ? 500 : 400,
            }}>
              {l.kind === 'step' ? '▸ ' : '  '}{l.text}
            </div>
          ))}
          {phase === 'progress' && currentStep && (
            <div style={{ color: 'var(--text-2)', marginTop: 4 }}>… ({currentStep})</div>
          )}
        </div>
        {phase === 'done' && (
          <div style={{
            padding: 10, background: 'rgba(16,185,129,.12)',
            border: '1px solid rgba(16,185,129,.35)', borderRadius: 'var(--r-sm)',
            fontSize: 12.5, color: '#34D399',
          }}>VPS conectado correctamente.</div>
        )}
        {phase === 'error' && (
          <div style={{
            padding: 10, background: 'rgba(239,68,68,.10)',
            border: '1px solid rgba(239,68,68,.35)', borderRadius: 'var(--r-sm)',
            fontSize: 12.5, color: '#F87171',
          }}>Falló: {errorMsg}</div>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
          {phase === 'error' && (
            <button className="btn-reset" onClick={() => setPhase('form')}
              style={{ padding: '9px 14px', fontSize: 12.5, color: 'var(--text-1)' }}>
              Volver al formulario
            </button>
          )}
          <button className="btn-reset" onClick={onCancel}
            style={{ padding: '9px 14px', fontSize: 12.5, color: 'var(--text-1)' }}>
            Cerrar
          </button>
        </div>
      </div>
    );
  }

  return (
    <form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <Field label="Slug (id)" hint="2–30 chars, minúsculas + guiones. Ej: vps-b">
        <input value={id} onChange={(e) => setId(e.target.value)} className="mono"
          placeholder="vps-b" autoFocus style={inputStyle(slugValid || id === '')} />
      </Field>
      <Field label="Nombre">
        <input value={name} onChange={(e) => setName(e.target.value)}
          placeholder="VPS de producción" style={inputStyle(true)} />
      </Field>
      <Field label="IP del VPS">
        <input value={ipAddress} onChange={(e) => setIpAddress(e.target.value)}
          className="mono" placeholder="123.45.67.89" style={inputStyle(ipValid || ipAddress === '')} />
      </Field>
      <Field label="Puerto SSH">
        <input type="number" value={sshPort} onChange={(e) => setSshPort(e.target.value)}
          style={inputStyle(true)} />
      </Field>
      <Field label="Contraseña root" hint="No se guarda. Se usa una vez para deployar una clave SSH y después se descarta.">
        <input type="password" value={rootPassword} onChange={(e) => setRootPassword(e.target.value)}
          autoComplete="off" style={inputStyle(true)} />
      </Field>
      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 6 }}>
        <button type="button" className="btn-reset" onClick={onCancel}
          style={{ padding: '9px 14px', fontSize: 12.5, color: 'var(--text-1)' }}>
          Cancelar
        </button>
        <button type="submit" className="btn-reset" disabled={!canSubmit}
          style={{
            padding: '9px 14px', fontSize: 12.5, fontWeight: 600,
            background: canSubmit ? 'var(--text-0)' : 'var(--bg-2)',
            color: canSubmit ? 'var(--bg-0)' : 'var(--text-2)',
            borderRadius: 'var(--r-sm)',
          }}>
          Conectar
        </button>
      </div>
    </form>
  );
}

// Reuses helpers defined in projects.jsx — load order matters in
// Workspace.html (projects.jsx must come first).
function Field({ label, hint, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
      <label style={{ fontSize: 11, color: 'var(--text-2)', letterSpacing: 0.4, textTransform: 'uppercase' }}>{label}</label>
      {children}
      {hint && <div style={{ fontSize: 11, color: 'var(--text-3)' }}>{hint}</div>}
    </div>
  );
}
function inputStyle(valid) {
  return {
    padding: '9px 12px',
    background: 'var(--bg-0)',
    border: `1px solid ${valid ? 'var(--border)' : 'rgba(239,68,68,.5)'}`,
    borderRadius: 'var(--r-sm)',
    color: 'var(--text-0)',
    fontSize: 13,
    outline: 'none',
  };
}

// Globally available open helper. The TopBar's "Gestionar VPS" menu item
// calls window.openVpsManager(); we lift state into a module-level
// listener that any component can subscribe to.
const openListeners = new Set();
window.openVpsManager = () => {
  openListeners.forEach((cb) => cb());
};

function VpsManagerHost() {
  const [open, setOpen] = React.useState(false);
  React.useEffect(() => {
    const cb = () => setOpen(true);
    openListeners.add(cb);
    return () => openListeners.delete(cb);
  }, []);
  if (!open) return null;
  return <VpsManager onClose={() => setOpen(false)} />;
}

window.VpsManagerHost = VpsManagerHost;
