// All views/screens for RodaiClientes
const { useState: useStateV, useEffect: useEffectV, useMemo: useMemoV, useRef: useRefV } = React;

// ---------------- LOGIN ----------------
function LoginView({ onLogin }) {
  const [user, setUser] = useStateV('admin');
  const [pwd, setPwd] = useStateV('');
  const [loading, setLoading] = useStateV(false);
  const [error, setError] = useStateV('');

  const submit = async (e) => {
    e?.preventDefault?.();
    if (!user || !pwd) { setError('Preencha usuário e senha'); return; }
    setLoading(true);
    setError('');
    try {
      const r = await fetch('/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: user, password: pwd }),
      });
      if (r.ok) {
        const data = await r.json();
        onLogin(data.access_token);
      } else {
        const err = await r.json().catch(() => ({}));
        setError(err.detail || 'Credenciais inválidas');
      }
    } catch (_) {
      setError('Erro de conexão — verifique o servidor');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="login-view" style={{ minHeight: '100%', display: 'grid', placeItems: 'center', background: 'var(--bg)', position: 'relative', overflow: 'hidden' }}>
      {/* subtle grid backdrop */}
      <div aria-hidden="true" style={{
        position: 'absolute', inset: 0,
        backgroundImage: 'linear-gradient(var(--line-soft) 1px, transparent 1px), linear-gradient(90deg, var(--line-soft) 1px, transparent 1px)',
        backgroundSize: '32px 32px',
        maskImage: 'radial-gradient(ellipse at center, black 30%, transparent 75%)',
        opacity: 0.6,
      }} />
      <div className="card login-card" style={{ width: 380, padding: 24, position: 'relative' }}>
        <div className="row" style={{ gap: 10, marginBottom: 20 }}>
          <BrandMark size={28} />
          <div className="col" style={{ lineHeight: 1.1 }}>
            <span style={{ fontWeight: 600, fontSize: 16 }}>RodaiClientes</span>
            <span className="dim" style={{ fontSize: 11.5 }}>Painel interno · RodaiLAB</span>
          </div>
        </div>

        <form onSubmit={submit} className="col gap-3">
          <div className="col" style={{ gap: 5 }}>
            <label className="uc">Usuário</label>
            <Input value={user} onChange={setUser} placeholder="admin" icon="user" autoFocus />
          </div>
          <div className="col" style={{ gap: 5 }}>
            <label className="uc">Senha</label>
            <Input value={pwd} onChange={setPwd} placeholder="••••••••" icon="shield" type="password" />
          </div>

          <div className="row" style={{ marginTop: 4, gap: 8 }}>
            <label className="row" style={{ gap: 6, fontSize: 12 }}>
              <input type="checkbox" defaultChecked /> Manter sessão por 30 dias
            </label>
            <div style={{ flex: 1 }} />
            <a className="link" href="mailto:suporte@rodailab.com.br" style={{ fontSize: 12 }}>Suporte</a>
          </div>

          <Btn kind="primary" size="md" iconRight="arrowR" onClick={submit} disabled={loading}>
            {loading ? 'Entrando…' : 'Entrar'}
          </Btn>

          {error && (
            <div style={{ fontSize: 12, color: 'var(--err)', background: 'color-mix(in srgb, var(--err) 10%, transparent)', border: '1px solid color-mix(in srgb, var(--err) 30%, transparent)', borderRadius: 6, padding: '6px 10px', textAlign: 'center' }}>
              {error}
            </div>
          )}

          <div className="row" style={{ gap: 6, marginTop: 6, justifyContent: 'center', fontSize: 11.5, color: 'var(--text-dim)' }}>
            <Icon name="shield" size={11} /> Acesso restrito · IP {`192.168.10.42`} · sessão 4h
          </div>
        </form>

        <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px solid var(--line)', display: 'flex', gap: 12, fontSize: 11.5 }}>
          <span className="dim">4 servidores</span>
          <span className="row gap-1"><span className="dot dot-ok" /><span className="muted">3 online</span></span>
          <span className="row gap-1"><span className="dot dot-err" /><span className="muted">1 offline</span></span>
          <span style={{ marginLeft: 'auto' }} className="dim font-mono">v0.4.2</span>
        </div>
      </div>
    </div>
  );
}

// ---------------- DASHBOARD HELPERS ----------------
function SparkLine({ data, color, width = 90, height = 32, dashed = false }) {
  if (!data || data.length < 2) {
    return (
      <svg width={width} height={height} style={{ display: 'block' }}>
        <line x1="4" y1={height / 2} x2={width - 4} y2={height / 2}
          stroke={color || 'var(--line-strong)'} strokeWidth="1.5"
          strokeDasharray="4 3" opacity="0.5" />
      </svg>
    );
  }
  const min = Math.min(...data);
  const max = Math.max(...data);
  const range = max - min || 1;
  const pts = data.map((v, i) => [
    4 + (i / (data.length - 1)) * (width - 8),
    (height - 6) - ((v - min) / range) * (height - 10) + 3,
  ]);
  const d = pts.reduce((acc, pt, i) => {
    if (i === 0) return `M${pt[0].toFixed(1)},${pt[1].toFixed(1)}`;
    const prev = pts[i - 1];
    const cx = ((prev[0] + pt[0]) / 2).toFixed(1);
    return `${acc} C${cx},${prev[1].toFixed(1)} ${cx},${pt[1].toFixed(1)} ${pt[0].toFixed(1)},${pt[1].toFixed(1)}`;
  }, '');
  return (
    <svg width={width} height={height} style={{ display: 'block' }}>
      {dashed
        ? <path d={d} fill="none" stroke={color || 'var(--err)'} strokeWidth="1.5" strokeDasharray="4 3" strokeLinecap="round" opacity="0.6" />
        : <path d={d} fill="none" stroke={color || 'var(--accent)'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
      }
    </svg>
  );
}

function _parseUptimeStr(raw) {
  if (!raw) return { uptime: '—', load: null };
  const loadM = raw.match(/load averages?:\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
  const load = loadM ? [parseFloat(loadM[1]), parseFloat(loadM[2]), parseFloat(loadM[3])] : null;
  const daysM = raw.match(/up\s+(\d+)\s+days?/);
  const hmM   = raw.match(/(\d+):(\d+)/);
  const minM  = raw.match(/up\s+(\d+)\s+min/);
  let uptime = '—';
  if (daysM && hmM) uptime = `${daysM[1]} d ${hmM[1].padStart(2,'0')}:${hmM[2]}`;
  else if (hmM)     uptime = `${hmM[1]}h ${hmM[2]}min`;
  else if (minM)    uptime = `${minM[1]} min`;
  return { uptime, load };
}

function _apiToMetrics(apiResult, sparkHistory) {
  if (!apiResult) {
    return { ping: null, cpu: null, ram: null, disk: null, diskTotal: null, swap: null, swapTotal: null, temp: null, backup: {}, uptime: '?', load: null, svc: {}, users: {}, spark: [], alerts: [], status: 'saudavel', online: true };
  }
  const { online, ping_ms, uptime: raw, cpu_pct, ram_pct, disk_pct, disk_total, swap_pct, swap_total, temp_c, backup_status, backup_detail, backup_time, services = {}, users = {} } = apiResult;
  const { uptime, load } = _parseUptimeStr(raw);
  const cpu = Number.isFinite(cpu_pct) ? cpu_pct : null;
  const ram = Number.isFinite(ram_pct) ? ram_pct : null;
  const disk = Number.isFinite(disk_pct) ? disk_pct : null;
  const swap = Number.isFinite(swap_pct) ? swap_pct : null;
  const temp = Number.isFinite(temp_c) ? temp_c : null;
  const backup = { status: backup_status || 'unknown', detail: backup_detail || null, time: backup_time || null };
  const alerts = [];
  if (!online) alerts.push('ssh_timeout');
  if (online && disk !== null && disk > 70) alerts.push('disk');
  if (online && cpu !== null && cpu > 85) alerts.push('cpu');
  if (online && ram !== null && ram > 85) alerts.push('ram');
  if (online && swap !== null && swap > 75) alerts.push('swap');
  if (online && temp !== null && temp > 80) alerts.push('temp');
  if (online && ['fail', 'missing', 'unknown'].includes(backup.status)) alerts.push(`backup_${backup.status}`);

  const svc = {
    ssh: online ? 'ok' : 'err',
    rsync: online && services.rsync === true ? 'ok' : 'err',
    cron: online && services.cron === true ? 'ok' : 'err',
    monitor: online && services.monitor === true ? 'ok' : 'err',
  };
  const userStatus = {};
  const userNames = (window.RC && window.RC.users && window.RC.users.length) ? window.RC.users : Object.keys(users || {});
  userNames.forEach(name => {
    userStatus[name] = online && users && users[name] === true ? 'ok' : 'err';
    if (online && userStatus[name] === 'err') alerts.push(`user_${name}`);
  });
  if (online && svc.rsync === 'err') alerts.push('svc_rsync');
  if (online && svc.cron === 'err') alerts.push('svc_cron');
  if (online && svc.monitor === 'err') alerts.push('svc_monitor');

  const status = !online ? 'offline' : alerts.length ? 'atencao' : 'saudavel';
  return {
    ping: online ? ping_ms : null,
    cpu,
    ram,
    disk,
    diskTotal: disk_total || null,
    swap,
    swapTotal: swap_total || null,
    temp,
    backup,
    uptime: online ? uptime : 'down',
    load,
    svc,
    users: userStatus,
    spark: sparkHistory || [],
    alerts,
    status,
    online,
  };
}

function MetricBlock({ label, value, unit = '%', hint, tone = 'accent' }) {
  const pct = Number.isFinite(value) ? Math.max(0, Math.min(100, Math.round(value))) : null;
  const color = tone === 'err' ? 'var(--err)' : tone === 'warn' ? 'var(--warn)' : 'var(--accent)';
  return (
    <div className="server-metric-block">
      <div className="row" style={{ justifyContent: 'space-between', gap: 8 }}>
        <span className="uc">{label}</span>
        <span className="font-mono" style={{ fontSize: 10.5, color: pct === null ? 'var(--text-dim)' : 'var(--text-muted)' }}>{hint || (pct === null ? 'offline' : 'normal')}</span>
      </div>
      <div className="font-mono" style={{ marginTop: 6, fontWeight: 700, letterSpacing: '-0.04em' }}>
        <span style={{ fontSize: 25, color: pct === null ? 'var(--text-dim)' : 'var(--text)' }}>{pct === null ? '?' : pct}</span>
        {pct !== null ? <span style={{ fontSize: 12, color: 'var(--text-muted)', marginLeft: 2 }}>{unit}</span> : null}
      </div>
      <div className="progress" style={{ height: 6, marginTop: 8, background: 'var(--bg-3)' }}>
        <span style={{ width: `${pct ?? 0}%`, background: color }} />
      </div>
    </div>
  );
}

function StatusBlock({ label, value, hint, tone = 'accent' }) {
  const color = tone === 'err' ? 'var(--err)' : tone === 'warn' ? 'var(--warn)' : tone === 'ok' ? 'var(--ok)' : 'var(--accent)';
  return (
    <div className="server-metric-block">
      <div className="row" style={{ justifyContent: 'space-between', gap: 8 }}>
        <span className="uc">{label}</span>
        <span className="font-mono" style={{ fontSize: 10.5, color: 'var(--text-muted)', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{hint || 'sem leitura'}</span>
      </div>
      <div className="font-mono" style={{ marginTop: 7, fontWeight: 700, minWidth: 0 }}>
        <span style={{ fontSize: 21, color, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', display: 'block' }}>{value || '?'}</span>
      </div>
      <div className="progress" style={{ height: 6, marginTop: 9, background: 'var(--bg-3)' }}>
        <span style={{ width: '100%', background: color, opacity: 0.75 }} />
      </div>
    </div>
  );
}

function _shortTimeLabel(raw) {
  if (!raw) return null;
  const m = String(raw).match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
  if (m) return m[2];
  return String(raw).slice(0, 16);
}

function _backupSummary(backup = {}) {
  const time = _shortTimeLabel(backup.time);
  const detail = backup.detail || '';
  if (backup.status === 'ok') return { value: 'OK', hint: time ? `hoje ${time}` : 'concluido', tone: 'ok' };
  if (backup.status === 'fail') return { value: 'FALHOU', hint: time ? `${detail || 'erro'} ${time}` : (detail || 'erro'), tone: 'err' };
  if (backup.status === 'missing') return { value: 'SEM HOJE', hint: time ? `ultimo ${time}` : 'sem registro', tone: 'warn' };
  if (backup.status === 'running') return { value: 'RODANDO', hint: time ? `desde ${time}` : 'em execucao', tone: 'warn' };
  return { value: '?', hint: detail || 'sem log', tone: 'warn' };
}

function ServidorCard({ server, metrics, onRecheck, checking, onEdit }) {
  if (!metrics) {
    return (
      <div className="card server-status-card" style={{ padding: 0, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
        <div className="server-status-head">
          <span className="dot dot-warn" />
          <div style={{ minWidth: 0 }}>
            <div className="font-mono" style={{ fontWeight: 700, fontSize: 14 }}>{server.name}</div>
            <div className="font-mono" style={{ fontSize: 11, color: 'var(--text-dim)' }}>{server.host}</div>
          </div>
          <div style={{ flex: 1 }} />
          <span className="chip chip-acc">verificando...</span>
        </div>
        <div style={{ padding: 18, display: 'flex', alignItems: 'center', gap: 10, color: 'var(--text-muted)', fontSize: 12 }}>
          <div className="spin" style={{ width: 14, height: 14, border: '2px solid var(--line-strong)', borderTopColor: 'var(--accent)', borderRadius: '50%' }} />
          Conectando via SSH...
        </div>
      </div>
    );
  }

  const { cpu, ram, disk, diskTotal, swap, swapTotal, temp, backup = {}, ping, uptime, load, svc = {}, users = {}, spark, alerts = [], status = 'saudavel', online } = metrics;
  const cardTone = status === 'offline' ? 'err' : status === 'atencao' ? 'warn' : 'ok';
  const sparkColor = cardTone === 'err' ? 'var(--err)' : 'var(--accent)';
  const statusLabel = status === 'saudavel' ? 'SAUD\u00c1VEL' : status === 'atencao' ? 'ATEN\u00c7\u00c3O' : 'OFFLINE';
  const cpuTone = !online || cpu === null ? 'mut' : cpu > 85 ? 'err' : cpu > 70 ? 'warn' : 'accent';
  const ramTone = !online || ram === null ? 'mut' : ram > 85 ? 'err' : ram > 70 ? 'warn' : 'accent';
  const diskTone = disk === null ? 'mut' : disk > 85 ? 'err' : disk > 70 ? 'warn' : 'accent';
  const swapTone = !online || swap === null ? 'mut' : swap > 85 ? 'err' : swap > 70 ? 'warn' : 'accent';
  const tempTone = !online || temp === null ? 'mut' : temp > 85 ? 'err' : temp > 75 ? 'warn' : 'accent';
  const backupSummary = _backupSummary(backup);
  const alertMsg = {
    cpu: `CPU passou de 85% (${Math.round(cpu || 0)}%)`,
    ram: `RAM passou de 85% (${Math.round(ram || 0)}%)`,
    disk: `Disco passou de 70% (${Math.round(disk || 0)}%)`,
    swap: `Swap passou de 75% (${Math.round(swap || 0)}%)`,
    temp: `Temperatura passou de 80C (${Math.round(temp || 0)}C)`,
    backup_fail: 'Backup diario falhou na ultima execucao',
    backup_missing: 'Backup diario ainda nao registrado hoje',
    backup_unknown: 'Backup diario sem leitura do log',
    ssh_timeout: 'SSH timeout \u2014 servidor inalcan\u00e7\u00e1vel',
    svc_rsync: 'Rsync indisponivel no servidor',
    svc_cron: 'Cron inativo no servidor',
    svc_monitor: 'Monitor rodaiuploader inativo',
  };
  const svcLabels = { ssh: 'ssh', rsync: 'rsync', cron: 'cron', monitor: 'monitor' };

  return (
    <div className={`card server-status-card server-status-${cardTone}`}>
      <div className="server-status-head">
        <span className={`dot ${cardTone === 'err' ? 'dot-err' : cardTone === 'warn' ? 'dot-warn' : 'dot-ok'}`} />
        <div style={{ minWidth: 0 }}>
          <div className="font-mono" style={{ fontWeight: 700, fontSize: 14 }}>{server.name}</div>
          <div className="font-mono" style={{ fontSize: 11, color: 'var(--text-dim)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{server.host}</div>
        </div>
        <div style={{ flex: 1 }} />
        <span className={`server-status-pill server-status-pill-${cardTone}`}>{statusLabel}</span>
      </div>

      <div className="server-status-body">
        <div className="row" style={{ alignItems: 'flex-end', gap: 12 }}>
          <div>
            <div className="uc">{'Lat\u00eancia'}</div>
            <div className="font-mono" style={{ marginTop: 2, fontWeight: 700, letterSpacing: '-0.05em' }}>
              <span style={{ fontSize: 28, color: ping != null ? 'var(--text)' : 'var(--text-dim)' }}>{ping != null ? ping : '?'}</span>
              {ping != null ? <span style={{ fontSize: 11, color: 'var(--text-muted)', marginLeft: 2 }}>ms</span> : null}
            </div>
          </div>
          <div style={{ flex: 1 }} />
          <SparkLine data={spark} color={sparkColor} width={155} height={34} dashed={!online} />
        </div>

        <div className="server-metric-grid">
          <MetricBlock label="CPU" value={online ? cpu : null} tone={cpuTone === 'err' ? 'err' : cpuTone === 'warn' ? 'warn' : 'accent'} hint={!online ? 'offline' : undefined} />
          <MetricBlock label="RAM" value={online ? ram : null} tone={ramTone === 'err' ? 'err' : ramTone === 'warn' ? 'warn' : 'accent'} hint={!online ? 'offline' : undefined} />
          <MetricBlock label="DISCO" value={disk} tone={diskTone === 'err' ? 'err' : diskTone === 'warn' ? 'warn' : 'accent'} hint={diskTotal || undefined} />
        </div>

        <div className="server-metric-grid">
          <MetricBlock label="SWAP" value={online ? swap : null} tone={swapTone === 'err' ? 'err' : swapTone === 'warn' ? 'warn' : 'accent'} hint={!online ? 'offline' : (swapTotal || 'sem swap')} />
          <MetricBlock label="TEMP" value={online ? temp : null} unit="C" tone={tempTone === 'err' ? 'err' : tempTone === 'warn' ? 'warn' : 'accent'} hint={!online ? 'offline' : (temp === null ? 'sem sensor' : 'normal')} />
          <StatusBlock label="BACKUP" value={online ? backupSummary.value : '?'} hint={online ? backupSummary.hint : 'offline'} tone={online ? backupSummary.tone : 'warn'} />
        </div>

        <div className="server-meta-grid">
          <div><span>Uptime</span><strong className="font-mono">{online ? uptime : 'down'}</strong></div>
          <div><span>Load avg</span><strong className="font-mono">{load ? load.map(l => l.toFixed(2)).join(' ') : '?'}</strong></div>
          <div><span>Disco total</span><strong className="font-mono">{diskTotal || '?'}</strong></div>
          <div><span>Visto</span><strong>{server.lastSeen || 'agora'}</strong></div>
        </div>

        <div className="server-chip-section">
          <div className="uc" style={{ marginBottom: 6 }}>Usuarios</div>
          <div className="server-service-row">
            {Object.entries(users).map(([name, state]) => (
              <span key={name} className={`server-service-chip server-service-${state}`}>
                <span />{name} {state === 'ok' ? 'ON' : 'OFF'}
              </span>
            ))}
          </div>
        </div>

        <div className="server-chip-section">
          <div className="uc" style={{ marginBottom: 6 }}>Servicos</div>
          <div className="server-service-row">
            {Object.entries(svc).map(([name, state]) => (
              <span key={name} className={`server-service-chip server-service-${state}`}>
                <span />{svcLabels[name] || name}
              </span>
            ))}
          </div>
        </div>

        {alerts.map((a, i) => (
          <div key={i} className={`server-alert server-alert-${a === 'ssh_timeout' ? 'err' : 'warn'}`}>
            <Icon name="alert" size={11} /> {alertMsg[a] || (a.startsWith('user_') ? `${a.slice(5)} OFF - HRC nao esta ativo` : a)}
          </div>
        ))}
      </div>

      <div className="server-status-foot">
        <Btn size="sm" kind="ghost" icon="terminal">SSH</Btn>
        <Btn size="sm" kind="ghost" icon="refresh" onClick={onRecheck} disabled={checking}>
          {checking ? 'Verificando...' : 'Re-checar'}
        </Btn>
        {onEdit ? <Btn size="sm" kind="ghost" icon="edit" onClick={onEdit}>Editar</Btn> : null}
        <div style={{ flex: 1 }} />
        <Btn size="sm" kind="ghost" iconRight="arrowR">Detalhes</Btn>
      </div>
    </div>
  );
}

// ---------------- DASHBOARD ----------------
function DashboardView({ go }) {
  const [apiResults, setApiResults] = useStateV(null);  // null = ainda carregando
  const [fetchErr, setFetchErr]     = useStateV('');
  const [checking, setChecking]     = useStateV({});
  const [showTelegramAlerts, setShowTelegramAlerts] = useStateV(false);
  const [telegramEvents, setTelegramEvents] = useStateV([]);
  const [telegramUnreadOnly, setTelegramUnreadOnly] = useStateV(false);
  const [telegramUnreadCount, setTelegramUnreadCount] = useStateV(0);
  const [telegramTotalCount, setTelegramTotalCount] = useStateV(0);
  const [telegramBusy, setTelegramBusy] = useStateV(false);
  const sparkRef = useRefV({});  // { srvId: number[] } — histórico de latência

  const _addSpark = (srvId, ping) => {
    if (ping == null) return;
    if (!sparkRef.current[srvId]) sparkRef.current[srvId] = [];
    sparkRef.current[srvId].push(ping);
    if (sparkRef.current[srvId].length > 16) sparkRef.current[srvId].shift();
  };

  const fetchTelegramAlerts = async (unreadOnly = telegramUnreadOnly) => {
    try {
      const r = await window.rcFetch(`/api/servidores/alertas-telegram?limit=80&unread_only=${unreadOnly ? 'true' : 'false'}`);
      if (r.ok) {
        const data = await r.json();
        setTelegramEvents(Array.isArray(data.events) ? data.events : []);
        setTelegramUnreadCount(Number(data.unread_count || 0));
        setTelegramTotalCount(Number(data.total_count || 0));
      }
    } catch (_) {}
  };

  const toggleTelegramUnread = async () => {
    const next = !telegramUnreadOnly;
    setTelegramUnreadOnly(next);
    await fetchTelegramAlerts(next);
  };

  const markTelegramRead = async () => {
    setTelegramBusy(true);
    try {
      const r = await window.rcFetch('/api/servidores/alertas-telegram/marcar-lidas', { method: 'POST' });
      if (r.ok) await fetchTelegramAlerts(telegramUnreadOnly);
    } catch (_) {
    } finally {
      setTelegramBusy(false);
    }
  };

  const clearTelegramAlerts = async () => {
    if (!telegramTotalCount) return;
    if (!window.confirm('Limpar mensagens encaminhadas ao Telegram?')) return;
    setTelegramBusy(true);
    try {
      const r = await window.rcFetch('/api/servidores/alertas-telegram/limpar', { method: 'POST' });
      if (r.ok) {
        setTelegramEvents([]);
        setTelegramUnreadCount(0);
        setTelegramTotalCount(0);
      }
    } catch (_) {
    } finally {
      setTelegramBusy(false);
    }
  };

  const fetchAll = async () => {
    setFetchErr('');
    const all = {};
    window.RC.servers.forEach(s => { all[s.id] = true; });
    setChecking(all);
    try {
      const r = await window.rcFetch('/api/servidores/testar-todos', { method: 'POST' });
      if (r.ok) {
        const data = await r.json();
        data.forEach(d => _addSpark(d.servidor_id, d.ping_ms));
        setApiResults(data);
        await fetchTelegramAlerts();
      } else {
        setFetchErr('Falha ao verificar servidores');
      }
    } catch (e) {
      setFetchErr('Erro de conexao: ' + (e.message || e));
    } finally {
      setChecking({});
    }
  };

  const recheckServer = async (srvId) => {
    setChecking(c => ({ ...c, [srvId]: true }));
    try {
      const r = await window.rcFetch(`/api/servidores/${srvId}/testar`, { method: 'POST' });
      if (r.ok) {
        const res = await r.json();
        _addSpark(srvId, res.ping_ms);
        setApiResults(prev => (prev || []).map(d => d.servidor_id === srvId ? res : d));
        await fetchTelegramAlerts();
      }
    } catch (_) {}
    setChecking(c => ({ ...c, [srvId]: false }));
  };

  const recheckAll = fetchAll;

  useEffectV(() => {
    fetchTelegramAlerts();
    fetchAll();
    const h = () => fetchAll();
    window.addEventListener('rc-recheck-all', h);
    return () => window.removeEventListener('rc-recheck-all', h);
  }, []);

  const onlineSrvs  = (apiResults || []).filter(r => r.online);
  const offlineSrvs = (apiResults || []).filter(r => !r.online);
  const pings    = onlineSrvs.map(r => r.ping_ms).filter(Boolean);
  const avgPing  = pings.length ? Math.round(pings.reduce((a, v) => a + v, 0) / pings.length) : null;
  const disks    = (apiResults || []).map(r => r.disk_pct || 0);
  const avgDisk  = disks.length ? Math.round(disks.reduce((a, v) => a + v, 0) / disks.length) : 0;
  const activeAlertRows = (apiResults || []).flatMap(r => {
    const srv = window.RC.servers.find(s => s.id === r.servidor_id) || {};
    const nome = srv.name || r.nome || `Servidor ${r.servidor_id}`;
    const rows = [];
    const add = (kind, label, tone = 'warn') => rows.push({
      id: `${r.servidor_id}-${kind}`,
      tone,
      title: `${nome}: ${label}`,
      detail: 'Falha ativa detectada na ultima verificacao.',
    });

    if (!r.online) {
      add('ssh', 'SSH indisponivel', 'err');
      return rows;
    }

    const services = r.services || {};
    if (services.rsync !== true) add('rsync', 'rsync indisponivel');
    if (services.cron !== true) add('cron', 'cron inativo');
    if (services.monitor !== true) add('monitor', 'monitor rodaiuploader inativo');

    Object.entries(r.users || {}).forEach(([name, ok]) => {
      if (ok !== true) add(`user-${name}`, `${name} OFF - HRC nao esta ativo`);
    });

    const backupLabels = {
      fail: 'backup diario falhou',
      missing: 'backup diario sem registro hoje',
      unknown: 'backup diario sem leitura do log',
    };
    if (backupLabels[r.backup_status]) add(`backup-${r.backup_status}`, backupLabels[r.backup_status], r.backup_status === 'fail' ? 'err' : 'warn');
    return rows;
  });
  const alertCnt = activeAlertRows.length;
  const telegramAlertRows = (telegramEvents || []).map((event, idx) => ({
    id: event.id || `${event.servidor_id || 'telegram'}-${event.created_at || idx}-${idx}`,
    tone: event.tone || (event.tipo === 'recuperado' ? 'ok' : 'warn'),
    title: event.title || `${event.servidor || 'Servidor'}: ${event.tipo === 'recuperado' ? 'recuperado' : 'alerta enviado'}`,
    detail: event.detail || event.created_at_label || 'Mensagem encaminhada ao Telegram.',
    badge: event.badge || (event.tipo === 'recuperado' ? 'Recuperado no Telegram' : 'Telegram enviado'),
    unread: !event.read_at,
  }));
  const telegramPanelBadge = telegramUnreadOnly
    ? `${telegramAlertRows.length} nao lida${telegramAlertRows.length !== 1 ? 's' : ''}`
    : telegramUnreadCount
      ? `${telegramUnreadCount} nao lida${telegramUnreadCount !== 1 ? 's' : ''}`
      : (telegramAlertRows.length ? `${telegramAlertRows.length} recente${telegramAlertRows.length !== 1 ? 's' : ''}` : (apiResults ? '0 recentes' : 'verificando'));

  const activeClients = window.RC.clients.filter(c => !c.archived).length;
  const syncedClients = window.RC.clients.filter(c => !c.archived && c.status.every(s => s === 0)).length;
  const diffCount     = window.RC.syncDiff.length;
  const liveOp        = window.RC.liveOp;

  const opStatusEl = (s) => (
    s === 'running'  ? <span className="row gap-1"><span className="dot dot-acc pulse-dot" /><span style={{ color: 'var(--accent)' }}>rodando</span></span>
    : s === 'success' ? <span className="row gap-1"><span className="dot dot-ok" /><span className="muted">sucesso</span></span>
    : s === 'partial' ? <span className="row gap-1"><span className="dot dot-warn" /><span style={{ color: 'var(--warn)' }}>parcial</span></span>
    : <span className="row gap-1"><span className="dot dot-err" /><span style={{ color: 'var(--err)' }}>falhou</span></span>
  );

  return (
    <div className="view-page dashboard-view" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>

      {/* ── HERO: SAÚDE DOS SERVIDORES ─────────────────────────────────────── */}
      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>

        {/* section header */}
        <div className="row" style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)', gap: 10 }}>
          <Icon name="server" size={15} style={{ color: 'var(--accent)' }} />
          <div>
            <div style={{ fontWeight: 600, fontSize: 14 }}>Saúde dos servidores</div>
            <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 1 }}>
              Telemetria das {window.RC.servers.length} máquinas RodaiLAB · alertas espelhados no Telegram
            </div>
          </div>
          <div style={{ flex: 1 }} />
          <Btn kind="ghost" size="sm" icon="bell" onClick={() => setShowTelegramAlerts(v => !v)}>Alertas</Btn>
          <Btn kind="secondary" size="sm" icon="refresh" onClick={recheckAll} disabled={Object.values(checking).some(Boolean)}>
            Re-checar todos
          </Btn>
        </div>

        {/* summary stats bar */}
        <div className="dashboard-summary-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', borderBottom: '1px solid var(--line)' }}>
          {[
            {
              label: 'ONLINE',
              value: apiResults ? `${onlineSrvs.length}/${window.RC.servers.length}` : '—',
              sub: !apiResults ? 'verificando…' : offlineSrvs.length ? `${offlineSrvs.length} offline` : 'todos online',
              valColor: !apiResults ? 'var(--text-muted)' : offlineSrvs.length ? 'var(--err)' : 'var(--ok)',
            },
            {
              label: 'LATÊNCIA MÉDIA',
              value: avgPing !== null ? `${avgPing}ms` : '—',
              sub: 'p/ servidores online',
              valColor: avgPing > 100 ? 'var(--warn)' : 'var(--text)',
            },
            {
              label: 'USO DE DISCO',
              value: `${avgDisk}%`,
              sub: 'média da frota',
              valColor: avgDisk > 70 ? 'var(--warn)' : 'var(--text)',
            },
            {
              label: 'ALERTAS ATIVOS',
              value: alertCnt,
              sub: !apiResults ? 'verificando...' : alertCnt ? `${alertCnt} ativo${alertCnt > 1 ? 's' : ''} agora` : 'nenhum incidente',
              valColor: alertCnt ? 'var(--warn)' : 'var(--ok)',
            },
          ].map((stat, i) => (
            <div key={i} style={{ padding: '12px 16px', borderRight: i < 3 ? '1px solid var(--line)' : undefined }}>
              <div style={{ fontSize: 10, color: 'var(--text-muted)', fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', marginBottom: 4 }}>{stat.label}</div>
              <div className="row" style={{ alignItems: 'baseline', gap: 8 }}>
                <span style={{ fontSize: 24, fontWeight: 700, letterSpacing: '-0.03em', fontFamily: 'Geist Mono', color: stat.valColor }}>{stat.value}</span>
                {i === 0 && offlineSrvs.length ? <span style={{ fontSize: 12, color: 'var(--err)', fontWeight: 500 }}>· {offlineSrvs.length} offline</span> : null}
              </div>
              <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 3 }}>{stat.sub}</div>
            </div>
          ))}
        </div>

        {/* Telegram status bar */}
        <div className="row" style={{ padding: '6px 16px', borderTop: '1px solid var(--line)', background: 'var(--bg-3)', gap: 8, fontSize: 11.5 }}>
          <Icon name="bell" size={11} style={{ color: 'var(--text-muted)' }} />
          <span style={{ color: 'var(--text-muted)' }}>Telegram</span>
          <span style={{ fontSize: 10, padding: '1px 7px', borderRadius: 10, background: 'color-mix(in srgb, var(--ok) 20%, transparent)', color: 'var(--ok)', border: '1px solid color-mix(in srgb, var(--ok) 30%, transparent)' }}>
            conectado
          </span>
          <span className="font-mono" style={{ fontSize: 10.5, color: 'var(--text-dim)' }}>@rodai_server_status_bot</span>
          <div style={{ flex: 1 }} />
          <Btn kind="ghost" size="sm" iconRight="arrowR" onClick={() => setShowTelegramAlerts(v => !v)}>Alertas</Btn>
          <Btn kind="ghost" size="sm" iconRight="arrowR" onClick={() => go('historico')}>Histórico</Btn>
        </div>

        {(showTelegramAlerts || telegramAlertRows.length > 0 || activeAlertRows.length > 0) && (
          <div style={{ padding: '10px 16px 12px', borderTop: '1px solid var(--line)', display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div className="row" style={{ gap: 8, flexWrap: 'wrap' }}>
              <Icon name="bell" size={12} style={{ color: 'var(--accent)' }} />
              <strong style={{ fontSize: 12.5 }}>Mensagens encaminhadas ao Telegram</strong>
              <span className={`chip ${telegramUnreadCount ? 'chip-warn' : ''}`} style={{ fontSize: 10.5 }}>{telegramPanelBadge}</span>
              <div style={{ flex: 1, minWidth: 12 }} />
              <Btn kind={telegramUnreadOnly ? 'secondary' : 'ghost'} size="sm" icon="filter" onClick={toggleTelegramUnread} disabled={telegramBusy}>
                {telegramUnreadOnly ? 'Todas' : 'Nao lidas'}
              </Btn>
              <Btn kind="ghost" size="sm" icon="check" onClick={markTelegramRead} disabled={telegramBusy || !telegramUnreadCount}>Marcar lidas</Btn>
              <Btn kind="ghost" size="sm" icon="x" onClick={clearTelegramAlerts} disabled={telegramBusy || !telegramTotalCount}>Limpar</Btn>
            </div>
            {!apiResults && !telegramAlertRows.length ? (
              <div style={{ fontSize: 12, color: 'var(--text-muted)', padding: '7px 10px', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 6 }}>
                Verificando servidores antes de listar os envios ao Telegram.
              </div>
            ) : telegramAlertRows.length ? (
              <div style={{ display: 'grid', gap: 6 }}>
                {telegramAlertRows.map(alert => (
                  <div key={alert.id} className={`server-alert server-alert-${alert.tone}`} style={{ alignItems: 'flex-start', flexWrap: 'wrap' }}>
                    <Icon name="alert" size={12} style={{ marginTop: 2 }} />
                    <div style={{ minWidth: 0, flex: '1 1 220px' }}>
                      <div style={{ fontWeight: 600 }}>{alert.title}</div>
                      <div style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 1 }}>{alert.detail}</div>
                    </div>
                    <span className={`chip ${alert.unread ? 'chip-warn' : 'chip-ok'}`} style={{ marginLeft: 'auto', fontSize: 10.5, flexShrink: 0 }}>{alert.unread ? 'Nao lida' : alert.badge}</span>
                  </div>
                ))}
              </div>
            ) : (
              <div style={{ fontSize: 12, color: 'var(--text-muted)', padding: '7px 10px', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 6 }}>
                Nenhuma mensagem de alerta encaminhada ao Telegram agora.
              </div>
            )}
          </div>
        )}
      </div>

      {/* ── CARDS INDIVIDUAIS DE SERVIDOR ──────────────────────────────────── */}
      {fetchErr && (
        <div style={{ padding: '8px 14px', fontSize: 12, color: 'var(--err)', background: 'var(--err-soft)', borderRadius: 6, border: '1px solid color-mix(in srgb, var(--err) 25%, transparent)' }}>
          <Icon name="alert" size={11} /> {fetchErr}
          <button onClick={fetchAll} style={{ marginLeft: 10, fontSize: 11, color: 'var(--accent)', background: 'none', border: 'none', cursor: 'pointer' }}>Tentar novamente</button>
        </div>
      )}
      <div className="server-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
        {window.RC.servers.map((srv) => {
          const apiR = (apiResults || []).find(r => r.servidor_id === srv.id);
          const mtr  = _apiToMetrics(apiR, sparkRef.current[srv.id]);
          return (
            <ServidorCard
              key={srv.id}
              server={srv}
              metrics={apiResults ? mtr : null}
              onRecheck={() => recheckServer(srv.id)}
              checking={!!checking[srv.id]}
            />
          );
        })}
      </div>

      {/* ── BOTTOM ROW ─────────────────────────────────────────────────────── */}
      <div className="dashboard-bottom-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>

        {/* Live operation */}
        <div className="card" style={{ overflow: 'hidden' }}>
          <div className="row" style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)', gap: 8 }}>
            <Icon name="bolt" size={13} style={{ color: 'var(--accent)' }} />
            <strong style={{ fontSize: 13 }}>Operação em andamento</strong>
            {liveOp && <span className="chip chip-acc" style={{ fontSize: 10.5 }}>{liveOp.kind}</span>}
            <div style={{ flex: 1 }} />
            {liveOp && <Btn kind="secondary" size="sm" iconRight="arrowR" onClick={() => go('operacao')}>Abrir</Btn>}
          </div>
          {liveOp ? (
            <div style={{ padding: 14 }}>
              <div className="row" style={{ gap: 6, marginBottom: 12 }}>
                <span className="dot dot-acc pulse-dot" />
                <span className="font-mono" style={{ fontWeight: 600 }}>{liveOp.target}</span>
                <span className="font-mono" style={{ fontSize: 11, color: 'var(--text-dim)' }}>{liveOp.id}</span>
              </div>
              {(liveOp.servers || []).map(s => (
                <div key={s.name} className="row" style={{ gap: 8, marginBottom: 6 }}>
                  <span className="font-mono" style={{ fontSize: 11, minWidth: 76 }}>{s.name}</span>
                  <div className="progress" style={{ flex: 1, height: 5 }}>
                    <span style={{ width: `${Math.round(s.progress * 100)}%`, background: s.state === 'ok' ? 'var(--ok)' : s.state === 'err' ? 'var(--err)' : 'var(--accent)', transition: 'width 0.6s' }} />
                  </div>
                  <span className="font-mono" style={{ fontSize: 10.5, color: 'var(--text-muted)', minWidth: 32, textAlign: 'right' }}>{Math.round(s.progress * 100)}%</span>
                </div>
              ))}
            </div>
          ) : (
            <div style={{ padding: '28px 14px', textAlign: 'center' }}>
              <Icon name="check" size={18} style={{ color: 'var(--ok)', display: 'block', margin: '0 auto 8px' }} />
              <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Nenhuma operação em andamento</div>
            </div>
          )}
        </div>

        {/* Divergências */}
        <div className="card" style={{ overflow: 'hidden' }}>
          <div className="row" style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)', gap: 8 }}>
            <strong style={{ fontSize: 13 }}>Divergências</strong>
            <span className={`chip ${diffCount ? 'chip-warn' : 'chip-ok'}`} style={{ fontSize: 10.5 }}>{diffCount}</span>
            <div style={{ flex: 1 }} />
            <Btn kind="ghost" size="sm" iconRight="arrowR" onClick={() => go('sync')}>Resolver</Btn>
          </div>
          {diffCount > 0 ? (
            <div style={{ padding: '4px 0' }}>
              {window.RC.syncDiff.slice(0, 6).map((d, i) => (
                <div key={i} className="row" style={{ padding: '5px 14px', gap: 8, fontSize: 12 }}>
                  <span className={`dot ${d.kind === 'extra' ? 'dot-acc' : 'dot-err'}`} />
                  <span className="font-mono" style={{ fontWeight: 500 }}>{d.client}</span>
                  <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>{d.kind === 'extra' ? 'extra' : 'falta'}</span>
                  <span className="font-mono" style={{ marginLeft: 'auto', fontSize: 10.5, color: 'var(--text-dim)' }}>
                    {(d.server || '').replace(/RODAI-?/i, 'R')}
                  </span>
                </div>
              ))}
            </div>
          ) : (
            <div style={{ padding: '28px 14px', textAlign: 'center' }}>
              <Icon name="check" size={18} style={{ color: 'var(--ok)', display: 'block', margin: '0 auto 8px' }} />
              <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Tudo sincronizado</div>
            </div>
          )}
        </div>

        {/* Clientes summary */}
        <div className="card" style={{ overflow: 'hidden' }}>
          <div className="row" style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)', gap: 8 }}>
            <strong style={{ fontSize: 13 }}>Clientes</strong>
            <div style={{ flex: 1 }} />
            <Btn kind="ghost" size="sm" iconRight="arrowR" onClick={() => go('clientes')}>Ver clientes</Btn>
          </div>
          <div style={{ padding: '14px 14px 10px' }}>
            <div className="row" style={{ alignItems: 'baseline', gap: 8, marginBottom: 14 }}>
              <span style={{ fontSize: 36, fontWeight: 700, letterSpacing: '-0.04em', fontFamily: 'Geist Mono' }}>{activeClients}</span>

            </div>
            {[
              { label: 'Sincronizados',  val: syncedClients,                                          dot: 'dot-ok'  },
              { label: 'Divergentes',    val: diffCount,                                              dot: 'dot-warn' },
              { label: 'Em transição',   val: window.RC.clients.filter(c => c.running).length,       dot: 'dot-acc'  },
            ].map(row => (
              <div key={row.label} className="row" style={{ padding: '4px 0', fontSize: 13 }}>
                <span className={`dot ${row.dot}`} style={{ marginRight: 7 }} />
                <span style={{ color: 'var(--text-muted)' }}>{row.label}</span>
                <div style={{ flex: 1 }} />
                <span style={{ fontWeight: 600 }}>{row.val}</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* ── HISTÓRICO RECENTE ───────────────────────────────────────────────── */}
      <div className="card" style={{ overflow: 'hidden' }}>
        <div className="row" style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)' }}>
          <strong style={{ fontSize: 13 }}>Histórico recente</strong>
          <div style={{ flex: 1 }} />
          <Btn kind="ghost" size="sm" iconRight="arrowR" onClick={() => go('historico')}>Ver tudo</Btn>
        </div>
        <table className="tbl">
          <thead>
            <tr>
              <th style={{ width: 90 }}>OP</th>
              <th style={{ width: 100 }}>Tipo</th>
              <th>Alvo</th>
              <th style={{ width: 100 }}>Quando</th>
              <th style={{ width: 110 }}>Status</th>
            </tr>
          </thead>
          <tbody>
            {window.RC.ops.slice(0, 6).map(op => (
              <tr key={op.id}>
                <td className="font-mono" style={{ fontSize: 11.5, color: 'var(--text-dim)' }}>{op.id}</td>
                <td><span className="chip" style={{ fontSize: 10.5 }}>{op.kind}</span></td>
                <td className="font-mono" style={{ fontWeight: 500 }}>{op.target}</td>
                <td style={{ fontSize: 12, color: 'var(--text-muted)' }}>{op.at}</td>
                <td>{opStatusEl(op.status)}</td>
              </tr>
            ))}
            {window.RC.ops.length === 0 && (
              <tr>
                <td colSpan="5" style={{ textAlign: 'center', padding: 20, fontSize: 12, color: 'var(--text-muted)' }}>
                  Sem operações registradas
                </td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ---------------- LISTA DE CLIENTES ----------------
function ClientesView({ go, openClient }) {
  const [q, setQ] = useStateV('');
  const [team, setTeam] = useStateV('all');
  const [statusFilter, setStatusFilter] = useStateV('all'); // all | ok | warn | err | running
  const subTeams = window.RC.teams.filter(t => t.kind === 'team');
  const [selected, setSelected] = useStateV(new Set());
  const [showArchived, setShowArchived] = useStateV(false);
  const [showBulkMover, setShowBulkMover] = useStateV(false);
  const [bulkArquivando, setBulkArquivando] = useStateV(false);

  const teamLabel = (t) => window.RC.teamById(t)?.name || String(t ?? '');

  const data = window.RC.clients
    .filter(c => showArchived ? c.archived : !c.archived)
    .filter(c => team === 'all' ? true : c.team === team)
    .filter(c => {
      if (statusFilter === 'all') return true;
      const max = Math.max(...c.status);
      if (statusFilter === 'ok') return max === 0 && !c.running;
      if (statusFilter === 'running') return c.running || c.status.includes(1);
      if (statusFilter === 'warn') return c.status.includes(2);
      if (statusFilter === 'err') return c.status.includes(3);
      return true;
    })
    .filter(c => q === '' ? true : (
      c.name.toLowerCase().includes(q.toLowerCase()) ||
      teamLabel(c.team).toLowerCase().includes(q.toLowerCase()) ||
      c.mods.some(m => m.toLowerCase().includes(q.toLowerCase()))
    ));
  const stateOf = (c) => {
    if (c.running || c.status.includes(1)) return { dot: 'dot-acc', label: 'em transição', kind: 'acc', pulse: true };
    if (c.status.includes(3)) return { dot: 'dot-err', label: 'pasta ausente', kind: 'err' };
    if (c.status.includes(2)) return { dot: 'dot-warn', label: 'divergente',    kind: 'warn' };
    return { dot: 'dot-ok', label: 'sincronizado', kind: 'ok' };
  };

  const toggleSel = (id) => {
    setSelected(prev => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
  };

  return (
    <div className="view-page data-view clientes-view" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* filter bar */}
      <div style={{ borderBottom: '1px solid var(--line)', background: 'var(--bg-1)' }}>
        {/* linha 1: busca + status + acoes */}
        <div className="row filter-bar" style={{ gap: 8, padding: '10px 16px 8px' }}>
          <Input value={q} onChange={setQ} placeholder="Buscar cliente, time ou modalidade…" icon="search" kbd="/" style={{ width: 320 }} />
          <div className="vdivider" />
          <Segmented
            value={statusFilter}
            onChange={setStatusFilter}
            options={[
              { value: 'all', label: 'Todos status' },
              { value: 'ok', label: 'OK' },
              { value: 'warn', label: 'Diverg.' },
              { value: 'err', label: 'Falta' },
              { value: 'running', label: 'Transição' },
            ]}
          />
          <div style={{ flex: 1 }} />
          <label className="row gap-1" style={{ fontSize: 12, color: 'var(--text-muted)' }}>
            <input type="checkbox" checked={showArchived} onChange={(e) => setShowArchived(e.target.checked)} /> arquivados
          </label>
          <Btn kind="secondary" size="sm" icon="download">Exportar</Btn>
          <Btn kind="primary" size="sm" icon="plus" onClick={() => go('criar')}>Novo cliente <span className="kbd" style={{ marginLeft: 4, background: 'rgba(255,255,255,0.18)', borderColor: 'transparent', color: '#fff' }}>N</span></Btn>
        </div>
        {/* linha 2: filtro de times — quebra linha */}
        <div className="row" style={{ gap: 6, padding: '0 16px 10px', flexWrap: 'wrap' }}>
          {[
            { value: 'all', label: 'Todos' },
            ...subTeams.map(t => ({ value: t.id, label: t.name })),
          ].map(opt => (
            <button
              key={opt.value}
              className={team === opt.value ? 'seg-btn active' : 'seg-btn'}
              onClick={() => setTeam(opt.value)}
              style={{ padding: '3px 12px', borderRadius: 20, border: '1px solid',
                borderColor: team === opt.value ? 'var(--accent)' : 'var(--line)',
                background: team === opt.value ? 'var(--accent-soft)' : 'transparent',
                color: team === opt.value ? 'var(--accent)' : 'var(--text-muted)',
                fontSize: 12, cursor: 'pointer', fontWeight: team === opt.value ? 600 : 400,
                whiteSpace: 'nowrap' }}
            >
              {opt.label}
            </button>
          ))}
        </div>
      </div>

      {/* bulk action bar */}
      {selected.size > 0 && (
        <div className="row" style={{ gap: 8, padding: '6px 16px', background: 'var(--accent-soft)', borderBottom: '1px solid var(--accent-line)', fontSize: 12 }}>
          <span><strong>{selected.size}</strong> selecionado{selected.size > 1 ? 's' : ''}</span>
          <div style={{ flex: 1 }} />
          <Btn kind="secondary" size="sm" icon="move" onClick={() => setShowBulkMover(true)}>Mover</Btn>
          <Btn kind="secondary" size="sm" icon="refresh">Sincronizar</Btn>
          <Btn kind="secondary" size="sm" icon="archive" disabled={bulkArquivando}
            onClick={async () => {
              if (!confirm(`Arquivar ${selected.size} cliente(s)? As pastas serao renomeadas em todos os servidores.`)) return;
              setBulkArquivando(true);
              for (const id of selected) {
                await window.rcFetch(`/api/clientes/${id}/arquivar`, { method: 'POST' }).catch(() => null);
              }
              await window.RC.reload?.();
              setSelected(new Set()); setBulkArquivando(false);
            }}>
            {bulkArquivando ? 'Arquivando...' : 'Arquivar'}
          </Btn>
          <Btn kind="ghost" size="sm" icon="x" onClick={() => setSelected(new Set())}>Limpar</Btn>
        </div>
      )}
      {showBulkMover && (() => {
        const selClients = data.filter(c => selected.has(c.id));
        const uniqTeam = selClients.length > 0 && selClients.every(c => c.team === selClients[0].team) ? selClients[0].team : null;
        return (
          <MoverModal
            clientIds={[...selected]}
            clientNames={selClients.map(c => c.name)}
            currentTeam={uniqTeam}
            onClose={() => setShowBulkMover(false)}
            onDone={() => { setShowBulkMover(false); setSelected(new Set()); }}
          />
        );
      })()}

      {/* table */}
      <div className="table-scroll" style={{ flex: 1, overflow: 'auto' }}>
        <table className="tbl">
          <thead>
            <tr>
              <th style={{ width: 32 }}>
                <input type="checkbox"
                  checked={selected.size === data.length && data.length > 0}
                  onChange={(e) => setSelected(e.target.checked ? new Set(data.map(d => d.id)) : new Set())} />
              </th>
              <th style={{ width: 140 }}>Estado</th>
              <th>Cliente</th>
              <th style={{ width: 130 }}>Time</th>
              <th>Modalidades</th>
              <th style={{ width: 110, textAlign: 'left' }}>Servidores</th>
              <th style={{ width: 110 }}>Atualizado</th>
              <th style={{ width: 36 }}></th>
            </tr>
          </thead>
          <tbody>
            {data.map(c => {
              const st = stateOf(c);
              return (
                <tr key={c.id} className={selected.has(c.id) ? 'is-selected' : ''} onClick={() => openClient(c)} style={{ cursor: 'pointer' }}>
                  <td onClick={(e) => e.stopPropagation()}>
                    <input type="checkbox" checked={selected.has(c.id)} onChange={() => toggleSel(c.id)} />
                  </td>
                  <td>
                    <span className="row gap-1" style={{ fontSize: 12 }}>
                      <span className={`dot ${st.dot} ${st.pulse ? 'pulse-dot' : ''}`} />
                      <span style={{ color: st.kind === 'ok' ? 'var(--text-muted)' : `var(--${st.kind})` }}>{st.label}</span>
                    </span>
                  </td>
                  <td>
                    <div className="row gap-2">
                      <span className="font-mono" style={{ fontWeight: 500 }}>{c.name}</span>
                      {c.archived ? <span className="chip">arquivado</span> : null}
                    </div>
                  </td>
                  <td className="muted" style={{ fontSize: 12 }}>{teamLabel(c.team)}</td>
                  <td>
                    <div className="row" style={{ gap: 4, flexWrap: 'wrap' }}>
                      {c.mods.slice(0, 4).map(m => <span key={m} className="chip" style={{ height: 18, fontSize: 10.5 }}>{m}</span>)}
                      {c.mods.length > 4 ? <span className="dim font-mono" style={{ fontSize: 11 }}>+{c.mods.length - 4}</span> : null}
                    </div>
                  </td>
                  <td>
                    <div className="row" style={{ gap: 6, alignItems: 'center' }}>
                      <MiniBars status={c.status} />
                      <span className="dim font-mono" style={{ fontSize: 10.5 }}>
                        {c.status.filter(s => s === 0).length}/4
                      </span>
                    </div>
                  </td>
                  <td className="dim" style={{ fontSize: 12 }}>{c.updated}</td>
                  <td onClick={(e) => e.stopPropagation()}>
                    <IconBtn name="more" size="sm" title="Mais ações" />
                  </td>
                </tr>
              );
            })}
            {data.length === 0 ? (
              <tr><td colSpan={8} style={{ height: 200, textAlign: 'center' }}>
                <div className="col" style={{ alignItems: 'center', gap: 8 }}>
                  <Icon name="users" size={20} className="faint" />
                  <div className="muted" style={{ fontSize: 13 }}>Nenhum cliente bate com os filtros</div>
                  <Btn kind="ghost" size="sm" onClick={() => { setQ(''); setTeam('all'); setStatusFilter('all'); }}>Limpar filtros</Btn>
                </div>
              </td></tr>
            ) : null}
          </tbody>
        </table>
      </div>

      {/* legend footer */}
      <div className="row" style={{ padding: '6px 16px', borderTop: '1px solid var(--line)', gap: 16, background: 'var(--bg-1)', fontSize: 11.5 }}>
        <span className="dim">Legenda:</span>
        <span className="row gap-1"><span className="minibars"><i className="s-ok"/><i className="s-ok"/><i className="s-ok"/><i className="s-ok"/></span><span className="muted">tudo OK</span></span>
        <span className="row gap-1"><span className="minibars"><i className="s-ok"/><i className="s-ok"/><i className="s-diverged"/><i className="s-ok"/></span><span className="muted">divergente</span></span>
        <span className="row gap-1"><span className="minibars"><i className="s-ok"/><i className="s-ok"/><i className="s-ok"/><i className="s-missing"/></span><span className="muted">pasta ausente</span></span>
        <span className="row gap-1"><span className="minibars"><i className="s-syncing"/><i className="s-syncing"/><i className="s-syncing"/><i className="s-syncing"/></span><span className="muted">em transição</span></span>
        <div style={{ flex: 1 }} />
        <span className="dim font-mono">{data.length} cliente{data.length !== 1 ? 's' : ''}</span>
      </div>
    </div>
  );
}

// ---------------- DRAWER DETALHE CLIENTE ----------------
// ---------------- MODAL MOVER ----------------
function MoverModal({ clientIds, clientNames, currentTeam, onClose, onDone }) {
  const subTeams = window.RC.teams.filter(t => t.kind === 'team');
  const [timeId, setTimeId] = useStateV(null);
  const [loading, setLoading] = useStateV(false);
  const [erro, setErro] = useStateV('');

  const doMover = async () => {
    if (!timeId) return;
    setLoading(true); setErro('');
    try {
      for (const id of clientIds) {
        const r = await window.rcFetch(`/api/clientes/${id}/mover`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ novo_time_id: timeId }),
        });
        if (!r.ok) {
          const err = await r.json().catch(() => ({}));
          const d = err.detail || {};
          setErro(typeof d === 'object' ? (d.message || JSON.stringify(d)) : String(d));
          setLoading(false); return;
        }
      }
      await window.RC.reload?.(); onDone();
    } catch (_) { setErro('Erro de conexao'); setLoading(false); }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="row" style={{ padding: '14px 16px', borderBottom: '1px solid var(--line)', gap: 8 }}>
          <Icon name="move" size={15} className="muted" />
          <span style={{ fontWeight: 600 }}>
            {clientIds.length > 1 ? `Mover ${clientIds.length} clientes` : `Mover "${clientNames[0]}"`}
          </span>
          <div style={{ flex: 1 }} />
          <IconBtn name="x" size="sm" onClick={onClose} />
        </div>
        <div className="col gap-2" style={{ padding: 16, maxHeight: 340, overflowY: 'auto' }}>
          {subTeams.map(t => (
            <div key={t.id} className="row card"
              style={{ padding: '10px 14px', cursor: 'pointer', gap: 10,
                background: timeId === t.id ? 'var(--accent-soft)' : undefined,
                border: timeId === t.id ? '1px solid var(--accent-line)' : undefined }}
              onClick={() => setTimeId(t.id)}>
              <span style={{ fontWeight: 500 }}>{t.name}</span>
              {t.id === currentTeam && <span className="chip">atual</span>}
              <div style={{ flex: 1 }} />
              {timeId === t.id && <Icon name="check" size={13} style={{ color: 'var(--accent)' }} />}
            </div>
          ))}
          {subTeams.length === 0 && (
            <div className="muted" style={{ textAlign: 'center', padding: 16, fontSize: 13 }}>
              Nenhum time disponivel.
            </div>
          )}
        </div>
        {erro && (
          <div style={{ padding: '6px 16px', fontSize: 11.5, color: 'var(--err)', background: 'var(--err-soft)',
            borderTop: '1px solid color-mix(in srgb, var(--err) 30%, transparent)' }}>{erro}</div>
        )}
        <div className="row" style={{ padding: 12, borderTop: '1px solid var(--line)', gap: 8, justifyContent: 'flex-end' }}>
          <Btn kind="ghost" onClick={onClose}>Cancelar</Btn>
          <Btn kind="primary" onClick={doMover}
            disabled={loading || !timeId || (clientIds.length === 1 && timeId === currentTeam)}>
            {loading ? 'Movendo...' : 'Mover'}
          </Btn>
        </div>
      </div>
    </div>
  );
}

// ---------------- MODAL DELETAR ----------------
function DeletarModal({ client, onClose, onDone }) {
  const [input, setInput] = useStateV('');
  const [loading, setLoading] = useStateV(false);
  const [erro, setErro] = useStateV('');
  const match = input === client.name;

  const doDelete = async () => {
    if (!match) return;
    setLoading(true); setErro('');
    try {
      const r = await window.rcFetch(`/api/clientes/${client.id}`, {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ confirmacao_nome: input }),
      });
      if (r.ok) { await window.RC.reload?.(); onDone(); }
      else {
        const err = await r.json().catch(() => ({}));
        const d = err.detail || {};
        setErro(typeof d === 'object' ? (d.message || JSON.stringify(d)) : String(d));
      }
    } catch (_) { setErro('Erro de conexao'); }
    finally { setLoading(false); }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="row" style={{ padding: '14px 16px', borderBottom: '1px solid var(--line)', gap: 8 }}>
          <Icon name="trash" size={15} style={{ color: 'var(--err)' }} />
          <span style={{ fontWeight: 600, color: 'var(--err)' }}>Deletar permanentemente</span>
          <div style={{ flex: 1 }} />
          <IconBtn name="x" size="sm" onClick={onClose} />
        </div>
        <div className="col gap-3" style={{ padding: 16 }}>
          <div style={{ fontSize: 13, lineHeight: 1.6 }}>
            Esta acao e <strong>irreversivel</strong>. Todas as pastas de{' '}
            <span className="font-mono" style={{ background: 'var(--bg-2)', padding: '1px 6px', borderRadius: 4 }}>
              {client.name}
            </span>{' '}
            serao removidas de <strong>todos os servidores</strong>.
          </div>
          <div className="col gap-1">
            <label style={{ fontSize: 12, color: 'var(--text-muted)' }}>
              Para confirmar, digite o nome do cliente:
            </label>
            <label className="input">
              <input value={input} onChange={e => setInput(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter' && match) doDelete(); }}
                placeholder={client.name}
                style={{ fontFamily: 'monospace', letterSpacing: 1 }} autoFocus />
            </label>
            {input.length > 0 && !match && (
              <span style={{ fontSize: 11, color: 'var(--err)' }}>Nome incorreto</span>
            )}
          </div>
        </div>
        {erro && (
          <div style={{ padding: '6px 16px', fontSize: 11.5, color: 'var(--err)', background: 'var(--err-soft)',
            borderTop: '1px solid color-mix(in srgb, var(--err) 30%, transparent)' }}>{erro}</div>
        )}
        <div className="row" style={{ padding: 12, borderTop: '1px solid var(--line)', gap: 8, justifyContent: 'flex-end' }}>
          <Btn kind="ghost" onClick={onClose}>Cancelar</Btn>
          <Btn kind="danger" onClick={doDelete} disabled={!match || loading}>
            {loading ? 'Deletando...' : 'Deletar permanentemente'}
          </Btn>
        </div>
      </div>
    </div>
  );
}

function ClientDrawer({ client, onClose, go }) {
  const [tab, setTab] = useStateV('overview');
  const [arquivando, setArquivando] = useStateV(false);
  const [actionBusy, setActionBusy] = useStateV('');
  const [actionErro, setActionErro] = useStateV('');
  const [showMover, setShowMover] = useStateV(false);
  const [showDeletar, setShowDeletar] = useStateV(false);

  const readActionError = async (r, fallback = 'Erro na operacao') => {
    const err = await r.json().catch(() => ({}));
    const detail = err.detail || err.message || fallback;
    return typeof detail === 'object' ? (detail.message || JSON.stringify(detail)) : String(detail);
  };

  const runClientAction = async (key, fn) => {
    setActionBusy(key);
    setActionErro('');
    try {
      await fn();
    } catch (e) {
      setActionErro(e?.message || 'Erro de conexao');
    } finally {
      setActionBusy('');
    }
  };

  const doRenomear = () => runClientAction('rename', async () => {
    const novoNome = prompt('Novo nome do cliente:', client.name);
    if (!novoNome || novoNome.trim() === client.name) return;
    const r = await window.rcFetch(`/api/clientes/${client.id}/renomear`, {
      method: 'POST',
      body: JSON.stringify({ novo_nome: novoNome.trim() }),
    });
    if (!r.ok) throw new Error(await readActionError(r, 'Erro ao renomear cliente'));
    await window.RC.reload?.();
  });

  const doMover = () => setShowMover(true);

  const updateModalidades = (nextMods) => runClientAction('mods', async () => {
    const modalidadeIds = nextMods.map(nome => window.RC._modMap[nome]).filter(Boolean);
    if (!modalidadeIds.length) throw new Error('Cliente precisa ter pelo menos uma modalidade');
    const r = await window.rcFetch(`/api/clientes/${client.id}/modalidades`, {
      method: 'PUT',
      body: JSON.stringify({ modalidade_ids: modalidadeIds }),
    });
    if (!r.ok) throw new Error(await readActionError(r, 'Erro ao atualizar modalidades'));
    await window.RC.reload?.();
  });

  const doArquivar = async () => {
    if (!confirm(`Arquivar "${client.name}"? As pastas serao renomeadas com sufixo _ARQUIVO_DATA em todos os servidores.`)) return;
    setArquivando(true);
    setActionErro('');
    try {
      const r = await window.rcFetch(`/api/clientes/${client.id}/arquivar`, { method: 'POST' });
      if (r.ok) {
        await window.RC.reload?.();
        onClose();
      } else {
        setActionErro(await readActionError(r, 'Erro ao arquivar cliente'));
      }
    } catch (_) {
      setActionErro('Erro de conexao');
    } finally {
      setArquivando(false);
    }
  };

  if (!client) return null;
  const teamLabel = window.RC.teamById(client.team)?.name || client.team;
  const stateOf = (c) => {
    if (c.running || c.status.includes(1)) return { kind: 'acc',  label: 'em transi\u00e7\u00e3o', dot: 'dot-acc', pulse: true };
    if (c.status.includes(3)) return { kind: 'err',  label: 'pasta ausente em 1 servidor', dot: 'dot-err' };
    if (c.status.includes(2)) return { kind: 'warn', label: 'divergente',     dot: 'dot-warn' };
    return { kind: 'ok',   label: 'sincronizado', dot: 'dot-ok' };
  };
  const st = stateOf(client);
  const busy = !!actionBusy || arquivando;

  return (
    <>
      <div className="drawer-backdrop" onClick={onClose} />
      <div className="drawer">
        <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--line)' }}>
          <div className="row" style={{ gap: 8 }}>
            <Icon name="user" size={14} className="muted" />
            <span className="font-mono dim" style={{ fontSize: 11 }}>#{client.id}</span>
            <div style={{ flex: 1 }} />
            <IconBtn name="x" size="sm" onClick={onClose} title="Fechar" />
          </div>
          <div className="row" style={{ gap: 10, marginTop: 6 }}>
            <h2 style={{ margin: 0, fontSize: 19, fontFamily: 'Geist Mono', fontWeight: 600 }}>{client.name}</h2>
            <span className="row gap-1" style={{ fontSize: 12 }}>
              <span className={`dot ${st.dot} ${st.pulse ? 'pulse-dot' : ''}`} />
              <span style={{ color: `var(--${st.kind})` }}>{st.label}</span>
            </span>
          </div>
          <div className="crumbs" style={{ marginTop: 6 }}>
            <a>RODAI_CLIENTES</a><span className="sep">/</span><a>{teamLabel}</a><span className="sep">/</span><span className="now">{client.name}</span>
          </div>
        </div>

        <div className="row" style={{ gap: 4, padding: '0 12px', borderBottom: '1px solid var(--line)' }}>
          {[
            { id: 'overview', label: 'Vis\u00e3o' },
            { id: 'servers',  label: 'Servidores' },
            { id: 'mods',     label: 'Modalidades' },
            { id: 'history',  label: 'Hist\u00f3rico' },
          ].map(t => (
            <button
              key={t.id}
              onClick={() => setTab(t.id)}
              style={{
                padding: '10px 10px',
                background: 'transparent', border: 0,
                color: tab === t.id ? 'var(--text)' : 'var(--text-muted)',
                fontSize: 13, fontWeight: 500, cursor: 'pointer',
                borderBottom: tab === t.id ? '2px solid var(--accent)' : '2px solid transparent',
                marginBottom: -1,
              }}
            >{t.label}</button>
          ))}
        </div>

        <div className="split-detail" style={{ flex: 1, overflow: 'auto', padding: 16 }}>
          {tab === 'overview' && (
            <div className="col gap-4">
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
                <div className="card" style={{ padding: 12 }}>
                  <div className="uc">Time</div>
                  <div style={{ fontSize: 14, fontWeight: 500, marginTop: 2 }}>{teamLabel}</div>
                </div>
                <div className="card" style={{ padding: 12 }}>
                  <div className="uc">Modalidades</div>
                  <div style={{ fontSize: 14, fontWeight: 500, marginTop: 2 }}>{client.mods.length} ativas</div>
                </div>
                <div className="card" style={{ padding: 12 }}>
                  <div className="uc">{'\u00daltima opera\u00e7\u00e3o'}</div>
                  <div style={{ fontSize: 14, fontWeight: 500, marginTop: 2 }}>{client.updated}</div>
                </div>
                <div className="card" style={{ padding: 12 }}>
                  <div className="uc">Status</div>
                  <div style={{ fontSize: 14, fontWeight: 500, marginTop: 2, color: `var(--${st.kind})` }}>{st.label}</div>
                </div>
              </div>

              <div className="card" style={{ padding: 12 }}>
                <div className="row" style={{ gap: 8, marginBottom: 10 }}>
                  <strong style={{ fontSize: 13 }}>Estrutura nos servidores</strong>
                  <span className="dim font-mono" style={{ fontSize: 11 }}>4 x 4 = 16 pastas esperadas</span>
                  <div style={{ flex: 1 }} />
                  <Btn size="sm" kind="ghost" icon="refresh" onClick={() => go('sync')}>Verificar agora</Btn>
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
                  {window.RC.servers.map((srv, idx) => {
                    const s = client.status[idx];
                    const stMap = { 0: { c: 'ok', l: 'OK' }, 1: { c: 'acc', l: 'sync' }, 2: { c: 'warn', l: 'div' }, 3: { c: 'err', l: 'falta' } };
                    const cur = stMap[s] || stMap[0];
                    return (
                      <div key={srv.id} style={{ border: '1px solid var(--line)', borderRadius: 6, padding: '8px 10px' }}>
                        <div className="row" style={{ gap: 6 }}>
                          <span className={`dot dot-${cur.c} ${s === 1 ? 'pulse-dot' : ''}`} />
                          <span className="font-mono" style={{ fontSize: 12, fontWeight: 500 }}>{srv.name}</span>
                        </div>
                        <div className="dim" style={{ fontSize: 10.5, marginTop: 6 }}>{'4 usu\u00e1rios'}</div>
                        <div className="row" style={{ marginTop: 4, gap: 3 }}>
                          {[0,1,2,3].map(u => (
                            <span key={u} style={{ flex: 1, height: 4, background: s === 0 ? 'var(--ok)' : s === 3 ? 'var(--err)' : s === 2 && u === 1 ? 'var(--warn)' : 'var(--ok)' }} />
                          ))}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>

              <div className="card" style={{ padding: 12 }}>
                <strong style={{ fontSize: 13 }}>Modalidades</strong>
                <div className="row" style={{ flexWrap: 'wrap', gap: 6, marginTop: 8 }}>
                  {client.mods.map(m => (
                    <button key={m} type="button" className="chip" onClick={() => updateModalidades(client.mods.filter(x => x !== m))} disabled={busy} style={{ height: 22, fontSize: 12, border: '1px solid var(--line)', cursor: busy ? 'not-allowed' : 'pointer' }}>
                      {m}
                      <Icon name="x" size={10} className="dim" style={{ marginLeft: 4 }} />
                    </button>
                  ))}
                  <Btn size="sm" kind="ghost" icon="plus" onClick={() => setTab('mods')}>Adicionar</Btn>
                </div>
              </div>
            </div>
          )}

          {tab === 'servers' && (
            <div className="col gap-2">
              {window.RC.servers.map((srv, idx) => {
                const s = client.status[idx];
                const stMap = { 0: { c: 'ok', l: 'sincronizado' }, 1: { c: 'acc', l: 'em transi\u00e7\u00e3o' }, 2: { c: 'warn', l: 'divergente' }, 3: { c: 'err', l: 'pasta ausente' } };
                const cur = stMap[s] || stMap[0];
                return (
                  <div key={srv.id} className="card" style={{ padding: 12 }}>
                    <div className="row" style={{ gap: 8 }}>
                      <Icon name="server" size={13} className="muted" />
                      <span className="font-mono" style={{ fontWeight: 500 }}>{srv.name}</span>
                      <span className="row gap-1" style={{ fontSize: 12 }}><span className={`dot dot-${cur.c}`} /><span style={{ color: `var(--${cur.c})` }}>{cur.l}</span></span>
                      <div style={{ flex: 1 }} />
                      <Btn size="sm" kind="ghost" icon="refresh" onClick={() => go('sync')}>Re-sincronizar</Btn>
                    </div>
                    <div className="font-mono dim" style={{ fontSize: 11, marginTop: 6 }}>
                      /clients/{teamLabel}/{client.name}/{`{${client.mods.join(',')}}`}
                    </div>
                  </div>
                );
              })}
            </div>
          )}

          {tab === 'mods' && (
            <div className="col gap-2">
              {window.RC.modalities.map(m => {
                const has = client.mods.includes(m);
                return (
                  <div key={m} className="row card" style={{ padding: '8px 12px', gap: 8 }}>
                    <span className={`dot ${has ? 'dot-ok' : 'dot-mut'}`} />
                    <span className="font-mono">{m}</span>
                    <div style={{ flex: 1 }} />
                    {has
                      ? <Btn size="sm" kind="ghost" onClick={() => updateModalidades(client.mods.filter(x => x !== m))} disabled={busy}>Remover</Btn>
                      : <Btn size="sm" kind="secondary" onClick={() => updateModalidades([...client.mods, m])} disabled={busy}>Adicionar</Btn>}
                  </div>
                );
              })}
            </div>
          )}

          {tab === 'history' && (
            <div className="col gap-1">
              {window.RC.ops.filter(o => o.target === client.name).map(op => (
                <div key={op.id} className="row card" style={{ padding: '8px 12px', gap: 8, fontSize: 12 }}>
                  <span className="font-mono dim" style={{ fontSize: 11 }}>{op.id}</span>
                  <span className="chip">{op.kind}</span>
                  <div style={{ flex: 1 }} />
                  <span className="dim">{op.actor}</span>
                  <span className="dim">{op.at}</span>
                  <span className={`dot ${op.status === 'success' ? 'dot-ok' : op.status === 'partial' ? 'dot-warn' : op.status === 'running' ? 'dot-acc' : 'dot-err'}`} />
                </div>
              ))}
              {window.RC.ops.filter(o => o.target === client.name).length === 0 && (
                <div className="muted" style={{ fontSize: 12, padding: 16, textAlign: 'center' }}>{'Sem opera\u00e7\u00f5es registradas para este cliente.'}</div>
              )}
            </div>
          )}
        </div>

        {(actionErro) && (
          <div style={{ padding: '6px 12px', fontSize: 11.5, color: 'var(--err)', background: 'var(--err-soft)', borderTop: '1px solid color-mix(in srgb, var(--err) 30%, transparent)' }}>
            {actionErro}
          </div>
        )}
        <div className="row" style={{ padding: 12, borderTop: '1px solid var(--line)', gap: 6, background: 'var(--bg-2)' }}>
          <Btn size="sm" kind="ghost" icon="edit" onClick={doRenomear} disabled={busy || client.archived}>{actionBusy === 'rename' ? 'Renomeando...' : 'Renomear'}</Btn>
          <Btn size="sm" kind="ghost" icon="move" onClick={doMover} disabled={busy || client.archived}>{actionBusy === 'move' ? 'Movendo...' : 'Mover'}</Btn>
          <Btn size="sm" kind="ghost" icon="archive" onClick={doArquivar} disabled={busy || client.archived}>
            {arquivando ? 'Arquivando...' : 'Arquivar'}
          </Btn>
          <Btn size="sm" kind="danger" icon="trash" onClick={() => setShowDeletar(true)} disabled={busy}>Deletar</Btn>
          <div style={{ flex: 1 }} />
          <Btn size="sm" kind="secondary" icon="refresh" onClick={() => go('sync')}>Sincronizar</Btn>
          <Btn size="sm" kind="primary" iconRight="arrowR" onClick={() => go(window.RC.liveOp?.target === client.name ? 'operacao' : 'historico')}>{'Ver opera\u00e7\u00e3o'}</Btn>
        </div>
      </div>
      {showMover && (
        <MoverModal
          clientIds={[client.id]}
          clientNames={[client.name]}
          currentTeam={client.team}
          onClose={() => setShowMover(false)}
          onDone={() => { setShowMover(false); onClose(); }}
        />
      )}
      {showDeletar && (
        <DeletarModal
          client={client}
          onClose={() => setShowDeletar(false)}
          onDone={() => { setShowDeletar(false); onClose(); }}
        />
      )}
    </>
  );
}

// ---------------- ARVORE DE TIMES ----------------
function TimesView({ openClient, go }) {
  const [expanded, setExpanded] = useStateV(() => new Set(window.RC.teams.map(t => t.id)));
  const [active, setActive] = useStateV(() => {
    const first = window.RC.teams.find(t => t.kind === 'team');
    return first ? first.id : (window.RC.teams[0]?.id ?? null);
  });
  const [moverClient, setMoverClient] = useStateV(null);
  const [timeActionBusy, setTimeActionBusy] = useStateV(false);
  const [timeActionErro, setTimeActionErro] = useStateV('');

  const doRenomearTime = async () => {
    if (!activeTeam || activeTeam.kind === 'root') return;
    const novo = prompt('Novo nome para o time:', activeTeam.name);
    if (!novo || novo.trim() === activeTeam.name) return;
    setTimeActionBusy(true); setTimeActionErro('');
    try {
      const r = await window.rcFetch(`/api/times/${activeTeam.id}/renomear`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ novo_nome: novo.trim().toUpperCase() }),
      });
      if (r.ok) { await window.RC.reload?.(); }
      else {
        const e = await r.json().catch(() => ({}));
        setTimeActionErro((e.detail?.message || e.detail || 'Erro ao renomear'));
      }
    } catch (_) { setTimeActionErro('Erro de conexao'); }
    finally { setTimeActionBusy(false); }
  };

  const doNovoTime = async () => {
    const nome = prompt('Nome do novo sub-time:');
    if (!nome || !nome.trim()) return;
    setTimeActionBusy(true); setTimeActionErro('');
    try {
      const parent = active;
      const r = await window.rcFetch('/api/times', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ nome: nome.trim().toUpperCase(), parent_id: parent }),
      });
      if (r.ok) { await window.RC.reload?.(); }
      else {
        const e = await r.json().catch(() => ({}));
        setTimeActionErro((e.detail?.message || e.detail || 'Erro ao criar time'));
      }
    } catch (_) { setTimeActionErro('Erro de conexao'); }
    finally { setTimeActionBusy(false); }
  };

  const toggle = (id) => {
    setExpanded(prev => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
  };

  const renderNode = (team, depth = 0) => {
    const isOpen = expanded.has(team.id);
    const children = window.RC.teams.filter(t => t.parent === team.id);
    const tClients = window.RC.clientsByTeam(team.id).filter(c => !c.archived);
    const hasKids = children.length > 0 || tClients.length > 0;
    return (
      <div key={team.id}>
        <div
          className={`tree-row ${active === team.id ? 'active' : ''}`}
          style={{ paddingLeft: 8 + depth * 14 }}
          onClick={() => { setActive(team.id); if (hasKids) toggle(team.id); }}
        >
          <span className="tree-chev">
            {hasKids ? <Icon name={isOpen ? 'chevD' : 'chevR'} size={11} /> : null}
          </span>
          <Icon name="folder" size={13} style={{ color: isOpen ? 'var(--accent)' : 'var(--text-dim)' }} />
          <span className="font-mono" style={{ fontWeight: 500 }}>{team.name}</span>
          <span className="dim font-mono" style={{ fontSize: 10.5, marginLeft: 6 }}>
            {tClients.length}{children.length > 0 ? '·' + children.length : ''}
          </span>
        </div>
        {isOpen && (
          <>
            {children.map(c => renderNode(c, depth + 1))}
            {tClients.map(cl => (
              <div
                key={cl.id}
                className="tree-row"
                style={{ paddingLeft: 8 + (depth + 1) * 14, color: 'var(--text-muted)' }}
                onClick={() => openClient(cl)}
              >
                <span className="tree-chev" />
                <Icon name="user" size={12} className="dim" />
                <span className="font-mono">{cl.name}</span>
                <span style={{ marginLeft: 'auto' }}>
                  <MiniBars status={cl.status} />
                </span>
              </div>
            ))}
          </>
        )}
      </div>
    );
  };

  const root = window.RC.teams.find(t => t.parent === null);
  const activeTeam = window.RC.teamById(active);
  const activeClients = window.RC.clientsByTeam(active);
  const activeChildren = window.RC.teams.filter(t => t.parent === active);

  return (
    <>
    <div className="view-page split-view times-view" style={{ display: 'flex', height: '100%' }}>
      {/* tree */}
      <div className="split-sidebar" style={{ width: 320, flex: '0 0 320px', borderRight: '1px solid var(--line)', display: 'flex', flexDirection: 'column' }}>
        <div className="row" style={{ padding: '8px 12px', borderBottom: '1px solid var(--line)', gap: 6 }}>
          <Input value="" placeholder="Filtrar árvore…" icon="search" style={{ flex: 1 }} />
          <IconBtn name="plus" size="sm" title="Novo sub-time" onClick={doNovoTime} disabled={timeActionBusy} />
        </div>
        <div style={{ flex: 1, overflow: 'auto', padding: 6 }}>
          {root ? renderNode(root) : null}
        </div>
        <div className="row" style={{ padding: '6px 12px', borderTop: '1px solid var(--line)', fontSize: 11 }}>
          <span className="dim">{window.RC.teams.length} times · {window.RC.clients.filter(c => !c.archived).length} clientes</span>
        </div>
      </div>

      {/* detail */}
      <div style={{ flex: 1, overflow: 'auto', padding: 16 }}>
        <div className="row" style={{ gap: 8, marginBottom: 4 }}>
          <Icon name="folder" size={16} style={{ color: 'var(--accent)' }} />
          <span className="font-mono dim" style={{ fontSize: 11 }}>{activeTeam?.kind === 'root' ? 'RAIZ' : 'TIME'}</span>
        </div>
        <div className="row" style={{ gap: 12, alignItems: 'baseline', marginBottom: 12 }}>
          <h2 style={{ margin: 0, fontFamily: 'Geist Mono', fontSize: 22, fontWeight: 600, letterSpacing: '-0.01em' }}>{activeTeam?.name}</h2>
          <span className="dim" style={{ fontSize: 12 }}>{activeClients.length} clientes diretos · {activeChildren.length} sub-times</span>
          <div style={{ flex: 1 }} />
          <Btn size="sm" kind="ghost" icon="edit" onClick={doRenomearTime}
            disabled={timeActionBusy || !activeTeam || activeTeam.kind === 'root'}>Renomear</Btn>
          <Btn size="sm" kind="primary" icon="plus"
            onClick={() => go && go('criar', { teamId: activeTeam?.id })}>Novo cliente</Btn>
        </div>

        {timeActionErro && (
        <div style={{ marginBottom: 8, padding: '6px 12px', fontSize: 11.5, color: 'var(--err)',
          background: 'var(--err-soft)', borderRadius: 6, border: '1px solid color-mix(in srgb, var(--err) 30%, transparent)' }}>
          {timeActionErro}
          <span style={{ float: 'right', cursor: 'pointer' }} onClick={() => setTimeActionErro('')}>×</span>
        </div>
      )}
      {/* sub-teams */}
        {activeChildren.length > 0 && (
          <div style={{ marginBottom: 16 }}>
            <div className="uc" style={{ marginBottom: 6 }}>Sub-times</div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 8 }}>
              {activeChildren.map(c => {
                const n = window.RC.clientsByTeam(c.id).filter(x => !x.archived).length;
                return (
                  <div key={c.id} className="card" style={{ padding: '10px 12px', cursor: 'pointer' }} onClick={() => setActive(c.id)}>
                    <div className="row" style={{ gap: 6 }}>
                      <Icon name="folder" size={13} className="muted" />
                      <span className="font-mono" style={{ fontWeight: 500 }}>{c.name}</span>
                    </div>
                    <div className="dim" style={{ fontSize: 11.5, marginTop: 4 }}>{n} clientes</div>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {/* clients */}
        <div className="uc" style={{ marginBottom: 6 }}>Clientes</div>
        <div className="card" style={{ overflow: 'hidden' }}>
          <table className="tbl">
            <thead>
              <tr>
                <th>Cliente</th>
                <th style={{ width: 130 }}>Modalidades</th>
                <th style={{ width: 120 }}>Servidores</th>
                <th style={{ width: 100 }}>Atualizado</th>
                <th style={{ width: 100 }}></th>
              </tr>
            </thead>
            <tbody>
              {activeClients.map(c => (
                <tr key={c.id} onClick={() => openClient(c)} style={{ cursor: 'pointer' }}>
                  <td>
                    <div className="row gap-2">
                      <span className="font-mono" style={{ fontWeight: 500 }}>{c.name}</span>
                      {c.archived ? <span className="chip">arq.</span> : null}
                    </div>
                  </td>
                  <td className="dim font-mono" style={{ fontSize: 11.5 }}>{c.mods.length} ativas</td>
                  <td><MiniBars status={c.status} /></td>
                  <td className="dim" style={{ fontSize: 12 }}>{c.updated}</td>
                  <td onClick={(e) => e.stopPropagation()}>
                    <Btn size="sm" kind="ghost" icon="move" onClick={() => setMoverClient(c)}>Mover</Btn>
                  </td>
                </tr>
              ))}
              {activeClients.length === 0 ? (
                <tr><td colSpan={5} className="muted" style={{ textAlign: 'center', padding: '24px 0' }}>Nenhum cliente direto neste time.</td></tr>
              ) : null}
            </tbody>
          </table>
        </div>
      </div>
    </div>
    {moverClient && (
      <MoverModal
        clientIds={[moverClient.id]}
        clientNames={[moverClient.name]}
        currentTeam={moverClient.team}
        onClose={() => setMoverClient(null)}
        onDone={async () => { setMoverClient(null); await window.RC.reload?.(); }}
      />
    )}
    </>
  );
}

// ---------------- CRIAR CLIENTE ----------------
function CriarClienteView({ go, onSubmit }) {
  const [name, setName] = useStateV('');
  const [team, setTeam] = useStateV(() => {
    const first = window.RC.teams.find(t => t.kind === 'team');
    return first ? first.id : null;
  });
  const [mods, setMods] = useStateV(() => (window.RC.modalities || ['PKO', 'VANILLA', 'MYSTERY']).slice());
  const [running, setRunning] = useStateV(false);
  const [error, setError] = useStateV('');
  const [sshResult, setSshResult] = useStateV(null); // { ok, resultado_por_servidor, operacao_status }

  const toggleMod = (m) => {
    setMods(prev => prev.includes(m) ? prev.filter(x => x !== m) : [...prev, m]);
  };

  const normalizeClientName = (v) => v
    .toUpperCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/\s+/g, '_')
    .replace(/[^A-Z0-9_]/g, '')
    .slice(0, 25);
  const validName = /^[A-Z0-9_]{2,25}$/.test(name);
  const canSubmit = validName && mods.length > 0 && team !== null;

  const submit = async (e) => {
    e?.preventDefault?.();
    if (!canSubmit) return;
    setRunning(true);
    setError('');
    setSshResult(null);
    try {
      const modalidadeIds = mods.map(nome => window.RC._modMap[nome]).filter(Boolean);
      const r = await window.rcFetch('/api/clientes', {
        method: 'POST',
        body: JSON.stringify({ nome: name, time_id: team, modalidades: modalidadeIds }),
      });
      const data = await r.json().catch(() => ({}));
      if (r.ok) {
        setSshResult({ ok: true, resultado_por_servidor: data.resultado_por_servidor || {}, operacao_status: data.operacao_status });
        await window.RC.reload?.();
        onSubmit(name);
        setTimeout(() => go('clientes'), 2000);
      } else {
        const detail = data.detail || {};
        if (typeof detail === 'object' && detail.resultado_por_servidor) {
          setSshResult({ ok: false, resultado_por_servidor: detail.resultado_por_servidor, operacao_status: detail.operacao_status });
          setError(detail.message || 'Falha ao propagar para os servidores');
        } else {
          setError(typeof detail === 'string' ? detail : 'Erro ao criar cliente');
        }
      }
    } catch (_) {
      setError('Erro de conexão');
    } finally {
      setRunning(false);
    }
  };

  return (
    <div className="view-page create-view" style={{ padding: 16, maxWidth: 920, display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 16 }}>
      <form onSubmit={submit} className="card" style={{ padding: 18 }}>
        <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>Novo cliente</h2>
        <p className="muted" style={{ margin: '4px 0 16px', fontSize: 12.5 }}>
          Cria um cliente em todos os 4 servidores, em todos os 4 usuários, com as modalidades selecionadas. Total: {window.RC.servers.length * window.RC.users.length * mods.length} pastas.
        </p>

        <div className="col gap-3">
          <div className="col" style={{ gap: 5 }}>
            <label className="uc">Nome do cliente</label>
            <Input value={name} onChange={(v) => setName(normalizeClientName(v))} placeholder="EX: 123_PEDRO_K" icon="user" autoFocus maxLength={25} />
            <div className="dim" style={{ fontSize: 11, marginTop: 2 }}>
              Maiusculas, digitos e <span className="font-mono">_</span>. Espaco vira <span className="font-mono">_</span>. 2 a 25 caracteres.
              {name && !validName ? <span style={{ color: 'var(--err)', marginLeft: 6 }}>· nome inválido</span> : null}
            </div>
          </div>

          <div className="col" style={{ gap: 5 }}>
            <label className="uc">Time</label>
            <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden', maxHeight: 180, overflowY: 'auto', background: 'var(--bg-2)' }}>
              {[...window.RC.teams.filter(t => t.kind === 'team')].sort((a, b) => a.name.localeCompare(b.name)).map((t, i, arr) => (
                <button
                  key={t.id} type="button"
                  onClick={() => setTeam(t.id)}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 8,
                    width: '100%', padding: '7px 12px', textAlign: 'left',
                    background: team === t.id ? 'var(--accent-soft)' : 'transparent',
                    color: team === t.id ? 'var(--accent)' : 'var(--text)',
                    border: 'none', borderBottom: i < arr.length - 1 ? '1px solid var(--line)' : 'none',
                    cursor: 'pointer', fontSize: 13,
                  }}
                >
                  <Icon name="folder" size={12} style={{ color: team === t.id ? 'var(--accent)' : 'var(--text-muted)', flexShrink: 0 }} />
                  {t.name}
                  {team === t.id && <Icon name="check" size={11} style={{ marginLeft: 'auto', color: 'var(--accent)' }} />}
                </button>
              ))}
            </div>
          </div>

          <div className="col" style={{ gap: 5 }}>
            <label className="uc">Modalidades · {mods.length} selecionada{mods.length !== 1 ? 's' : ''}</label>
            <div className="choice-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6 }}>
              {window.RC.modalities.map(m => {
                const on = mods.includes(m);
                return (
                  <button key={m} type="button" onClick={() => toggleMod(m)}
                    className="row"
                    style={{
                      gap: 6, height: 30, padding: '0 10px',
                      background: on ? 'var(--accent-soft)' : 'var(--bg-2)',
                      border: '1px solid ' + (on ? 'var(--accent-line)' : 'var(--line)'),
                      color: on ? 'var(--accent)' : 'var(--text)',
                      borderRadius: 6, cursor: 'pointer',
                      fontSize: 12, fontFamily: 'Geist Mono', fontWeight: 500,
                    }}>
                    <Icon name={on ? 'check' : 'plus'} size={11} />
                    {m}
                  </button>
                );
              })}
            </div>
          </div>
        </div>

        {error && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'var(--err)', background: 'var(--err-soft)', border: '1px solid color-mix(in srgb, var(--err) 30%, transparent)', borderRadius: 6, padding: '6px 10px' }}>
            {error}
          </div>
        )}

        {sshResult && (
          <div style={{ marginTop: 10, borderRadius: 6, border: `1px solid ${sshResult.ok ? 'var(--ok-line, var(--line))' : 'color-mix(in srgb, var(--err) 30%, transparent)'}`, background: sshResult.ok ? 'var(--ok-soft, var(--bg-2))' : 'var(--err-soft)', padding: '8px 10px' }}>
            <div className="row" style={{ gap: 6, marginBottom: 6, fontSize: 12, fontWeight: 600, color: sshResult.ok ? 'var(--ok, var(--text))' : 'var(--err)' }}>
              <Icon name={sshResult.ok ? 'check' : 'alert'} size={12} />
              {sshResult.ok ? 'Propagado com sucesso — redirecionando…' : 'Falha na propagação SSH'}
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
              {Object.entries(sshResult.resultado_por_servidor).map(([srv, usuarios]) => {
                const users = Object.values(usuarios);
                const ok = users.every(u => u.sucesso);
                const nok = users.filter(u => !u.sucesso).length;
                return (
                  <div key={srv} className="row" style={{ gap: 5, fontSize: 11.5, padding: '3px 6px', borderRadius: 4, background: 'var(--bg-3)' }}>
                    <span className={`dot ${ok ? 'dot-ok' : 'dot-err'}`} />
                    <span className="font-mono">{srv}</span>
                    {!ok && <span style={{ marginLeft: 'auto', color: 'var(--err)', fontSize: 10.5 }}>{nok} falha{nok > 1 ? 's' : ''}</span>}
                  </div>
                );
              })}
            </div>
          </div>
        )}

        <div className="row" style={{ marginTop: 18, gap: 8 }}>
          <Btn kind="ghost" size="md" onClick={() => go('clientes')}>Cancelar</Btn>
          <div style={{ flex: 1 }} />
          <Btn kind="primary" size="md" iconRight="arrowR" onClick={submit} disabled={!canSubmit || running || (sshResult && sshResult.ok)}>
            {running ? 'Criando…' : 'Criar e propagar'}
          </Btn>
        </div>
      </form>

      {/* preview side */}
      <div className="card" style={{ padding: 14 }}>
        <strong style={{ fontSize: 13 }}>Preview da operação</strong>
        <div className="dim" style={{ fontSize: 11.5, marginTop: 2 }}>O que vai rodar quando você clicar em "Criar e propagar".</div>

        <div className="row" style={{ gap: 6, margin: '12px 0 6px' }}>
          <Icon name="folder" size={12} className="muted" />
          <span className="uc">Caminho</span>
        </div>
        <div className="font-mono" style={{ fontSize: 11.5, color: 'var(--text-muted)', background: 'var(--bg-3)', padding: '8px 10px', borderRadius: 6, lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>
{`/clients/${window.RC.teamById(team)?.name || team}/${name || 'NOVO_CLIENTE'}/`}
{(mods.length ? mods : ['(modalidades)']).map(m => `  └─ ${m}/`).join('\n')}
        </div>

        <div className="row" style={{ gap: 6, margin: '14px 0 6px' }}>
          <Icon name="server" size={12} className="muted" />
          <span className="uc">Sub-operações ({4 * 4} pastas {mods.length > 1 ? `× ${mods.length} modalidades = ${16 * mods.length}` : ''})</span>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
          {window.RC.servers.map(s => (
            <div key={s.id} className="card" style={{ padding: '8px 10px' }}>
              <div className="row" style={{ gap: 6 }}>
                <span className={`dot ${s.online ? 'dot-ok' : 'dot-err'}`} />
                <span className="font-mono" style={{ fontSize: 12, fontWeight: 500 }}>{s.name}</span>
                {!s.online ? <span className="chip chip-err" style={{ marginLeft: 'auto' }}>offline</span> : null}
              </div>
              <div className="dim" style={{ fontSize: 11, marginTop: 4 }}>4 usuários · {mods.length || 0} modalidades</div>
            </div>
          ))}
        </div>

        {window.RC.servers.some(s => !s.online) && (
          <div style={{ marginTop: 14, padding: '8px 10px', background: 'var(--warn-soft)', border: '1px solid transparent', borderRadius: 6, fontSize: 12, color: 'var(--warn)' }}>
            <Icon name="alert" size={11} /> {window.RC.servers.filter(s => !s.online).map(s => s.name).join(', ')} {window.RC.servers.filter(s => !s.online).length === 1 ? 'está offline' : 'estão offline'}. A operação rodará nos servidores online e tentará novamente quando voltarem.
          </div>
        )}
      </div>
    </div>
  );
}

// ---------------- PROGRESSO DE OPERAÇÃO ----------------
function OperacaoView({ goBack }) {
  const op = window.RC.liveOp;
  const [tick, setTick] = useStateV(0);
  useEffectV(() => {
    const id = setInterval(() => setTick(t => t + 1), 600);
    return () => clearInterval(id);
  }, []);

  if (!op) {
    return (
      <div style={{ padding: 16 }}>
        <div className="card" style={{ padding: 18 }}>
          <div className="row" style={{ gap: 8 }}>
            <Icon name="bolt" size={14} className="muted" />
            <strong style={{ fontSize: 13 }}>Nenhuma operação em andamento</strong>
            <div style={{ flex: 1 }} />
            <Btn kind="secondary" size="sm" icon="arrowL" onClick={goBack}>Voltar</Btn>
          </div>
          <div className="dim" style={{ marginTop: 8, fontSize: 12 }}>
            As operações concluídas aparecem no histórico.
          </div>
        </div>
      </div>
    );
  }

  const opServers = op.servers || window.RC.servers.map(s => ({
    id: s.id,
    name: s.name,
    state: 'running',
    progress: 0.45,
    users: window.RC.users.map(u => ({ name: u, state: 'running' })),
  }));

  // animate the running server's progress slowly
  const animatedServers = opServers.map(s => {
    if (s.state !== 'running') return s;
    const next = Math.min(0.97, s.progress + (tick % 6) * 0.005);
    return { ...s, progress: next };
  });

  const stIcon = (st) => {
    if (st === 'ok') return <Icon name="check" size={11} style={{ color: 'var(--ok)' }} />;
    if (st === 'fail') return <Icon name="x" size={11} style={{ color: 'var(--err)' }} />;
    if (st === 'running') return <Icon name="loader" size={11} className="spin" style={{ color: 'var(--accent)' }} />;
    return <span style={{ width: 11, height: 11, borderRadius: '50%', border: '1px dashed var(--text-faint)', display: 'inline-block' }} />;
  };
  const stLabel = (st) => st === 'ok' ? 'pronto' : st === 'fail' ? 'falhou' : st === 'running' ? 'rodando' : 'na fila';

  return (
    <div className="view-page operation-view" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
      {/* op header */}
      <div className="card" style={{ padding: 14 }}>
        <div className="row" style={{ gap: 10 }}>
          <Icon name="bolt" size={16} style={{ color: 'var(--accent)' }} />
          <span className="font-mono dim" style={{ fontSize: 11 }}>{op.id.toUpperCase()}</span>
          <span className="chip chip-acc">{op.kind}</span>
          <span className="font-mono" style={{ fontWeight: 600, fontSize: 14 }}>{op.target}</span>
          <span className="dim">·</span>
          <span className="muted">{op.teamName || window.RC.teamById(op.team)?.name}</span>
          <div style={{ flex: 1 }} />
          <span className="dim font-mono" style={{ fontSize: 11.5 }}>iniciada {op.startedAt} · por {op.actor}</span>
        </div>

        {/* overall summary */}
        <div className="row" style={{ marginTop: 12, gap: 14 }}>
          <div className="col" style={{ gap: 2 }}>
            <span className="uc">Concluído</span>
            <span style={{ fontSize: 18, fontWeight: 600 }}>
              {animatedServers.filter(s => s.state === 'done').length}/{animatedServers.length}
              <span className="dim font-mono" style={{ fontSize: 11, marginLeft: 6 }}>servidores</span>
            </span>
          </div>
          <div className="vdivider" />
          <div className="col" style={{ gap: 2 }}>
            <span className="uc">Sub-operações</span>
            <span style={{ fontSize: 18, fontWeight: 600 }}>
              {animatedServers.flatMap(s => s.users).filter(u => u.state === 'ok').length}/{animatedServers.flatMap(s => s.users).length}
              <span className="dim font-mono" style={{ fontSize: 11, marginLeft: 6 }}>usuários</span>
            </span>
          </div>
          <div className="vdivider" />
          <div className="col" style={{ gap: 2 }}>
            <span className="uc">Falhas</span>
            <span style={{ fontSize: 18, fontWeight: 600, color: 'var(--err)' }}>
              {animatedServers.flatMap(s => s.users).filter(u => u.state === 'fail').length}
            </span>
          </div>
          <div className="vdivider" />
          <div className="col" style={{ gap: 2 }}>
            <span className="uc">Tempo decorrido</span>
            <span className="font-mono" style={{ fontSize: 18, fontWeight: 600 }}>00:{String(tick).padStart(2,'0')}</span>
          </div>

          <div style={{ flex: 1 }} />
          <Btn kind="secondary" size="sm" icon="rotate">Re-tentar falhas</Btn>
          <Btn kind="danger" size="sm" icon="x">Abortar</Btn>
        </div>
      </div>

      {/* servers grid */}
      <div className="operation-server-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 }}>
        {animatedServers.map(s => (
          <div key={s.id} className="card" style={{ overflow: 'hidden' }}>
            <div className="serv-card-head">
              <div className="row" style={{ gap: 6 }}>
                <span className={`dot ${s.state === 'done' ? 'dot-ok' : s.state === 'fail' ? 'dot-err' : 'dot-acc'} ${s.state === 'running' ? 'pulse-dot' : ''}`} />
                <span className="font-mono" style={{ fontWeight: 500 }}>{s.name}</span>
              </div>
              <span className={`chip ${s.state === 'done' ? 'chip-ok' : s.state === 'fail' ? 'chip-err' : 'chip-acc'}`}>
                {s.state === 'done' ? 'concluído' : s.state === 'fail' ? 'falhou' : s.state === 'running' ? 'rodando' : 'na fila'}
              </span>
            </div>
            <div style={{ padding: '10px 12px' }}>
              <div className="row" style={{ gap: 6, marginBottom: 8 }}>
                <span className="dim font-mono" style={{ fontSize: 11 }}>{Math.round(s.progress * 100)}%</span>
                <div style={{ flex: 1 }}>
                  <div className="progress" style={{ height: 4 }}>
                    <span style={{
                      width: `${s.progress * 100}%`,
                      background: s.state === 'fail' ? 'var(--err)' : s.state === 'done' ? 'var(--ok)' : 'var(--accent)',
                    }} />
                  </div>
                </div>
              </div>
              <div className="col" style={{ gap: 4 }}>
                {s.users.map(u => (
                  <div key={u.name} className="row" style={{ gap: 8, fontSize: 12, height: 22 }}>
                    {stIcon(u.state)}
                    <span className="font-mono" style={{ width: 50 }}>{u.name}</span>
                    <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} className="dim font-mono">
                      {u.detail || (u.state === 'queued' ? '— aguardando' : u.state === 'ok' ? 'pasta criada' : u.state === 'fail' ? 'erro' : '')}
                    </span>
                    <span className="dim" style={{ fontSize: 10.5, fontFamily: 'Geist Mono' }}>{stLabel(u.state)}</span>
                  </div>
                ))}
              </div>
              {s.error ? (
                <div style={{ marginTop: 10, padding: '6px 8px', background: 'var(--err-soft)', borderRadius: 4, fontSize: 11, color: 'var(--err)', display: 'flex', gap: 6 }}>
                  <Icon name="alert" size={11} />
                  <span className="font-mono">{s.error}</span>
                </div>
              ) : null}
            </div>
          </div>
        ))}
      </div>

      {/* live log */}
      <div className="card" style={{ overflow: 'hidden' }}>
        <div className="row" style={{ padding: '8px 14px', borderBottom: '1px solid var(--line)', gap: 8 }}>
          <Icon name="log" size={13} className="muted" />
          <strong style={{ fontSize: 13 }}>Log da operação</strong>
          <span className="chip chip-acc" style={{ marginLeft: 6 }}>tempo real</span>
          <div style={{ flex: 1 }} />
          <Btn size="sm" kind="ghost" icon="download">Baixar log</Btn>
        </div>
        <pre className="font-mono" style={{ margin: 0, padding: 12, fontSize: 11.5, color: 'var(--text-muted)', background: 'var(--bg-2)', maxHeight: 200, overflow: 'auto' }}>
{(op.log && op.log.length > 0)
  ? op.log.join('\n')
  : '— sem log disponível —'}
        </pre>
      </div>

      <div className="row" style={{ gap: 8, justifyContent: 'flex-end' }}>
        <Btn kind="ghost" size="sm" icon="arrowL" onClick={goBack}>Voltar</Btn>
        <Btn kind="secondary" size="sm">Notificar quando terminar</Btn>
      </div>
    </div>
  );
}

// ---------------- MODAL EDIÇÃO SERVIDOR ----------------
function ServidorEditModal({ servidor, onClose, onSaved }) {
  const [host, setHost]         = useStateV(servidor.host);
  const [porta, setPorta]       = useStateV(String(servidor.porta_ssh ?? 22));
  const [sshUser, setSshUser]   = useStateV(servidor.ssh_user ?? 'root');
  const [keyPath, setKeyPath]   = useStateV(servidor.ssh_key_path ?? '');
  const [saving, setSaving]     = useStateV(false);
  const [erro, setErro]         = useStateV('');

  useEffectV(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  const salvar = async (eTestar = false) => {
    if (!host.trim()) { setErro('Host obrigatório'); return; }
    setSaving(true); setErro('');
    try {
      const r = await window.rcFetch(`/api/servidores/${servidor.id}`, {
        method: 'PUT',
        body: JSON.stringify({
          host: host.trim(),
          porta_ssh: parseInt(porta) || 22,
          ssh_user: sshUser.trim(),
          ssh_key_path: keyPath.trim(),
        }),
      });
      if (!r.ok) {
        const d = await r.json().catch(() => ({}));
        setErro(d.detail || 'Erro ao salvar');
        return;
      }
      await window.RC.reload?.();
      if (eTestar) {
        onSaved(servidor.id, true);
      } else {
        onSaved(servidor.id, false);
      }
      onClose();
    } catch (_) {
      setErro('Erro de conexão');
    } finally {
      setSaving(false);
    }
  };

  const field = (label, val, set, opts = {}) => (
    <div className="col" style={{ gap: 5 }}>
      <label className="uc">{label}</label>
      <Input value={val} onChange={set} placeholder={opts.placeholder || ''} type={opts.type || 'text'} />
      {opts.hint && <span className="dim" style={{ fontSize: 11 }}>{opts.hint}</span>}
    </div>
  );

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ width: 540 }} onClick={e => e.stopPropagation()}>
        {/* cabeçalho */}
        <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--line)' }}>
          <div className="row" style={{ gap: 8 }}>
            <Icon name="server" size={14} className="muted" />
            <strong style={{ fontSize: 14 }}>Configurar <span className="font-mono">{servidor.nome}</span></strong>
            <div style={{ flex: 1 }} />
            <IconBtn name="x" size="sm" onClick={onClose} title="Fechar" />
          </div>
        </div>

        {/* formulário */}
        <div className="col gap-3" style={{ padding: 16 }}>
          {field('Host / IP', host, setHost, { placeholder: '10.0.0.1 ou hostname' })}

          <div className="row" style={{ gap: 10 }}>
            <div className="col" style={{ gap: 5, flex: '0 0 110px' }}>
              <label className="uc">Porta SSH</label>
              <Input value={porta} onChange={setPorta} placeholder="22" type="text" />
            </div>
            <div className="col" style={{ gap: 5, flex: 1 }}>
              <label className="uc">Usuário SSH</label>
              <Input value={sshUser} onChange={setSshUser} placeholder="root" />
            </div>
          </div>

          {field('Caminho da chave privada (no RodaiLAB)', keyPath, setKeyPath, {
            placeholder: '/etc/rodai-clientes/ssh-keys/rodai-01.pem',
            hint: 'Arquivo deve existir no servidor RodaiLAB com chmod 600.',
          })}

          {erro && (
            <div style={{ fontSize: 12, color: 'var(--err)', background: 'var(--err-soft)', padding: '6px 10px', borderRadius: 6 }}>
              <Icon name="alert" size={11} /> {erro}
            </div>
          )}
        </div>

        {/* rodapé */}
        <div className="row" style={{ padding: '10px 12px', borderTop: '1px solid var(--line)', gap: 6, background: 'var(--bg-2)', justifyContent: 'flex-end' }}>
          <Btn kind="ghost" size="sm" onClick={onClose}>Cancelar</Btn>
          <Btn kind="secondary" size="sm" disabled={saving} onClick={() => salvar(false)}>
            {saving ? 'Salvando…' : 'Salvar'}
          </Btn>
          <Btn kind="primary" size="sm" icon="refresh" disabled={saving} onClick={() => salvar(true)}>
            {saving ? 'Salvando…' : 'Salvar e testar'}
          </Btn>
        </div>
      </div>
    </div>
  );
}

// ---------------- SERVIDORES ----------------
function ServidoresView() {
  const [checking, setChecking] = useStateV(new Set());
  const [results, setResults]   = useStateV(null);
  const [fetchErr, setFetchErr] = useStateV('');
  const [editando, setEditando] = useStateV(null);
  const sparkRef = useRefV({});

  const _addSpark = (srvId, ping) => {
    if (ping == null) return;
    if (!sparkRef.current[srvId]) sparkRef.current[srvId] = [];
    sparkRef.current[srvId].push(ping);
    if (sparkRef.current[srvId].length > 16) sparkRef.current[srvId].shift();
  };

  const handleSaved = useRefV(null);
  handleSaved.current = (id, eTestar) => {
    if (eTestar) {
      const srv = window.RC.servers.find(s => s.id === id);
      if (srv) verificarUm.current(srv);
    }
  };

  const verificarUm = useRefV(null);
  verificarUm.current = async (s) => {
    setFetchErr('');
    setChecking(prev => new Set([...prev, s.id]));
    try {
      const r = await window.rcFetch(`/api/servidores/${s.id}/testar`, { method: 'POST' });
      if (r.ok) {
        const data = await r.json();
        _addSpark(s.id, data.ping_ms);
        setResults(prev => ({ ...(prev || {}), [s.id]: data }));
        await window.RC.reload?.();
      } else {
        setFetchErr('Falha ao verificar servidor');
      }
    } catch (e) {
      setFetchErr('Erro de conexao: ' + (e.message || e));
    } finally {
      setChecking(prev => { const n = new Set(prev); n.delete(s.id); return n; });
    }
  };

  const verificarTodos = useRefV(null);
  verificarTodos.current = async () => {
    const ids = window.RC.servers.map(s => s.id);
    setFetchErr('');
    setChecking(new Set(ids));
    try {
      const r = await window.rcFetch('/api/servidores/testar-todos', { method: 'POST' });
      if (r.ok) {
        const lista = await r.json();
        const mapa = {};
        lista.forEach(d => {
          _addSpark(d.servidor_id, d.ping_ms);
          mapa[d.servidor_id] = d;
        });
        setResults(mapa);
        await window.RC.reload?.();
      } else {
        setFetchErr('Falha ao verificar servidores');
      }
    } catch (e) {
      setFetchErr('Erro de conexao: ' + (e.message || e));
    } finally {
      setChecking(new Set());
    }
  };

  useEffectV(() => {
    verificarTodos.current?.();
    const handler = () => verificarTodos.current?.();
    window.addEventListener('rc-recheck-all', handler);
    return () => window.removeEventListener('rc-recheck-all', handler);
  }, []);

  return (
    <div className="view-page servers-view" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
      {fetchErr && (
        <div style={{ padding: '8px 14px', fontSize: 12, color: 'var(--err)', background: 'var(--err-soft)', borderRadius: 6, border: '1px solid color-mix(in srgb, var(--err) 25%, transparent)' }}>
          <Icon name="alert" size={11} /> {fetchErr}
          <button onClick={() => verificarTodos.current?.()} style={{ marginLeft: 10, fontSize: 11, color: 'var(--accent)', background: 'none', border: 'none', cursor: 'pointer' }}>Tentar novamente</button>
        </div>
      )}

      <div className="server-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
        {window.RC.servers.map(s => {
          const res = results?.[s.id];
          const mtr = _apiToMetrics(res, sparkRef.current[s.id]);
          return (
            <ServidorCard
              key={s.id}
              server={s}
              metrics={results ? mtr : null}
              onRecheck={() => verificarUm.current(s)}
              checking={checking.has(s.id)}
              onEdit={() => setEditando(s)}
            />
          );
        })}
      </div>

      {editando && (
        <ServidorEditModal
          servidor={editando}
          onClose={() => setEditando(null)}
          onSaved={(id, eTestar) => handleSaved.current(id, eTestar)}
        />
      )}
    </div>
  );
}

// ---------------- SINCRONIZAÇÃO ----------------
function SyncView() {
  const [running, setRunning] = useStateV(false);
  const [done, setDone] = useStateV(false);
  const [lastVerified, setLastVerified] = useStateV(null);
  const [selected, setSelected] = useStateV(new Set());

  const toggle = (id) => {
    setSelected(prev => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
  };

  const run = () => {
    setRunning(true); setDone(false);
    setTimeout(() => { setRunning(false); setDone(true); setLastVerified(new Date()); }, 1800);
  };

  const groupByClient = {};
  window.RC.syncDiff.forEach(d => {
    (groupByClient[d.client] = groupByClient[d.client] || []).push(d);
  });

  return (
    <div className="view-page sync-view data-view" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* control bar */}
      <div className="row filter-bar" style={{ gap: 10, padding: '10px 16px', borderBottom: '1px solid var(--line)', background: 'var(--bg-1)' }}>
        <Icon name="diff" size={14} className="muted" />
        <strong style={{ fontSize: 13 }}>Comparação banco × servidores</strong>
        {running ? <span className="chip chip-acc"><Icon name="loader" size={10} className="spin" /> verificando…</span>
          : done ? <span className="chip chip-warn">{window.RC.syncDiff.length} divergências</span>
          : <span className="chip">aguardando</span>}
        {lastVerified && <span className="dim font-mono" style={{ fontSize: 11.5 }}>última verificação: {lastVerified.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}</span>}
        <div style={{ flex: 1 }} />
        <Segmented value="all" onChange={() => {}} options={[
          { value: 'all', label: 'Todos' }, { value: 'extra', label: 'Extras' }, { value: 'missing', label: 'Faltando' },
        ]} />
        <Btn kind="secondary" size="sm" icon="refresh" onClick={run} disabled={running}>{running ? 'Verificando…' : 'Verificar agora'}</Btn>
      </div>

      {running && (
        <div style={{ padding: '0' }}>
          <div className="progress indeterminate" style={{ height: 2, borderRadius: 0 }}><span /></div>
        </div>
      )}

      {/* selection bar */}
      {selected.size > 0 && (
        <div className="row" style={{ padding: '6px 16px', gap: 8, background: 'var(--accent-soft)', borderBottom: '1px solid var(--accent-line)', fontSize: 12 }}>
          <span><strong>{selected.size}</strong> divergência{selected.size > 1 ? 's' : ''} selecionada{selected.size > 1 ? 's' : ''}</span>
          <div style={{ flex: 1 }} />
          <Btn kind="secondary" size="sm" icon="check">Aceitar (atualizar banco)</Btn>
          <Btn kind="primary" size="sm" icon="refresh">Reconciliar (atualizar servidor)</Btn>
          <IconBtn name="x" size="sm" onClick={() => setSelected(new Set())} title="Limpar" />
        </div>
      )}

      {/* diffs */}
      <div className="table-scroll" style={{ flex: 1, overflow: 'auto', padding: 16 }}>
        {Object.keys(groupByClient).length === 0 ? (
          <div className="col" style={{ alignItems: 'center', padding: 60, gap: 8 }}>
            <Icon name="ok" size={28} style={{ color: 'var(--ok)' }} />
            <div style={{ fontSize: 14, fontWeight: 500 }}>Tudo sincronizado</div>
            <div className="muted" style={{ fontSize: 12 }}>Banco e servidores estão alinhados.</div>
          </div>
        ) : Object.entries(groupByClient).map(([cName, items]) => (
          <div key={cName} className="card" style={{ marginBottom: 10, overflow: 'hidden' }}>
            <div className="row" style={{ padding: '10px 14px', borderBottom: '1px solid var(--line)', background: 'var(--bg-2)' }}>
              <Icon name="user" size={13} className="muted" />
              <span className="font-mono" style={{ fontWeight: 500 }}>{cName}</span>
              <span className="chip chip-warn" style={{ marginLeft: 8 }}>{items.length} divergência{items.length > 1 ? 's' : ''}</span>
              <div style={{ flex: 1 }} />
              <Btn size="sm" kind="secondary" icon="refresh">Reconciliar tudo</Btn>
            </div>
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 32 }}></th>
                  <th style={{ width: 90 }}>Tipo</th>
                  <th style={{ width: 110 }}>Servidor</th>
                  <th>Caminho</th>
                  <th style={{ width: 240 }}>Ação sugerida</th>
                </tr>
              </thead>
              <tbody>
                {items.map(d => (
                  <tr key={d.id}>
                    <td><input type="checkbox" checked={selected.has(d.id)} onChange={() => toggle(d.id)} /></td>
                    <td>
                      {d.kind === 'extra'
                        ? <span className="chip chip-info">+ extra no servidor</span>
                        : <span className="chip chip-err">− faltando no servidor</span>}
                    </td>
                    <td className="font-mono">{d.server.replace('rodai-', 'RODAI-')}</td>
                    <td className="font-mono dim" style={{ fontSize: 11.5 }}>{d.path}</td>
                    <td>
                      {d.kind === 'extra'
                        ? <span className="muted">Adicionar ao banco <span className="dim">ou</span> remover do servidor</span>
                        : <span className="muted">Criar pasta no servidor <span className="dim">ou</span> remover do banco</span>}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------------- HISTÓRICO ----------------
function HistoricoView() {
  return (
    <div className="view-page historico-view" style={{ padding: 16 }}>
      <div className="card" style={{ overflow: 'hidden' }}>
        <table className="tbl">
          <thead>
            <tr>
              <th style={{ width: 92 }}>OP</th>
              <th style={{ width: 100 }}>Tipo</th>
              <th>Alvo</th>
              <th style={{ width: 110 }}>Time</th>
              <th style={{ width: 90 }}>Por</th>
              <th style={{ width: 100 }}>Quando</th>
              <th style={{ width: 80 }}>Duração</th>
              <th style={{ width: 110 }}>Status</th>
            </tr>
          </thead>
          <tbody>
            {window.RC.ops.map(op => (
              <tr key={op.id}>
                <td className="font-mono dim" style={{ fontSize: 11.5 }}>{op.id}</td>
                <td><span className="chip">{op.kind}</span></td>
                <td className="font-mono" style={{ fontWeight: 500 }}>{op.target}</td>
                <td className="muted" style={{ fontSize: 12 }}>{op.teamName || window.RC.teamById(op.team)?.name || '-'}</td>
                <td className="font-mono dim" style={{ fontSize: 11.5 }}>{op.actor}</td>
                <td className="dim" style={{ fontSize: 12 }}>{op.at}</td>
                <td className="font-mono dim" style={{ fontSize: 11.5 }}>{op.duration}</td>
                <td>
                  {op.status === 'running' ? <span className="row gap-1"><span className="dot dot-acc pulse-dot" /><span style={{ color: 'var(--accent)' }}>rodando</span></span>
                    : op.status === 'success' ? <span className="row gap-1"><span className="dot dot-ok" /><span className="muted">sucesso</span></span>
                    : op.status === 'partial' ? <span className="row gap-1"><span className="dot dot-warn" /><span style={{ color: 'var(--warn)' }}>parcial</span></span>
                    : <span className="row gap-1"><span className="dot dot-err" /><span style={{ color: 'var(--err)' }}>falhou</span></span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Object.assign(window, {
  LoginView, DashboardView, ClientesView, ClientDrawer, TimesView,
  MoverModal, DeletarModal,
  CriarClienteView, OperacaoView, ServidoresView, SyncView, HistoricoView,
});
