"use client"

import { useState, useMemo, useEffect, useCallback, type ReactNode } from "react"
import { PageHeader } from "@/components/page-header"
import { Frame, FramePanel } from "@/components/reui/frame"
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
import { TrafficRxTxChart } from "@/components/reui-kit/traffic-rx-tx-chart"
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, GitBranchIcon, PlusIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { fmtGB, fmtRate } from "@/lib/fmt-rate"
import { useDataSource } from "@/lib/data-source"
import { useTrafficLive } from "@/hooks/use-traffic-live"
import { useFlowLive } from "@/hooks/use-flow-live"
import { requestJson } from "@/shared/api/http-client"
import { getFlowAnalytics, getFlowClients, getFlowExporters, getFlowMonthly, getTrafficFlows } from "@/shared/api/traffic-flow"
import { listServers } from "@/shared/api/servers"
import { FlowOverlaySheet } from "@/components/traffic/flow-overlay-sheet"
import { FlowAnalyticsDetail, FlowEntityCardView } from "@/components/traffic/flow-analytics-panel"
import type { FlowAnalyticsDto, FlowEntityCard, FlowMonthlyDto, FlowStatsDto } from "@mmapp/contracts/traffic-flow"
import type { ServerRead } from "@mmapp/contracts/servers"
import { Badge } from "@/components/reui/badge"
import {
  IFACE_TYPE_LABEL,
  INIT_USERS,
  ROLE_LABEL,
  type InterfaceType,
} from "@/lib/users"
import { TYPE_VARIANT } from "@/components/data-grids/users-expanded-detail"

// ─── 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 flowIngestLine(stats: FlowStatsDto | null): string | null {
  if (!stats) return null
  const listener = stats.listenerBound
    ? (stats.listenerAddress ?? "слушает")
    : "не слушает"
  const last = stats.lastDatagramAt
    ? new Date(stats.lastDatagramAt).toLocaleString("ru-RU")
    : "—"
  const exporter = stats.lastExporterIp ? ` · ${stats.lastExporterIp}` : ""
  const err = stats.lastError ? ` · ${stats.lastError}` : ""
  return `Коллектор: ${listener} · пакеты ${stats.packetsReceived ?? 0} · последний ${last}${exporter}${err}`
}

function flowEmptyHint(stats: FlowStatsDto | null, collectorAlive?: boolean): string | undefined {
  if (!stats) return undefined
  if (stats.lastError) return stats.lastError
  if (stats.packetsReceived) {
    return `IPFIX приходит (${stats.lastExporterIp ?? "экспортёр"}), но сессии ещё не записаны.`
  }
  if (stats.listenerBound === false && !collectorAlive) {
    return "Коллектор UDP не слушает. Подключите JH ещё раз — ingest включится автоматически."
  }
  if (stats.listenerBound || collectorAlive) {
    return "Коллектор жив, IPFIX ещё не доходит. На jump-host у target Src должен быть 0.0.0.0 (авто)."
  }
  return "IPFIX ещё не доходит до коллектора. На jump-host у target Src должен быть 0.0.0.0 (авто). На хосте MM проверьте bind 10.255.254.1:4739 после wg-flow."
}

function monthlyToAnalytics(m: FlowMonthlyDto): FlowAnalyticsDto {
  const emptySeries = Array(60).fill(0) as number[]
  return {
    bpsNow: 0,
    bytes: m.bytes,
    packets: 0,
    conversations: 0,
    conversationsRaw: 0,
    uniqueSrc: 0,
    uniqueDst: 0,
    topProto: "—",
    topCategory: "—",
    rxSeries: emptySeries,
    txSeries: emptySeries,
    applications: [],
    protocols: [],
    sources: [],
    destinations: [],
    interfaces: [],
    asns: m.asns,
    countries: m.countries,
    categories: [],
    services: m.services,
    mapEdges: [],
    conversationsList: [],
    paths: [],
    ifaces: [],
    live: false,
    degraded: false,
    bytesPayload: m.bytes,
    bytesOverlay: 0,
    bytesMesh: 0,
    bytesWire: 0,
  }
}

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

interface BoundIfaceTraffic {
  id: string
  bindingId: string
  userId: string
  userLogin: string
  userName: string
  interfaceName: string
  interfaceType: InterfaceType
  peerPublicKey?: string
  peerName?: string
  comment: string
  serverId: string
  serverName: string
  serverSite: string
  serverCountry: 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[]
  boundIfaces: BoundIfaceTraffic[]
}

interface UserTraffic {
  id: string
  login: string
  displayName: string
  role: string
  active?: boolean
  interfaces: BoundIfaceTraffic[]
  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 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> {
    return requestJson<T>(backendUrl, path, init)
  }
}

function hashSeed(s: string): number {
  let h = 0
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0
  return Math.abs(h)
}

function boundIfaceLabel(c: Pick<BoundIfaceTraffic, "interfaceName" | "interfaceType" | "peerName">): string {
  if (c.interfaceType === "wg" && c.peerName) return `${c.peerName} · ${c.interfaceName}`
  return c.interfaceName
}

function mockBoundFromUsers(): BoundIfaceTraffic[] {
  return INIT_USERS.flatMap((u) =>
    u.bindings.map((b) => {
      const seed = hashSeed(b.id)
      const offline = !u.active || b.interfaceName.includes("retail") || b.interfaceName.includes("warehouse")
      const rxNow = offline ? 0 : 12 + (seed % 140)
      const txNow = offline ? 0 : 8 + (seed % 110)
      return {
        id: `${b.userId}:${b.serverId}:${b.interfaceName}:${b.peerPublicKey ?? "_iface"}`,
        bindingId: b.id,
        userId: u.id,
        userLogin: u.login,
        userName: u.name,
        interfaceName: b.interfaceName,
        interfaceType: b.interfaceType,
        peerPublicKey: b.peerPublicKey,
        peerName: b.peerName,
        comment: b.comment,
        serverId: b.serverId,
        serverName: b.serverName,
        serverSite: b.serverSite,
        serverCountry: b.serverCountry,
        rxNow,
        txNow,
        rxPeak: rxNow + 40,
        txPeak: txNow + 28,
        rxTotal: offline ? 0 : +(rxNow / 8).toFixed(1),
        txTotal: offline ? 0 : +(txNow / 10).toFixed(1),
        rxSeries: offline ? Array(60).fill(0) : buildSeries(seed, rxNow, Math.max(4, rxNow / 4)),
        txSeries: offline ? Array(60).fill(0) : buildSeries(seed + 17, txNow, Math.max(3, txNow / 4)),
        status: (offline ? "offline" : "online") as "online" | "offline",
      }
    }),
  )
}

const boundIfaces: BoundIfaceTraffic[] = mockBoundFromUsers()

// ─── mock server data (with bound interfaces 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),
    boundIfaces: boundIfaces.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),
    boundIfaces: [],
  },
  {
    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),
    boundIfaces: [],
  },
  {
    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),
    boundIfaces: [],
  },
  {
    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),
    boundIfaces: [],
  },
  {
    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),
    boundIfaces: [],
  },
  {
    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),
    boundIfaces: boundIfaces.filter(c => c.serverId === "srv7"),
  },
]

// ─── user traffic (aggregated from bound interfaces) ──────────────────────────

function buildUserTraffic(
  id: string, login: string, displayName: string, role: string,
  clients: BoundIfaceTraffic[],
  active = true,
): 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, active, interfaces: clients, rxNow, txNow, rxPeak, txPeak, rxTotal, txTotal, rxSeries, txSeries }
}

const userTraffic: UserTraffic[] = INIT_USERS.map((u) =>
  buildUserTraffic(
    u.id,
    u.login,
    u.name,
    ROLE_LABEL[u.role],
    boundIfaces.filter((c) => c.userId === u.id),
    u.active,
  ),
)

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

/** Ключи совпадают с `rangeToMinutes` в API (`/api/traffic/...`). */
const TRAFFIC_RANGE_KEYS = ["5m", "15m", "1h", "4h", "24h"] as const
type Range = (typeof TRAFFIC_RANGE_KEYS)[number] | "30d"

const TRAFFIC_RANGE_LABELS: Record<(typeof TRAFFIC_RANGE_KEYS)[number], string> = {
  "5m": "5м",
  "15m": "15м",
  "1h": "1ч",
  "4h": "4ч",
  "24h": "24ч",
}

type GroupMode = "servers" | "users" | "ifaces" | "flows"
type FlowScope = "servers" | "users"
type SortField = "rx" | "tx" | "name" | "sessions"
type SortDir   = "asc" | "desc"

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

function StatChip({ icon, label, value }: { icon: 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="var(--chart-rx)" fillOpacity={0.12} />
      <polyline points={line(rx)} fill="none" stroke="var(--chart-rx)" strokeWidth="1.4" strokeLinejoin="round" />
      <path d={area(tx)} fill="var(--chart-tx)" fillOpacity={0.10} />
      <polyline points={line(tx)} fill="none" stroke="var(--chart-tx)" strokeWidth="1.4" strokeLinejoin="round" />
    </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">{fmtRate(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">{fmtRate(s.txNow)}</span>
        </div>
        {s.boundIfaces.length > 0 && (
          <div className="flex items-center gap-0.5 text-[10px] text-muted-foreground">
            <CableIcon className="size-3" />{s.boundIfaces.length}
          </div>
        )}
      </div>
    </button>
  )
}

function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean; onClick: () => void }) {
  const hasClients = u.interfaces.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.interfaces.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">{fmtRate(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">{fmtRate(u.txNow)}</span>
            </div>
          </div>
        </>
      ) : (
        <p className="text-[10px] text-muted-foreground">Нет привязанных интерфейсов</p>
      )}
    </button>
  )
}

function IfaceCard({ c, selected, onClick }: { c: BoundIfaceTraffic; 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">{boundIfaceLabel(c)}</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">{fmtRate(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">{fmtRate(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 IfaceRow({ c, showServer = false }: { c: BoundIfaceTraffic; 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",
    )}>
      <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">{boundIfaceLabel(c)}</span>
          <Badge variant={TYPE_VARIANT[c.interfaceType]} size="sm">{IFACE_TYPE_LABEL[c.interfaceType]}</Badge>
          {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.comment || c.userLogin}</p>
      </div>
      <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" />{fmtRate(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" />{fmtRate(c.txNow)}
        </div>
      </div>
      <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>
      <span className={cn(
        "shrink-0 text-[10px] px-1.5 py-0.5 rounded-full font-medium tabular-nums",
        c.status === "online" ? "bg-success/10 text-success" : "bg-muted text-muted-foreground",
      )}>
        {c.status === "online" ? "online" : "offline"}
      </span>
    </div>
  )
}

function BoundIfacesList({ clients, showServer = false }: { clients: BoundIfaceTraffic[]; showServer?: boolean }) {
  if (clients.length === 0)
    return <p className="text-xs text-muted-foreground py-1">Нет привязанных интерфейсов</p>
  return (
    <div className="flex flex-col gap-1.5">
      {clients.map((c) => <IfaceRow key={c.id} c={c} showServer={showServer} />)}
    </div>
  )
}

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

function DetailHeader({ range, setRange, children }: {
  range: Range; setRange: (r: Range) => void; children: 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">
        {TRAFFIC_RANGE_KEYS.map((r) => (
          <Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
            className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
            {TRAFFIC_RANGE_LABELS[r]}
          </Button>
        ))}
      </div>
    </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="var(--chart-rx)" 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="var(--chart-tx)" filled />
      </div>
    </div>
  )
}

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

function ServerDetail({
  sel, range, setRange, liveRx, liveTx, liveHint,
}: {
  sel: ServerTraffic
  range: Range
  setRange: (r: Range) => void
  liveRx?: number
  liveTx?: number
  liveHint?: string
}) {
  const rxNow = liveRx ?? sel.rxNow
  const txNow = liveTx ?? sel.txNow
  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>
      {sel.status === "offline" ? <OfflinePlaceholder /> : <TrafficRxTxChart rx={sel.rxSeries} tx={sel.txSeries} range={range} />}
      <div className="mt-4 pt-4 border-t">
        <KpiStatGrid
          aria-label="Скорость выбранного сервера"
          items={[
            {
              id: "rx-now",
              label: "RX сейчас",
              value: fmtRate(rxNow),
              hint: liveHint,
              icon: <ArrowDownIcon className="size-4" />,
              iconClassName: "text-success",
            },
            {
              id: "tx-now",
              label: "TX сейчас",
              value: fmtRate(txNow),
              hint: liveHint,
              icon: <ArrowUpIcon className="size-4" />,
              iconClassName: "text-info",
            },
            {
              id: "rx-peak",
              label: "Пик RX",
              value: fmtRate(sel.rxPeak),
              icon: <TrendingUpIcon className="size-4" />,
              iconClassName: "text-warning",
            },
            {
              id: "tx-peak",
              label: "Пик TX",
              value: fmtRate(sel.txPeak),
              icon: <TrendingDownIcon className="size-4" />,
              iconClassName: "text-warning",
            },
          ]}
        />
      </div>
      <TotalsRow rxTotal={sel.rxTotal} txTotal={sel.txTotal} rxSeries={sel.rxSeries} txSeries={sel.txSeries} />
      {sel.boundIfaces.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" />
            Привязанные интерфейсы
            <span className="text-xs text-muted-foreground font-normal">({sel.boundIfaces.length})</span>
          </h3>
          <BoundIfacesList clients={sel.boundIfaces} />
        </div>
      )}
    </>
  )
}

function UserDetail({ sel, range, setRange }: { sel: UserTraffic; range: Range; setRange: (r: Range) => void }) {
  const hasClients = sel.interfaces.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="Нет привязанных интерфейсов у этого пользователя" />
      ) : (
        <>
          <TrafficRxTxChart rx={sel.rxSeries} tx={sel.txSeries} range={range} />
          <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={fmtRate(sel.rxNow)} />
            <StatChip icon={<ArrowUpIcon    className="size-3.5 text-blue-500"    />} label="TX сейчас"    value={fmtRate(sel.txNow)} />
            <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик RX"       value={fmtRate(sel.rxPeak)} />
            <StatChip icon={<CableIcon      className="size-3.5 text-purple-500"  />} label="Интерфейсы" value={`${sel.interfaces.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" />
              Привязанные интерфейсы
              <span className="text-xs text-muted-foreground font-normal">({sel.interfaces.length})</span>
            </h3>
            <BoundIfacesList clients={sel.interfaces} showServer />
          </div>
        </>
      )}
    </>
  )
}

function IfaceDetail({ sel, range, setRange }: { sel: BoundIfaceTraffic; 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">{boundIfaceLabel(sel)}</h2>
          <p className="text-xs text-muted-foreground truncate">{sel.comment || sel.userLogin}</p>
        </div>
        <Badge variant={TYPE_VARIANT[sel.interfaceType]} size="sm">{IFACE_TYPE_LABEL[sel.interfaceType]}</Badge>
        <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>
      {sel.status === "offline" ? <OfflinePlaceholder /> : <TrafficRxTxChart rx={sel.rxSeries} tx={sel.txSeries} range={range} />}
      <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={fmtRate(sel.rxNow)} />
        <StatChip icon={<ArrowUpIcon    className="size-3.5 text-blue-500"    />} label="TX сейчас" value={fmtRate(sel.txNow)} />
        <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик RX"    value={fmtRate(sel.rxPeak)} />
        <StatChip icon={<TrendingUpIcon className="size-3.5 text-amber-500"   />} label="Пик TX"    value={fmtRate(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">Сервер</p>
          <p className="text-sm font-mono">{sel.serverName}</p>
        </div>
        <div>
          <p className="text-xs text-muted-foreground mb-0.5">Тип</p>
          <p className="text-sm">{IFACE_TYPE_LABEL[sel.interfaceType]}</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: ReactNode; label: string }> = [
  { mode: "servers", icon: <ServerIcon className="size-3" />, label: "Серверы" },
  { mode: "users",   icon: <UsersIcon  className="size-3" />, label: "Клиенты" },
  { mode: "ifaces",  icon: <CableIcon  className="size-3" />, label: "Интерфейсы" },
  { mode: "flows",   icon: <GitBranchIcon className="size-3" />, label: "Потоки" },
]

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", "users", "flows"] },
]

export default function TrafficPage() {
  const { mode, backendUrl } = useDataSource()
  const isLive = mode === "live"
  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>("1h")
  const [search, setSearch] = useState("")
  const [liveServers, setLiveServers] = useState<ServerTraffic[]>([])
  const [liveBusy, setLiveBusy] = useState(false)
  const [liveError, setLiveError] = useState<string | null>(null)
  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 [liveUsers, setLiveUsers] = useState<UserTraffic[]>([])
  const [liveBoundIfaces, setLiveBoundIfaces] = useState<BoundIfaceTraffic[]>([])
  const [flowStats, setFlowStats] = useState<FlowStatsDto | null>(null)
  const [flowScope, setFlowScope] = useState<FlowScope>("servers")
  const [flowExporters, setFlowExporters] = useState<FlowEntityCard[]>([])
  const [flowClients, setFlowClients] = useState<FlowEntityCard[]>([])
  const [flowAnalytics, setFlowAnalytics] = useState<FlowAnalyticsDto | null>(null)
  const [flowIface, setFlowIface] = useState("__all__")
  const [flowDedup, setFlowDedup] = useState(true)
  const [flowExcludeMesh, setFlowExcludeMesh] = useState(true)
  const [flowExcludeOverlay, setFlowExcludeOverlay] = useState(true)
  const [overlayOpen, setOverlayOpen] = useState(false)
  const [catalogServers, setCatalogServers] = useState<ServerRead[]>([])
  const effectiveMode: GroupMode = groupMode
  const { sample: liveSample, error: liveStreamError } = useTrafficLive({
    enabled: isLive && effectiveMode === "servers" && liveServers.some((s) => s.id === selectedId),
    backendUrl,
    serverId: selectedId,
    iface: selectedIface,
  })
  const flowLiveEnabled = isLive && effectiveMode === "flows" && Boolean(selectedId) && range === "5m"
  const { sample: flowLiveSample, error: flowLiveError } = useFlowLive({
    enabled: flowLiveEnabled,
    backendUrl,
    range,
    serverId: flowScope === "servers" ? selectedId : undefined,
    userId: flowScope === "users" ? selectedId : undefined,
    iface: flowIface,
    dedup: flowDedup,
    excludeMesh: flowExcludeMesh,
    excludeOverlay: flowExcludeOverlay,
  })

  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),
      boundIfaces: [],
    }
  }

  const toLiveBound = (c: BoundIfaceTraffic): BoundIfaceTraffic => ({
    ...c,
    interfaceType: (["ether", "gre", "wg", "other"].includes(c.interfaceType) ? c.interfaceType : "other") as InterfaceType,
    rxSeries: c.rxSeries.length ? c.rxSeries : Array(60).fill(0),
    txSeries: c.txSeries.length ? c.txSeries : Array(60).fill(0),
  })

  const toLiveUser = (u: UserTraffic & { interfaces?: BoundIfaceTraffic[] }): UserTraffic => {
    const ifaces = (u.interfaces ?? []).map(toLiveBound)
    return {
      ...u,
      role: ROLE_LABEL[u.role as keyof typeof ROLE_LABEL] ?? u.role,
      interfaces: ifaces,
      rxSeries: u.rxSeries.length ? u.rxSeries : Array(60).fill(0),
      txSeries: u.txSeries.length ? u.txSeries : Array(60).fill(0),
    }
  }

  const loadLiveTraffic = useCallback(async (targetRange: Range) => {
    if (!isLive) return
    setLiveBusy(true)
    setLiveError(null)
    try {
      const q = encodeURIComponent(targetRange === "30d" ? "24h" : targetRange)
      const [srvRes, usersRes, ifacesRes] = await Promise.all([
        apiFetch<{ servers: LiveTrafficServer[] }>(`/api/traffic/servers?range=${q}`),
        apiFetch<{ users: UserTraffic[] }>(`/api/traffic/users?range=${q}`),
        apiFetch<{ interfaces: BoundIfaceTraffic[] }>(`/api/traffic/bound-interfaces?range=${q}`),
      ])
      const liveIfaces = ifacesRes.interfaces.map(toLiveBound)
      const mapped: ServerTraffic[] = srvRes.servers.map((s) => {
        const base = toLiveServer(s)
        return { ...base, boundIfaces: liveIfaces.filter((i) => i.serverId === String(s.id)) }
      })
      const mappedUsers = usersRes.users.map(toLiveUser)
      setLiveServers(mapped)
      setLiveUsers(mappedUsers)
      setLiveBoundIfaces(liveIfaces)
      setSelectedId((prev) => {
        if (groupMode === "users") return mappedUsers.some((x) => x.id === prev) ? prev : (mappedUsers[0]?.id ?? "")
        if (groupMode === "ifaces") return liveIfaces.some((x) => x.id === prev) ? prev : (liveIfaces[0]?.id ?? "")
        return mapped.some((x) => x.id === prev) ? prev : (mapped[0]?.id ?? "")
      })
    } catch (e) {
      setLiveError(e instanceof Error ? e.message : "Не удалось загрузить live трафик")
    } finally {
      setLiveBusy(false)
    }
  }, [apiFetch, isLive, groupMode])

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

  const loadFlows = useCallback(async () => {
    if (!isLive) return
    setLiveBusy(true)
    setLiveError(null)
    try {
      const [stats, exporters, clients] = await Promise.all([
        getTrafficFlows(backendUrl, range),
        getFlowExporters(backendUrl, range),
        getFlowClients(backendUrl, range),
      ])
      setFlowStats(stats)
      setFlowExporters(exporters.exporters)
      setFlowClients(clients.clients)
      setSelectedId((prev) => {
        const list = flowScope === "users" ? clients.clients : exporters.exporters
        if (list.some((x) => x.id === prev)) return prev
        return list[0]?.id ?? ""
      })
    } catch (e) {
      setLiveError(e instanceof Error ? e.message : "Не удалось загрузить потоки")
    } finally {
      setLiveBusy(false)
    }
  }, [isLive, backendUrl, range, flowScope])

  useEffect(() => {
    if (!isLive || effectiveMode !== "flows") return
    void loadFlows()
  }, [isLive, effectiveMode, loadFlows])

  useEffect(() => {
    if (!isLive || effectiveMode !== "flows" || !selectedId) {
      setFlowAnalytics(null)
      return
    }
    if (range === "5m") {
      setFlowAnalytics(null)
      return
    }
    if (range === "30d") {
      const month = new Date().toISOString().slice(0, 7)
      void getFlowMonthly(backendUrl, {
        month,
        serverId: flowScope === "servers" ? selectedId : undefined,
      }).then((m) => setFlowAnalytics(monthlyToAnalytics(m))).catch(() => setFlowAnalytics(null))
      return
    }
    void getFlowAnalytics(backendUrl, {
      range,
      serverId: flowScope === "servers" ? selectedId : undefined,
      userId: flowScope === "users" ? selectedId : undefined,
      iface: flowIface,
      dedup: flowDedup,
      excludeMesh: flowExcludeMesh,
      excludeOverlay: flowExcludeOverlay,
    }).then(setFlowAnalytics).catch(() => setFlowAnalytics(null))
  }, [isLive, effectiveMode, selectedId, range, flowScope, flowIface, flowDedup, flowExcludeMesh, flowExcludeOverlay, backendUrl])

  useEffect(() => {
    setFlowIface("__all__")
  }, [selectedId, flowScope])

  useEffect(() => {
    if (!isLive) return
    void listServers(backendUrl).then(setCatalogServers).catch(() => setCatalogServers([]))
  }, [isLive, backendUrl])

  const activeServerTraffic = isLive ? liveServers : serverTraffic
  const activeUserTraffic = isLive ? liveUsers : userTraffic
  const activeBoundIfaces = isLive ? liveBoundIfaces : boundIfaces
  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),
        boundIfaces: liveBoundIfaces.filter((i) => i.serverId === selectedId),
      }))
      .catch(() => setLiveDetailServer(null))
      .finally(() => setDetailBusy(false))
  }, [isLive, effectiveMode, selectedId, range, selectedIface, apiFetch, liveBoundIfaces])

  const handleModeChange = (next: GroupMode) => {
    setGroupMode(next)
    if (next === "servers") {
      setSelectedId(activeServerTraffic[0]?.id ?? "srv1")
      if (range === "30d") setRange("1h")
    } else if (next === "users") {
      setSelectedId((isLive ? liveUsers : userTraffic)[0]?.id ?? "u1")
      if (range === "30d") setRange("1h")
    } else if (next === "ifaces") {
      setSelectedId((isLive ? liveBoundIfaces : boundIfaces)[0]?.id ?? "")
      if (range === "30d") setRange("1h")
    } else if (next === "flows") {
      setFlowScope("servers")
      setFlowIface("__all__")
      setRange("5m")
      setSelectedId(flowExporters[0]?.id ?? "")
    }
    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 [...activeUserTraffic]
      .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.interfaces.length - b.interfaces.length
        return sortDir === "desc" ? -v : v
      })
  }, [sortField, sortDir, q, activeUserTraffic])

  const sortedIfaces = useMemo(() => {
    return [...activeBoundIfaces]
      .filter(c => !q
        || c.interfaceName.toLowerCase().includes(q)
        || (c.peerName ?? "").toLowerCase().includes(q)
        || c.comment.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.interfaceName.localeCompare(b.interfaceName)
        return sortDir === "desc" ? -v : v
      })
  }, [sortField, sortDir, q, activeBoundIfaces])

  const flowCards = flowScope === "users" ? flowClients : flowExporters
  const sortedFlowCards = useMemo(() => {
    return [...flowCards]
      .filter((c) => !q || c.name.toLowerCase().includes(q) || c.subtitle.toLowerCase().includes(q) || c.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
      })
  }, [flowCards, q, sortField, sortDir])
  const selFlowCard = sortedFlowCards.find((c) => c.id === selectedId) ?? sortedFlowCards[0] ?? null
  const displayedFlow = flowLiveSample ?? flowAnalytics

  const selServer = useMemo(() => activeServerTraffic.find(s => s.id === selectedId) ?? activeServerTraffic[0], [selectedId, activeServerTraffic])
  const detailServer = liveDetailServer?.id === selectedId ? liveDetailServer : selServer
  const selUser   = useMemo(() => activeUserTraffic.find(u => u.id === selectedId)   ?? activeUserTraffic[0],   [selectedId, activeUserTraffic])
  const selIface  = useMemo(() => activeBoundIfaces.find(c => c.id === selectedId)    ?? activeBoundIfaces[0],    [selectedId, activeBoundIfaces])

  const kpiSource = effectiveMode === "users"
    ? activeUserTraffic
    : effectiveMode === "ifaces"
      ? activeBoundIfaces
      : activeServerTraffic
  const totalRx = kpiSource.reduce((a, s) => a + s.rxNow, 0)
  const totalTx = kpiSource.reduce((a, s) => a + s.txNow, 0)
  const peakRx  = kpiSource.reduce((a, s) => Math.max(a, s.rxPeak), 0)
  const peakTx  = kpiSource.reduce((a, s) => Math.max(a, s.txPeak), 0)

  const visibleSortFields = SORT_FIELDS.filter(s => !s.modesOnly || s.modesOnly.includes(effectiveMode))
  const ingestLine = flowIngestLine(flowStats)
  const collectorAlive = Boolean(flowStats?.listenerBound || flowStats?.packetsReceived)
  const flowError = liveError
    || (flowLiveError && !(collectorAlive && /live HTTP 500/.test(flowLiveError)) ? flowLiveError : null)
    || (displayedFlow?.degraded ? "Коллектор перегружен: упрощённая аналитика" : null)

  const flowKpiItems = [
    {
      id: "exporters",
      label: "Экспортёры",
      value: String(flowExporters.length),
      icon: <ServerIcon className="size-4" />,
      iconClassName: "text-info",
    },
    {
      id: "bps",
      label: "Скорость",
      value: displayedFlow ? fmtRate(displayedFlow.bpsNow / 1_000_000) : (flowStats ? fmtRate((flowStats.bytesPerMin * 8) / 1_000_000) : "—"),
      hint: displayedFlow?.live ? "live" : undefined,
      icon: <ActivityIcon className="size-4" />,
      iconClassName: "text-success",
    },
    {
      id: "src",
      label: "Уник. src",
      value: String(displayedFlow?.uniqueSrc ?? flowStats?.uniqueSrc ?? 0),
      icon: <ArrowUpIcon className="size-4" />,
      iconClassName: "text-muted-foreground",
    },
    {
      id: "proto",
      label: "Топ протокол",
      value: displayedFlow?.topProto ?? flowStats?.topProto ?? "—",
      icon: <GitBranchIcon className="size-4" />,
      iconClassName: "text-warning",
    },
  ]

  const counterKpiItems = [
              {
                id: "rx",
                label: "RX сейчас",
                value: fmtRate(totalRx),
                icon: <ArrowDownIcon className="size-4" />,
                iconClassName: "text-success",
              },
              {
                id: "tx",
                label: "TX сейчас",
                value: fmtRate(totalTx),
                icon: <ArrowUpIcon className="size-4" />,
                iconClassName: "text-info",
              },
              {
                id: "peak-rx",
                label: "Пик RX",
                value: fmtRate(peakRx),
                icon: <TrendingUpIcon className="size-4" />,
                iconClassName: "text-warning",
              },
              {
                id: "peak-tx",
                label: "Пик TX",
                value: fmtRate(peakTx),
                icon: <TrendingUpIcon className="size-4" />,
                iconClassName: "text-warning",
              },
            ]

  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Обзор" }, { label: "Трафик" }]}
        actions={
          <>
            <Button
              variant="outline"
              size="sm"
              onClick={() => {
                if (effectiveMode === "flows") void loadFlows()
                else void loadLiveTraffic(range)
              }}
              disabled={isLive && liveBusy}
            >
              <RefreshCwIcon className={cn("size-4", isLive && liveBusy && "animate-spin")} />Обновить
            </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.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">
          <KpiStatGrid
            aria-label="Сводка трафика"
            items={effectiveMode === "flows" ? flowKpiItems : counterKpiItems}
          />

          {effectiveMode === "flows" ? (
            <div className="flex flex-col gap-3">
              <div className="flex items-center justify-between gap-2 flex-wrap">
                <p className="text-xs text-muted-foreground font-mono truncate min-w-0">
                  {ingestLine ?? "IPFIX коллектор"}
                </p>
                <Button size="sm" onClick={() => setOverlayOpen(true)} disabled={!isLive}>
                  <PlusIcon className="size-4" />
                  Подключить JH
                </Button>
              </div>
              {flowError && (
                <div className="text-xs text-destructive bg-destructive/10 border border-destructive/20 rounded-md px-3 py-2">
                  {flowError}
                </div>
              )}
              <div className="grid grid-cols-[300px_1fr] gap-5 items-start">
                <div className="flex flex-col gap-3">
                  <div className="flex gap-1">
                    <button
                      type="button"
                      onClick={() => { setFlowScope("servers"); setFlowIface("__all__") }}
                      className={cn(
                        "text-[10px] px-2 py-0.5 rounded border transition-colors",
                        flowScope === "servers"
                          ? "border-primary bg-primary/10 text-primary font-medium"
                          : "border-border text-muted-foreground hover:text-foreground",
                      )}
                    >
                      Серверы
                    </button>
                    <button
                      type="button"
                      onClick={() => { setFlowScope("users"); setFlowIface("__all__") }}
                      className={cn(
                        "text-[10px] px-2 py-0.5 rounded border transition-colors",
                        flowScope === "users"
                          ? "border-primary bg-primary/10 text-primary font-medium"
                          : "border-border text-muted-foreground hover:text-foreground",
                      )}
                    >
                      Клиенты
                    </button>
                  </div>
                  <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>
                  <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}
                        type="button"
                        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>
                  <div className="flex flex-col gap-2">
                    {sortedFlowCards.map((card) => (
                      <FlowEntityCardView
                        key={card.id}
                        card={card}
                        selected={card.id === (selFlowCard?.id ?? selectedId)}
                        onClick={() => setSelectedId(card.id)}
                      />
                    ))}
                    {sortedFlowCards.length === 0 ? (
                      <p className="text-xs text-muted-foreground">
                        {flowEmptyHint(flowStats, collectorAlive) ?? "Нет экспортёров IPFIX. Подключите jump-host."}
                      </p>
                    ) : null}
                  </div>
                </div>
                <Frame dense className="w-full flex flex-col">
                  <FramePanel className="flex-1 px-5 pb-5 pt-5">
                    <FlowAnalyticsDetail
                      card={selFlowCard}
                      analytics={displayedFlow}
                      range={range}
                      onRange={(r) => setRange(r as Range)}
                      selectedIface={flowIface}
                      onIface={setFlowIface}
                      dedup={flowDedup}
                      onDedup={setFlowDedup}
                      excludeMesh={flowExcludeMesh}
                      onExcludeMesh={setFlowExcludeMesh}
                      excludeOverlay={flowExcludeOverlay}
                      onExcludeOverlay={setFlowExcludeOverlay}
                      liveHint={displayedFlow?.live ? "live" : undefined}
                      emptyHint={flowEmptyHint(flowStats, collectorAlive)}
                    />
                  </FramePanel>
                </Frame>
              </div>
              <FlowOverlaySheet
                open={overlayOpen}
                onOpenChange={setOverlayOpen}
                servers={catalogServers}
                backendUrl={backendUrl}
                onDone={() => { void loadFlows() }}
              />
            </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 === "ifaces" && sortedIfaces.map(c => (
                  <IfaceCard key={c.id} c={c} selected={c.id === selectedId} onClick={() => setSelectedId(c.id)} />
                ))}
              </div>
            </div>

            {/* ── detail panel ── */}
            <Frame dense className="w-full flex flex-col">
              <FramePanel 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, index) => {
                        const active = selectedIface === iface.name
                        return (
                          <button
                            key={`${iface.name}:${index}`}
                            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" && detailServer && (
                  <ServerDetail
                    sel={detailServer}
                    range={range}
                    setRange={setRange}
                    liveRx={liveSample?.rxMbps}
                    liveTx={liveSample?.txMbps}
                    liveHint={liveSample ? "live" : (liveStreamError ? "история" : undefined)}
                  />
                )}
                {effectiveMode === "users"   && selUser && <UserDetail sel={selUser} range={range} setRange={setRange} />}
                {effectiveMode === "ifaces"  && selIface && <IfaceDetail sel={selIface} range={range} setRange={setRange} />}
              </FramePanel>
            </Frame>

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