"use client"

import { useState, useMemo, useEffect, useRef, useCallback } from "react"
import { PageHeader } from "@/components/page-header"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Flag } from "@/components/flag"
import { StatusDot } from "@/components/status-dot"
import { Sparkline } from "@/components/sparkline"
import { cn } from "@/lib/utils"
import { servers as mockServers, pingProbes as INIT_PROBES, filters, type Server } from "@/lib/data"
import type { PingProbe } from "@/lib/data"
import { useDataSource } from "@/lib/data-source"
import {
  RefreshCwIcon, PlusIcon, SearchIcon, XIcon,
  ChevronDownIcon, ChevronRightIcon,
  TrashIcon, ArrowRightIcon,
  CpuIcon, HardDriveIcon, ThermometerIcon, ClockIcon,
  AlertCircleIcon, ServerIcon, PauseIcon, PlayIcon,
  ArrowUpIcon, ArrowDownIcon, ArrowUpDownIcon,
  ServerCrashIcon, ChevronUpIcon, DownloadIcon,
  PencilIcon,
} from "lucide-react"
import {
  Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter,
} from "@/components/ui/sheet"

function makeApiFetch(backendUrl: string) {
  return async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
    const headers: HeadersInit = init?.body ? { "Content-Type": "application/json" } : {}
    const res = await fetch(backendUrl.replace(/\/$/, "") + path, {
      ...init,
      headers: { ...headers, ...(init?.headers ?? {}) },
    })
    if (!res.ok) {
      let message = res.statusText
      try {
        const err = await res.json() as { error?: string }
        message = err.error ?? message
      } catch {
        const text = await res.text().catch(() => "")
        if (text) message = text
      }
      throw new Error(message)
    }
    return res.json() as Promise<T>
  }
}

// ── helpers ────────────────────────────────────────────────────────────────────

function jitter(base: number, pct: number) {
  return Math.round(Math.max(1, base + (Math.random() - 0.5) * base * pct * 2))
}

function rttColor(rtt: number | null, loss: number): string {
  if (rtt === null || loss >= 100) return "text-red-600 dark:text-red-400"
  if (loss > 1 || rtt > 60)       return "text-amber-600 dark:text-amber-400"
  return "text-emerald-600 dark:text-emerald-400"
}

function probeSparkColor(status: PingProbe["status"]): string {
  return status === "down" ? "hsl(0 84% 60%)" : status === "warn" ? "hsl(32 94% 44%)" : "hsl(142 76% 36%)"
}

// ── shared components ──────────────────────────────────────────────────────────

function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
  return (
    <button type="button" onClick={() => onChange(!checked)}
      className={cn(
        "relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors cursor-pointer",
        checked ? "bg-primary" : "bg-input",
      )}>
      <span className={cn(
        "pointer-events-none block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
        checked ? "translate-x-4" : "translate-x-0",
      )} />
    </button>
  )
}

function TypeChip({ type }: { type: "jump-host" | "exit-node" | "home-router" }) {
  return (
    <span className={cn(
      "inline-flex items-center rounded px-1.5 py-0.5 text-[10px] font-bold border shrink-0",
      type === "home-router"
        ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20"
        : type === "jump-host"
          ? "bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20"
          : "bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20",
    )}>
      {type === "jump-host" ? "JH" : type === "home-router" ? "HR" : "EN"}
    </span>
  )
}

function StatChip({
  label, value, color, active, onClick,
}: {
  label: string; value: number; color?: "emerald" | "amber" | "red"; active?: boolean; onClick?: () => void
}) {
  return (
    <button
      onClick={onClick}
      className={cn(
        "flex items-center gap-1.5 px-3 py-1.5 rounded-md border text-xs transition-all",
        active
          ? "bg-foreground text-background border-foreground"
          : "border-border hover:border-foreground/30 text-muted-foreground hover:text-foreground",
      )}>
      <span>{label}</span>
      <span className={cn(
        "font-semibold tabular-nums",
        !active && color === "emerald" ? "text-emerald-600 dark:text-emerald-400" :
        !active && color === "amber"   ? "text-amber-600 dark:text-amber-400" :
        !active && color === "red"     ? "text-red-600 dark:text-red-400" : "",
      )}>{value}</span>
    </button>
  )
}

function Field({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) {
  return (
    <div className="flex flex-col gap-1.5">
      <label className="text-xs font-medium">
        {label}
        {hint && <span className="font-normal text-muted-foreground ml-1">{hint}</span>}
      </label>
      {children}
    </div>
  )
}

// ── Resource monitoring types + helpers ───────────────────────────────────────

interface ServerResource {
  serverId: string
  cpu: number
  cpuHistory: number[]
  ramUsed: number   // MB
  ramTotal: number  // MB
  hddUsed: number   // MB
  hddTotal: number  // MB
  uptimeSeconds: number
  boardName: string
  temp?: number     // °C
}

interface BackendServer {
  id: number
  name: string
  host: string
  type: "jump-host" | "exit-node" | "home-router"
  site: string
  country: string
  enabled: boolean
  status: "online" | "offline" | null
  latency: number | null
  os: string | null
}

interface SpeedTestRun {
  id: string
  startedAt: number
  srcServerId: string
  dstServerId: string
  srcInterface?: string
  dstInterface?: string
  protocol: "tcp" | "udp"
  direction: "transmit" | "receive" | "both"
  durationSec: number
  txAvgMbps: number
  rxAvgMbps: number
  status: "running" | "done" | "error"
  command: string
  lines: string[]
}

interface SpeedProbeRow {
  id: string
  srcServerId: string
  dstServerId: string
  srcInterface: string
  dstInterface: string
  protocol: "tcp" | "udp"
  direction: "transmit" | "receive" | "both"
  durationSec: string
  enabled: boolean
}

function fmtMB(mb: number): string {
  if (mb >= 1024) return `${(mb / 1024).toFixed(mb >= 10240 ? 0 : 1)} ГБ`
  return `${mb} МБ`
}

function fmtUptime(sec: number): string {
  const d = Math.floor(sec / 86400)
  const h = Math.floor((sec % 86400) / 3600)
  const m = Math.floor((sec % 3600) / 60)
  if (d > 0) return `${d}д ${h}ч`
  if (h > 0) return `${h}ч ${m}м`
  return `${m}м`
}

function resPctColor(pct: number, warn = 70, crit = 85): string {
  if (pct >= crit) return "text-red-600 dark:text-red-400"
  if (pct >= warn) return "text-amber-600 dark:text-amber-400"
  return "text-emerald-600 dark:text-emerald-400"
}

function resBarColor(pct: number, warn = 70, crit = 85): string {
  if (pct >= crit) return "bg-red-500"
  if (pct >= warn) return "bg-amber-500"
  return "bg-emerald-500"
}

const BOARD_MAP: Record<string, string> = {
  "jump-host":   "RB5009UG+S+IN",
  "exit-node":   "RB4011iGS+RM",
  "home-router": "hAP ax²",
}

// Deterministic pseudo-random initial resource values per server
const INIT_RESOURCES: ServerResource[] = mockServers.map(s => {
  const h = s.id.split("").reduce((a, c) => a + c.charCodeAt(0), 0)
  const cpu      = 4 + (h % 68)
  const ramTotal = s.type === "jump-host" ? 8192 : s.type === "exit-node" ? 4096 : 1024
  const hddTotal = s.type === "home-router" ? 2048 : 16384
  const ramPct   = 12 + (h % 72)
  const hddPct   = 8  + (h % 75)
  return {
    serverId:      s.id,
    cpu,
    cpuHistory:    Array.from({ length: 40 }, (_, i) =>
      Math.max(1, Math.min(99, cpu + Math.round(Math.sin(i * 0.7 + h * 0.1) * 15)))
    ),
    ramUsed:       Math.round(ramTotal * ramPct / 100),
    ramTotal,
    hddUsed:       Math.round(hddTotal * hddPct / 100),
    hddTotal,
    uptimeSeconds: (1 + h % 200) * 86400 + (h % 24) * 3600 + (h % 60) * 60,
    boardName:     BOARD_MAP[s.type] ?? "RouterBOARD",
    temp:          s.type !== "home-router" ? 34 + (h % 32) : undefined,
  }
})

const DEFAULT_SPEED_PROBE: SpeedProbeRow | null = (() => {
  const enabled = mockServers.filter((s) => s.enabled)
  if (!enabled.length) return null
  const src = enabled[0]?.id ?? ""
  const dst = enabled.find((s) => s.id !== src)?.id ?? src
  return {
    id: "sp-default",
    srcServerId: src,
    dstServerId: dst,
    srcInterface: "",
    dstInterface: "",
    protocol: "tcp",
    direction: "both",
    durationSec: "10",
    enabled: true,
  }
})()

// ── MiniBar ───────────────────────────────────────────────────────────────────

function MiniBar({ pct, warn = 70, crit = 85, className }: {
  pct: number; warn?: number; crit?: number; className?: string
}) {
  return (
    <div className={cn("h-1.5 rounded-full bg-muted overflow-hidden", className)}>
      <div
        className={cn("h-full rounded-full transition-all duration-700", resBarColor(pct, warn, crit))}
        style={{ width: `${Math.min(100, Math.max(0, pct))}%` }}
      />
    </div>
  )
}

// ── Sort indicator ─────────────────────────────────────────────────────────────

type ResSortKey = "name" | "cpu" | "ram" | "hdd" | "uptime" | "temp"

function SortIcon({ k, sortKey, sortAsc }: { k: ResSortKey; sortKey: ResSortKey; sortAsc: boolean }) {
  if (sortKey !== k) return <ArrowUpDownIcon className="size-3 opacity-25 ml-0.5" />
  return sortAsc
    ? <ArrowUpIcon   className="size-3 ml-0.5 text-foreground" />
    : <ArrowDownIcon className="size-3 ml-0.5 text-foreground" />
}

// ── ResourcesTab ──────────────────────────────────────────────────────────────

type ResTypeFilter = "all" | "jump-host" | "exit-node" | "home-router"

function ResourcesTab({ resources, serversList }: { resources: ServerResource[]; serversList: Server[] }) {
  const [sortKey,    setSortKey]    = useState<ResSortKey>("name")
  const [sortAsc,    setSortAsc]    = useState(true)
  const [resSearch,  setResSearch]  = useState("")
  const [typeFilter, setTypeFilter] = useState<ResTypeFilter>("all")

  const rows = useMemo(() => resources.map(r => ({
    ...r,
    server:  serversList.find(s => s.id === r.serverId),
    ramPct:  Math.round(r.ramUsed  / r.ramTotal  * 100),
    hddPct:  Math.round(r.hddUsed  / r.hddTotal  * 100),
  })).filter(r => r.server !== undefined), [resources, serversList])

  // KPI aggregates
  const online  = rows.filter(r => r.server!.status === "online")
  const avgCpu  = online.length ? Math.round(online.reduce((s, r) => s + r.cpu,    0) / online.length) : 0
  const avgRam  = online.length ? Math.round(online.reduce((s, r) => s + r.ramPct, 0) / online.length) : 0
  const highCpu = rows.filter(r => r.server!.status === "online" && r.cpu    >= 85).length
  const highRam = rows.filter(r => r.server!.status === "online" && r.ramPct >= 85).length
  const highHdd = rows.filter(r => r.server!.status === "online" && r.hddPct >= 85).length

  // Alerts
  const alerts = useMemo(() =>
    rows.filter(r => r.server!.status === "online" && (r.cpu >= 85 || r.ramPct >= 85 || r.hddPct >= 85 || (r.temp ?? 0) >= 70)),
    [rows],
  )

  // Filtered + sorted
  const visible = useMemo(() => {
    let list = rows
    if (typeFilter !== "all") list = list.filter(r => r.server!.type === typeFilter)
    if (resSearch.trim()) {
      const q = resSearch.toLowerCase()
      list = list.filter(r =>
        r.server!.name.toLowerCase().includes(q) ||
        r.server!.site.toLowerCase().includes(q) ||
        r.boardName.toLowerCase().includes(q)
      )
    }
    list = [...list].sort((a, b) => {
      let diff = 0
      switch (sortKey) {
        case "name":   diff = a.server!.name.localeCompare(b.server!.name); break
        case "cpu":    diff = a.cpu    - b.cpu;    break
        case "ram":    diff = a.ramPct - b.ramPct; break
        case "hdd":    diff = a.hddPct - b.hddPct; break
        case "uptime": diff = a.uptimeSeconds - b.uptimeSeconds; break
        case "temp":   diff = (a.temp ?? -1) - (b.temp ?? -1); break
      }
      return sortAsc ? diff : -diff
    })
    return list
  }, [rows, typeFilter, resSearch, sortKey, sortAsc])

  function toggleSort(k: ResSortKey) {
    if (sortKey === k) setSortAsc(v => !v)
    else { setSortKey(k); setSortAsc(false) } // default desc for metrics
  }

  function exportCsv() {
    const header = ["Сервер", "Тип", "Площадка", "CPU %", "RAM %", "RAM использ.", "RAM всего", "HDD %", "HDD использ.", "HDD всего", "Uptime", "Температура °C", "RouterOS"]
    const rowsCsv = visible.map(r => {
      const s = r.server!
      return [s.name, s.type, s.site, r.cpu, r.ramPct, fmtMB(r.ramUsed), fmtMB(r.ramTotal),
              r.hddPct, fmtMB(r.hddUsed), fmtMB(r.hddTotal), fmtUptime(r.uptimeSeconds),
              r.temp ?? "", s.os].join(",")
    })
    const csv = [header.join(","), ...rowsCsv].join("\n")
    const blob = new Blob([csv], { type: "text/csv;charset=utf-8" })
    const url = URL.createObjectURL(blob)
    const a = document.createElement("a")
    a.href = url; a.download = `resources-${new Date().toISOString().slice(0,10)}.csv`; a.click()
    URL.revokeObjectURL(url)
  }

  const typeOpts: { value: ResTypeFilter; label: string }[] = [
    { value: "all",         label: "Все"         },
    { value: "jump-host",   label: "JumpHost"    },
    { value: "exit-node",   label: "Exit Node"   },
    { value: "home-router", label: "Home Router" },
  ]

  return (
    <div className="p-6 flex flex-col gap-4">

      {/* ── Alert banner ──────────────────────────────────────────────────── */}
      {alerts.length > 0 && (
        <div className="flex items-start gap-3 rounded-lg border border-red-500/30 bg-red-500/5 px-4 py-3">
          <ServerCrashIcon className="size-4 text-red-500 shrink-0 mt-0.5" />
          <div className="flex-1 min-w-0">
            <p className="text-xs font-semibold text-red-600 dark:text-red-400 mb-1">
              {alerts.length} {alerts.length === 1 ? "сервер требует внимания" : "сервера требуют внимания"}
            </p>
            <div className="flex flex-wrap gap-1.5">
              {alerts.map(r => {
                const s = r.server!
                const issues: string[] = []
                if (r.cpu >= 85)    issues.push(`CPU ${r.cpu}%`)
                if (r.ramPct >= 85) issues.push(`RAM ${r.ramPct}%`)
                if (r.hddPct >= 85) issues.push(`HDD ${r.hddPct}%`)
                if ((r.temp ?? 0) >= 70) issues.push(`${r.temp}°C`)
                return (
                  <span key={r.serverId} className="inline-flex items-center gap-1 text-[11px] font-mono
                    rounded border border-red-500/30 bg-red-500/10 px-2 py-0.5 text-red-400">
                    <Flag code={s.country} size={10} />
                    {s.name} — {issues.join(", ")}
                  </span>
                )
              })}
            </div>
          </div>
        </div>
      )}

      {/* ── KPI summary ───────────────────────────────────────────────────── */}
      <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
        {[
          { icon: <ServerIcon      className="size-4 text-muted-foreground" />, label: String(rows.length), sub: "серверов всего",  color: "text-foreground" },
          { icon: <CpuIcon         className="size-4" />,                       label: `${avgCpu}%`,         sub: "средний CPU",     color: resPctColor(avgCpu) },
          { icon: <HardDriveIcon   className="size-4" />,                       label: `${avgRam}%`,         sub: "средний RAM",     color: resPctColor(avgRam) },
          { icon: <AlertCircleIcon className="size-4" />,                       label: String(highCpu),      sub: "CPU > 85%",       color: highCpu > 0 ? "text-red-500"    : "text-muted-foreground" },
          { icon: <AlertCircleIcon className="size-4" />,                       label: String(highRam),      sub: "RAM > 85%",       color: highRam > 0 ? "text-red-500"    : "text-muted-foreground" },
          { icon: <AlertCircleIcon className="size-4" />,                       label: String(highHdd),      sub: "Диск > 85%",      color: highHdd > 0 ? "text-amber-500"  : "text-muted-foreground" },
        ].map(kpi => (
          <Card key={kpi.sub}>
            <CardContent className="px-4 py-3 flex items-start gap-3">
              <div className={cn("mt-0.5 shrink-0", kpi.color)}>{kpi.icon}</div>
              <div className="min-w-0">
                <p className={cn("text-xl font-semibold tabular-nums leading-tight", kpi.color)}>{kpi.label}</p>
                <p className="text-[11px] text-muted-foreground mt-0.5">{kpi.sub}</p>
              </div>
            </CardContent>
          </Card>
        ))}
      </div>

      {/* ── Toolbar ───────────────────────────────────────────────────────── */}
      <div className="flex items-center gap-3 flex-wrap">
        {/* Type filter */}
        <div className="flex items-center gap-0.5 rounded-md border border-border bg-muted/40 p-0.5 shrink-0">
          {typeOpts.map(o => (
            <button key={o.value} onClick={() => setTypeFilter(o.value)}
              className={cn(
                "px-2.5 py-1 text-xs rounded transition-colors whitespace-nowrap",
                typeFilter === o.value
                  ? "bg-background text-foreground shadow-sm"
                  : "text-muted-foreground hover:text-foreground",
              )}>
              {o.label}
            </button>
          ))}
        </div>

        {/* Search */}
        <div className="relative min-w-[180px] flex-1 max-w-xs">
          <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground pointer-events-none" />
          <Input className="pl-8 h-8 text-sm" placeholder="Поиск по имени, площадке…"
            value={resSearch} onChange={e => setResSearch(e.target.value)} />
          {resSearch && (
            <button onClick={() => setResSearch("")}
              className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground">
              <XIcon className="size-3.5" />
            </button>
          )}
        </div>

        <span className="text-xs text-muted-foreground ml-auto shrink-0">
          {visible.length} из {rows.length} серверов
        </span>

        {/* Export CSV */}
        <Button variant="outline" size="sm" className="h-8 gap-1.5 shrink-0" onClick={exportCsv}>
          <DownloadIcon className="size-3.5" />CSV
        </Button>
      </div>

      {/* ── Table ─────────────────────────────────────────────────────────── */}
      <Card className="overflow-hidden">
        <div className="overflow-x-auto">
          <table className="w-full text-sm">
            <thead>
              <tr className="border-b bg-muted/30 text-xs text-muted-foreground font-medium">

                {/* Sortable: name */}
                <th className="text-left px-5 py-3 cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("name")}>
                  <span className="flex items-center gap-0.5">
                    Сервер <SortIcon k="name" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>

                <th className="text-left px-4 py-3 hidden md:table-cell whitespace-nowrap">Модель · ROS</th>

                {/* Sortable: cpu */}
                <th className="text-left px-4 py-3 min-w-[160px] cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("cpu")}>
                  <span className="flex items-center gap-1.5">
                    <CpuIcon className="size-3.5" />CPU
                    <SortIcon k="cpu" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>

                {/* Sortable: ram */}
                <th className="text-left px-4 py-3 min-w-[175px] cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("ram")}>
                  <span className="flex items-center gap-1.5">
                    <HardDriveIcon className="size-3.5" />RAM
                    <SortIcon k="ram" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>

                {/* Sortable: hdd */}
                <th className="text-left px-4 py-3 min-w-[175px] cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("hdd")}>
                  <span className="flex items-center gap-1.5">
                    <HardDriveIcon className="size-3.5" />Диск
                    <SortIcon k="hdd" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>

                {/* Sortable: uptime */}
                <th className="text-left px-4 py-3 cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("uptime")}>
                  <span className="flex items-center gap-1.5">
                    <ClockIcon className="size-3.5" />Uptime
                    <SortIcon k="uptime" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>

                {/* Sortable: temp */}
                <th className="text-left px-4 py-3 cursor-pointer hover:text-foreground transition-colors select-none"
                  onClick={() => toggleSort("temp")}>
                  <span className="flex items-center gap-1.5">
                    <ThermometerIcon className="size-3.5" />°C
                    <SortIcon k="temp" sortKey={sortKey} sortAsc={sortAsc} />
                  </span>
                </th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {visible.length === 0 && (
                <tr>
                  <td colSpan={7} className="text-center text-sm text-muted-foreground py-12">
                    <SearchIcon className="size-6 mx-auto mb-2 opacity-20" />
                    Ничего не найдено
                  </td>
                </tr>
              )}
              {visible.map(r => {
                const srv = r.server!
                const offline = srv.status !== "online"
                const isCrit  = !offline && (r.cpu >= 85 || r.ramPct >= 85 || r.hddPct >= 85 || (r.temp ?? 0) >= 70)
                const cpuColor = r.cpu >= 85 ? "hsl(0 84% 60%)" : r.cpu >= 70 ? "hsl(38 92% 50%)" : "hsl(142 76% 36%)"
                return (
                  <tr key={r.serverId} className={cn(
                    "hover:bg-muted/30 transition-colors",
                    offline && "opacity-50",
                    isCrit  && "bg-red-500/3",
                  )}>

                    {/* Server */}
                    <td className="px-5 py-3">
                      <div className="flex items-center gap-2 flex-wrap">
                        {isCrit && <AlertCircleIcon className="size-3.5 text-red-500 shrink-0" />}
                        {!isCrit && <StatusDot status={srv.status} pulse={!offline} />}
                        <Flag code={srv.country} size={16} />
                        <span className="font-mono font-semibold">{srv.name}</span>
                        <TypeChip type={srv.type} />
                        <span className="text-xs text-muted-foreground hidden xl:inline">{srv.site}</span>
                      </div>
                    </td>

                    {/* Board + ROS */}
                    <td className="px-4 py-3 hidden md:table-cell">
                      <div className="flex flex-col leading-tight">
                        <span className="font-mono text-xs text-muted-foreground">{r.boardName}</span>
                        <span className="text-[10px] text-muted-foreground/50">{srv.os}</span>
                      </div>
                    </td>

                    {/* CPU */}
                    <td className="px-4 py-3">
                      {offline
                        ? <span className="text-xs text-muted-foreground/30">—</span>
                        : (
                          <div className="flex flex-col gap-1.5 min-w-[140px]">
                            <div className="flex items-center gap-2">
                              <span className={cn("font-mono text-sm font-semibold tabular-nums w-10 shrink-0", resPctColor(r.cpu))}>
                                {r.cpu}%
                              </span>
                              <MiniBar pct={r.cpu} className="flex-1" />
                            </div>
                            <Sparkline data={r.cpuHistory} width={120} height={18} color={cpuColor} filled />
                          </div>
                        )}
                    </td>

                    {/* RAM */}
                    <td className="px-4 py-3">
                      {offline
                        ? <span className="text-xs text-muted-foreground/30">—</span>
                        : (
                          <div className="flex flex-col gap-1.5 min-w-[155px]">
                            <div className="flex items-center justify-between text-xs">
                              <span className={cn("font-mono font-semibold", resPctColor(r.ramPct))}>{r.ramPct}%</span>
                              <span className="text-muted-foreground/60 font-mono text-[10px]">
                                {fmtMB(r.ramUsed)}/{fmtMB(r.ramTotal)}
                              </span>
                            </div>
                            <MiniBar pct={r.ramPct} />
                          </div>
                        )}
                    </td>

                    {/* HDD */}
                    <td className="px-4 py-3">
                      {offline
                        ? <span className="text-xs text-muted-foreground/30">—</span>
                        : (
                          <div className="flex flex-col gap-1.5 min-w-[155px]">
                            <div className="flex items-center justify-between text-xs">
                              <span className={cn("font-mono font-semibold", resPctColor(r.hddPct))}>{r.hddPct}%</span>
                              <span className="text-muted-foreground/60 font-mono text-[10px]">
                                {fmtMB(r.hddUsed)}/{fmtMB(r.hddTotal)}
                              </span>
                            </div>
                            <MiniBar pct={r.hddPct} />
                          </div>
                        )}
                    </td>

                    {/* Uptime */}
                    <td className="px-4 py-3">
                      <span className="font-mono text-xs text-muted-foreground">
                        {offline ? "—" : fmtUptime(r.uptimeSeconds)}
                      </span>
                    </td>

                    {/* Temp */}
                    <td className="px-4 py-3">
                      {r.temp !== undefined && !offline ? (
                        <span className={cn("font-mono text-sm font-semibold tabular-nums",
                          r.temp >= 70 ? "text-red-600 dark:text-red-400"
                          : r.temp >= 55 ? "text-amber-600 dark:text-amber-400"
                          : "text-muted-foreground",
                        )}>
                          {r.temp}°C
                        </span>
                      ) : (
                        <span className="text-muted-foreground/30 text-xs">—</span>
                      )}
                    </td>
                  </tr>
                )
              })}
            </tbody>
          </table>
        </div>
      </Card>

      <p className="text-xs text-muted-foreground/40 text-center">
        Обновление каждые 5 сек · /system/resource via RouterOS REST API · demo-режим
      </p>
    </div>
  )
}

// ── page ───────────────────────────────────────────────────────────────────────

export default function UptimePage() {
  const [allServers, setAllServers] = useState<Server[]>(mockServers)
  const { mode, backendUrl, backendStatus } = useDataSource()
  const isLive = mode === "live" && backendStatus === true
  const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])

  const [tab,          setTab]          = useState<"probes" | "resources" | "speed">("probes")
  const [probes,       setProbes]       = useState<PingProbe[]>(INIT_PROBES)
  const [resources,    setResources]    = useState<ServerResource[]>(INIT_RESOURCES)
  const [isPaused,     setIsPaused]     = useState(false)
  const [search,       setSearch]       = useState("")
  const [statusFilter, setStatusFilter] = useState<"all" | "up" | "warn" | "down">("all")
  const [serverFilter, setServerFilter] = useState<string[]>([])
  const [collapsed,    setCollapsed]    = useState<Set<string>>(new Set())
  const [sheetOpen,    setSheetOpen]    = useState(false)
  const [opError,      setOpError]      = useState<string | null>(null)
  const [settingsBusy, setSettingsBusy] = useState(false)
  const [uptimeSettings, setUptimeSettings] = useState<{
    enabled: boolean
    intervalSec: number
    retentionDays: number
    lastCollectedAt: string | null
    lastDurationMs: number | null
    lastError: string | null
  } | null>(null)
  const [intervalDraft, setIntervalDraft] = useState("15")
  const [retentionDraft, setRetentionDraft] = useState("14")
  const [speedBusy, setSpeedBusy] = useState(false)
  const [speedError, setSpeedError] = useState<string | null>(null)
  const [speedRuns, setSpeedRuns] = useState<SpeedTestRun[]>([])
  const [speedProbes, setSpeedProbes] = useState<SpeedProbeRow[]>(() => (DEFAULT_SPEED_PROBE ? [DEFAULT_SPEED_PROBE] : []))
  const [speedIfaces, setSpeedIfaces] = useState<Record<string, Array<{ name: string; running: boolean; disabled: boolean; addresses: string[] }>>>({})
  const [speedCollapsed, setSpeedCollapsed] = useState<Set<string>>(new Set())
  const [speedSheetOpen, setSpeedSheetOpen] = useState(false)
  const [speedDraft, setSpeedDraft] = useState<SpeedProbeRow>({
    id: "",
    srcServerId: "",
    dstServerId: "",
    srcInterface: "",
    dstInterface: "",
    protocol: "tcp",
    direction: "both",
    durationSec: "10",
    enabled: true,
  })

  useEffect(() => {
    if (!isLive) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setAllServers(mockServers)
      return
    }
    apiFetch<BackendServer[]>("/api/servers")
      .then((data) => {
        const mapped: Server[] = data.map((s) => ({
          id: String(s.id),
          name: s.name || s.host,
          host: s.host,
          model: "—",
          os: s.os ?? "—",
          site: s.site || "—",
          country: s.country || "UN",
          asn: "",
          type: s.type,
          enabled: s.enabled,
          status: (s.status ?? "offline") as Server["status"],
          latency: s.latency != null ? Math.round(s.latency) : null,
          sessions: 0,
        }))
        setAllServers(mapped)
      })
      .catch(() => setAllServers([]))
  }, [isLive, apiFetch])

  const isPausedRef = useRef(isPaused)
  useEffect(() => { isPausedRef.current = isPaused }, [isPaused])

  const loadLiveOverview = useCallback(async () => {
    if (!isLive) return
    setOpError(null)
    try {
      const data = await apiFetch<{ probes: PingProbe[]; resources: ServerResource[] }>("/api/uptime/overview?range=1h")
      setProbes(data.probes)
      setResources(data.resources)
    } catch (e) {
      setOpError(e instanceof Error ? e.message : "Не удалось загрузить uptime")
    }
  }, [apiFetch, isLive])

  const loadLiveSettings = useCallback(async () => {
    if (!isLive) return
    try {
      const s = await apiFetch<{
        enabled: boolean
        intervalSec: number
        retentionDays: number
        lastCollectedAt: string | null
        lastDurationMs: number | null
        lastError: string | null
      }>("/api/uptime/settings")
      setUptimeSettings(s)
      setIntervalDraft(String(s.intervalSec))
      setRetentionDraft(String(s.retentionDays))
    } catch (e) {
      setOpError(e instanceof Error ? e.message : "Не удалось загрузить настройки uptime")
    }
  }, [apiFetch, isLive])

  useEffect(() => {
    if (!isLive) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setUptimeSettings(null)
      return
    }
    void loadLiveOverview()
    void loadLiveSettings()
  }, [isLive, loadLiveOverview, loadLiveSettings])

  // add-probe form
  const [newSrcId,   setNewSrcId]   = useState("")
  const [newSrcInterface, setNewSrcInterface] = useState("")
  const [newName,    setNewName]    = useState("")
  const [newTarget,  setNewTarget]  = useState("")
  const [newFilter,  setNewFilter]  = useState("—")
  const [editingProbeId, setEditingProbeId] = useState<string | null>(null)
  const [srcInterfaces, setSrcInterfaces] = useState<Array<{ name: string; running: boolean; disabled: boolean }>>([])
  const [srcInterfacesBusy, setSrcInterfacesBusy] = useState(false)

  const selectableSources = useMemo(
    () => allServers.filter(s => s.enabled),
    [allServers],
  )

  const loadSpeedInterfaces = useCallback(async (serverId: string) => {
    if (!isLive) return
    if (!serverId || speedIfaces[serverId]) return
    const id = Number.parseInt(serverId, 10)
    if (!Number.isFinite(id)) return
    try {
      const res = await apiFetch<{ interfaces: Array<{ name: string; running: boolean; disabled: boolean; addresses: string[] }> }>(`/api/uptime/speed-test/endpoints/${id}/interfaces`)
      setSpeedIfaces((prev) => ({ ...prev, [serverId]: res.interfaces ?? [] }))
    } catch {
      setSpeedIfaces((prev) => ({ ...prev, [serverId]: [] }))
    }
  }, [apiFetch, isLive, speedIfaces])

  useEffect(() => {
    if (!sheetOpen) return
    if (!newSrcId || !selectableSources.some(s => s.id === newSrcId)) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setNewSrcId(selectableSources[0]?.id ?? "")
    }
  }, [sheetOpen, newSrcId, selectableSources])

  useEffect(() => {
    if (!sheetOpen || !newSrcId) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setSrcInterfaces([])
      setNewSrcInterface("")
      return
    }
    if (!isLive) {
      setSrcInterfaces([])
      setNewSrcInterface("")
      return
    }
    const serverId = Number.parseInt(newSrcId, 10)
    if (!Number.isFinite(serverId)) {
      setSrcInterfaces([])
      setNewSrcInterface("")
      return
    }
    setSrcInterfacesBusy(true)
    void apiFetch<{ interfaces: Array<{ name: string; running: boolean; disabled: boolean }> }>(`/api/uptime/sources/${serverId}/interfaces`)
      .then((data) => {
        const list = data.interfaces ?? []
        setSrcInterfaces(list)
        setNewSrcInterface((prev) => (prev && list.some((i) => i.name === prev) ? prev : ""))
      })
      .catch(() => {
        setSrcInterfaces([])
        setNewSrcInterface("")
      })
      .finally(() => setSrcInterfacesBusy(false))
  }, [sheetOpen, newSrcId, isLive, apiFetch])

  // servers that have at least one probe (preserve data-order)
  const probedServerIds = useMemo(
    () => [...new Set(probes.map(p => p.srcServerId))],
    [probes],
  )
  const probedServers = useMemo(
    () => allServers.filter(s => probedServerIds.includes(s.id)),
    [probedServerIds, allServers],
  )

  // live RTT tick
  useEffect(() => {
    const id = setInterval(() => {
      if (isLive) return
      if (isPausedRef.current) return
      setProbes(prev => prev.map(p => {
        if (!p.enabled || p.status === "down" || p.rtt === null) return p
        const newRtt    = jitter(p.rtt, 0.12)
        const newSeries = [...p.series.slice(1), newRtt]
        return { ...p, rtt: newRtt, series: newSeries }
      }))
    }, 3000)
    return () => clearInterval(id)
  }, [isLive])

  // live resource tick
  useEffect(() => {
    const id = setInterval(() => {
      if (isLive) return
      if (isPausedRef.current) return
      setResources(prev => prev.map(r => {
        const srv = allServers.find(s => s.id === r.serverId)
        if (!srv || srv.status !== "online") return r
        const newCpu  = Math.min(99, Math.max(1,  r.cpu    + Math.round((Math.random() - 0.48) * 8)))
        const newRam  = Math.min(r.ramTotal - 64, Math.max(256, r.ramUsed + Math.round((Math.random() - 0.5) * 128)))
        const newTemp = r.temp !== undefined
          ? Math.min(90, Math.max(28, r.temp + Math.round((Math.random() - 0.5) * 3)))
          : undefined
        return {
          ...r,
          cpu:           newCpu,
          cpuHistory:    [...r.cpuHistory.slice(1), newCpu],
          ramUsed:       newRam,
          uptimeSeconds: r.uptimeSeconds + 5,
          temp:          newTemp,
        }
      }))
    }, 5000)
    return () => clearInterval(id)
  }, [isLive, allServers])

  // ── derived ──
  const stats = useMemo(() => ({
    total: probes.length,
    up:    probes.filter(p => p.status === "up").length,
    warn:  probes.filter(p => p.status === "warn").length,
    down:  probes.filter(p => p.status === "down").length,
  }), [probes])

  const alertCount = useMemo(() =>
    resources.filter(r => {
      const s = allServers.find(x => x.id === r.serverId)
      if (!s || s.status !== "online") return false
      const ramPct = Math.round(r.ramUsed / r.ramTotal * 100)
      const hddPct = Math.round(r.hddUsed / r.hddTotal * 100)
      return r.cpu >= 85 || ramPct >= 85 || hddPct >= 85 || (r.temp ?? 0) >= 70
    }).length,
    [resources, allServers],
  )

  const filtered = useMemo(() => probes.filter(p => {
    if (serverFilter.length > 0 && !serverFilter.includes(p.srcServerId)) return false
    if (statusFilter !== "all" && p.status !== statusFilter) return false
    if (search) {
      const q = search.toLowerCase()
      return (
        p.name.toLowerCase().includes(q)   ||
        p.target.toLowerCase().includes(q) ||
        p.filter.toLowerCase().includes(q)
      )
    }
    return true
  }), [probes, serverFilter, statusFilter, search])

  const grouped = useMemo(() => {
    const byServer = new Map<string, Map<string, { name: string; target: string; probes: PingProbe[] }>>()
    for (const p of filtered) {
      if (!byServer.has(p.srcServerId)) byServer.set(p.srcServerId, new Map())
      const key = `${p.name.trim().toLowerCase()}|${p.target.trim().toLowerCase()}`
      const serverMap = byServer.get(p.srcServerId)!
      if (!serverMap.has(key)) serverMap.set(key, { name: p.name, target: p.target, probes: [] })
      serverMap.get(key)!.probes.push(p)
    }
    return probedServers
      .map((server) => ({
        server,
        groups: [...(byServer.get(server.id)?.values() ?? [])]
          .sort((a, b) => (a.target + a.name).localeCompare(b.target + b.name)),
      }))
      .filter((g) => g.groups.length > 0)
  }, [filtered, probedServers])

  const speedGrouped = useMemo(() => {
    const map = new Map<string, SpeedProbeRow[]>()
    for (const p of speedProbes) {
      const arr = map.get(p.srcServerId) ?? []
      arr.push(p)
      map.set(p.srcServerId, arr)
    }
    return selectableSources
      .filter((s) => map.has(s.id))
      .map((s) => ({
        server: s,
        probes: (map.get(s.id) ?? []).sort((a, b) => (a.dstServerId + a.id).localeCompare(b.dstServerId + b.id)),
      }))
  }, [speedProbes, selectableSources])

  // ── actions ──
  const toggleProbe  = (id: string, v: boolean) =>
    setProbes(p => p.map(x => x.id === id ? { ...x, enabled: v } : x))

  const deleteProbe  = (id: string) =>
    setProbes(p => p.filter(x => x.id !== id))

  const toggleCollapse = (serverId: string) =>
    setCollapsed(prev => {
      const next = new Set(prev)
      if (next.has(serverId)) next.delete(serverId); else next.add(serverId)
      return next
    })

  const collapseAll = useCallback(() =>
    setCollapsed(new Set(grouped.map(g => g.server.id))), [grouped])

  const expandAll = useCallback(() =>
    setCollapsed(new Set()), [])

  const allCollapsed = grouped.length > 0 && grouped.every(g => collapsed.has(g.server.id))

  const toggleServerFilter = (id: string) =>
    setServerFilter(prev =>
      prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]
    )

  const cycleStatusFilter = (s: typeof statusFilter) =>
    setStatusFilter(prev => prev === s ? "all" : s)

  const openEditProbe = (probe: PingProbe) => {
    setEditingProbeId(probe.id)
    setNewSrcId(probe.srcServerId)
    setNewSrcInterface(probe.srcInterface || "")
    setNewName(probe.name)
    setNewTarget(probe.target)
    setNewFilter(probe.filter || "—")
    setSheetOpen(true)
  }

  const openAddSheet = () => {
    setEditingProbeId(null)
    setNewSrcId(selectableSources[0]?.id ?? "")
    setNewSrcInterface("")
    setNewName(""); setNewTarget(""); setNewFilter("—")
    setSheetOpen(true)
  }

  const handleSaveProbe = () => {
    if (!newName.trim() || !newTarget.trim() || !newSrcId) return
    const probeBase: PingProbe = {
      id: editingProbeId ?? `p${Date.now()}`,
      srcServerId: newSrcId,
      srcInterface: newSrcInterface,
      name:        newName.trim(),
      target:      newTarget.trim(),
      filter:      newFilter || "—",
      rtt:         null,
      loss:        0,
      status:      "up",
      series:      Array(40).fill(0),
      enabled:     true,
    }
    if (editingProbeId) {
      setProbes(prev => prev.map((p) => {
        if (p.id !== editingProbeId) return p
        return {
          ...p,
          srcServerId: probeBase.srcServerId,
          srcInterface: probeBase.srcInterface,
          name: probeBase.name,
          target: probeBase.target,
          filter: probeBase.filter,
        }
      }))
    } else {
      setProbes(p => [...p, probeBase])
    }
    setEditingProbeId(null)
    setSheetOpen(false)
  }

  const buildSpeedCommand = useCallback((probe: SpeedProbeRow) => {
    const src = allServers.find((s) => s.id === probe.srcServerId)
    const dst = allServers.find((s) => s.id === probe.dstServerId)
    const dstIp = probe.dstInterface
      ? (speedIfaces[probe.dstServerId]?.find((i) => i.name === probe.dstInterface)?.addresses[0] ?? (dst?.host ?? "0.0.0.0"))
      : (dst?.host ?? "0.0.0.0")
    return `[${src?.name ?? "src"}] /tool bandwidth-test address=${dstIp} user=${dst?.username ?? "admin"} protocol=${probe.protocol} direction=${probe.direction} duration=${Math.max(3, Number.parseInt(probe.durationSec, 10) || 10)}s`
  }, [allServers, speedIfaces])

  const runSpeedTest = async (probe: SpeedProbeRow) => {
    if (!probe.srcServerId || !probe.dstServerId || probe.srcServerId === probe.dstServerId) return
    setSpeedError(null)
    setSpeedBusy(true)
    const durationSec = Math.max(3, Number.parseInt(probe.durationSec, 10) || 10)
    const runId = `speed-${Date.now()}`
    setSpeedRuns((prev) => [{
      id: runId,
      startedAt: Date.now(),
      srcServerId: probe.srcServerId,
      dstServerId: probe.dstServerId,
      srcInterface: probe.srcInterface || undefined,
      dstInterface: probe.dstInterface || undefined,
      protocol: probe.protocol,
      direction: probe.direction,
      durationSec,
      txAvgMbps: 0,
      rxAvgMbps: 0,
      status: "running",
      command: buildSpeedCommand(probe),
      lines: ["status: running..."],
    }, ...prev].slice(0, 20))
    try {
      if (isLive) {
        const payload = {
          srcServerId: Number.parseInt(probe.srcServerId, 10),
          dstServerId: Number.parseInt(probe.dstServerId, 10),
          srcInterface: probe.srcInterface || undefined,
          dstInterface: probe.dstInterface || undefined,
          protocol: probe.protocol,
          direction: probe.direction,
          durationSec,
        }
        const res = await apiFetch<{ result: { txAvgMbps: number; rxAvgMbps: number; raw?: Array<Record<string, string>> } }>("/api/uptime/speed-test", {
          method: "POST",
          body: JSON.stringify(payload),
        })
        const lines = (res.result.raw ?? []).flatMap((row) =>
          Object.entries(row).map(([k, v]) => `${k}: ${String(v)}`),
        )
        setSpeedRuns((prev) => prev.map((r) => r.id === runId ? ({
          ...r,
          txAvgMbps: Math.round(res.result.txAvgMbps),
          rxAvgMbps: Math.round(res.result.rxAvgMbps),
          status: "done",
          lines: lines.length ? lines : [`tx-total-average: ${Math.round(res.result.txAvgMbps)}Mbps`, `rx-total-average: ${Math.round(res.result.rxAvgMbps)}Mbps`],
        }) : r))
      } else {
        const tx = Math.max(10, Math.round(250 + Math.random() * 500))
        const rx = Math.max(10, Math.round(tx * (0.85 + Math.random() * 0.2)))
        setSpeedRuns((prev) => prev.map((r) => r.id === runId ? ({
          ...r,
          txAvgMbps: tx,
          rxAvgMbps: rx,
          status: "done",
          lines: [
            "status: done testing",
            `tx-total-average: ${tx}Mbps`,
            `rx-total-average: ${rx}Mbps`,
          ],
        }) : r))
      }
    } catch (e) {
      const message = e instanceof Error ? e.message : "Не удалось выполнить speed test"
      setSpeedRuns((prev) => prev.map((r) => r.id === runId ? ({
        ...r,
        status: "error",
        lines: [`status: error`, message],
      }) : r))
      setSpeedError(message)
    } finally {
      setSpeedBusy(false)
    }
  }

  const updateSpeedProbe = (id: string, patch: Partial<SpeedProbeRow>) => {
    setSpeedProbes((prev) => prev.map((p) => {
      if (p.id !== id) return p
      const next = { ...p, ...patch }
      if (patch.srcServerId && patch.srcServerId === next.dstServerId) {
        next.dstServerId = selectableSources.find((s) => s.id !== patch.srcServerId)?.id ?? patch.srcServerId
      }
      return next
    }))
  }

  const openAddSpeedSheet = () => {
    const src = selectableSources[0]?.id ?? ""
    const dst = selectableSources.find((s) => s.id !== src)?.id ?? src
    setSpeedDraft({
      id: "",
      srcServerId: src,
      dstServerId: dst,
      srcInterface: "",
      dstInterface: "",
      protocol: "tcp",
      direction: "both",
      durationSec: "10",
      enabled: true,
    })
    setSpeedSheetOpen(true)
    void loadSpeedInterfaces(src)
    void loadSpeedInterfaces(dst)
  }

  const addSpeedProbe = () => {
    if (!speedDraft.srcServerId || !speedDraft.dstServerId) return
    setSpeedProbes((prev) => [{
      ...speedDraft,
      id: `sp-${Date.now()}`,
    }, ...prev])
    setSpeedSheetOpen(false)
  }

  const deleteSpeedProbe = (id: string) => {
    setSpeedProbes((prev) => prev.filter((p) => p.id !== id))
  }

  const toggleSpeedCollapse = (serverId: string) => {
    setSpeedCollapsed((prev) => {
      const next = new Set(prev)
      if (next.has(serverId)) next.delete(serverId); else next.add(serverId)
      return next
    })
  }

  useEffect(() => {
    if (!isLive) return
    const t = setTimeout(() => {
      void apiFetch("/api/uptime/probes", {
        method: "PUT",
        body: JSON.stringify({
          probes: probes.map((p) => ({
            id: p.id,
            srcServerId: p.srcServerId,
            srcInterface: p.srcInterface || "",
            name: p.name,
            target: p.target,
            filter: p.filter,
            enabled: p.enabled,
          })),
        }),
      }).catch(() => {})
    }, 250)
    return () => clearTimeout(t)
  }, [isLive, probes, apiFetch])

  // ── render ──
  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Обзор" }, { label: "Мониторинг" }]}
        actions={
          <>
            {/* Live / Pause toggle */}
            <Button
              variant="outline" size="sm"
              onClick={() => setIsPaused(v => !v)}
              className={cn(isPaused && "border-amber-500/50 text-amber-600 dark:text-amber-400")}
            >
              {isPaused
                ? <><PlayIcon  className="size-4" />Возобновить</>
                : <><PauseIcon className="size-4" />Пауза</>}
            </Button>

            <Button variant="outline" size="sm" onClick={() => {
              if (isLive) {
                setSettingsBusy(true)
                void apiFetch("/api/uptime/collect-now", { method: "POST" })
                  .then(() => Promise.all([loadLiveOverview(), loadLiveSettings()]))
                  .catch((e) => setOpError(e instanceof Error ? e.message : "Не удалось выполнить сбор"))
                  .finally(() => setSettingsBusy(false))
                return
              }
              setProbes(INIT_PROBES)
              setResources(INIT_RESOURCES)
            }}>
              <RefreshCwIcon className={cn("size-4", settingsBusy && "animate-spin")} />{isLive ? "Собрать сейчас" : "Сбросить"}
            </Button>

            {tab === "probes" && (
              <Button size="sm" onClick={openAddSheet}>
                <PlusIcon className="size-4" />Новая проба
              </Button>
            )}
            {tab === "speed" && (
              <Button size="sm" onClick={openAddSpeedSheet}>
                <PlusIcon className="size-4" />Новая speed-проба
              </Button>
            )}
          </>
        }
      />

      {/* ── Live indicator ── */}
      {!isPaused && (
        <div className="flex items-center gap-2 px-6 py-1.5 border-b bg-emerald-500/5 text-[11px] text-emerald-600 dark:text-emerald-400 shrink-0">
          <span className="size-1.5 rounded-full bg-emerald-500 animate-pulse" />
          {isLive
            ? `Live (backend) · сбор каждые ${uptimeSettings?.intervalSec ?? 15}с`
            : "Live · проба обновляется каждые 3с · ресурсы каждые 5с"}
        </div>
      )}
      {isPaused && (
        <div className="flex items-center gap-2 px-6 py-1.5 border-b bg-amber-500/5 text-[11px] text-amber-600 dark:text-amber-400 shrink-0">
          <PauseIcon className="size-3" />
          Обновление приостановлено
        </div>
      )}

      {/* ── tab switcher ── */}
      <div className="border-b bg-muted/10 px-6 flex items-center gap-1 shrink-0">
        {(["probes", "resources", "speed"] as const).map(t => (
          <button
            key={t}
            onClick={() => setTab(t)}
            className={cn(
              "px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px",
              tab === t
                ? "border-foreground text-foreground"
                : "border-transparent text-muted-foreground hover:text-foreground",
            )}
          >
            {t === "probes" ? (
              <span className="flex items-center gap-2">
                <span>Пинг-пробы</span>
                {stats.down > 0 && (
                  <span className="inline-flex items-center justify-center min-w-[18px] h-4.5 px-1 rounded-full
                    text-[10px] font-bold bg-red-500 text-white leading-none">
                    {stats.down}
                  </span>
                )}
                <span className="text-[10px] font-mono opacity-50">{probes.length}</span>
              </span>
            ) : t === "resources" ? (
              <span className="flex items-center gap-2">
                <CpuIcon className="size-3.5" />
                <span>Ресурсы</span>
                {alertCount > 0 && (
                  <span className="inline-flex items-center justify-center min-w-[18px] h-4.5 px-1 rounded-full
                    text-[10px] font-bold bg-red-500 text-white leading-none">
                    {alertCount}
                  </span>
                )}
                <span className="text-[10px] font-mono opacity-50">{resources.length}</span>
              </span>
            ) : (
              <span className="flex items-center gap-2">
                <ArrowUpDownIcon className="size-3.5" />
                <span>Скорость</span>
                <span className="text-[10px] font-mono opacity-50">{speedRuns.length}</span>
              </span>
            )}
          </button>
        ))}
      </div>

      <div className="flex-1 overflow-y-auto">
        {isLive && uptimeSettings && (
          <div className="px-6 pt-4">
            <Card>
              <CardContent className="pt-4 pb-4 px-4">
                <div className="flex flex-wrap items-end gap-3">
                  <div className="flex flex-col gap-1">
                    <span className="text-[11px] text-muted-foreground">Сбор uptime</span>
                    <div className="flex rounded-md border border-input overflow-hidden h-8">
                      <button
                        className={cn("px-3 text-xs", uptimeSettings.enabled ? "bg-emerald-600 text-white" : "text-muted-foreground hover:bg-muted")}
                        onClick={async () => {
                          setSettingsBusy(true)
                          try {
                            await apiFetch("/api/uptime/settings", { method: "PUT", body: JSON.stringify({ enabled: true }) })
                            await loadLiveSettings()
                          } finally { setSettingsBusy(false) }
                        }}
                        disabled={settingsBusy}
                      >Вкл</button>
                      <button
                        className={cn("px-3 text-xs border-l border-input", !uptimeSettings.enabled ? "bg-muted-foreground text-white" : "text-muted-foreground hover:bg-muted")}
                        onClick={async () => {
                          setSettingsBusy(true)
                          try {
                            await apiFetch("/api/uptime/settings", { method: "PUT", body: JSON.stringify({ enabled: false }) })
                            await loadLiveSettings()
                          } finally { setSettingsBusy(false) }
                        }}
                        disabled={settingsBusy}
                      >Выкл</button>
                    </div>
                  </div>

                  <div className="flex flex-col gap-1">
                    <span className="text-[11px] text-muted-foreground">Интервал (сек)</span>
                    <Input className="h-8 w-24 text-sm" value={intervalDraft} onChange={(e) => setIntervalDraft(e.target.value)} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <span className="text-[11px] text-muted-foreground">Хранение (дней)</span>
                    <Input className="h-8 w-24 text-sm" value={retentionDraft} onChange={(e) => setRetentionDraft(e.target.value)} />
                  </div>
                  <Button
                    variant="outline"
                    size="sm"
                    className="h-8"
                    disabled={settingsBusy}
                    onClick={async () => {
                      setSettingsBusy(true)
                      try {
                        await apiFetch("/api/uptime/settings", {
                          method: "PUT",
                          body: JSON.stringify({
                            intervalSec: Number.parseInt(intervalDraft, 10) || 15,
                            retentionDays: Number.parseInt(retentionDraft, 10) || 14,
                          }),
                        })
                        await loadLiveSettings()
                      } catch (e) {
                        setOpError(e instanceof Error ? e.message : "Не удалось сохранить настройки")
                      } finally {
                        setSettingsBusy(false)
                      }
                    }}
                  >Сохранить</Button>

                  <div className="ml-auto text-xs text-muted-foreground">
                    <p>Последний сбор: {uptimeSettings.lastCollectedAt ? new Date(uptimeSettings.lastCollectedAt).toLocaleString("ru-RU") : "—"}</p>
                    <p>Длительность: {uptimeSettings.lastDurationMs != null ? `${uptimeSettings.lastDurationMs} мс` : "—"}</p>
                    {uptimeSettings.lastError && <p className="text-destructive">Ошибка: {uptimeSettings.lastError}</p>}
                  </div>
                </div>
              </CardContent>
            </Card>
          </div>
        )}

        {/* ── resources tab ── */}
        {tab === "resources" && <ResourcesTab resources={resources} serversList={allServers} />}

        {/* ── speed tab ── */}
        {tab === "speed" && (
          <div className="p-6 flex flex-col gap-4">
            <Card>
              <CardContent className="pt-4 pb-4 px-4">
                <div className="flex items-center justify-between gap-3 mb-3">
                  <p className="text-sm font-medium">Speed-пробы между серверами</p>
                  <p className="text-xs text-muted-foreground">{speedProbes.length} проб</p>
                </div>
                <p className="text-xs text-muted-foreground">Создание через модалку, запуск/удаление — в строках ниже.</p>
                {speedError && (
                  <p className="mt-3 text-xs text-destructive">{speedError}</p>
                )}
              </CardContent>
            </Card>

            {speedGrouped.length === 0 && (
              <Card>
                <CardContent className="py-10 text-center text-muted-foreground text-sm">
                  Нет speed-проб. Нажми `Новая speed-проба`.
                </CardContent>
              </Card>
            )}

            {speedGrouped.map(({ server, probes: srvProbes }) => {
              const isCollapsed = speedCollapsed.has(server.id)
              return (
                <Card key={server.id} className="overflow-hidden py-0 gap-0">
                  <button
                    onClick={() => toggleSpeedCollapse(server.id)}
                    className="w-full flex items-center gap-2.5 px-4 py-3 hover:bg-muted/30 transition-colors text-left border-b"
                  >
                    {isCollapsed
                      ? <ChevronRightIcon className="size-4 text-muted-foreground shrink-0" />
                      : <ChevronDownIcon className="size-4 text-muted-foreground shrink-0" />}
                    <StatusDot status={server.status} pulse={server.status === "online"} />
                    <Flag code={server.country} size={16} />
                    <span className="font-mono text-sm font-semibold">{server.name}</span>
                    <TypeChip type={server.type} />
                    <span className="text-xs text-muted-foreground">{srvProbes.length} speed-проб</span>
                  </button>

                  {!isCollapsed && (
                    <>
                      <div className="grid items-center gap-3 px-4 py-1.5 bg-muted/30 border-b text-[10px] font-semibold uppercase tracking-widest text-muted-foreground"
                        style={{ gridTemplateColumns: "36px 1fr 120px 120px 70px 70px 60px 1fr 130px" }}>
                        <span />
                        <span>Назначение</span>
                        <span>If src</span>
                        <span>If dst</span>
                        <span>Proto</span>
                        <span>Dir</span>
                        <span>Sec</span>
                        <span>Команда</span>
                        <span>Действия</span>
                      </div>

                      <div className="divide-y divide-border/60">
                        {srvProbes.map((probe) => {
                          const dst = allServers.find((s) => s.id === probe.dstServerId)
                          const run = speedRuns.find((r) =>
                            r.srcServerId === probe.srcServerId &&
                            r.dstServerId === probe.dstServerId &&
                            (r.srcInterface ?? "") === probe.srcInterface &&
                            (r.dstInterface ?? "") === probe.dstInterface,
                          )
                          return (
                            <div key={probe.id}
                              className={cn("grid items-center gap-3 px-4 py-2.5 hover:bg-muted/20 transition-colors", !probe.enabled && "opacity-40")}
                              style={{ gridTemplateColumns: "36px 1fr 120px 120px 70px 70px 60px 1fr 130px" }}>
                              <Toggle checked={probe.enabled} onChange={(v) => updateSpeedProbe(probe.id, { enabled: v })} />
                              <div className="min-w-0">
                                <p className="text-sm font-medium truncate">{dst?.name ?? probe.dstServerId}</p>
                                {run && (
                                  <p className={cn(
                                    "text-[11px] font-mono",
                                    run.status === "error" ? "text-red-500" : run.status === "running" ? "text-amber-500" : "text-emerald-600 dark:text-emerald-400",
                                  )}>
                                    {run.status === "running" ? "running..." : `TX ${run.txAvgMbps} / RX ${run.rxAvgMbps} Mbps`}
                                  </p>
                                )}
                              </div>
                              <span className="font-mono text-xs text-muted-foreground truncate">{probe.srcInterface || "auto"}</span>
                              <span className="font-mono text-xs text-muted-foreground truncate">{probe.dstInterface || "auto"}</span>
                              <span className="font-mono text-xs">{probe.protocol.toUpperCase()}</span>
                              <span className="font-mono text-xs">{probe.direction}</span>
                              <span className="font-mono text-xs">{probe.durationSec}s</span>
                              <span className="font-mono text-[10px] text-muted-foreground truncate">{buildSpeedCommand(probe)}</span>
                              <div className="flex items-center gap-1">
                                <Button size="sm" className="h-8"
                                  disabled={speedBusy || !probe.enabled || !probe.srcServerId || !probe.dstServerId || probe.srcServerId === probe.dstServerId}
                                  onClick={() => void runSpeedTest(probe)}>
                                  <RefreshCwIcon className={cn("size-3.5", speedBusy && "animate-spin")} />
                                </Button>
                                <Button size="sm" variant="ghost" className="h-8 text-destructive" onClick={() => deleteSpeedProbe(probe.id)}>
                                  <TrashIcon className="size-3.5" />
                                </Button>
                              </div>
                            </div>
                          )
                        })}
                      </div>
                    </>
                  )}
                </Card>
              )
            })}

            <Card className="overflow-hidden">
              <div className="overflow-x-auto">
                <table className="w-full text-sm">
                  <thead>
                    <tr className="border-b bg-muted/30 text-xs text-muted-foreground">
                      <th className="px-4 py-2 text-left">Время</th>
                      <th className="px-4 py-2 text-left">Путь</th>
                      <th className="px-4 py-2 text-left">Параметры</th>
                      <th className="px-4 py-2 text-left">TX avg</th>
                      <th className="px-4 py-2 text-left">RX avg</th>
                    </tr>
                  </thead>
                  <tbody className="divide-y divide-border">
                    {speedRuns.length === 0 && (
                      <tr>
                        <td colSpan={5} className="px-4 py-10 text-center text-muted-foreground">Запусти первый BTTest</td>
                      </tr>
                    )}
                    {speedRuns.map((run) => {
                      const src = allServers.find((s) => s.id === run.srcServerId)
                      const dst = allServers.find((s) => s.id === run.dstServerId)
                      return (
                        <tr key={run.id} className="hover:bg-muted/20">
                          <td className="px-4 py-2 text-xs text-muted-foreground">{new Date(run.startedAt).toLocaleString("ru-RU")}</td>
                          <td className="px-4 py-2 font-mono">{src?.name ?? run.srcServerId} → {dst?.name ?? run.dstServerId}</td>
                          <td className="px-4 py-2 text-xs text-muted-foreground">{run.protocol.toUpperCase()} · {run.direction} · {run.durationSec}s</td>
                          <td className="px-4 py-2 font-mono text-emerald-600 dark:text-emerald-400">{run.txAvgMbps} Mbps</td>
                          <td className="px-4 py-2 font-mono text-sky-600 dark:text-sky-400">{run.rxAvgMbps} Mbps</td>
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
              </div>
            </Card>
          </div>
        )}

        {/* ── probes tab ── */}
        {tab === "probes" && <>

        {opError && (
          <div className="px-6 pt-4">
            <div className="text-xs text-destructive bg-destructive/10 border border-destructive/20 rounded-md px-3 py-2">
              {opError}
            </div>
          </div>
        )}

        {/* ── summary + server filter ── */}
        <div className="border-b bg-muted/20 px-6 py-3 flex items-center gap-3 flex-wrap">
          <StatChip label="Всего"          value={stats.total} />
          <StatChip label="OK"             value={stats.up}   color="emerald" active={statusFilter === "up"}   onClick={() => cycleStatusFilter("up")} />
          <StatChip label="Предупреждение" value={stats.warn} color="amber"   active={statusFilter === "warn"} onClick={() => cycleStatusFilter("warn")} />
          <StatChip label="Недоступен"     value={stats.down} color="red"     active={statusFilter === "down"} onClick={() => cycleStatusFilter("down")} />

          <div className="w-px h-4 bg-border mx-1 shrink-0" />

          {/* server chips */}
          {probedServers.map(s => {
            const active    = serverFilter.includes(s.id)
            const sProbes   = probes.filter(p => p.srcServerId === s.id)
            const hasIssues = sProbes.some(p => p.status !== "up")
            return (
              <button key={s.id} onClick={() => toggleServerFilter(s.id)}
                className={cn(
                  "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-all",
                  active
                    ? "bg-foreground text-background border-foreground"
                    : "border-border text-muted-foreground hover:text-foreground hover:border-foreground/40",
                )}>
                <Flag code={s.country} size={12} />
                <span className="font-mono">{s.name}</span>
                <TypeChip type={s.type} />
                {hasIssues && !active && (
                  <span className="size-1.5 rounded-full bg-amber-500 shrink-0" />
                )}
              </button>
            )
          })}
        </div>

        {/* ── toolbar ── */}
        <div className="px-6 py-3 flex items-center gap-3 border-b flex-wrap">
          <div className="relative min-w-[200px] max-w-xs flex-1">
            <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground pointer-events-none" />
            <Input className="pl-8 h-8 text-sm" placeholder="Имя, IP-адрес, фильтр…"
              value={search} onChange={e => setSearch(e.target.value)} />
            {search && (
              <button onClick={() => setSearch("")}
                className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground">
                <XIcon className="size-3.5" />
              </button>
            )}
          </div>

          {(search || statusFilter !== "all" || serverFilter.length > 0) && (
            <Button variant="ghost" size="sm" className="text-muted-foreground h-8 px-2"
              onClick={() => { setSearch(""); setStatusFilter("all"); setServerFilter([]) }}>
              <XIcon className="size-3.5" />Сбросить
            </Button>
          )}

          {/* Collapse / Expand all */}
          <Button variant="outline" size="sm" className="h-8 gap-1.5 ml-auto"
            onClick={allCollapsed ? expandAll : collapseAll}>
            {allCollapsed
              ? <><ChevronDownIcon className="size-3.5" />Развернуть все</>
              : <><ChevronUpIcon   className="size-3.5" />Свернуть все</>}
          </Button>

          <p className="text-xs text-muted-foreground shrink-0">
            {filtered.length} из {probes.length} проб
          </p>
        </div>

        {/* ── probe groups ── */}
        <div className="p-6 flex flex-col gap-4">

          {grouped.length === 0 && (
            <div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-2">
              <SearchIcon className="size-8 opacity-20" />
              <p className="text-sm">Пробы не найдены</p>
            </div>
          )}

          {grouped.map(({ server: srv, groups: probeGroups }) => {
            const isCollapsed = collapsed.has(srv.id)
            const allServerProbes = probeGroups.flatMap((g) => g.probes)
            const issues      = allServerProbes.filter(p => p.status !== "up").length
            const downCount   = allServerProbes.filter(p => p.status === "down").length
            const warnCount   = allServerProbes.filter(p => p.status === "warn").length

            return (
              <Card key={srv.id} className="overflow-hidden py-0 gap-0">

                {/* server header */}
                <button onClick={() => toggleCollapse(srv.id)}
                  className="w-full flex items-center gap-2.5 px-4 py-3 hover:bg-muted/30 transition-colors text-left border-b">
                  {isCollapsed
                    ? <ChevronRightIcon className="size-4 text-muted-foreground shrink-0" />
                    : <ChevronDownIcon  className="size-4 text-muted-foreground shrink-0" />}
                  <StatusDot status={srv.status} pulse={srv.status === "online"} />
                  <Flag code={srv.country} size={16} />
                  <span className="font-mono text-sm font-semibold">{srv.name}</span>
                  <TypeChip type={srv.type} />
                  <span className="text-xs text-muted-foreground">{srv.site}</span>
                  <div className="ml-auto flex items-center gap-3 text-xs">
                    {downCount > 0 && (
                      <span className="text-red-600 dark:text-red-400 font-medium">{downCount} down</span>
                    )}
                    {warnCount > 0 && (
                      <span className="text-amber-600 dark:text-amber-400 font-medium">{warnCount} warn</span>
                    )}
                    {issues === 0 && (
                      <span className="text-emerald-600 dark:text-emerald-400 font-medium">all ok</span>
                    )}
                    <span className="text-muted-foreground">{allServerProbes.length} проб</span>
                  </div>
                </button>

                {!isCollapsed && (
                  <>
                    <div className="divide-y divide-border/60">
                      {probeGroups.map((group) => (
                        <div key={`${group.name}|${group.target}`}>
                          <div className="px-4 py-2 border-b bg-muted/20 flex items-center gap-2 text-xs">
                            <ArrowRightIcon className="size-3.5 text-muted-foreground/60" />
                            <span className="font-medium truncate">{group.name}</span>
                            <span className="font-mono text-muted-foreground">{group.target}</span>
                            <span className="ml-auto text-muted-foreground">{group.probes.length} интерф.</span>
                          </div>

                          <div className="grid items-center gap-3 px-4 py-1.5 bg-muted/30 border-b text-[10px] font-semibold uppercase tracking-widest text-muted-foreground"
                            style={{ gridTemplateColumns: "36px 16px 130px 120px 140px 70px 44px 96px 72px" }}>
                            <span />
                            <span />
                            <span>Интерфейс</span>
                            <span>Проба</span>
                            <span>Фильтр</span>
                            <span>RTT</span>
                            <span>Потери</span>
                            <span>График</span>
                            <span>Действия</span>
                          </div>

                          {group.probes.map((p) => (
                            <div key={p.id}
                              className={cn(
                                "grid items-center gap-3 px-4 py-2.5 hover:bg-muted/20 transition-colors",
                                !p.enabled && "opacity-40",
                              )}
                              style={{ gridTemplateColumns: "36px 16px 130px 120px 140px 70px 44px 96px 72px" }}>
                              <Toggle checked={p.enabled} onChange={v => toggleProbe(p.id, v)} />
                              <StatusDot
                                status={p.status === "up" ? "online" : p.status === "warn" ? "degraded" : "offline"}
                                pulse={p.status === "up" && p.enabled}
                              />
                              <span className="font-mono text-xs text-muted-foreground truncate">{p.srcInterface || "auto"}</span>
                              <span className="text-sm font-medium truncate">{p.name}</span>
                              {p.filter === "—"
                                ? <span className="text-muted-foreground/40 text-xs">—</span>
                                : <span className="text-[11px] font-medium text-sky-600 dark:text-sky-400 truncate">{p.filter}</span>
                              }
                              <span className={cn("font-mono text-sm font-semibold tabular-nums", rttColor(p.rtt, p.loss))}>
                                {p.rtt === null ? "—" : `${p.rtt}мс`}
                              </span>
                              <span className={cn("font-mono text-xs tabular-nums",
                                p.loss === 0    ? "text-muted-foreground"              :
                                p.loss >= 100   ? "text-red-600 dark:text-red-400"    :
                                p.loss > 5      ? "text-red-600 dark:text-red-400"    :
                                                  "text-amber-600 dark:text-amber-400",
                              )}>
                                {p.loss}%
                              </span>
                              <Sparkline data={p.series} width={96} height={24} color={probeSparkColor(p.status)} filled />
                              <div className="flex justify-end gap-0.5">
                                <Button size="sm" variant="ghost"
                                  className="size-7 p-0 text-muted-foreground hover:text-foreground"
                                  onClick={() => openEditProbe(p)} title="Редактировать пробу">
                                  <PencilIcon className="size-3.5" />
                                </Button>
                                <Button size="sm" variant="ghost"
                                  className="size-7 p-0 text-muted-foreground hover:text-destructive"
                                  onClick={() => deleteProbe(p.id)} title="Удалить пробу">
                                  <TrashIcon className="size-3.5" />
                                </Button>
                              </div>
                            </div>
                          ))}

                          <button
                            onClick={() => {
                              setEditingProbeId(null)
                              setNewSrcId(srv.id)
                              setNewSrcInterface("")
                              setNewName(group.name); setNewTarget(group.target); setNewFilter("—")
                              setSheetOpen(true)
                            }}
                            className="w-full flex items-center gap-2 px-4 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/20 transition-colors border-t">
                            <PlusIcon className="size-3.5" />
                            Добавить интерфейс для {group.name} ({group.target})
                          </button>
                        </div>
                      ))}
                    </div>
                  </>
                )}
              </Card>
            )
          })}
        </div>
        </>}
      </div>

      {/* ── add speed-probe sheet ── */}
      <Sheet open={speedSheetOpen} onOpenChange={(v) => setSpeedSheetOpen(v)}>
        <SheetContent side="right" className="sm:max-w-[420px] flex flex-col p-0" showCloseButton={false}>
          <SheetHeader className="px-5 pt-5 pb-4 border-b shrink-0">
            <div className="flex items-center justify-between gap-2">
              <div>
                <SheetTitle className="text-base">Новая speed-проба</SheetTitle>
                <SheetDescription className="text-xs mt-0.5">
                  BTTest между выбранными серверами
                </SheetDescription>
              </div>
              <Button variant="ghost" size="icon-sm" onClick={() => setSpeedSheetOpen(false)}>
                <XIcon className="size-4" />
              </Button>
            </div>
          </SheetHeader>

          <div className="flex-1 overflow-y-auto px-5 py-5 flex flex-col gap-5">
            <Field label="Источник">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={speedDraft.srcServerId}
                onChange={(e) => {
                  const nextSrc = e.target.value
                  const nextDst = speedDraft.dstServerId === nextSrc
                    ? (selectableSources.find((s) => s.id !== nextSrc)?.id ?? nextSrc)
                    : speedDraft.dstServerId
                  setSpeedDraft((prev) => ({ ...prev, srcServerId: nextSrc, dstServerId: nextDst, srcInterface: "" }))
                  void loadSpeedInterfaces(nextSrc)
                }}>
                {selectableSources.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
              </select>
            </Field>

            <Field label="Назначение">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={speedDraft.dstServerId}
                onChange={(e) => {
                  const nextDst = e.target.value
                  setSpeedDraft((prev) => ({ ...prev, dstServerId: nextDst, dstInterface: "" }))
                  void loadSpeedInterfaces(nextDst)
                }}>
                {selectableSources.filter((s) => s.id !== speedDraft.srcServerId).map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
              </select>
            </Field>

            <Field label="Интерфейс источника">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={speedDraft.srcInterface}
                onFocus={() => { void loadSpeedInterfaces(speedDraft.srcServerId) }}
                onChange={(e) => setSpeedDraft((prev) => ({ ...prev, srcInterface: e.target.value }))}>
                <option value="">auto</option>
                {(speedIfaces[speedDraft.srcServerId] ?? []).map((i) => <option key={i.name} value={i.name}>{i.name}</option>)}
              </select>
            </Field>

            <Field label="Интерфейс назначения">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={speedDraft.dstInterface}
                onFocus={() => { void loadSpeedInterfaces(speedDraft.dstServerId) }}
                onChange={(e) => setSpeedDraft((prev) => ({ ...prev, dstInterface: e.target.value }))}>
                <option value="">auto</option>
                {(speedIfaces[speedDraft.dstServerId] ?? []).map((i) => <option key={i.name} value={i.name}>{i.name}</option>)}
              </select>
            </Field>

            <div className="grid grid-cols-3 gap-3">
              <Field label="Протокол">
                <select className="w-full text-sm bg-background border border-input rounded-md px-3 h-9"
                  value={speedDraft.protocol}
                  onChange={(e) => setSpeedDraft((prev) => ({ ...prev, protocol: e.target.value as "tcp" | "udp" }))}>
                  <option value="tcp">TCP</option>
                  <option value="udp">UDP</option>
                </select>
              </Field>
              <Field label="Direction">
                <select className="w-full text-sm bg-background border border-input rounded-md px-3 h-9"
                  value={speedDraft.direction}
                  onChange={(e) => setSpeedDraft((prev) => ({ ...prev, direction: e.target.value as "transmit" | "receive" | "both" }))}>
                  <option value="both">both</option>
                  <option value="transmit">tx</option>
                  <option value="receive">rx</option>
                </select>
              </Field>
              <Field label="Сек">
                <Input className="h-9" value={speedDraft.durationSec} onChange={(e) => setSpeedDraft((prev) => ({ ...prev, durationSec: e.target.value }))} />
              </Field>
            </div>
          </div>

          <SheetFooter className="px-5 py-4 border-t shrink-0 gap-2">
            <Button variant="outline" className="flex-1" onClick={() => setSpeedSheetOpen(false)}>Отмена</Button>
            <Button className="flex-1" onClick={addSpeedProbe} disabled={!speedDraft.srcServerId || !speedDraft.dstServerId || speedDraft.srcServerId === speedDraft.dstServerId}>
              <PlusIcon className="size-4" />Добавить
            </Button>
          </SheetFooter>
        </SheetContent>
      </Sheet>

      {/* ── add probe sheet ── */}
      <Sheet open={sheetOpen} onOpenChange={v => {
        if (!v) {
          setSheetOpen(false)
          setEditingProbeId(null)
        }
      }}>
        <SheetContent side="right" className="sm:max-w-[420px] flex flex-col p-0" showCloseButton={false}>

          <SheetHeader className="px-5 pt-5 pb-4 border-b shrink-0">
            <div className="flex items-center justify-between gap-2">
              <div>
                <SheetTitle className="text-base">{editingProbeId ? "Редактирование пробы" : "Новая проба"}</SheetTitle>
                <SheetDescription className="text-xs mt-0.5">
                  Ping от выбранного сервера к целевому хосту
                </SheetDescription>
              </div>
              <Button variant="ghost" size="icon-sm" onClick={() => setSheetOpen(false)}>
                <XIcon className="size-4" />
              </Button>
            </div>
          </SheetHeader>

          <div className="flex-1 overflow-y-auto px-5 py-5 flex flex-col gap-5">

            {/* from → to visual */}
            <div className="flex items-center gap-2 rounded-lg bg-muted/40 border px-4 py-3">
              <div className="flex-1 min-w-0">
                <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-0.5">Источник</p>
                {newSrcId ? (() => {
                  const s = allServers.find(x => x.id === newSrcId)
                  return s ? (
                    <div className="flex items-center gap-1.5">
                      <Flag code={s.country} size={14} />
                      <span className="text-sm font-mono font-medium truncate">{s.name}</span>
                      <TypeChip type={s.type} />
                    </div>
                  ) : null
                })() : <span className="text-xs text-muted-foreground">не выбран</span>}
              </div>
              <ArrowRightIcon className="size-4 text-muted-foreground/40 shrink-0" />
              <div className="flex-1 min-w-0 text-right">
                <p className="text-[10px] text-muted-foreground uppercase tracking-widest mb-0.5">Цель</p>
                <p className="text-sm font-mono truncate">
                  {newTarget || <span className="text-muted-foreground/40">0.0.0.0</span>}
                </p>
              </div>
            </div>

            <Field label="Источник (кто пингует)">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={newSrcId}
                onChange={e => setNewSrcId(e.target.value)}>
                <optgroup label="Jump Host">
                  {allServers.filter(s => s.type === "jump-host" && s.enabled).map(s => (
                    <option key={s.id} value={s.id}>{s.name} · {s.site}</option>
                  ))}
                </optgroup>
                <optgroup label="Exit Node">
                  {allServers.filter(s => s.type === "exit-node" && s.enabled).map(s => (
                    <option key={s.id} value={s.id}>{s.name} · {s.site}</option>
                  ))}
                </optgroup>
                <optgroup label="Home Router">
                  {allServers.filter(s => s.type === "home-router" && s.enabled).map(s => (
                    <option key={s.id} value={s.id}>{s.name} · {s.site}</option>
                  ))}
                </optgroup>
              </select>
            </Field>

            <Field label="Интерфейс источника" hint="(необязательно)">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={newSrcInterface}
                onChange={e => setNewSrcInterface(e.target.value)}
                disabled={srcInterfacesBusy || (!srcInterfaces.length && isLive)}
              >
                <option value="">— авто (по маршруту) —</option>
                {srcInterfaces.map((iface) => (
                  <option key={iface.name} value={iface.name}>
                    {iface.name}{iface.disabled ? " · disabled" : iface.running ? " · running" : " · !running"}
                  </option>
                ))}
              </select>
            </Field>

            <Field label="Имя пробы">
              <Input className="h-9 text-sm" placeholder="youtube.com"
                value={newName} onChange={e => setNewName(e.target.value)} />
            </Field>

            <Field label="Целевой IP / хост">
              <Input className="h-9 text-sm font-mono" placeholder="142.250.74.110"
                value={newTarget} onChange={e => setNewTarget(e.target.value)} />
            </Field>

            <Field label="Связанный фильтр" hint="(необязательно)">
              <select
                className="w-full text-sm bg-background border border-input rounded-md px-3 h-9 focus:outline-none focus:ring-1 focus:ring-ring"
                value={newFilter}
                onChange={e => setNewFilter(e.target.value)}>
                <option value="—">— нет —</option>
                {filters.map(f => (
                  <option key={f.id} value={f.name}>{f.name}</option>
                ))}
              </select>
            </Field>
          </div>

          <SheetFooter className="px-5 py-4 border-t shrink-0 gap-2">
            <Button variant="outline" className="flex-1" onClick={() => { setSheetOpen(false); setEditingProbeId(null) }}>
              Отмена
            </Button>
            <Button className="flex-1"
              disabled={!newName.trim() || !newTarget.trim() || !newSrcId}
              onClick={handleSaveProbe}>
              <PlusIcon className="size-4" />{editingProbeId ? "Сохранить" : "Добавить"}
            </Button>
          </SheetFooter>
        </SheetContent>
      </Sheet>
    </div>
  )
}
