"use client"

import { useState, useMemo, useEffect, useCallback } from "react"
import { PageHeader } from "@/components/page-header"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Sparkline } from "@/components/sparkline"
import { StatusDot } from "@/components/status-dot"
import { Flag } from "@/components/flag"
import {
  RefreshCwIcon, DownloadIcon, TrendingUpIcon, TrendingDownIcon,
  ArrowUpIcon, ArrowDownIcon, ActivityIcon, UsersIcon, CableIcon, ServerIcon, SearchIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { useDataSource } from "@/lib/data-source"

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

function seededRand(seed: number) {
  let s = seed
  return () => { s = (s * 9301 + 49297) % 233280; return s / 233280 }
}

function buildSeries(seed: number, base: number, range: number, n = 60): number[] {
  const rand = seededRand(seed)
  return Array.from({ length: n }, () => Math.max(0, Math.round(base + (rand() - 0.5) * range * 2)))
}

function addSeries(a: number[], b: number[]): number[] {
  return a.map((v, i) => v + (b[i] ?? 0))
}

function fmtMbps(v: number) {
  if (v >= 1000) return `${(v / 1000).toFixed(2)} Гбит/с`
  return `${v} Мбит/с`
}

function fmtGB(v: number) {
  if (v >= 1000) return `${(v / 1000).toFixed(2)} ТБ`
  return `${v.toFixed(1)} ГБ`
}

// ─── data model ───────────────────────────────────────────────────────────────

interface GreClientTraffic {
  id: string
  subUserId: string
  userId: string
  userLogin: string
  login: string
  description: string
  serverId: string
  serverName: string
  serverSite: string
  serverCountry: string
  clientIp: string
  rxNow: number
  txNow: number
  rxPeak: number
  txPeak: number
  rxTotal: number
  txTotal: number
  rxSeries: number[]
  txSeries: number[]
  status: "online" | "offline"
}

interface ServerTraffic {
  id: string
  name: string
  site: string
  country: string
  status: "online" | "offline" | "degraded"
  rxNow: number
  txNow: number
  rxPeak: number
  txPeak: number
  rxTotal: number
  txTotal: number
  sessions: number
  rxSeries: number[]
  txSeries: number[]
  greClients: GreClientTraffic[]
}

interface UserTraffic {
  id: string
  login: string
  displayName: string
  role: string
  greClients: GreClientTraffic[]
  rxNow: number
  txNow: number
  rxPeak: number
  txPeak: number
  rxTotal: number
  txTotal: number
  rxSeries: number[]
  txSeries: number[]
}

interface LiveTrafficServer {
  id: string
  name: string
  site: string
  country: string
  status: "online" | "offline" | "degraded"
  rxNow: number
  txNow: number
  rxPeak: number
  txPeak: number
  rxTotal: number
  txTotal: number
  sessions: number
  rxSeries: number[]
  txSeries: number[]
}

interface TrafficSettingsDto {
  enabled: boolean
  intervalSec: number
  retentionDays: number
  lastCollectedAt: string | null
  lastDurationMs: number | null
  lastError: string | null
  collectorRunning: boolean
}

interface LiveTrafficInterface {
  name: string
  running: boolean
  disabled: boolean
  rxNow: number
  txNow: number
}

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>
  }
}

// ─── mock GRE client data ─────────────────────────────────────────────────────

const greClients: GreClientTraffic[] = [
  // u1 / admin (Иванов)
  {
    id: "su1-srv1", subUserId: "su1", userId: "u1", userLogin: "admin",
    login: "gre-ivanov-01", description: "Иванов — основной тоннель MSK",
    serverId: "srv1", serverName: "mt-msk-core-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.0.10",
    rxNow: 84, txNow: 62, rxPeak: 180, txPeak: 130, rxTotal: 28.4, txTotal: 21.2,
    rxSeries: buildSeries(301, 84, 25), txSeries: buildSeries(401, 62, 20),
    status: "online",
  },
  {
    id: "su1-srv7", subUserId: "su1", userId: "u1", userLogin: "admin",
    login: "gre-ivanov-01", description: "Иванов — основной тоннель LAB",
    serverId: "srv7", serverName: "mt-msk-lab-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.0.10",
    rxNow: 6, txNow: 4, rxPeak: 20, txPeak: 14, rxTotal: 1.8, txTotal: 1.1,
    rxSeries: buildSeries(302, 6, 3), txSeries: buildSeries(402, 4, 2),
    status: "online",
  },
  {
    id: "su2-srv1", subUserId: "su2", userId: "u1", userLogin: "admin",
    login: "gre-ivanov-02", description: "Иванов — резервный MSK",
    serverId: "srv1", serverName: "mt-msk-core-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.0.11",
    rxNow: 42, txNow: 28, rxPeak: 96, txPeak: 64, rxTotal: 14.2, txTotal: 9.6,
    rxSeries: buildSeries(303, 42, 15), txSeries: buildSeries(403, 28, 12),
    status: "online",
  },
  {
    id: "su3-srv7", subUserId: "su3", userId: "u1", userLogin: "admin",
    login: "gre-ivanov-03", description: "Иванов — лабораторный тоннель",
    serverId: "srv7", serverName: "mt-msk-lab-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.0.12",
    rxNow: 4, txNow: 3, rxPeak: 12, txPeak: 8, rxTotal: 1.2, txTotal: 0.8,
    rxSeries: buildSeries(304, 4, 2), txSeries: buildSeries(404, 3, 1),
    status: "online",
  },
  // u2 / operator1 (Петров)
  {
    id: "su4-srv1", subUserId: "su4", userId: "u2", userLogin: "operator1",
    login: "gre-petrov-01", description: "Петров — основной MSK",
    serverId: "srv1", serverName: "mt-msk-core-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.1.10",
    rxNow: 128, txNow: 96, rxPeak: 248, txPeak: 184, rxTotal: 42.8, txTotal: 32.4,
    rxSeries: buildSeries(305, 128, 35), txSeries: buildSeries(405, 96, 28),
    status: "online",
  },
  {
    id: "su4-srv7", subUserId: "su4", userId: "u2", userLogin: "operator1",
    login: "gre-petrov-01", description: "Петров — основной LAB",
    serverId: "srv7", serverName: "mt-msk-lab-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.1.10",
    rxNow: 2, txNow: 1, rxPeak: 8, txPeak: 6, rxTotal: 0.6, txTotal: 0.4,
    rxSeries: buildSeries(306, 2, 1), txSeries: buildSeries(406, 1, 1),
    status: "online",
  },
  // u4 / operator2 (Козлов, неактивный)
  {
    id: "su5-srv1", subUserId: "su5", userId: "u4", userLogin: "operator2",
    login: "gre-kozlov-01", description: "Козлов — MSK",
    serverId: "srv1", serverName: "mt-msk-core-01", serverSite: "MSK", serverCountry: "RU",
    clientIp: "10.200.2.10",
    rxNow: 0, txNow: 0, rxPeak: 44, txPeak: 32, rxTotal: 0, txTotal: 0,
    rxSeries: Array(60).fill(0), txSeries: Array(60).fill(0),
    status: "offline",
  },
]

// ─── mock server data (with GRE clients attached) ─────────────────────────────

const serverTraffic: ServerTraffic[] = [
  {
    id: "srv1", name: "mt-msk-core-01", site: "MSK", country: "RU", status: "online",
    rxNow: 342, txNow: 287, rxPeak: 614, txPeak: 521, rxTotal: 124.8, txTotal: 98.2, sessions: 4,
    rxSeries: buildSeries(101, 342, 80), txSeries: buildSeries(201, 287, 70),
    greClients: greClients.filter(c => c.serverId === "srv1"),
  },
  {
    id: "srv2", name: "mt-spb-edge-01", site: "SPB", country: "RU", status: "online",
    rxNow: 218, txNow: 164, rxPeak: 412, txPeak: 310, rxTotal: 78.4, txTotal: 61.1, sessions: 6,
    rxSeries: buildSeries(102, 218, 60), txSeries: buildSeries(202, 164, 50),
    greClients: [],
  },
  {
    id: "srv3", name: "mt-fra-edge-01", site: "FRA", country: "DE", status: "online",
    rxNow: 184, txNow: 141, rxPeak: 388, txPeak: 282, rxTotal: 66.2, txTotal: 51.4, sessions: 3,
    rxSeries: buildSeries(103, 184, 55), txSeries: buildSeries(203, 141, 45),
    greClients: [],
  },
  {
    id: "srv4", name: "mt-ams-edge-01", site: "AMS", country: "NL", status: "degraded",
    rxNow: 88, txNow: 61, rxPeak: 244, txPeak: 188, rxTotal: 32.1, txTotal: 24.8, sessions: 2,
    rxSeries: buildSeries(104, 88, 40), txSeries: buildSeries(204, 61, 30),
    greClients: [],
  },
  {
    id: "srv5", name: "mt-sgp-edge-01", site: "SGP", country: "SG", status: "offline",
    rxNow: 0, txNow: 0, rxPeak: 0, txPeak: 0, rxTotal: 0, txTotal: 0, sessions: 0,
    rxSeries: Array(60).fill(0), txSeries: Array(60).fill(0),
    greClients: [],
  },
  {
    id: "srv6", name: "mt-ams-test-01", site: "AMS", country: "NL", status: "online",
    rxNow: 42, txNow: 28, rxPeak: 118, txPeak: 84, rxTotal: 14.2, txTotal: 9.8, sessions: 1,
    rxSeries: buildSeries(105, 42, 20), txSeries: buildSeries(205, 28, 14),
    greClients: [],
  },
  {
    id: "srv7", name: "mt-msk-lab-01", site: "MSK", country: "RU", status: "online",
    rxNow: 12, txNow: 8, rxPeak: 44, txPeak: 31, rxTotal: 4.2, txTotal: 2.9, sessions: 2,
    rxSeries: buildSeries(106, 12, 6), txSeries: buildSeries(206, 8, 4),
    greClients: greClients.filter(c => c.serverId === "srv7"),
  },
]

// ─── user traffic (aggregated from GRE clients) ───────────────────────────────

function buildUserTraffic(
  id: string, login: string, displayName: string, role: string,
  clients: GreClientTraffic[],
): UserTraffic {
  const rxNow   = clients.reduce((a, c) => a + c.rxNow,   0)
  const txNow   = clients.reduce((a, c) => a + c.txNow,   0)
  const rxPeak  = clients.reduce((a, c) => a + c.rxPeak,  0)
  const txPeak  = clients.reduce((a, c) => a + c.txPeak,  0)
  const rxTotal = clients.reduce((a, c) => a + c.rxTotal, 0)
  const txTotal = clients.reduce((a, c) => a + c.txTotal, 0)
  const rxSeries = clients.reduce((a, c) => addSeries(a, c.rxSeries), Array(60).fill(0) as number[])
  const txSeries = clients.reduce((a, c) => addSeries(a, c.txSeries), Array(60).fill(0) as number[])
  return { id, login, displayName, role, greClients: clients, rxNow, txNow, rxPeak, txPeak, rxTotal, txTotal, rxSeries, txSeries }
}

const userTraffic: UserTraffic[] = [
  buildUserTraffic("u1", "admin",     "Иванов А.П.",  "Администратор", greClients.filter(c => c.userId === "u1")),
  buildUserTraffic("u2", "operator1", "Петров В.С.",  "Оператор",      greClients.filter(c => c.userId === "u2")),
  buildUserTraffic("u3", "viewer",    "Сидоров К.А.", "Наблюдатель",   greClients.filter(c => c.userId === "u3")),
  buildUserTraffic("u4", "operator2", "Козлов М.Н.",  "Оператор",      greClients.filter(c => c.userId === "u4")),
]

// ─── types ────────────────────────────────────────────────────────────────────

const RANGES = ["5м", "15м", "1ч", "4ч", "24ч"] as const
type Range = typeof RANGES[number]

type GroupMode = "servers" | "users" | "gre"
type SortField = "rx" | "tx" | "name" | "sessions"
type SortDir   = "asc" | "desc"

// ─── shared sub-components ────────────────────────────────────────────────────

function StatChip({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {
  return (
    <div className="flex items-center gap-2">
      <div className="flex items-center justify-center size-7 rounded-md bg-muted shrink-0">{icon}</div>
      <div className="leading-tight">
        <p className="text-xs text-muted-foreground">{label}</p>
        <p className="text-sm font-semibold tabular-nums">{value}</p>
      </div>
    </div>
  )
}

function MiniAreaChart({ rx, tx, height = 44 }: { rx: number[]; tx: number[]; height?: number }) {
  const W = 300, H = height
  const maxVal = Math.max(...rx, ...tx, 1) * 1.1
  const xAt = (i: number) => (i / (rx.length - 1)) * W
  const yAt = (v: number) => H - (v / maxVal) * H
  const area = (arr: number[]) => {
    const pts = arr.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(" L ")
    return `M 0,${H} L ${pts} L ${W},${H} Z`
  }
  const line = (arr: number[]) => arr.map((v, i) => `${xAt(i).toFixed(1)},${yAt(v).toFixed(1)}`).join(" ")
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", height, display: "block" }} preserveAspectRatio="none">
      <path d={area(rx)} fill="hsl(var(--primary))" fillOpacity={0.12} />
      <polyline points={line(rx)} fill="none" stroke="hsl(var(--primary))" strokeWidth="1.4" strokeLinejoin="round" />
      <path d={area(tx)} fill="#3b82f6" fillOpacity={0.10} />
      <polyline points={line(tx)} fill="none" stroke="#3b82f6" strokeWidth="1.4" strokeLinejoin="round" />
    </svg>
  )
}

function BigChart({ rx, tx }: { rx: number[]; tx: number[] }) {
  const W = 900, H = 220
  const pad = { l: 56, r: 16, t: 14, b: 32 }
  const iw = W - pad.l - pad.r
  const ih = H - pad.t - pad.b
  const maxVal = Math.max(...rx, ...tx, 1) * 1.15
  const xAt = (i: number, n: number) => pad.l + (i / (n - 1)) * iw
  const yAt = (v: number) => pad.t + (1 - v / maxVal) * ih
  const area = (arr: number[]) => {
    const pts = arr.map((v, i) => `${xAt(i, arr.length).toFixed(1)},${yAt(v).toFixed(1)}`).join(" L ")
    return `M ${pad.l},${pad.t + ih} L ${pts} L ${pad.l + iw},${pad.t + ih} Z`
  }
  const poly = (arr: number[]) => arr.map((v, i) => `${xAt(i, arr.length).toFixed(1)},${yAt(v).toFixed(1)}`).join(" ")
  const gridVals = [0, 0.25, 0.5, 0.75, 1]
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: "100%", height: H, display: "block" }}>
      <defs>
        <linearGradient id="rx-big" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"   stopColor="hsl(var(--primary))" stopOpacity="0.22" />
          <stop offset="100%" stopColor="hsl(var(--primary))" stopOpacity="0" />
        </linearGradient>
        <linearGradient id="tx-big" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"   stopColor="#3b82f6" stopOpacity="0.18" />
          <stop offset="100%" stopColor="#3b82f6" stopOpacity="0" />
        </linearGradient>
      </defs>
      {gridVals.map((p, i) => {
        const y = pad.t + ih * (1 - p)
        return (
          <g key={i}>
            <line x1={pad.l} x2={W - pad.r} y1={y} y2={y}
              stroke="hsl(var(--border))" strokeDasharray={p === 0 ? "0" : "2 4"} />
            <text x={pad.l - 8} y={y + 4} textAnchor="end" fontSize="10"
              fill="hsl(var(--muted-foreground))" fontFamily="monospace">
              {fmtMbps(Math.round(maxVal * p))}
            </text>
          </g>
        )
      })}
      <path d={area(rx)} fill="url(#rx-big)" />
      <polyline points={poly(rx)} fill="none" stroke="hsl(var(--primary))" strokeWidth="1.8" strokeLinejoin="round" />
      <path d={area(tx)} fill="url(#tx-big)" />
      <polyline points={poly(tx)} fill="none" stroke="#3b82f6" strokeWidth="1.8" strokeLinejoin="round" />
      {[0, 12, 24, 36, 48, 59].map(i => (
        <text key={i} x={xAt(i, 60)} y={H - 8} textAnchor="middle" fontSize="10"
          fill="hsl(var(--muted-foreground))" fontFamily="monospace">
          -{60 - i}м
        </text>
      ))}
    </svg>
  )
}

// ─── left-panel entity cards ──────────────────────────────────────────────────

function ServerCard({ s, selected, onClick }: { s: ServerTraffic; selected: boolean; onClick: () => void }) {
  return (
    <button onClick={onClick} className={cn(
      "text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
      selected ? "border-primary bg-primary/5" : "border-border bg-card",
      s.status === "offline" && "opacity-60",
    )}>
      <div className="flex items-center justify-between gap-2 mb-2">
        <div className="flex items-center gap-1.5 min-w-0">
          <StatusDot status={s.status} />
          <span className="text-xs font-medium truncate">{s.name}</span>
        </div>
        <span className="text-[10px] font-mono text-muted-foreground shrink-0 flex items-center gap-1">
          <Flag code={s.country} />{s.site}
        </span>
      </div>
      <MiniAreaChart rx={s.rxSeries} tx={s.txSeries} />
      <div className="flex justify-between mt-2 gap-2">
        <div className="flex items-center gap-1 text-[11px]">
          <ArrowDownIcon className="size-3 text-emerald-500" />
          <span className="font-mono font-medium text-emerald-500">{fmtMbps(s.rxNow)}</span>
        </div>
        <div className="flex items-center gap-1 text-[11px]">
          <ArrowUpIcon className="size-3 text-blue-500" />
          <span className="font-mono font-medium text-blue-500">{fmtMbps(s.txNow)}</span>
        </div>
        {s.greClients.length > 0 && (
          <div className="flex items-center gap-0.5 text-[10px] text-muted-foreground">
            <CableIcon className="size-3" />{s.greClients.length}
          </div>
        )}
      </div>
    </button>
  )
}

function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean; onClick: () => void }) {
  const hasClients = u.greClients.length > 0
  return (
    <button onClick={onClick} className={cn(
      "text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
      selected ? "border-primary bg-primary/5" : "border-border bg-card",
      !hasClients && "opacity-60",
    )}>
      <div className="flex items-center justify-between gap-2 mb-2">
        <div className="flex items-center gap-1.5 min-w-0">
          <div className="size-5 rounded-full bg-muted flex items-center justify-center shrink-0">
            <UsersIcon className="size-3 text-muted-foreground" />
          </div>
          <div className="min-w-0">
            <p className="text-xs font-medium truncate">{u.login}</p>
            <p className="text-[10px] text-muted-foreground truncate">{u.displayName}</p>
          </div>
        </div>
        <div className="flex items-center gap-0.5 text-[10px] text-muted-foreground shrink-0">
          <CableIcon className="size-3" />{u.greClients.length}
        </div>
      </div>
      {hasClients ? (
        <>
          <MiniAreaChart rx={u.rxSeries} tx={u.txSeries} />
          <div className="flex justify-between mt-2 gap-2">
            <div className="flex items-center gap-1 text-[11px]">
              <ArrowDownIcon className="size-3 text-emerald-500" />
              <span className="font-mono font-medium text-emerald-500">{fmtMbps(u.rxNow)}</span>
            </div>
            <div className="flex items-center gap-1 text-[11px]">
              <ArrowUpIcon className="size-3 text-blue-500" />
              <span className="font-mono font-medium text-blue-500">{fmtMbps(u.txNow)}</span>
            </div>
          </div>
        </>
      ) : (
        <p className="text-[10px] text-muted-foreground">Нет GRE-клиентов</p>
      )}
    </button>
  )
}

function GreCard({ c, selected, onClick }: { c: GreClientTraffic; selected: boolean; onClick: () => void }) {
  return (
    <button onClick={onClick} className={cn(
      "text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
      selected ? "border-primary bg-primary/5" : "border-border bg-card",
      c.status === "offline" && "opacity-60",
    )}>
      <div className="flex items-center justify-between gap-2 mb-2">
        <div className="flex items-center gap-1.5 min-w-0">
          <StatusDot status={c.status} />
          <div className="min-w-0">
            <p className="text-xs font-mono font-medium truncate">{c.login}</p>
            <p className="text-[10px] text-muted-foreground truncate">{c.userLogin}</p>
          </div>
        </div>
        <span className="text-[10px] font-mono text-muted-foreground shrink-0 flex items-center gap-1">
          <Flag code={c.serverCountry} />{c.serverSite}
        </span>
      </div>
      <MiniAreaChart rx={c.rxSeries} tx={c.txSeries} />
      <div className="flex justify-between mt-2 gap-2">
        <div className="flex items-center gap-1 text-[11px]">
          <ArrowDownIcon className="size-3 text-emerald-500" />
          <span className="font-mono font-medium text-emerald-500">{fmtMbps(c.rxNow)}</span>
        </div>
        <div className="flex items-center gap-1 text-[11px]">
          <ArrowUpIcon className="size-3 text-blue-500" />
          <span className="font-mono font-medium text-blue-500">{fmtMbps(c.txNow)}</span>
        </div>
      </div>
    </button>
  )
}

// ─── GRE client row (replaces bulky grid tables) ─────────────────────────────

/**
 * Compact single-row card for a GRE client.
 * showServer=true adds a server chip (used in user-detail where server is not implied).
 */
function GreClientRow({ c, showServer = false }: { c: GreClientTraffic; showServer?: boolean }) {
  return (
    <div className={cn(
      "flex items-center gap-3 px-3 py-2.5 rounded-lg border bg-card hover:bg-muted/30 transition-colors",
      c.status === "offline" && "opacity-55",
    )}>
      {/* identity */}
      <StatusDot status={c.status} />

      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-2 flex-wrap">
          <span className="text-xs font-mono font-semibold">{c.login}</span>
          {showServer && (
            <span className="inline-flex items-center gap-1 text-[10px] font-mono text-muted-foreground
              bg-muted px-1.5 py-0.5 rounded">
              <Flag code={c.serverCountry} />{c.serverSite}
            </span>
          )}
        </div>
        <p className="text-[10px] text-muted-foreground truncate mt-0.5">{c.description}</p>
      </div>

      {/* live RX / TX */}
      <div className="shrink-0 text-right leading-tight">
        <div className="flex items-center gap-1 justify-end text-xs font-mono font-medium text-emerald-600 dark:text-emerald-400">
          <ArrowDownIcon className="size-3" />{fmtMbps(c.rxNow)}
        </div>
        <div className="flex items-center gap-1 justify-end text-xs font-mono font-medium text-blue-600 dark:text-blue-400">
          <ArrowUpIcon className="size-3" />{fmtMbps(c.txNow)}
        </div>
      </div>

      {/* totals today */}
      <div className="shrink-0 text-right leading-tight text-[10px] text-muted-foreground w-[72px]">
        <p className="tabular-nums">{fmtGB(c.rxTotal)}</p>
        <p className="tabular-nums">{fmtGB(c.txTotal)}</p>
      </div>

      {/* status dot — badge style */}
      <span className={cn(
        "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full font-medium tabular-nums",
        c.status === "online"
          ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-400"
          : "bg-muted text-muted-foreground",
      )}>
        {c.status === "online" ? "online" : "offline"}
      </span>
    </div>
  )
}

function GreClientsList({ clients, showServer = false }: { clients: GreClientTraffic[]; showServer?: boolean }) {
  if (clients.length === 0)
    return <p className="text-xs text-muted-foreground py-1">Нет GRE-клиентов</p>
  return (
    <div className="flex flex-col gap-1.5">
      {clients.map(c => <GreClientRow key={c.id} c={c} showServer={showServer} />)}
    </div>
  )
}

// ─── detail panel helpers ─────────────────────────────────────────────────────

function DetailHeader({ range, setRange, children }: {
  range: Range; setRange: (r: Range) => void; children: React.ReactNode
}) {
  return (
    <div className="flex items-start justify-between mb-3 gap-3">
      <div className="flex items-center gap-2 flex-wrap min-w-0">{children}</div>
      <div className="flex gap-1 shrink-0">
        {RANGES.map(r => (
          <Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
            className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
            {r}
          </Button>
        ))}
      </div>
    </div>
  )
}

function ChartLegend() {
  return (
    <div className="flex gap-5 mb-2">
      <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
        <span className="inline-block size-2 rounded-full bg-primary/70" />RX (входящий)
      </span>
      <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
        <span className="inline-block size-2 rounded-full bg-blue-500/70" />TX (исходящий)
      </span>
    </div>
  )
}

function OfflinePlaceholder({ text = "Нет данных — объект недоступен" }: { text?: string }) {
  return (
    <div className="flex items-center justify-center h-[220px] text-muted-foreground/40">
      <div className="text-center">
        <ActivityIcon className="size-10 mx-auto mb-2 opacity-40" />
        <p className="text-sm">{text}</p>
      </div>
    </div>
  )
}

function TotalsRow({ rxTotal, txTotal, rxSeries, txSeries }: {
  rxTotal: number; txTotal: number; rxSeries: number[]; txSeries: number[]
}) {
  return (
    <div className="mt-4 pt-4 border-t grid grid-cols-2 gap-4">
      <div>
        <p className="text-xs text-muted-foreground mb-1">Получено сегодня</p>
        <p className="text-lg font-semibold tabular-nums">
          {rxTotal.toFixed(1)} <span className="text-sm font-normal text-muted-foreground">ГБ</span>
        </p>
        <Sparkline data={rxSeries} width={180} height={28} color="hsl(var(--primary))" filled />
      </div>
      <div>
        <p className="text-xs text-muted-foreground mb-1">Отправлено сегодня</p>
        <p className="text-lg font-semibold tabular-nums">
          {txTotal.toFixed(1)} <span className="text-sm font-normal text-muted-foreground">ГБ</span>
        </p>
        <Sparkline data={txSeries} width={180} height={28} color="#3b82f6" filled />
      </div>
    </div>
  )
}

// ─── detail panels ────────────────────────────────────────────────────────────

function ServerDetail({ sel, range, setRange }: { sel: ServerTraffic; range: Range; setRange: (r: Range) => void }) {
  return (
    <>
      <DetailHeader range={range} setRange={setRange}>
        <StatusDot status={sel.status} />
        <h2 className="text-base font-semibold">{sel.name}</h2>
        <span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded flex items-center gap-1">
          <Flag code={sel.country} />{sel.site}
        </span>
      </DetailHeader>
      <ChartLegend />
      {sel.status === "offline" ? <OfflinePlaceholder /> : <BigChart rx={sel.rxSeries} tx={sel.txSeries} />}
      <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-4 pt-4 border-t">
        <StatChip icon={<ArrowDownIcon    className="size-3.5 text-emerald-500" />} label="RX сейчас" value={fmtMbps(sel.rxNow)} />
        <StatChip icon={<ArrowUpIcon      className="size-3.5 text-blue-500"    />} label="TX сейчас" value={fmtMbps(sel.txNow)} />
        <StatChip icon={<TrendingUpIcon   className="size-3.5 text-amber-500"   />} label="Пик RX"    value={fmtMbps(sel.rxPeak)} />
        <StatChip icon={<TrendingDownIcon className="size-3.5 text-purple-500"  />} label="Сессий"    value={`${sel.sessions}`} />
      </div>
      <TotalsRow rxTotal={sel.rxTotal} txTotal={sel.txTotal} rxSeries={sel.rxSeries} txSeries={sel.txSeries} />
      {sel.greClients.length > 0 && (
        <div className="mt-4 pt-4 border-t">
          <h3 className="text-sm font-semibold mb-2 flex items-center gap-2">
            <CableIcon className="size-4 text-muted-foreground" />
            GRE-клиенты
            <span className="text-xs text-muted-foreground font-normal">({sel.greClients.length})</span>
          </h3>
          <GreClientsList clients={sel.greClients} />
        </div>
      )}
    </>
  )
}

function UserDetail({ sel, range, setRange }: { sel: UserTraffic; range: Range; setRange: (r: Range) => void }) {
  const hasClients = sel.greClients.length > 0
  return (
    <>
      <DetailHeader range={range} setRange={setRange}>
        <div className="size-7 rounded-full bg-muted flex items-center justify-center shrink-0">
          <UsersIcon className="size-4 text-muted-foreground" />
        </div>
        <div className="leading-tight">
          <h2 className="text-base font-semibold">{sel.login}</h2>
          <p className="text-xs text-muted-foreground">{sel.displayName} · {sel.role}</p>
        </div>
      </DetailHeader>
      {!hasClients ? (
        <OfflinePlaceholder text="Нет GRE-клиентов у этого пользователя" />
      ) : (
        <>
          <ChartLegend />
          <BigChart rx={sel.rxSeries} tx={sel.txSeries} />
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-4 pt-4 border-t">
            <StatChip icon={<ArrowDownIcon  className="size-3.5 text-emerald-500" />} label="RX сейчас"    value={fmtMbps(sel.rxNow)} />
            <StatChip icon={<ArrowUpIcon    className="size-3.5 text-blue-500"    />} label="TX сейчас"    value={fmtMbps(sel.txNow)} />
            <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик RX"       value={fmtMbps(sel.rxPeak)} />
            <StatChip icon={<CableIcon      className="size-3.5 text-purple-500"  />} label="GRE-клиентов" value={`${sel.greClients.length}`} />
          </div>
          <TotalsRow rxTotal={sel.rxTotal} txTotal={sel.txTotal} rxSeries={sel.rxSeries} txSeries={sel.txSeries} />
          <div className="mt-4 pt-4 border-t">
            <h3 className="text-sm font-semibold mb-2 flex items-center gap-2">
              <CableIcon className="size-4 text-muted-foreground" />
              GRE-клиенты
              <span className="text-xs text-muted-foreground font-normal">({sel.greClients.length})</span>
            </h3>
            <GreClientsList clients={sel.greClients} showServer />
          </div>
        </>
      )}
    </>
  )
}

function GreDetail({ sel, range, setRange }: { sel: GreClientTraffic; range: Range; setRange: (r: Range) => void }) {
  return (
    <>
      <DetailHeader range={range} setRange={setRange}>
        <StatusDot status={sel.status} />
        <div className="leading-tight min-w-0">
          <h2 className="text-base font-mono font-semibold">{sel.login}</h2>
          <p className="text-xs text-muted-foreground truncate">{sel.description}</p>
        </div>
        <span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded flex items-center gap-1 shrink-0">
          <Flag code={sel.serverCountry} />{sel.serverSite}
        </span>
      </DetailHeader>
      <ChartLegend />
      {sel.status === "offline" ? <OfflinePlaceholder /> : <BigChart rx={sel.rxSeries} tx={sel.txSeries} />}
      <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-4 pt-4 border-t">
        <StatChip icon={<ArrowDownIcon  className="size-3.5 text-emerald-500" />} label="RX сейчас" value={fmtMbps(sel.rxNow)} />
        <StatChip icon={<ArrowUpIcon    className="size-3.5 text-blue-500"    />} label="TX сейчас" value={fmtMbps(sel.txNow)} />
        <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик RX"    value={fmtMbps(sel.rxPeak)} />
        <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик TX"    value={fmtMbps(sel.txPeak)} />
      </div>
      <TotalsRow rxTotal={sel.rxTotal} txTotal={sel.txTotal} rxSeries={sel.rxSeries} txSeries={sel.txSeries} />
      <div className="mt-4 pt-4 border-t grid grid-cols-2 sm:grid-cols-3 gap-4">
        <div>
          <p className="text-xs text-muted-foreground mb-0.5">Сервер JH</p>
          <p className="text-sm font-mono">{sel.serverName}</p>
        </div>
        <div>
          <p className="text-xs text-muted-foreground mb-0.5">IP клиента</p>
          <p className="text-sm font-mono">{sel.clientIp}</p>
        </div>
        <div>
          <p className="text-xs text-muted-foreground mb-0.5">Аккаунт</p>
          <p className="text-sm font-mono">{sel.userLogin}</p>
        </div>
      </div>
    </>
  )
}

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

const GROUP_MODES: Array<{ mode: GroupMode; icon: React.ReactNode; label: string }> = [
  { mode: "servers", icon: <ServerIcon className="size-3" />, label: "Серверы" },
  { mode: "users",   icon: <UsersIcon  className="size-3" />, label: "Клиенты" },
  { mode: "gre",     icon: <CableIcon  className="size-3" />, label: "GRE" },
]

const SORT_FIELDS: Array<{ field: SortField; label: string; modesOnly?: GroupMode[] }> = [
  { field: "rx",       label: "RX" },
  { field: "tx",       label: "TX" },
  { field: "name",     label: "Имя" },
  { field: "sessions", label: "Сессий", modesOnly: ["servers"] },
]

export default function TrafficPage() {
  const { mode, backendUrl, backendStatus } = useDataSource()
  const isLive = mode === "live" && backendStatus === true
  const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])

  const [groupMode, setGroupMode] = useState<GroupMode>("servers")
  const [sortField, setSortField] = useState<SortField>("rx")
  const [sortDir,   setSortDir]   = useState<SortDir>("desc")
  const [selectedId, setSelectedId] = useState("srv1")
  const [range, setRange] = useState<Range>("1ч")
  const [search, setSearch] = useState("")
  const [liveServers, setLiveServers] = useState<ServerTraffic[]>([])
  const [liveBusy, setLiveBusy] = useState(false)
  const [liveError, setLiveError] = useState<string | null>(null)
  const [settingsBusy, setSettingsBusy] = useState(false)
  const [trafficSettings, setTrafficSettings] = useState<TrafficSettingsDto | null>(null)
  const [intervalDraft, setIntervalDraft] = useState("30")
  const [retentionDraft, setRetentionDraft] = useState("14")
  const [serverIfaces, setServerIfaces] = useState<LiveTrafficInterface[]>([])
  const [selectedIface, setSelectedIface] = useState("__all__")
  const [hideDisabledIfaces, setHideDisabledIfaces] = useState(true)
  const [detailBusy, setDetailBusy] = useState(false)
  const [liveDetailServer, setLiveDetailServer] = useState<ServerTraffic | null>(null)
  const effectiveMode: GroupMode = isLive ? "servers" : groupMode

  const toLiveServer = (s: LiveTrafficServer): ServerTraffic => {
    return {
      id: String(s.id),
      name: s.name,
      site: s.site || "—",
      country: s.country || "UN",
      status: s.status,
      rxNow: s.rxNow,
      txNow: s.txNow,
      rxPeak: s.rxPeak,
      txPeak: s.txPeak,
      rxTotal: s.rxTotal,
      txTotal: s.txTotal,
      sessions: s.sessions,
      rxSeries: s.rxSeries.length ? s.rxSeries : Array(60).fill(0),
      txSeries: s.txSeries.length ? s.txSeries : Array(60).fill(0),
      greClients: [],
    }
  }

  const loadLiveTraffic = useCallback(async (targetRange: Range) => {
    if (!isLive) return
    setLiveBusy(true)
    setLiveError(null)
    try {
      const res = await apiFetch<{ servers: LiveTrafficServer[] }>(`/api/traffic/servers?range=${encodeURIComponent(targetRange)}`)
      const mapped: ServerTraffic[] = res.servers.map(toLiveServer)
      setLiveServers(mapped)
      setSelectedId((prev) => mapped.some((x) => x.id === prev) ? prev : (mapped[0]?.id ?? ""))
    } catch (e) {
      setLiveError(e instanceof Error ? e.message : "Не удалось загрузить live трафик")
    } finally {
      setLiveBusy(false)
    }
  }, [apiFetch, isLive])

  const loadTrafficSettings = useCallback(async () => {
    if (!isLive) return
    try {
      const s = await apiFetch<TrafficSettingsDto>("/api/traffic/settings")
      setTrafficSettings(s)
      setIntervalDraft(String(s.intervalSec))
      setRetentionDraft(String(s.retentionDays))
    } catch (e) {
      setLiveError(e instanceof Error ? e.message : "Не удалось загрузить настройки сбора")
    }
  }, [apiFetch, isLive])

  useEffect(() => {
    if (!isLive) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setLiveServers([])
      setLiveError(null)
      setTrafficSettings(null)
      return
    }
    if (groupMode !== "servers") setGroupMode("servers")
    void loadLiveTraffic(range)
    void loadTrafficSettings()
  }, [isLive, groupMode, range, loadLiveTraffic, loadTrafficSettings])

  const activeServerTraffic = isLive ? liveServers : serverTraffic
  const visibleIfaces = useMemo(
    () => hideDisabledIfaces ? serverIfaces.filter((i) => !i.disabled && i.running) : serverIfaces,
    [serverIfaces, hideDisabledIfaces],
  )

  useEffect(() => {
    if (!isLive || effectiveMode !== "servers" || !selectedId) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setServerIfaces([])
      setSelectedIface("__all__")
      setLiveDetailServer(null)
      return
    }
    setSelectedIface("__all__")
    apiFetch<{ interfaces: LiveTrafficInterface[] }>(`/api/traffic/servers/${encodeURIComponent(selectedId)}/interfaces`)
      .then((res) => setServerIfaces(res.interfaces))
      .catch(() => setServerIfaces([]))
  }, [isLive, selectedId, effectiveMode, apiFetch])

  useEffect(() => {
    if (!isLive || effectiveMode !== "servers" || !selectedId) {
      // eslint-disable-next-line react-hooks/set-state-in-effect
      setLiveDetailServer(null)
      return
    }
    setDetailBusy(true)
    apiFetch<{ server: LiveTrafficServer }>(`/api/traffic/servers/${encodeURIComponent(selectedId)}?range=${encodeURIComponent(range)}&iface=${encodeURIComponent(selectedIface)}`)
      .then((res) => setLiveDetailServer(toLiveServer(res.server)))
      .catch(() => setLiveDetailServer(null))
      .finally(() => setDetailBusy(false))
  }, [isLive, effectiveMode, selectedId, range, selectedIface, apiFetch])

  const handleModeChange = (mode: GroupMode) => {
    if (isLive && mode !== "servers") return
    setGroupMode(mode)
    if (mode === "servers") setSelectedId("srv1")
    else if (mode === "users") setSelectedId("u1")
    else setSelectedId("su1-srv1")
    // reset sort to rx desc when switching
    setSortField("rx")
    setSortDir("desc")
    setSearch("")
  }

  const toggleSort = (field: SortField) => {
    if (sortField === field) setSortDir(d => d === "desc" ? "asc" : "desc")
    else { setSortField(field); setSortDir("desc") }
  }

  const q = search.toLowerCase()

  const sortedServers = useMemo(() => {
    return [...activeServerTraffic]
      .filter(s => !q || s.name.toLowerCase().includes(q) || s.site.toLowerCase().includes(q))
      .sort((a, b) => {
        let v = 0
        if (sortField === "rx")            v = a.rxNow    - b.rxNow
        else if (sortField === "tx")       v = a.txNow    - b.txNow
        else if (sortField === "name")     v = a.name.localeCompare(b.name)
        else if (sortField === "sessions") v = a.sessions - b.sessions
        return sortDir === "desc" ? -v : v
      })
  }, [sortField, sortDir, q, activeServerTraffic])

  const sortedUsers = useMemo(() => {
    return [...userTraffic]
      .filter(u => !q || u.login.toLowerCase().includes(q) || u.displayName.toLowerCase().includes(q))
      .sort((a, b) => {
        let v = 0
        if (sortField === "rx")            v = a.rxNow - b.rxNow
        else if (sortField === "tx")       v = a.txNow - b.txNow
        else if (sortField === "name")     v = a.login.localeCompare(b.login)
        else if (sortField === "sessions") v = a.greClients.length - b.greClients.length
        return sortDir === "desc" ? -v : v
      })
  }, [sortField, sortDir, q])

  const sortedGre = useMemo(() => {
    return [...greClients]
      .filter(c => !q || c.login.toLowerCase().includes(q) || c.description.toLowerCase().includes(q) || c.userLogin.toLowerCase().includes(q))
      .sort((a, b) => {
        let v = 0
        if (sortField === "rx")        v = a.rxNow - b.rxNow
        else if (sortField === "tx")   v = a.txNow - b.txNow
        else if (sortField === "name") v = a.login.localeCompare(b.login)
        return sortDir === "desc" ? -v : v
      })
  }, [sortField, sortDir, q])

  const selServer = useMemo(() => activeServerTraffic.find(s => s.id === selectedId) ?? activeServerTraffic[0], [selectedId, activeServerTraffic])
  const selUser   = useMemo(() => userTraffic.find(u => u.id === selectedId)   ?? userTraffic[0],   [selectedId])
  const selGre    = useMemo(() => greClients.find(c => c.id === selectedId)    ?? greClients[0],    [selectedId])

  const totalRx = activeServerTraffic.reduce((a, s) => a + s.rxNow, 0)
  const totalTx = activeServerTraffic.reduce((a, s) => a + s.txNow, 0)
  const peakRx  = activeServerTraffic.reduce((a, s) => Math.max(a, s.rxPeak), 0)
  const peakTx  = activeServerTraffic.reduce((a, s) => Math.max(a, s.txPeak), 0)

  const visibleSortFields = SORT_FIELDS.filter(s => !s.modesOnly || s.modesOnly.includes(effectiveMode))

  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Обзор" }, { label: "Трафик" }]}
        actions={
          <>
            <Button
              variant="outline"
              size="sm"
              onClick={() => { void loadLiveTraffic(range) }}
              disabled={isLive && liveBusy}
            >
              <RefreshCwIcon className={cn("size-4", isLive && liveBusy && "animate-spin")} />Обновить
            </Button>
            {isLive && (
              <Button
                variant="outline"
                size="sm"
                onClick={async () => {
                  setSettingsBusy(true)
                  setLiveError(null)
                  try {
                    await apiFetch("/api/traffic/collect-now", { method: "POST" })
                    await loadLiveTraffic(range)
                    await loadTrafficSettings()
                  } catch (e) {
                    setLiveError(e instanceof Error ? e.message : "Не удалось выполнить сбор")
                  } finally {
                    setSettingsBusy(false)
                  }
                }}
                disabled={settingsBusy}
              >
                <ActivityIcon className={cn("size-4", settingsBusy && "animate-pulse")} />Собрать сейчас
              </Button>
            )}
            <Button variant="outline" size="sm"><DownloadIcon  className="size-4" />Экспорт</Button>
          </>
        }
      />

      {/* ── mode tab bar ── */}
      <div className="border-b bg-background shrink-0">
        <div className="flex items-center px-6">
          {GROUP_MODES.filter(m => !isLive || m.mode === "servers").map(({ mode, icon, label }) => (
            <button key={mode} onClick={() => handleModeChange(mode)}
              className={cn(
                "flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors -mb-px",
                effectiveMode === mode
                  ? "border-primary text-foreground"
                  : "border-transparent text-muted-foreground hover:text-foreground hover:border-border",
              )}>
              {icon}{label}
            </button>
          ))}
        </div>
      </div>

      <div className="flex-1 overflow-y-auto p-6">
        <div className="flex flex-col gap-5">
          {isLive && trafficSettings && (
            <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">Сбор статистики</span>
                    <div className="flex rounded-md border border-input overflow-hidden h-8">
                      <button
                        className={cn("px-3 text-xs", trafficSettings.enabled ? "bg-emerald-600 text-white" : "text-muted-foreground hover:bg-muted")}
                        onClick={async () => {
                          setSettingsBusy(true)
                          try {
                            await apiFetch("/api/traffic/settings", { method: "PUT", body: JSON.stringify({ enabled: true }) })
                            await loadTrafficSettings()
                          } finally { setSettingsBusy(false) }
                        }}
                        disabled={settingsBusy}
                      >Вкл</button>
                      <button
                        className={cn("px-3 text-xs border-l border-input", !trafficSettings.enabled ? "bg-muted-foreground text-white" : "text-muted-foreground hover:bg-muted")}
                        onClick={async () => {
                          setSettingsBusy(true)
                          try {
                            await apiFetch("/api/traffic/settings", { method: "PUT", body: JSON.stringify({ enabled: false }) })
                            await loadTrafficSettings()
                          } finally { setSettingsBusy(false) }
                        }}
                        disabled={settingsBusy}
                      >Выкл</button>
                    </div>
                  </div>

                  <div className="flex flex-col gap-1">
                    <span className="text-[11px] text-muted-foreground">Интервал (сек)</span>
                    <input
                      value={intervalDraft}
                      onChange={(e) => setIntervalDraft(e.target.value)}
                      className="h-8 w-24 text-xs bg-muted/50 border border-border rounded-md px-2"
                    />
                  </div>

                  <div className="flex flex-col gap-1">
                    <span className="text-[11px] text-muted-foreground">Хранение (дней)</span>
                    <input
                      value={retentionDraft}
                      onChange={(e) => setRetentionDraft(e.target.value)}
                      className="h-8 w-24 text-xs bg-muted/50 border border-border rounded-md px-2"
                    />
                  </div>

                  <Button
                    size="sm"
                    variant="outline"
                    className="h-8"
                    disabled={settingsBusy}
                    onClick={async () => {
                      setSettingsBusy(true)
                      try {
                        await apiFetch("/api/traffic/settings", {
                          method: "PUT",
                          body: JSON.stringify({
                            intervalSec: Number.parseInt(intervalDraft, 10) || 30,
                            retentionDays: Number.parseInt(retentionDraft, 10) || 14,
                          }),
                        })
                        await loadTrafficSettings()
                      } catch (e) {
                        setLiveError(e instanceof Error ? e.message : "Не удалось сохранить настройки")
                      } finally {
                        setSettingsBusy(false)
                      }
                    }}
                  >
                    Сохранить сбор
                  </Button>

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

          {/* ── summary stat cards ── */}
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
            {[
              { icon: <ArrowDownIcon  className="size-3.5 text-emerald-500" />, label: "RX сейчас", value: fmtMbps(totalRx) },
              { icon: <ArrowUpIcon    className="size-3.5 text-blue-500"    />, label: "TX сейчас", value: fmtMbps(totalTx) },
              { icon: <TrendingUpIcon className="size-3.5 text-amber-500"   />, label: "Пик RX",    value: fmtMbps(peakRx) },
              { icon: <TrendingUpIcon className="size-3.5 text-amber-500"   />, label: "Пик TX",    value: fmtMbps(peakTx) },
            ].map(({ icon, label, value }) => (
              <Card key={label} className="overflow-hidden">
                <CardContent className="pt-4 pb-3 px-4">
                  <p className="text-xs text-muted-foreground mb-1 flex items-center gap-1.5">{icon}{label}</p>
                  <p className="text-2xl font-semibold tabular-nums tracking-tight">{value}</p>
                </CardContent>
              </Card>
            ))}
          </div>

          <div className="grid grid-cols-[300px_1fr] gap-5 items-start">

            {/* ── left panel ── */}
            <div className="flex flex-col gap-3">

              {/* search */}
              <div className="relative">
                <SearchIcon className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground pointer-events-none" />
                <input
                  type="text"
                  value={search}
                  onChange={e => setSearch(e.target.value)}
                  placeholder="Поиск…"
                  className="w-full pl-8 pr-3 h-8 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
                />
              </div>

              {/* sort controls */}
              <div className="flex gap-1 flex-wrap">
                <span className="text-[10px] text-muted-foreground self-center mr-0.5">Сортировка:</span>
                {visibleSortFields.map(({ field, label }) => (
                  <button key={field} onClick={() => toggleSort(field)}
                    className={cn(
                      "text-[10px] px-2 py-0.5 rounded border transition-colors",
                      sortField === field
                        ? "border-primary bg-primary/10 text-primary font-medium"
                        : "border-border text-muted-foreground hover:text-foreground",
                    )}>
                    {label}{sortField === field ? (sortDir === "desc" ? " ↓" : " ↑") : ""}
                  </button>
                ))}
              </div>
              {liveError && (
                <div className="text-xs text-destructive bg-destructive/10 border border-destructive/20 rounded-md px-3 py-2">
                  {liveError}
                </div>
              )}

              {/* entity list */}
              <div className="flex flex-col gap-2">
                {effectiveMode === "servers" && sortedServers.map(s => (
                  <ServerCard key={s.id} s={s} selected={s.id === selectedId} onClick={() => setSelectedId(s.id)} />
                ))}
                {effectiveMode === "users" && sortedUsers.map(u => (
                  <UserCard key={u.id} u={u} selected={u.id === selectedId} onClick={() => setSelectedId(u.id)} />
                ))}
                {effectiveMode === "gre" && sortedGre.map(c => (
                  <GreCard key={c.id} c={c} selected={c.id === selectedId} onClick={() => setSelectedId(c.id)} />
                ))}
              </div>
            </div>

            {/* ── detail panel ── */}
            <Card className="flex flex-col">
              <CardContent className="flex-1 px-5 pb-5 pt-5">
                {isLive && effectiveMode === "servers" && (
                  <div className="mb-3 pb-3 border-b">
                    <div className="flex items-center gap-2 flex-wrap">
                      <span className="text-[11px] text-muted-foreground mr-1">Интерфейс:</span>
                      <button
                        onClick={() => setHideDisabledIfaces((v) => !v)}
                        className={cn(
                          "inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium transition-all",
                          hideDisabledIfaces
                            ? "bg-muted text-foreground border-border"
                            : "text-muted-foreground border-border hover:text-foreground hover:border-foreground/40",
                        )}
                      >
                        {hideDisabledIfaces ? "Показать отключенные" : "Скрыть отключенные"}
                      </button>
                      <button
                        onClick={() => setSelectedIface("__all__")}
                        className={cn(
                          "inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",
                          selectedIface === "__all__"
                            ? "bg-foreground text-background border-foreground"
                            : "border-border text-muted-foreground hover:text-foreground hover:border-foreground/40",
                        )}
                      >
                        Все
                      </button>
                      {visibleIfaces.map((iface) => {
                        const active = selectedIface === iface.name
                        return (
                          <button
                            key={iface.name}
                            onClick={() => setSelectedIface(iface.name)}
                            className={cn(
                              "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",
                              active
                                ? "bg-foreground text-background border-foreground"
                                : "border-border text-muted-foreground hover:text-foreground hover:border-foreground/40",
                              iface.disabled && !active && "opacity-40",
                            )}
                          >
                            <StatusDot status={iface.running && !iface.disabled ? "online" : "offline"} />
                            <span className="font-mono">{iface.name}</span>
                          </button>
                        )
                      })}
                      {detailBusy && <span className="text-[10px] text-muted-foreground ml-2">Загрузка...</span>}
                    </div>
                  </div>
                )}
                {effectiveMode === "servers" && (liveDetailServer ?? selServer) && <ServerDetail sel={(liveDetailServer ?? selServer)!} range={range} setRange={setRange} />}
                {effectiveMode === "users"   && <UserDetail   sel={selUser}   range={range} setRange={setRange} />}
                {effectiveMode === "gre"     && <GreDetail    sel={selGre}    range={range} setRange={setRange} />}
              </CardContent>
            </Card>

          </div>
        </div>
      </div>
    </div>
  )
}
