'use client';

import { useEffect, useState, useCallback, Fragment } from 'react';
import { SidebarNav } from '@/components/sidebar-nav';
import {
  vmApi,
  type VmHealthResult,
  type VmCheckLevel,
  type CronStatusResponse,
  type UnhealthyContainer,
  type OllamaModelsResponse,
  type TrendSeries,
  type ContainerInfo,
} from '@/lib/api';
import {
  CheckCircle,
  AlertTriangle,
  XCircle,
  RefreshCw,
  HardDrive,
  Cpu,
  Database,
  Server,
  Activity,
  Layers,
  ScrollText,
  Trash2,
  Terminal,
  ChevronDown,
  ChevronUp,
  Clock,
  Bot,
  RotateCw,
  Gauge,
  Shield,
  Zap,
  MemoryStick,
  TrendingUp,
  AlertCircle,
} from 'lucide-react';

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

type Level = VmCheckLevel;

// ── Shared helpers ─────────────────────────────────────────────────────────

function levelColor(level: Level) {
  switch (level) {
    case 'OK':   return 'text-green-700  bg-green-50  border-green-200';
    case 'WARN': return 'text-yellow-700 bg-yellow-50 border-yellow-200';
    case 'CRIT': return 'text-red-700    bg-red-50    border-red-200';
  }
}

function levelBadge(level: Level) {
  switch (level) {
    case 'OK':   return 'bg-green-100  text-green-800';
    case 'WARN': return 'bg-yellow-100 text-yellow-800';
    case 'CRIT': return 'bg-red-100    text-red-800';
  }
}

function LevelIcon({ level, className = 'w-5 h-5' }: { level: Level; className?: string }) {
  switch (level) {
    case 'OK':   return <CheckCircle   className={`${className} text-green-600`} />;
    case 'WARN': return <AlertTriangle className={`${className} text-yellow-600`} />;
    case 'CRIT': return <XCircle       className={`${className} text-red-600`} />;
  }
}

function relativeTime(iso: string | null | undefined): string {
  if (!iso) return '—';
  const diff = Date.now() - new Date(iso).getTime();
  if (isNaN(diff)) return '—';
  const mins = Math.floor(diff / 60000);
  if (mins < 2)  return 'just now';
  if (mins < 60) return `${mins}m ago`;
  const hrs = Math.floor(mins / 60);
  if (hrs < 24)  return `${hrs}h ago`;
  const days = Math.floor(hrs / 24);
  return `${days}d ago`;
}

function formatDate(iso: string | null | undefined): string {
  if (!iso) return '—';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '—';
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}

// ── Score card ─────────────────────────────────────────────────────────────

interface ScoreDim { label: string; pts: number; maxPts: number; detail: string }

function computeScore(
  health: VmHealthResult | null,
  unhealthyCount: number,
  cronData: CronStatusResponse | null,
): { total: number; dims: ScoreDim[] } {
  const checks = health?.checks ?? {};

  // 1. CPU efficiency — steal %
  const stealPct = parseFloat((checks.steal?.value ?? '0%').match(/^([\d.]+)/)?.[1] ?? '0');
  const cpuPts = stealPct < 2 ? 20 : stealPct < 5 ? 15 : stealPct < 10 ? 10 : 5;

  // 2. Memory pressure — available GB (first number in value, e.g. "11G / 15G")
  const ramAvailGb = parseFloat((checks.ram?.value ?? '0G').match(/^([\d.]+)/)?.[1] ?? '0');
  const ramPts = ramAvailGb > 6 ? 20 : ramAvailGb > 3 ? 15 : ramAvailGb > 1 ? 5 : 0;

  // 3. Disk health — used % (first number in value, e.g. "37% used, 123G free")
  const diskPct = parseInt((checks.disk?.value ?? '100%').match(/^(\d+)/)?.[1] ?? '100', 10);
  const diskPts = diskPct < 40 ? 15 : diskPct < 55 ? 10 : diskPct < 70 ? 5 : 0;

  // 4. Service health — unhealthy container count
  const svcPts = unhealthyCount === 0 ? 20 : unhealthyCount <= 2 ? 15 : unhealthyCount <= 5 ? 8 : 2;

  // 5. Maintenance hygiene — last successful cleanup
  let maintPts = 0;
  let maintDetail = 'no history';
  if (cronData?.recentRuns?.length) {
    const lastRun = cronData.recentRuns.find(r => r.success);
    if (lastRun) {
      const daysSince = (Date.now() - new Date(lastRun.timestamp).getTime()) / 86_400_000;
      maintPts = daysSince < 7 && lastRun.freedMB > 0 ? 15 : daysSince < 30 ? 8 : 0;
      maintDetail = `${Math.floor(daysSince)}d ago, freed ${lastRun.freedMB > 0 ? lastRun.freedMB + ' MB' : '0 MB'}`;
    }
  }

  // 6. LLM readiness — available RAM
  const llmPts = ramAvailGb > 8 ? 10 : ramAvailGb > 4 ? 7 : ramAvailGb > 2 ? 4 : 1;

  const total = cpuPts + ramPts + diskPts + svcPts + maintPts + llmPts;

  return {
    total,
    dims: [
      { label: 'CPU Efficiency',   pts: cpuPts,  maxPts: 20, detail: `${stealPct.toFixed(1)}% steal` },
      { label: 'Memory Pressure',  pts: ramPts,  maxPts: 20, detail: `${ramAvailGb}G available` },
      { label: 'Disk Health',      pts: diskPts, maxPts: 15, detail: `${diskPct}% used` },
      { label: 'Service Health',   pts: svcPts,  maxPts: 20, detail: `${unhealthyCount} unhealthy` },
      { label: 'Maintenance',      pts: maintPts, maxPts: 15, detail: maintDetail },
      { label: 'LLM Readiness',    pts: llmPts,  maxPts: 10, detail: `${ramAvailGb}G free` },
    ],
  };
}

function ScoreCard({
  health,
  unhealthyCount,
  cronData,
}: {
  health: VmHealthResult | null;
  unhealthyCount: number;
  cronData: CronStatusResponse | null;
}) {
  const { total, dims } = computeScore(health, unhealthyCount, cronData);
  const scoreColor = total >= 80 ? 'text-green-600' : total >= 60 ? 'text-yellow-600' : 'text-red-600';
  const scoreBg    = total >= 80 ? 'bg-green-50 border-green-200' : total >= 60 ? 'bg-yellow-50 border-yellow-200' : 'bg-red-50 border-red-200';

  return (
    <div className={`rounded-lg border p-5 ${scoreBg}`}>
      <div className="flex items-start gap-5">
        {/* Score gauge */}
        <div className="flex flex-col items-center gap-1 min-w-[80px]">
          <Gauge className="w-5 h-5 text-gray-400" />
          <span className={`text-5xl font-bold tabular-nums ${scoreColor}`}>{total}</span>
          <span className="text-xs text-gray-400 font-medium">/ 100</span>
        </div>

        {/* Dimension breakdown */}
        <div className="flex-1 grid grid-cols-2 sm:grid-cols-3 gap-x-6 gap-y-2">
          {dims.map(d => {
            const pct = Math.round((d.pts / d.maxPts) * 100);
            const barColor = pct >= 75 ? 'bg-green-400' : pct >= 50 ? 'bg-yellow-400' : 'bg-red-400';
            return (
              <div key={d.label}>
                <div className="flex items-center justify-between mb-0.5">
                  <span className="text-xs font-medium text-gray-600 truncate">{d.label}</span>
                  <span className="text-xs font-bold text-gray-700 ml-2 tabular-nums">{d.pts}/{d.maxPts}</span>
                </div>
                <div className="h-1.5 bg-gray-200 rounded-full overflow-hidden">
                  <div className={`h-full rounded-full ${barColor}`} style={{ width: `${pct}%` }} />
                </div>
                <p className="text-xs text-gray-400 mt-0.5">{d.detail}</p>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ── Unhealthy containers panel ─────────────────────────────────────────────

function UnhealthyContainersPanel({
  containers,
  onRestart,
  restarting,
}: {
  containers: UnhealthyContainer[];
  onRestart: (name: string) => Promise<void>;
  restarting: Set<string>;
}) {
  const [expanded, setExpanded] = useState<Set<string>>(new Set());

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

  if (containers.length === 0) return null;

  return (
    <div className="bg-white border border-yellow-200 rounded-lg overflow-hidden">
      <div className="px-6 py-4 bg-yellow-50 border-b border-yellow-200 flex items-center justify-between">
        <div className="flex items-center gap-2">
          <AlertTriangle className="w-5 h-5 text-yellow-600" />
          <span className="font-semibold text-yellow-900">
            {containers.length} Unhealthy Container{containers.length !== 1 ? 's' : ''}
          </span>
          <span className="text-xs text-yellow-600">process alive, health endpoint failing</span>
        </div>
      </div>

      <div className="divide-y divide-gray-100">
        {containers.map(c => (
          <div key={c.name}>
            <div className="px-6 py-3 flex items-center gap-3">
              <button
                onClick={() => toggle(c.name)}
                className="flex-1 flex items-center gap-3 text-left"
              >
                {expanded.has(c.name)
                  ? <ChevronUp   className="w-4 h-4 text-gray-400 flex-shrink-0" />
                  : <ChevronDown className="w-4 h-4 text-gray-400 flex-shrink-0" />}
                <span className="font-mono text-sm font-medium text-gray-900">{c.name}</span>
                <span className="text-xs text-gray-500">
                  unhealthy {relativeTime(c.unhealthySince)} · {c.restartCount} restarts
                </span>
              </button>
              <button
                onClick={() => onRestart(c.name)}
                disabled={restarting.has(c.name)}
                className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-md hover:bg-blue-100 disabled:opacity-50 flex-shrink-0"
              >
                <RotateCw className={`w-3.5 h-3.5 ${restarting.has(c.name) ? 'animate-spin' : ''}`} />
                Restart
              </button>
            </div>

            {expanded.has(c.name) && c.lastHealthLogs.length > 0 && (
              <div className="px-6 pb-3">
                <p className="text-xs font-medium text-gray-500 mb-1">Last health check output:</p>
                <pre className="text-xs font-mono bg-gray-50 rounded p-2 whitespace-pre-wrap text-gray-700 max-h-32 overflow-y-auto">
                  {c.lastHealthLogs.join('\n') || '(no output)'}
                </pre>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Cron status panel ──────────────────────────────────────────────────────

function CronStatusPanel({ data }: { data: CronStatusResponse | null }) {
  const [expandedRun, setExpandedRun] = useState<string | null>(null);

  if (!data) {
    return (
      <div className="bg-white border border-gray-200 rounded-lg p-6">
        <div className="flex items-center gap-2 text-gray-400">
          <Clock className="w-5 h-5" />
          <span className="text-sm">Maintenance schedule not available</span>
        </div>
      </div>
    );
  }

  const { jobs, recentRuns } = data;

  return (
    <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
      <div className="px-6 py-4 border-b border-gray-100 flex items-center gap-2">
        <Clock className="w-5 h-5 text-gray-500" />
        <h2 className="font-semibold text-gray-900">Maintenance Schedule</h2>
        {recentRuns.length > 0 && (
          <span className="ml-auto text-xs text-gray-400">
            {recentRuns.length} run{recentRuns.length !== 1 ? 's' : ''} in log
          </span>
        )}
      </div>

      {/* Jobs table */}
      <div className="overflow-x-auto">
        <table className="w-full text-sm">
          <thead>
            <tr className="border-b border-gray-100 bg-gray-50">
              <th className="px-6 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Job</th>
              <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Schedule</th>
              <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Last Run</th>
              <th className="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wide">Freed</th>
              <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Status</th>
              <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Next Run</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-gray-50">
            {jobs.map(job => {
              const lr = job.lastRun;
              return (
                <tr key={job.name} className="hover:bg-gray-50">
                  <td className="px-6 py-3">
                    <p className="font-medium text-gray-900">{job.description}</p>
                    <p className="text-xs text-gray-400 font-mono">{job.name}</p>
                  </td>
                  <td className="px-4 py-3 font-mono text-xs text-gray-600">{job.schedule}</td>
                  <td className="px-4 py-3 text-gray-600">
                    {lr ? (
                      <span title={lr.timestamp}>{relativeTime(lr.timestamp)}</span>
                    ) : (
                      <span className="text-gray-400">never</span>
                    )}
                  </td>
                  <td className="px-4 py-3 text-right font-mono text-xs">
                    {lr && lr.freedMB > 0
                      ? <span className="text-green-700">+{lr.freedMB} MB</span>
                      : <span className="text-gray-400">—</span>}
                  </td>
                  <td className="px-4 py-3">
                    {lr ? (
                      <span className={`inline-flex items-center gap-1 text-xs font-medium ${lr.success ? 'text-green-700' : 'text-red-700'}`}>
                        {lr.success
                          ? <CheckCircle className="w-3.5 h-3.5" />
                          : <XCircle    className="w-3.5 h-3.5" />}
                        {lr.success ? 'OK' : 'Failed'}
                      </span>
                    ) : (
                      <span className="text-gray-400 text-xs">—</span>
                    )}
                  </td>
                  <td className="px-4 py-3 text-xs text-gray-500">
                    {job.nextRun ? (
                      <span title={job.nextRun}>{relativeTime(job.nextRun).replace('ago', '').trim() || formatDate(job.nextRun)}</span>
                    ) : '—'}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Recent runs — collapsible step log */}
      {recentRuns.length > 0 && (
        <div className="border-t border-gray-100">
          <div className="px-6 py-3">
            <p className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2">Recent Runs</p>
            <div className="space-y-1">
              {recentRuns.slice(0, 10).map((run, i) => (
                <div key={`${run.timestamp}-${i}`} className="rounded-md border border-gray-100 overflow-hidden">
                  <button
                    className="w-full flex items-center gap-3 px-4 py-2 text-left hover:bg-gray-50 text-sm"
                    onClick={() => setExpandedRun(expandedRun === run.timestamp ? null : run.timestamp)}
                  >
                    {run.success
                      ? <CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0" />
                      : <XCircle    className="w-4 h-4 text-red-500 flex-shrink-0" />}
                    <span className="text-gray-600">{formatDate(run.timestamp)}</span>
                    <span className={`text-xs px-1.5 py-0.5 rounded font-medium ${run.mode === 'full' ? 'bg-orange-100 text-orange-700' : 'bg-blue-100 text-blue-700'}`}>
                      {run.mode}
                    </span>
                    {run.freedMB > 0 && (
                      <span className="text-xs text-green-700 font-medium">freed {run.freedMB} MB</span>
                    )}
                    <span className="ml-auto text-xs text-gray-400">{run.durationSecs}s</span>
                    {expandedRun === run.timestamp
                      ? <ChevronUp   className="w-3.5 h-3.5 text-gray-400" />
                      : <ChevronDown className="w-3.5 h-3.5 text-gray-400" />}
                  </button>
                  {expandedRun === run.timestamp && run.steps.length > 0 && (
                    <div className="px-4 pb-3 border-t border-gray-100">
                      <pre className="text-xs font-mono bg-gray-50 rounded p-2 mt-2 whitespace-pre-wrap text-gray-700 max-h-40 overflow-y-auto">
                        {run.steps.join('\n')}
                      </pre>
                    </div>
                  )}
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ── Ollama / LLM panel ─────────────────────────────────────────────────────

function OllamaPanel({
  data,
  ramAvailGb,
  onUnload,
  unloading,
}: {
  data: OllamaModelsResponse | null;
  ramAvailGb: number;
  onUnload: (name: string) => Promise<void>;
  unloading: Set<string>;
}) {
  if (!data) return null;
  const { models, running } = data;
  if (models.length === 0 && running.length === 0) return null;

  return (
    <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
      <div className="px-6 py-4 border-b border-gray-100 flex items-center gap-2">
        <Bot className="w-5 h-5 text-gray-500" />
        <h2 className="font-semibold text-gray-900">LLM Models (Ollama)</h2>
        <span className="ml-auto text-xs text-gray-400">
          {models.length} on disk · {running.length} loaded
        </span>
      </div>

      {/* Currently loaded */}
      {running.length > 0 && (
        <div className="px-6 py-3 bg-purple-50 border-b border-purple-100">
          <p className="text-xs font-medium text-purple-700 uppercase tracking-wide mb-2">Currently Loaded</p>
          {running.map(r => {
            const ramAfterUnloadGb = ramAvailGb + r.sizeGB;
            const pressureAfter = ramAfterUnloadGb < 2;
            return (
              <div key={r.name} className="flex items-center gap-3">
                <div className="flex-1">
                  <span className="font-mono text-sm font-medium text-gray-900">{r.name}</span>
                  <span className="ml-2 text-xs text-gray-500">{r.sizeGB} GB · {r.processor || 'CPU'}</span>
                  {r.expiresAt && (
                    <span className="ml-2 text-xs text-gray-400">expires {relativeTime(r.expiresAt)}</span>
                  )}
                  {ramAvailGb < 4 && (
                    <span className="ml-2 text-xs text-yellow-700 bg-yellow-100 px-1.5 py-0.5 rounded">
                      low RAM — swap pressure likely
                    </span>
                  )}
                  {pressureAfter && (
                    <span className="ml-1 text-xs text-gray-400">(unloading frees {r.sizeGB} GB)</span>
                  )}
                </div>
                <button
                  onClick={() => onUnload(r.name)}
                  disabled={unloading.has(r.name)}
                  className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-purple-700 bg-white border border-purple-200 rounded-md hover:bg-purple-50 disabled:opacity-50"
                >
                  {unloading.has(r.name)
                    ? <RefreshCw className="w-3.5 h-3.5 animate-spin" />
                    : <Zap className="w-3.5 h-3.5" />}
                  Unload
                </button>
              </div>
            );
          })}
        </div>
      )}

      {/* RAM bar */}
      {ramAvailGb > 0 && (
        <div className="px-6 py-3 border-b border-gray-100">
          <div className="flex items-center justify-between text-xs text-gray-500 mb-1">
            <span>RAM available</span>
            <span className="font-mono">{ramAvailGb.toFixed(1)} GB free</span>
          </div>
          <div className="h-2 bg-gray-200 rounded-full overflow-hidden">
            {running.map(r => (
              <div
                key={r.name}
                className="h-full bg-purple-400 rounded-full float-right"
                style={{ width: `${Math.min((r.sizeGB / 16) * 100, 100)}%` }}
                title={`${r.name}: ${r.sizeGB} GB`}
              />
            ))}
          </div>
          <p className="text-xs text-gray-400 mt-0.5">
            {running.length > 0
              ? `${running.reduce((s, r) => s + r.sizeGB, 0).toFixed(1)} GB used by loaded models`
              : 'No models loaded'}
          </p>
        </div>
      )}

      {/* All models */}
      <div className="divide-y divide-gray-50">
        {models.map(m => {
          const isLoaded = running.some(r => r.name === m.name);
          return (
            <div key={m.name} className="px-6 py-2.5 flex items-center gap-3">
              <MemoryStick className="w-4 h-4 text-gray-300 flex-shrink-0" />
              <span className="font-mono text-sm text-gray-700 flex-1">{m.name}</span>
              <span className="text-xs text-gray-400">{m.sizeGB} GB</span>
              {isLoaded && (
                <span className="text-xs bg-purple-100 text-purple-700 px-1.5 py-0.5 rounded font-medium">loaded</span>
              )}
              {m.modifiedAt && (
                <span className="text-xs text-gray-300" title={m.modifiedAt}>
                  {relativeTime(m.modifiedAt)}
                </span>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Trend charts ──────────────────────────────────────────────────────────

function Sparkline({
  points,
  color = '#3b82f6',
  fillColor,
  min: minOverride,
  max: maxOverride,
  height = 48,
}: {
  points: { t: number; v: number }[];
  color?: string;
  fillColor?: string;
  min?: number;
  max?: number;
  height?: number;
}) {
  if (points.length < 2) {
    return <div className="w-full bg-gray-50 rounded" style={{ height }} />;
  }
  const W = 400;
  const H = height;
  const vals = points.map(p => p.v);
  const lo   = minOverride ?? Math.min(...vals);
  const hi   = maxOverride ?? Math.max(...vals);
  const range = hi - lo || 1;

  const toX = (i: number) => (i / (points.length - 1)) * W;
  const toY = (v: number) => H - ((v - lo) / range) * (H - 4) - 2;

  const pts = points.map((p, i) => `${toX(i)},${toY(p.v)}`).join(' ');
  const fillPath = `M${toX(0)},${H} ` +
    points.map((p, i) => `L${toX(i)},${toY(p.v)}`).join(' ') +
    ` L${toX(points.length - 1)},${H} Z`;

  return (
    <svg
      viewBox={`0 0 ${W} ${H}`}
      className="w-full"
      style={{ height }}
      preserveAspectRatio="none"
    >
      {fillColor && <path d={fillPath} fill={fillColor} opacity="0.15" />}
      <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" />
      {/* Latest value dot */}
      <circle
        cx={toX(points.length - 1)}
        cy={toY(vals[vals.length - 1])}
        r="3"
        fill={color}
      />
    </svg>
  );
}

function TrendCard({
  title,
  series,
  color,
  fillColor,
  unit,
  warnThreshold,
  critThreshold,
  higherIsBetter = false,
  min,
  max,
  note,
}: {
  title: string;
  series: TrendSeries | null | undefined;
  color: string;
  fillColor: string;
  unit: string;
  warnThreshold?: number;
  critThreshold?: number;
  higherIsBetter?: boolean;
  min?: number;
  max?: number;
  note?: string;
}) {
  if (!series) {
    return (
      <div className="bg-white border border-gray-200 rounded-lg p-4 space-y-2">
        <p className="text-sm font-medium text-gray-500">{title}</p>
        <div className="h-12 bg-gray-50 rounded animate-pulse" />
      </div>
    );
  }

  const { latest, avg, peak, points } = series;
  const isBad = (v: number) => {
    if (higherIsBetter) return critThreshold !== undefined && v < critThreshold;
    return critThreshold !== undefined && v >= critThreshold;
  };
  const isWarn = (v: number) => {
    if (higherIsBetter) return warnThreshold !== undefined && v < warnThreshold;
    return warnThreshold !== undefined && v >= warnThreshold;
  };

  const latestColor = isBad(latest)
    ? 'text-red-600'
    : isWarn(latest)
      ? 'text-yellow-600'
      : 'text-gray-900';

  const fmt = (v: number) =>
    v < 10 ? v.toFixed(1) : Math.round(v).toString();

  return (
    <div className="bg-white border border-gray-200 rounded-lg p-4 space-y-2">
      <div className="flex items-center justify-between">
        <p className="text-sm font-medium text-gray-600">{title}</p>
        <span className={`text-lg font-bold tabular-nums ${latestColor}`}>
          {fmt(latest)}<span className="text-xs font-normal text-gray-400 ml-0.5">{unit}</span>
        </span>
      </div>
      <Sparkline points={points} color={color} fillColor={fillColor} min={min} max={max} />
      <div className="flex items-center justify-between text-xs text-gray-400">
        <span>avg {fmt(avg)}{unit}</span>
        <span>peak {fmt(peak)}{unit}</span>
        <span>{points.length}pts · 7d</span>
      </div>
      {note && (
        <p className="text-xs text-yellow-700 bg-yellow-50 rounded px-2 py-1">{note}</p>
      )}
    </div>
  );
}

interface TrendsData {
  disk:   TrendSeries | null;
  steal:  TrendSeries | null;
  ram:    TrendSeries | null;
  swap:   TrendSeries | null;
  io:     TrendSeries | null;
}

function TrendsPanel({
  data,
  loading,
  onOpen,
}: {
  data: TrendsData;
  loading: boolean;
  onOpen: () => void;
}) {
  const [open, setOpen] = useState(false);

  const handleToggle = () => {
    const next = !open;
    setOpen(next);
    if (next) onOpen();
  };

  const ioNote = data.io && data.io.avg > 0.5
    ? `VM writes avg ${data.io.avg.toFixed(1)} GB/hr (${(data.io.avg * 24).toFixed(0)} GB/day) — investigate source if sustained`
    : undefined;

  return (
    <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
      <button
        className="w-full flex items-center justify-between px-6 py-4 text-left hover:bg-gray-50"
        onClick={handleToggle}
      >
        <div className="flex items-center gap-2">
          <TrendingUp className="w-5 h-5 text-gray-500" />
          <span className="font-semibold text-gray-900">7-Day Trends</span>
          <span className="text-xs text-gray-400">Prometheus · 1h resolution</span>
          {data.io && data.io.avg > 0.5 && (
            <span className="flex items-center gap-1 text-xs text-yellow-700 bg-yellow-50 border border-yellow-200 px-2 py-0.5 rounded-full">
              <AlertCircle className="w-3 h-3" /> I/O anomaly
            </span>
          )}
        </div>
        {open
          ? <ChevronUp   className="w-4 h-4 text-gray-400" />
          : <ChevronDown className="w-4 h-4 text-gray-400" />}
      </button>

      {open && (
        <div className="border-t border-gray-100 p-4">
          {loading ? (
            <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4">
              {Array.from({ length: 5 }).map((_, i) => (
                <div key={i} className="bg-gray-50 rounded-lg h-28 animate-pulse" />
              ))}
            </div>
          ) : (
            <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4">
              <TrendCard
                title="Disk Used %"
                series={data.disk}
                color="#ef4444"
                fillColor="#ef4444"
                unit="%"
                warnThreshold={55}
                critThreshold={70}
                min={0}
                max={100}
              />
              <TrendCard
                title="RAM Available"
                series={data.ram}
                color="#3b82f6"
                fillColor="#3b82f6"
                unit="GB"
                warnThreshold={3}
                critThreshold={1}
                higherIsBetter
                min={0}
              />
              <TrendCard
                title="Swap Used"
                series={data.swap}
                color="#f59e0b"
                fillColor="#f59e0b"
                unit="GB"
                warnThreshold={1.5}
                critThreshold={3}
                min={0}
              />
              <TrendCard
                title="CPU Steal %"
                series={data.steal}
                color="#8b5cf6"
                fillColor="#8b5cf6"
                unit="%"
                warnThreshold={5}
                critThreshold={15}
                min={0}
              />
              <TrendCard
                title="VM Disk Writes (sda)"
                series={data.io}
                color="#f97316"
                fillColor="#f97316"
                unit="GB/hr"
                warnThreshold={0.5}
                critThreshold={1.5}
                min={0}
                note={ioNote}
              />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── Containers panel ───────────────────────────────────────────────────────

function ContainersPanel({
  restarting,
  onRestart,
}: {
  restarting: Set<string>;
  onRestart: (name: string) => Promise<void>;
}) {
  const [open, setOpen]               = useState(false);
  const [containers, setContainers]   = useState<ContainerInfo[]>([]);
  const [loading, setLoading]         = useState(false);
  const [loaded, setLoaded]           = useState(false);
  const [filter, setFilter]           = useState<'all' | 'running' | 'unhealthy' | 'nolimit'>('all');
  const [stackFilter, setStackFilter] = useState('');
  const [expandedLog, setExpandedLog] = useState<string | null>(null);
  const [logCache, setLogCache]       = useState<Record<string, string>>({});
  const [logsLoading, setLogsLoading] = useState<string | null>(null);
  const [bulkConfirm, setBulkConfirm] = useState(false);

  const loadContainers = async () => {
    if (loading) return;
    setLoading(true);
    try {
      const data = await vmApi.getAllContainers();
      setContainers(data);
      setLoaded(true);
    } catch (err) {
      console.error('Failed to load containers:', err);
    } finally {
      setLoading(false);
    }
  };

  const handleToggle = () => {
    const next = !open;
    setOpen(next);
    if (next && !loaded) loadContainers();
  };

  const handleViewLogs = async (name: string) => {
    if (expandedLog === name) { setExpandedLog(null); return; }
    setExpandedLog(name);
    if (!logCache[name]) {
      setLogsLoading(name);
      try {
        const { logs } = await vmApi.getContainerLogs(name, 50);
        setLogCache(prev => ({ ...prev, [name]: logs }));
      } catch {
        setLogCache(prev => ({ ...prev, [name]: '(failed to load logs)' }));
      } finally {
        setLogsLoading(null);
      }
    }
  };

  const unhealthy = containers.filter(c => c.health === 'unhealthy');
  const stacks    = [...new Set(containers.map(c => c.stack))].sort();

  const filtered = containers.filter(c => {
    const main =
      filter === 'running'   ? c.state === 'running' :
      filter === 'unhealthy' ? c.health === 'unhealthy' :
      filter === 'nolimit'   ? (c.state === 'running' && c.memLimitMiB === 0) :
      true;
    return main && (!stackFilter || c.stack === stackFilter);
  });

  const chips: Array<{ key: typeof filter; label: string; accent?: boolean }> = [
    { key: 'all',       label: `All (${containers.length})` },
    { key: 'running',   label: `Running (${containers.filter(c => c.state === 'running').length})` },
    { key: 'unhealthy', label: `Unhealthy (${unhealthy.length})`, accent: unhealthy.length > 0 },
    { key: 'nolimit',   label: `No Limit (${containers.filter(c => c.state === 'running' && c.memLimitMiB === 0).length})` },
  ];

  const healthClass = (h: string) =>
    h === 'healthy' ? 'text-green-600' : h === 'unhealthy' ? 'text-red-600' : h === 'starting' ? 'text-yellow-600' : 'text-gray-400';

  const fmtUptime = (s: number) =>
    s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m` : s < 86400 ? `${Math.floor(s / 3600)}h` : `${Math.floor(s / 86400)}d`;

  const fmtMem = (mib: number) =>
    mib >= 1024 ? `${(mib / 1024).toFixed(1)}G` : `${mib}M`;

  return (
    <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
      {/* Header toggle */}
      <button
        className="w-full flex items-center justify-between px-6 py-4 text-left hover:bg-gray-50"
        onClick={handleToggle}
      >
        <div className="flex items-center gap-2">
          <Server className="w-5 h-5 text-gray-500" />
          <span className="font-semibold text-gray-900">All Containers</span>
          {loaded && (
            <span className="text-xs text-gray-400">
              {containers.filter(c => c.state === 'running').length} running · {containers.length} total
            </span>
          )}
          {!open && unhealthy.length > 0 && (
            <span className="flex items-center gap-1 text-xs text-red-700 bg-red-50 border border-red-200 px-2 py-0.5 rounded-full">
              <AlertCircle className="w-3 h-3" /> {unhealthy.length} unhealthy
            </span>
          )}
        </div>
        {open ? <ChevronUp className="w-4 h-4 text-gray-400" /> : <ChevronDown className="w-4 h-4 text-gray-400" />}
      </button>

      {open && (
        <div className="border-t border-gray-100">
          {/* Toolbar */}
          <div className="px-4 py-3 border-b border-gray-100 flex flex-wrap items-center gap-3 justify-between">
            <div className="flex flex-wrap items-center gap-2">
              {chips.map(chip => (
                <button
                  key={chip.key}
                  onClick={() => setFilter(chip.key)}
                  className={`px-3 py-1 text-xs rounded-full border transition-colors ${
                    filter === chip.key
                      ? 'bg-blue-600 border-blue-500 text-white'
                      : chip.accent
                        ? 'bg-red-50 border-red-200 text-red-700 hover:bg-red-100'
                        : 'bg-gray-100 border-gray-200 text-gray-600 hover:bg-gray-200'
                  }`}
                >
                  {chip.label}
                </button>
              ))}
              <select
                value={stackFilter}
                onChange={e => setStackFilter(e.target.value)}
                className="px-2 py-1 text-xs border border-gray-200 rounded-full bg-white text-gray-600"
              >
                <option value="">Stack…</option>
                {stacks.map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </div>
            <div className="flex items-center gap-2">
              {unhealthy.length > 0 && (
                <button
                  onClick={() => setBulkConfirm(true)}
                  className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-red-700 bg-red-50 border border-red-200 rounded-md hover:bg-red-100"
                >
                  <RotateCw className="w-3.5 h-3.5" />
                  Restart {unhealthy.length} unhealthy
                </button>
              )}
              <button
                onClick={loadContainers}
                disabled={loading}
                className="p-1.5 text-gray-500 bg-gray-50 border border-gray-200 rounded-md hover:bg-gray-100 disabled:opacity-50"
                title="Refresh"
              >
                <RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
              </button>
            </div>
          </div>

          {/* Table */}
          {loading && !loaded ? (
            <div className="py-10 text-center text-sm text-gray-400">
              Collecting container stats…
            </div>
          ) : (
            <div className="overflow-x-auto">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b border-gray-100 bg-gray-50">
                    <th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Container</th>
                    <th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Health</th>
                    <th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">Uptime</th>
                    <th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wide">CPU</th>
                    <th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wide">RAM</th>
                    <th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase tracking-wide">↺</th>
                    <th className="px-3 py-2 w-16"></th>
                  </tr>
                </thead>
                <tbody className="divide-y divide-gray-50">
                  {filtered.map(c => (
                    <Fragment key={c.name}>
                      <tr className={`hover:bg-gray-50 ${c.health === 'unhealthy' ? 'bg-red-50/40' : ''}`}>
                        <td className="px-4 py-2.5">
                          <p className="font-mono text-xs font-medium text-gray-900 truncate max-w-[240px]" title={c.name}>{c.name}</p>
                          <p className="text-xs text-gray-400">{c.stack}</p>
                        </td>
                        <td className="px-3 py-2.5">
                          <span className={`text-xs font-medium ${healthClass(c.health)}`}>
                            ● {c.health === 'none' ? '—' : c.health}
                          </span>
                          {c.state !== 'running' && (
                            <p className="text-xs text-gray-400">{c.state}</p>
                          )}
                        </td>
                        <td className="px-3 py-2.5 text-xs text-gray-600 tabular-nums">
                          {c.state === 'running' ? fmtUptime(c.uptimeSecs) : '—'}
                        </td>
                        <td className="px-3 py-2.5 text-xs text-right tabular-nums text-gray-600">
                          {c.state === 'running' ? `${c.cpuPercent}%` : '—'}
                        </td>
                        <td className="px-3 py-2.5 text-xs text-right tabular-nums text-gray-600">
                          {c.state === 'running' ? (
                            <>
                              {fmtMem(c.memMiB)}
                              {c.memLimitMiB === 0 && (
                                <span className="ml-0.5 text-yellow-600" title="No memory limit set">∞</span>
                              )}
                            </>
                          ) : '—'}
                        </td>
                        <td className="px-3 py-2.5 text-right tabular-nums">
                          {c.restartCount > 0 ? (
                            <span className={`text-xs font-medium ${c.restartCount > 5 ? 'text-red-600' : 'text-yellow-700'}`}>
                              {c.restartCount}
                            </span>
                          ) : (
                            <span className="text-xs text-gray-300">0</span>
                          )}
                        </td>
                        <td className="px-3 py-2.5">
                          <div className="flex items-center gap-1 justify-end">
                            <button
                              onClick={() => handleViewLogs(c.name)}
                              className={`p-1 rounded hover:bg-gray-100 ${expandedLog === c.name ? 'text-blue-600' : 'text-gray-400 hover:text-gray-600'}`}
                              title="View logs"
                            >
                              <Terminal className="w-3.5 h-3.5" />
                            </button>
                            {c.state === 'running' && (
                              <button
                                onClick={() => onRestart(c.name)}
                                disabled={restarting.has(c.name)}
                                className="p-1 rounded text-gray-400 hover:text-blue-700 hover:bg-blue-50 disabled:opacity-40"
                                title="Restart"
                              >
                                <RotateCw className={`w-3.5 h-3.5 ${restarting.has(c.name) ? 'animate-spin' : ''}`} />
                              </button>
                            )}
                          </div>
                        </td>
                      </tr>
                      {expandedLog === c.name && (
                        <tr>
                          <td colSpan={7} className="px-4 pb-3 pt-0 bg-gray-50">
                            {logsLoading === c.name ? (
                              <p className="text-xs text-gray-400 py-2 text-center">Loading logs…</p>
                            ) : (
                              <pre className="text-xs font-mono bg-gray-900 text-gray-200 rounded p-3 max-h-52 overflow-y-auto whitespace-pre-wrap leading-relaxed">
                                {logCache[c.name] ?? '(no logs)'}
                              </pre>
                            )}
                          </td>
                        </tr>
                      )}
                    </Fragment>
                  ))}
                </tbody>
              </table>
              {filtered.length === 0 && (
                <p className="py-8 text-center text-sm text-gray-400">No containers match filter</p>
              )}
            </div>
          )}
        </div>
      )}

      {/* Bulk restart modal */}
      {bulkConfirm && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-xl shadow-xl p-6 max-w-sm w-full mx-4">
            <h4 className="font-semibold text-gray-900 mb-2">
              Restart {unhealthy.length} unhealthy container{unhealthy.length !== 1 ? 's' : ''}?
            </h4>
            <ul className="mb-4 space-y-1 max-h-32 overflow-y-auto">
              {unhealthy.map(c => (
                <li key={c.name} className="text-sm font-mono text-red-700">{c.name}</li>
              ))}
            </ul>
            <div className="flex gap-3 justify-end">
              <button
                onClick={() => setBulkConfirm(false)}
                className="px-4 py-2 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200"
              >
                Cancel
              </button>
              <button
                onClick={async () => {
                  setBulkConfirm(false);
                  for (const c of unhealthy) { await onRestart(c.name); }
                }}
                className="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700"
              >
                Restart All
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ── Check card meta ────────────────────────────────────────────────────────

const CHECK_META: Record<string, { label: string; icon: React.ElementType }> = {
  disk:               { label: 'Disk',             icon: HardDrive },
  load:               { label: 'CPU Load',         icon: Cpu },
  steal:              { label: 'CPU Steal',        icon: Shield },
  ram:                { label: 'Memory',           icon: Database },
  swap:               { label: 'Swap',             icon: Server },
  container_loops:    { label: 'Crash Loops',      icon: Activity },
  container_health:   { label: 'Container Health', icon: Layers },
  docker_daemon:      { label: 'Docker Daemon',    icon: Activity },
  build_cache:        { label: 'Build Cache',      icon: Layers },
  docker_images:      { label: 'Docker Images',    icon: Layers },
  journal:            { label: 'Journal Logs',     icon: ScrollText },
  syslog:             { label: 'Syslog',           icon: ScrollText },
  failed_units:       { label: 'Systemd Units',    icon: Activity },
  cron_missing_paths: { label: 'Cron Paths',       icon: Clock },
  gpu:                { label: 'GPU',              icon: Zap },
  image_freshness:    { label: 'Image Freshness',  icon: Layers },
};

const CHECK_ORDER = [
  'disk', 'load', 'steal', 'ram', 'swap',
  'container_loops', 'container_health', 'docker_daemon',
  'build_cache', 'docker_images', 'image_freshness',
  'journal', 'syslog',
  'failed_units', 'cron_missing_paths',
  'gpu',
];

// ── Main page ──────────────────────────────────────────────────────────────

export default function VmHealthPage() {
  const [health,     setHealth]     = useState<VmHealthResult | null>(null);
  const [cronData,   setCronData]   = useState<CronStatusResponse | null>(null);
  const [unhealthy,  setUnhealthy]  = useState<UnhealthyContainer[]>([]);
  const [ollamaData, setOllamaData] = useState<OllamaModelsResponse | null>(null);
  const [cleanupLog, setCleanupLog] = useState<string>('');
  const [loading,    setLoading]    = useState(true);
  const [refreshing, setRefreshing] = useState(false);

  const [cleanupRunning, setCleanupRunning] = useState(false);
  const [cleanupResult,  setCleanupResult]  = useState<{ success: boolean; output: string } | null>(null);

  const [restarting, setRestarting] = useState<Set<string>>(new Set());
  const [restartMsg, setRestartMsg] = useState<{ name: string; ok: boolean; msg: string } | null>(null);

  const [unloading, setUnloading] = useState<Set<string>>(new Set());

  const [trends, setTrends]           = useState<TrendsData>({ disk: null, steal: null, ram: null, swap: null, io: null });
  const [trendsLoading, setTrendsLoading] = useState(false);
  const [trendsLoaded, setTrendsLoaded]   = useState(false);

  const [showLog, setShowLog] = useState(false);
  const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);

  const loadAll = useCallback(async () => {
    try {
      const [healthData, logData, cronResult, unhealthyResult, ollamaResult] = await Promise.allSettled([
        vmApi.getHealth(),
        vmApi.getCleanupLog(40),
        vmApi.getCronStatus(),
        vmApi.getUnhealthyContainers(),
        vmApi.getOllamaModels(),
      ]);

      if (healthData.status    === 'fulfilled') setHealth(healthData.value);
      if (logData.status       === 'fulfilled') setCleanupLog(logData.value.log);
      if (cronResult.status    === 'fulfilled') setCronData(cronResult.value);
      if (unhealthyResult.status === 'fulfilled') setUnhealthy(unhealthyResult.value.containers);
      if (ollamaResult.status  === 'fulfilled') setOllamaData(ollamaResult.value);

      setLastRefreshed(new Date());
    } catch (e) {
      console.error('Failed to load VM data:', e);
    } finally {
      setLoading(false);
      setRefreshing(false);
    }
  }, []);

  useEffect(() => {
    loadAll();
    const interval = setInterval(loadAll, 60_000);
    return () => clearInterval(interval);
  }, [loadAll]);

  const handleRefresh = () => { setRefreshing(true); loadAll(); };

  const loadTrends = useCallback(async () => {
    if (trendsLoading) return;
    setTrendsLoading(true);
    try {
      const [diskRes, stealRes, memRes, ioRes] = await Promise.allSettled([
        vmApi.getTrend('disk',  '7d'),
        vmApi.getTrend('steal', '7d'),
        vmApi.getMemoryTrend('7d'),
        vmApi.getTrend('io',    '7d'),
      ]);
      setTrends({
        disk:  diskRes.status  === 'fulfilled' ? diskRes.value  : null,
        steal: stealRes.status === 'fulfilled' ? stealRes.value : null,
        ram:   memRes.status   === 'fulfilled' ? memRes.value.available : null,
        swap:  memRes.status   === 'fulfilled' ? memRes.value.swap      : null,
        io:    ioRes.status    === 'fulfilled' ? ioRes.value   : null,
      });
      setTrendsLoaded(true);
    } finally {
      setTrendsLoading(false);
    }
  }, [trendsLoading]);

  const handleCleanup = async (mode: 'weekly' | 'monthly' | 'dry-run') => {
    const msg =
      mode === 'monthly'  ? 'Run MONTHLY full cleanup? This removes build cache, pnpm store, old logs, and HOLD node_modules.' :
      mode === 'dry-run'  ? 'Run cleanup in DRY-RUN mode? Nothing will be deleted.' :
                            'Run weekly cleanup? This prunes Docker build cache, journal, apt, and .next/cache.';
    if (!confirm(msg)) return;
    setCleanupRunning(true);
    setCleanupResult(null);
    try {
      const result = await vmApi.runCleanup(mode);
      setCleanupResult(result);
      await loadAll();
    } catch (e) {
      setCleanupResult({ success: false, output: String(e) });
    } finally {
      setCleanupRunning(false);
    }
  };

  const handleRestart = async (name: string) => {
    setRestarting(prev => new Set(prev).add(name));
    setRestartMsg(null);
    try {
      const result = await vmApi.restartContainer(name);
      setRestartMsg({ name, ok: result.success, msg: result.message });
      if (result.success) {
        await new Promise(r => setTimeout(r, 3000));
        await loadAll();
      }
    } catch (e) {
      setRestartMsg({ name, ok: false, msg: String(e) });
    } finally {
      setRestarting(prev => { const s = new Set(prev); s.delete(name); return s; });
    }
  };

  const handleUnload = async (name: string) => {
    setUnloading(prev => new Set(prev).add(name));
    try {
      await vmApi.unloadOllamaModel(name);
      await loadAll();
    } catch (e) {
      console.error('Failed to unload model:', e);
    } finally {
      setUnloading(prev => { const s = new Set(prev); s.delete(name); return s; });
    }
  };

  // ── Loading ───────────────────────────────────────────────────────────────

  if (loading) {
    return (
      <div className="flex min-h-screen bg-gray-50">
        <SidebarNav />
        <main className="flex-1 flex items-center justify-center">
          <div className="text-gray-500">Loading VM health…</div>
        </main>
      </div>
    );
  }

  const overall    = health?.overall ?? 'CRIT';
  const checks     = health?.checks ?? {};
  const sortedKeys = [
    ...CHECK_ORDER.filter(k => k in checks),
    ...Object.keys(checks).filter(k => !CHECK_ORDER.includes(k)),
  ];
  const warnings = sortedKeys.filter(k => checks[k]?.level === 'WARN');
  const crits    = sortedKeys.filter(k => checks[k]?.level === 'CRIT');

  // Parse available RAM for Ollama panel RAM bar
  const ramAvailGb = parseFloat(
    (checks.ram?.value ?? '0G').match(/^([\d.]+)/)?.[1] ?? '0'
  );

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

  return (
    <div className="flex min-h-screen bg-gray-50">
      <SidebarNav />

      <main className="flex-1 min-w-0 overflow-y-auto">
        <div className="p-8 max-md:p-4 space-y-6">

          {/* ── Header ── */}
          <div className="flex items-center justify-between">
            <div>
              <h1 className="text-2xl font-bold text-gray-900">VM Health</h1>
              <p className="text-sm text-gray-500">
                {health?.hostname ?? 'srv1491630'} ·{' '}
                {lastRefreshed
                  ? `last checked ${lastRefreshed.toLocaleTimeString()}`
                  : 'checking…'}
              </p>
            </div>
            <button
              onClick={handleRefresh}
              disabled={refreshing}
              className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50"
            >
              <RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
              Refresh
            </button>
          </div>

          {/* ── Score card ── */}
          <ScoreCard health={health} unhealthyCount={unhealthy.length} cronData={cronData} />

          {/* ── Overall status banner ── */}
          <div className={`rounded-lg border p-4 flex items-center gap-3 ${levelColor(overall)}`}>
            <LevelIcon level={overall} className="w-6 h-6 flex-shrink-0" />
            <div className="flex-1">
              <p className="font-semibold">
                {overall === 'OK'
                  ? 'All checks passing'
                  : overall === 'WARN'
                    ? `${warnings.length} warning${warnings.length !== 1 ? 's' : ''}`
                    : `${crits.length} critical issue${crits.length !== 1 ? 's' : ''}`}
              </p>
              {health?.error && (
                <p className="text-sm mt-1 opacity-80">{health.error}</p>
              )}
              {(crits.length > 0 || warnings.length > 0) && !health?.error && (
                <p className="text-sm mt-1 opacity-80">
                  {[...crits, ...warnings].map(k => checks[k]?.message).join(' · ')}
                </p>
              )}
            </div>
            <span className={`px-3 py-1 rounded-full text-sm font-bold ${levelBadge(overall)}`}>
              {overall}
            </span>
          </div>

          {/* ── Check cards grid ── */}
          <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4">
            {sortedKeys.map(key => {
              const check = checks[key];
              if (!check) return null;
              const meta = CHECK_META[key] ?? { label: key, icon: Activity };
              const Icon = meta.icon;
              return (
                <div key={key} className={`rounded-lg border p-4 ${levelColor(check.level)}`}>
                  <div className="flex items-start gap-3">
                    <div className="mt-0.5">
                      <LevelIcon level={check.level} />
                    </div>
                    <div className="flex-1 min-w-0">
                      <div className="flex items-center gap-2 mb-1">
                        <Icon className="w-4 h-4 opacity-60 flex-shrink-0" />
                        <span className="text-sm font-semibold">{meta.label}</span>
                        <span className={`ml-auto px-2 py-0.5 rounded text-xs font-bold ${levelBadge(check.level)}`}>
                          {check.level}
                        </span>
                      </div>
                      <p className="text-sm leading-snug">{check.message}</p>
                      <p className="text-xs opacity-60 mt-1 font-mono truncate">{check.value}</p>
                    </div>
                  </div>
                </div>
              );
            })}
          </div>

          {/* ── Restart feedback ── */}
          {restartMsg && (
            <div className={`rounded-lg border p-3 flex items-center gap-2 text-sm ${restartMsg.ok ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800'}`}>
              {restartMsg.ok
                ? <CheckCircle className="w-4 h-4 flex-shrink-0" />
                : <XCircle    className="w-4 h-4 flex-shrink-0" />}
              <span><strong>{restartMsg.name}:</strong> {restartMsg.msg}</span>
              <button onClick={() => setRestartMsg(null)} className="ml-auto text-gray-400 hover:text-gray-600">✕</button>
            </div>
          )}

          {/* ── Unhealthy containers ── */}
          <UnhealthyContainersPanel
            containers={unhealthy}
            onRestart={handleRestart}
            restarting={restarting}
          />

          {/* ── Cron status ── */}
          <CronStatusPanel data={cronData} />

          {/* ── Ollama ── */}
          <OllamaPanel
            data={ollamaData}
            ramAvailGb={ramAvailGb}
            onUnload={handleUnload}
            unloading={unloading}
          />

          {/* ── 7-day trends ── */}
          <TrendsPanel
            data={trends}
            loading={trendsLoading}
            onOpen={() => { if (!trendsLoaded && !trendsLoading) loadTrends(); }}
          />

          {/* ── All containers ── */}
          <ContainersPanel
            restarting={restarting}
            onRestart={handleRestart}
          />

          {/* ── Cleanup section ── */}
          <div className="bg-white border border-gray-200 rounded-lg p-6">
            <div className="flex items-center gap-3 mb-5">
              <Trash2 className="w-5 h-5 text-gray-500" />
              <div>
                <h2 className="text-lg font-semibold text-gray-900">VM Cleanup</h2>
                <p className="text-sm text-gray-500">
                  Cron runs automatically: daily build-cache prune, weekly cleanup, monthly full cleanup.
                  Use buttons below to trigger manually.
                </p>
              </div>
            </div>

            {cleanupResult && (
              <div className={`rounded-lg border p-4 mb-4 ${cleanupResult.success ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200'}`}>
                <div className="flex items-center gap-2 mb-2">
                  {cleanupResult.success
                    ? <CheckCircle className="w-4 h-4 text-green-600" />
                    : <XCircle    className="w-4 h-4 text-red-600" />}
                  <span className={`text-sm font-medium ${cleanupResult.success ? 'text-green-800' : 'text-red-800'}`}>
                    {cleanupResult.success ? 'Cleanup completed' : 'Cleanup failed'}
                  </span>
                  <button onClick={() => setCleanupResult(null)} className="ml-auto text-gray-400 hover:text-gray-600 text-xs">
                    Dismiss
                  </button>
                </div>
                {cleanupResult.output && (
                  <pre className="text-xs font-mono whitespace-pre-wrap text-gray-700 bg-white/60 rounded p-2 max-h-48 overflow-y-auto">
                    {cleanupResult.output}
                  </pre>
                )}
              </div>
            )}

            <div className="flex flex-wrap gap-3">
              <button
                onClick={() => handleCleanup('dry-run')}
                disabled={cleanupRunning}
                className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-700 bg-gray-50 border border-gray-300 rounded-md hover:bg-gray-100 disabled:opacity-50"
              >
                <Terminal className="w-4 h-4" />
                Dry Run
              </button>
              <button
                onClick={() => handleCleanup('weekly')}
                disabled={cleanupRunning}
                className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 border border-blue-300 rounded-md hover:bg-blue-100 disabled:opacity-50"
              >
                {cleanupRunning
                  ? <RefreshCw className="w-4 h-4 animate-spin" />
                  : <Trash2 className="w-4 h-4" />}
                Weekly Cleanup
              </button>
              <button
                onClick={() => handleCleanup('monthly')}
                disabled={cleanupRunning}
                className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-orange-700 bg-orange-50 border border-orange-300 rounded-md hover:bg-orange-100 disabled:opacity-50"
              >
                {cleanupRunning
                  ? <RefreshCw className="w-4 h-4 animate-spin" />
                  : <Trash2 className="w-4 h-4" />}
                Monthly Full Cleanup
              </button>
            </div>
          </div>

          {/* ── Cleanup log ── */}
          {cleanupLog && (
            <div className="bg-white border border-gray-200 rounded-lg overflow-hidden">
              <button
                className="w-full flex items-center justify-between px-6 py-4 text-left"
                onClick={() => setShowLog(v => !v)}
              >
                <div className="flex items-center gap-2">
                  <ScrollText className="w-5 h-5 text-gray-500" />
                  <span className="font-semibold text-gray-900">Cleanup Log</span>
                  <span className="text-xs text-gray-400">(last 40 lines of /var/log/vm-cleanup.log)</span>
                </div>
                {showLog
                  ? <ChevronUp   className="w-4 h-4 text-gray-400" />
                  : <ChevronDown className="w-4 h-4 text-gray-400" />}
              </button>
              {showLog && (
                <div className="border-t border-gray-100 px-6 py-4">
                  <pre className="text-xs font-mono whitespace-pre-wrap text-gray-700 bg-gray-50 rounded p-3 max-h-80 overflow-y-auto">
                    {cleanupLog}
                  </pre>
                </div>
              )}
            </div>
          )}

        </div>
      </main>
    </div>
  );
}
