"use client"

import { useCallback, useEffect, useState, useMemo, useRef } from "react"
import Link from "next/link"
import { PageHeader } from "@/components/page-header"
import { DataPageCard } from "@/components/data-page-card"
import { RouteOptimizerWanMatrixDataGrid } from "@/components/data-grids/route-optimizer-wan-matrix-data-grid"
import { RouteOptimizerFullRoutesDataGrid } from "@/components/data-grids/route-optimizer-full-routes-data-grid"
import { RouteOptimizerCommRecsDataGrid } from "@/components/data-grids/route-optimizer-comm-recs-data-grid"
import { RouteOptimizerOspfPreviewDataGrid } from "@/components/data-grids/route-optimizer-ospf-preview-data-grid"
import { FormToggle } from "@/components/form-kit"
import { Frame, FramePanel } from "@/components/reui/frame"
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
import { OpsPanel } from "@/components/ops-panel"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Flag } from "@/components/flag"
import { cn } from "@/lib/utils"
import { requestJson } from "@/shared/api/http-client"
import { servers } from "@/lib/data"
import { useDataSource } from "@/lib/data-source"
import {
  type HomeRouter,
  type JumpHost,
  type ExitNode,
  type WanJhLeg,
  type JhExLeg,
  type FullRoute,
  type CommRec,
  type HomeEntry,
  type OptimizerData,
  type OptimizerSettings,
  type OptimizerApiServer,
  type RouteOptimizerSpeedProbe,
  buildLiveOptimizerData,
  DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,
  mapApiServersToTopology,
  readStoredRouteOptimizerSettings,
  ROUTE_OPTIMIZER_SETTINGS_STORAGE_KEY,
  calcRouteScore,
} from "@/lib/route-optimizer-data"
import {
  RefreshCwIcon, AlertCircleIcon, ArrowRightIcon,
  SettingsIcon, ChevronDownIcon, ChevronUpIcon, PinIcon,
  ZapIcon, PlayIcon, CheckCircleIcon, NetworkIcon, WifiIcon,
  MonitorIcon, ServerIcon,
  InfoIcon,
} from "lucide-react"

function makeApiFetch(backendUrl: string) {
  return async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
    return requestJson<T>(backendUrl, path, init)
  }
}

// ─── Topology derived from lib/data servers (mock) ───────────────────────────

const HOME_ROUTERS: HomeRouter[] = servers
  .filter(s => s.type === "home-router" && s.enabled)
  .map(s => ({
    id: s.id,
    label: s.name,
    site: s.site,
    country: s.country,
    model: s.model,
    ip: s.host,
    wans: (s.wanUplinks ?? []).map(w => ({
      id: w.id, name: w.name, isp: w.isp,
      iface: w.iface, ip: w.ip, maxDl: w.maxDl, maxUl: w.maxUl,
    })),
  }))

const JUMPHOSTS: JumpHost[] = servers
  .filter(s => s.type === "jump-host" && s.status !== "offline")
  .map(s => ({ id: s.id, label: s.name, site: s.site, country: s.country, ip: s.host }))

const EXIT_NODES: ExitNode[] = servers
  .filter(s => s.type === "exit-node" && s.status !== "offline")
  .map(s => ({ id: s.id, label: s.name, site: s.site, country: s.country, ip: s.host }))

const VRF_NAMES: string[] = ["main", ...Array.from(
  new Set(servers.flatMap(s => s.vrfNames ?? []))
).sort()]

// ─── Mock data generator ──────────────────────────────────────────────────────

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

function confidence(prob: number): "HIGH" | "MEDIUM" | "LOW" {
  return prob >= 55 ? "HIGH" : prob >= 30 ? "MEDIUM" : "LOW"
}

// Base latencies and bandwidths for each (WAN, JH) pair
const HW_BASE: Record<string, { ping: number; dl: number; ul: number; loss: number }> = {
  "w1a-jh1": { ping: 4,  dl: 480, ul: 420, loss: 0 },
  "w1a-jh2": { ping: 6,  dl: 480, ul: 420, loss: 0 },
  "w1b-jh1": { ping: 12, dl: 95,  ul: 90,  loss: 0 },
  "w1b-jh2": { ping: 14, dl: 94,  ul: 88,  loss: 0.5 },
  "w2a-jh1": { ping: 18, dl: 280, ul: 270, loss: 0 },
  "w2a-jh2": { ping: 20, dl: 278, ul: 265, loss: 0 },
  "w2b-jh1": { ping: 22, dl: 185, ul: 140, loss: 0 },
  "w2b-jh2": { ping: 25, dl: 182, ul: 138, loss: 1 },
}

// Base latencies for (JH, Exit) pairs
const JE_BASE: Record<string, { ping: number; dl: number; ul: number }> = {
  "jh1-ex1": { ping: 22,  dl: 880, ul: 820 },
  "jh1-ex2": { ping: 38,  dl: 740, ul: 700 },
  "jh1-ex3": { ping: 85,  dl: 580, ul: 540 },
  "jh1-ex4": { ping: 120, dl: 320, ul: 290 },
  "jh2-ex1": { ping: 28,  dl: 820, ul: 760 },
  "jh2-ex2": { ping: 42,  dl: 700, ul: 660 },
  "jh2-ex3": { ping: 90,  dl: 540, ul: 500 },
  "jh2-ex4": { ping: 130, dl: 300, ul: 270 },
}

function buildMockData(settings: OptimizerSettings): OptimizerData {
  const pw = settings.pingWeight

  // Pre-compute (JH, Exit) legs — shared across all homes
  const jhExLegs: JhExLeg[] = []
  for (const jh of JUMPHOSTS) {
    for (const ex of EXIT_NODES) {
      const base = JE_BASE[`${jh.id}-${ex.id}`]
      if (!base) continue
      jhExLegs.push({
        jhId: jh.id, exitId: ex.id,
        pingMs: jitter(base.ping, 4), dlMbps: jitter(base.dl, 30), ulMbps: jitter(base.ul, 25),
      })
    }
  }

  function getJhEx(jhId: string, exitId: string) {
    return jhExLegs.find(l => l.jhId === jhId && l.exitId === exitId)!
  }

  const homes: HomeEntry[] = HOME_ROUTERS.map((home) => {
    // (WAN × JH) legs
    const wanJhLegs: WanJhLeg[] = []
    for (const wan of home.wans) {
      for (const jh of JUMPHOSTS) {
        const base = HW_BASE[`${wan.id}-${jh.id}`]
        if (!base) continue
        const ping = jitter(base.ping, 3)
        const dl   = Math.min(wan.maxDl, jitter(base.dl, 20))
        const ul   = Math.min(wan.maxUl, jitter(base.ul, 15))
        wanJhLegs.push({
          wanId: wan.id, jhId: jh.id, pingMs: ping, dlMbps: dl, ulMbps: ul,
          loss: base.loss > 0 ? +(base.loss + (Math.random() - 0.5) * 0.5).toFixed(1) : 0,
          score: calcRouteScore(ping, dl, ul, pw),
        })
      }
    }

    // Full routes: every (WAN × JH × Exit) combo
    const fullRoutes: FullRoute[] = []
    for (const wan of home.wans) {
      for (const jh of JUMPHOSTS) {
        for (const ex of EXIT_NODES) {
          const hw = wanJhLegs.find(l => l.wanId === wan.id && l.jhId === jh.id)
          const je = getJhEx(jh.id, ex.id)
          if (!hw || !je) continue
          const totalPing = hw.pingMs + je.pingMs
          const dl = Math.min(hw.dlMbps, je.dlMbps)
          const ul = Math.min(hw.ulMbps, je.ulMbps)
          const score = calcRouteScore(totalPing, dl, ul, pw)
          fullRoutes.push({
            id: `${home.id}-${wan.id}-${jh.id}-${ex.id}`,
            homeId: home.id, wan, jh, exit: ex, hw, je,
            score,
            confidence: confidence(score),
            probabilityOptimal: 0, // assigned after ranking
          })
        }
      }
    }

    // Assign probabilityOptimal proportional to score rank
    const sorted = [...fullRoutes].sort((a, b) => b.score - a.score)
    const topScore = sorted[0]?.score ?? 1
    sorted.forEach((r, i) => {
      r.probabilityOptimal = Math.max(1, Math.round((topScore - i * 6 + jitter(0, 3)) * (i === 0 ? 1 : 0.85 ** i)))
    })
    sorted.sort((a, b) => b.probabilityOptimal - a.probabilityOptimal)

    // BGP community recommendations (per home)
    const best = sorted[0] ?? null
    const commRecs: CommRec[] = [
      { community: "65001:100", communityName: "youtube-bypass",
        current:     { wan: "WAN1-RT", jh: "mt-msk-core-01", exit: "mt-spb-edge-01", gateway: "10.0.1.1", prob: jitter(41, 5) },
        recommended: best ? { wan: best.wan.name, jh: best.jh.label, exit: best.exit.label, gateway: best.je.pingMs < 30 ? "10.0.1.1" : "10.0.2.1", prob: jitter(55, 5) } : null,
        shouldSwitch: false, pinnedBySettings: false,
      },
      { community: "65001:200", communityName: "streaming-eu",
        current:     { wan: "WAN1-RT", jh: "mt-msk-core-01", exit: "mt-fra-edge-01", gateway: "10.0.2.1", prob: jitter(70, 5) },
        recommended: { wan: "WAN1-RT", jh: "mt-msk-core-01", exit: "mt-fra-edge-01", gateway: "10.0.2.1", prob: jitter(74, 4) },
        shouldSwitch: false, pinnedBySettings: false,
      },
      { community: "65001:300", communityName: "cdn-bypass",
        current:     { wan: "WAN2-BL", jh: "mt-msk-core-01", exit: "mt-fra-edge-01", gateway: "10.0.2.1", prob: jitter(28, 5) },
        recommended: best ? { wan: best.wan.name, jh: best.jh.label, exit: "mt-spb-edge-01", gateway: "10.0.1.1", prob: jitter(62, 5) } : null,
        shouldSwitch: true, pinnedBySettings: false,
      },
    ].map(r => ({
      ...r,
      shouldSwitch: r.shouldSwitch &&
        ((r.recommended?.prob ?? 0) - (r.current?.prob ?? 0)) >= settings.switchThreshold,
    }))

    return { home, wanJhLegs, fullRoutes: sorted, bestRoute: sorted[0] ?? null, commRecs }
  })

  return { updatedAt: new Date().toLocaleTimeString("ru-RU"), homes }
}

// ─── Small UI helpers ─────────────────────────────────────────────────────────

function Chip({ children, color }: { children: React.ReactNode; color?: string }) {
  return (
    <span className={cn(
      "inline-flex items-center rounded px-1.5 py-0.5 text-[11px] font-medium border",
      color ?? "bg-muted text-muted-foreground border-border",
    )}>{children}</span>
  )
}

function ProbChip({ prob, best }: { prob: number; best?: boolean }) {
  return (
    <Chip color={best
      ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20"
      : "bg-muted text-muted-foreground border-border"
    }>{prob}%</Chip>
  )
}

function ConfChip({ conf }: { conf: string }) {
  const map: Record<string, string> = {
    HIGH:   "bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20",
    MEDIUM: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
    LOW:    "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
  }
  return <Chip color={map[conf] ?? map.LOW}>{conf}</Chip>
}

function LossChip({ loss }: { loss: number }) {
  if (loss === 0) return <span className="text-emerald-600 dark:text-emerald-400 text-[11px] font-mono">0%</span>
  return (
    <span className={cn("text-[11px] font-mono", loss > 1 ? "text-red-500" : "text-amber-500")}>
      {loss}%
    </span>
  )
}

function NInput({ value, onChange, min, max }: { value: number; onChange: (v: number) => void; min?: number; max?: number }) {
  return (
    <Input type="number" value={value} min={min} max={max}
      onChange={e => onChange(Math.max(min ?? 0, Math.min(max ?? 9999, parseInt(e.target.value) || 0)))}
      className="w-16 h-7 text-xs font-mono text-right" />
  )
}

function SettingRow({ label, unit, children }: { label: string; unit?: string; children: React.ReactNode }) {
  return (
    <div className="flex items-center justify-between gap-3">
      <span className="text-sm text-muted-foreground">{label}</span>
      <div className="flex items-center gap-1.5 shrink-0">
        {children}
        {unit && <span className="text-xs text-muted-foreground">{unit}</span>}
      </div>
    </div>
  )
}

// ─── Home Router card ─────────────────────────────────────────────────────────

type HomeTab = "wan-matrix" | "full-routes" | "bgp-community"

function HomeRouterCard({ entry, jumpHosts, settings, pinned, applied, applying, onPin, onApply }: {
  entry: HomeEntry
  jumpHosts: JumpHost[]
  settings: OptimizerSettings
  pinned: Set<string>
  applied: Set<string>
  applying: Set<string>
  onPin: (k: string) => void
  onApply: (comm: string, homeId: string) => void
}) {
  const [tab, setTab] = useState<HomeTab>("wan-matrix")
  const { home, wanJhLegs, fullRoutes, bestRoute, commRecs } = entry
  const switchCount = commRecs.filter(r => r.shouldSwitch && !pinned.has(`${home.id}::${r.community}`)).length

  return (
    <Frame dense className="w-full overflow-hidden">
      <FramePanel className="p-0 overflow-hidden">
      {/* Header */}
      <div className="flex items-center gap-3 px-4 py-3 border-b flex-wrap">
        <MonitorIcon className="size-4 text-muted-foreground shrink-0" />
        <div>
          <div className="flex items-center gap-2">
            <Flag code={home.country} />
            <span className="text-sm font-semibold font-mono">{home.label}</span>
            <span className="text-xs text-muted-foreground">{home.site}</span>
            <span className="text-[10px] text-muted-foreground border border-border rounded px-1.5 py-0.5 font-mono">{home.model}</span>
          </div>
          <div className="text-[11px] text-muted-foreground font-mono mt-0.5">
            {home.wans.map(w => `${w.name} (${w.isp} · ${w.ip})`).join(" · ")}
          </div>
        </div>

        {/* Best route summary */}
        {bestRoute && (
          <div className="flex items-center gap-1.5 ml-2 text-xs text-muted-foreground">
            <span className="font-mono font-semibold text-sky-600 dark:text-sky-400">{bestRoute.wan.name}</span>
            <ArrowRightIcon className="size-3" />
            <span>{bestRoute.jh.label}</span>
            <ArrowRightIcon className="size-3" />
            <span className="flex items-center gap-1"><Flag code={bestRoute.exit.country} />{bestRoute.exit.label}</span>
          </div>
        )}

        <div className="ml-auto flex items-center gap-2">
          {switchCount > 0 && (
            <Chip color="bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20">
              {switchCount} переключений
            </Chip>
          )}
          {bestRoute && (
            <>
              <ConfChip conf={bestRoute.confidence} />
              <ProbChip prob={bestRoute.probabilityOptimal} best />
            </>
          )}
        </div>
      </div>

      {/* Sub-tabs */}
      <div className="flex items-center gap-0 border-b bg-muted/20 px-1">
        {([
          { id: "wan-matrix",     label: `WAN × JH (${home.wans.length}×${jumpHosts.length})` },
          { id: "full-routes",    label: `Маршруты (${fullRoutes.length})` },
          { id: "bgp-community",  label: `BGP community (${commRecs.length})` },
        ] as { id: HomeTab; label: string }[]).map(t => (
          <button key={t.id} onClick={() => setTab(t.id)}
            className={cn(
              "px-4 py-2 text-xs font-medium border-b-2 transition-colors -mb-px",
              tab === t.id
                ? "border-foreground text-foreground"
                : "border-transparent text-muted-foreground hover:text-foreground",
            )}>
            {t.label}
          </button>
        ))}
      </div>

      {/* Tab content */}
      {tab === "wan-matrix" && (
        <RouteOptimizerWanMatrixDataGrid home={home} legs={wanJhLegs} jumpHosts={jumpHosts} />
      )}
      {tab === "full-routes" && (
        <RouteOptimizerFullRoutesDataGrid routes={fullRoutes} bestId={bestRoute?.id} />
      )}
      {tab === "bgp-community" && (
        <RouteOptimizerCommRecsDataGrid
          recs={commRecs}
          homeId={home.id}
          pinned={pinned} applied={applied} applying={applying}
          onPin={onPin} onApply={onApply}
        />
      )}
    </FramePanel>
    </Frame>
  )
}

// ─── Topology legend ──────────────────────────────────────────────────────────

function TopologyBar() {
  return (
    <div className="flex items-center gap-3 text-xs text-muted-foreground flex-wrap">
      <div className="flex items-center gap-1.5">
        <MonitorIcon className="size-3.5" />
        <span>Home Router</span>
      </div>
      <span className="text-border">→</span>
      <div className="flex items-center gap-1.5">
        <WifiIcon className="size-3.5 text-sky-500" />
        <span>WAN1 / WAN2</span>
      </div>
      <span className="text-border">→</span>
      <div className="flex items-center gap-1.5">
        <ServerIcon className="size-3.5 text-violet-500" />
        <span>JumpHost</span>
      </div>
      <span className="text-border">→</span>
      <div className="flex items-center gap-1.5">
        <NetworkIcon className="size-3.5 text-emerald-500" />
        <span>Exit Node</span>
      </div>
    </div>
  )
}

// ════════════════════════════════════════════════════════════════════════════

export default function RouteOptimizerPage() {
  const { mode, backendUrl, backendStatus } = useDataSource()
  /** Данные из API/БД при режиме «Живые»; не ждём /health — иначе до ответа показывались моки. */
  const useLiveData = mode === "live"
  const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])

  const [prefsLoaded, setPrefsLoaded] = useState(false)

  const [loading, setLoading] = useState(true)
  const [data, setData] = useState<OptimizerData | null>(null)
  const [error, setError] = useState("")
  const [settings, setSettings] = useState<OptimizerSettings>(DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS)
  const settingsRef = useRef(settings)
  useEffect(() => {
    settingsRef.current = settings
  }, [settings])

  useEffect(() => {
    queueMicrotask(() => {
      setSettings(readStoredRouteOptimizerSettings())
      setPrefsLoaded(true)
    })
  }, [])

  useEffect(() => {
    if (!prefsLoaded) return
    try {
      localStorage.setItem(ROUTE_OPTIMIZER_SETTINGS_STORAGE_KEY, JSON.stringify(settings))
    } catch {
      /* ignore quota */
    }
  }, [settings, prefsLoaded])

  const [liveJumpHosts, setLiveJumpHosts] = useState<JumpHost[]>([])
  const [liveExitNodes, setLiveExitNodes] = useState<ExitNode[]>([])
  const [liveServers, setLiveServers] = useState<OptimizerApiServer[]>([])

  const [showSettings, setShowSettings] = useState(false)
  const [pinned, setPinned] = useState<Set<string>>(new Set())
  const [applied, setApplied] = useState<Set<string>>(new Set())
  const [applying, setApplying] = useState<Set<string>>(new Set())
  const [ecmpEnabled, setEcmpEnabled] = useState(false)
  const [ecmpMaxPaths, setEcmpMaxPaths] = useState(4)
  const [ecmpAlgo, setEcmpAlgo] = useState<"per-dst" | "per-conn" | "per-packet">("per-dst")
  const [rpfMode, setRpfMode] = useState<"disabled" | "loose" | "strict">("disabled")
  const [selectedVrf, setSelectedVrf] = useState("main")
  const [ospfServerId, setOspfServerId] = useState("")
  const [ospfApplying, setOspfApplying] = useState(false)
  const [ospfApplyResult, setOspfApplyResult] = useState<{ serverName: string; optimizedCount: number } | null>(null)
  const [ospfApplyError, setOspfApplyError] = useState("")
  const [ospfMeta, setOspfMeta] = useState<{ interfaces: number; areas: number; serverName: string } | null>(null)
  const [ospfPreviewError, setOspfPreviewError] = useState("")
  const [ospfPreview, setOspfPreview] = useState<{
    changedCount: number
    interfacesTotal: number
    interfaces: Array<{
      interface: string
      currentCost: number
      optimalCost: number
      score: number
      pingMs: number
      dlMbps: number
      ulMbps: number
    }>
    changes: Array<{
      interface: string
      currentCost: number
      optimalCost: number
      score: number
      pingMs: number
      dlMbps: number
      ulMbps: number
    }>
  } | null>(null)

  const load = useCallback(async (override?: OptimizerSettings) => {
    const s = override ?? settingsRef.current
    setLoading(true)
    setError("")
    try {
      if (!useLiveData) {
        await new Promise((r) => setTimeout(r, 450))
        setData(buildMockData(s))
        setLiveJumpHosts([])
        setLiveExitNodes([])
        setLiveServers([])
        return
      }
      const rows = await apiFetch<OptimizerApiServer[]>("/api/servers")
      const enabledRows = rows.filter((r) => r.enabled)
      setLiveServers(enabledRows)
      setOspfServerId((prev) => (prev && enabledRows.some((r) => String(r.id) === prev) ? prev : String(enabledRows[0]?.id ?? "")))
      const { jumpHosts, exitNodes } = mapApiServersToTopology(rows)
      setLiveJumpHosts(jumpHosts)
      setLiveExitNodes(exitNodes)

      let rulesets: Array<{ serverId: string; rules: import("@/lib/data").FilterRule[] }> | null = null
      try {
        const fr = await apiFetch<{ rulesets: Array<{ serverId: string; rules: import("@/lib/data").FilterRule[] }> }>(
          "/api/filters/rules",
        )
        rulesets = fr.rulesets ?? null
      } catch {
        rulesets = null
      }
      let speedProbes: RouteOptimizerSpeedProbe[] = []
      try {
        const sp = await apiFetch<{ probes: RouteOptimizerSpeedProbe[] }>("/api/uptime/speed-probes")
        speedProbes = sp.probes ?? []
      } catch {
        speedProbes = []
      }

      setData(buildLiveOptimizerData(rows, rulesets, s, speedProbes))
    } catch (e) {
      setError(e instanceof Error ? e.message : "Ошибка загрузки данных")
      setData(null)
      setLiveJumpHosts([])
      setLiveExitNodes([])
      setLiveServers([])
    } finally {
      setLoading(false)
    }
  }, [apiFetch, useLiveData])

  useEffect(() => {
    if (!prefsLoaded) return
    queueMicrotask(() => {
      void load()
    })
  }, [load, useLiveData, prefsLoaded])

  const pollMs = useMemo(() => {
    if (!useLiveData) return 30_000
    const m = Math.min(Math.max(settings.probeIntervalMin, 1), 30)
    return m * 60_000
  }, [useLiveData, settings.probeIntervalMin])

  useEffect(() => {
    if (!prefsLoaded) return
    const id = setInterval(() => {
      void load()
    }, pollMs)
    return () => clearInterval(id)
  }, [load, pollMs, prefsLoaded])

  function togglePin(key: string) {
    setPinned(prev => { const n = new Set(prev); if (n.has(key)) n.delete(key); else n.add(key); return n })
  }

  function applyRec(community: string, homeId: string) {
    const key = `${homeId}::${community}`
    setApplying(prev => new Set(prev).add(key))
    setTimeout(() => {
      setApplied(prev => new Set(prev).add(key))
      setApplying(prev => { const n = new Set(prev); n.delete(key); return n })
    }, 1200)
  }

  const totalSwitches = useMemo(
    () =>
      data?.homes.flatMap((h) =>
        h.commRecs.filter(
          (r) => r.shouldSwitch && !pinned.has(`${h.home.id}::${r.community}`),
        ),
      ).length ?? 0,
    [data, pinned],
  )

  const jumpHostsForCards = useLiveData ? liveJumpHosts : JUMPHOSTS

  const statsChips = useMemo(() => {
    const homeCount = useLiveData ? (data?.homes.length ?? 0) : HOME_ROUTERS.length
    const wanCount = useLiveData
      ? (data?.homes.reduce((s, h) => s + h.home.wans.length, 0) ?? 0)
      : HOME_ROUTERS.reduce((s, h) => s + h.wans.length, 0)
    const jh = useLiveData ? liveJumpHosts : JUMPHOSTS
    const ex = useLiveData ? liveExitNodes : EXIT_NODES
    const jhSub = jh.length ? jh.map((j) => j.site).join(" · ") : "—"
    const exSub = ex.length ? ex.map((e) => e.site).join(" · ") : "—"
    return [
      {
        label: "Home роутеров",
        value: homeCount,
        sub: `${wanCount} WAN-аплинков`,
        icon: <MonitorIcon className="size-4" />,
      },
      {
        label: "JumpHost",
        value: jh.length,
        sub: jhSub,
        icon: <ServerIcon className="size-4" />,
      },
      {
        label: "Exit Node",
        value: ex.length,
        sub: exSub,
        icon: <NetworkIcon className="size-4" />,
      },
      {
        label: "Переключений",
        value: totalSwitches,
        sub: totalSwitches > 0 ? "требуют применения" : "всё оптимально",
        icon: <ZapIcon className="size-4" />,
      },
    ]
  }, [useLiveData, data, liveJumpHosts, liveExitNodes, totalSwitches])

  function applyAll() {
    data?.homes.forEach(h =>
      h.commRecs
        .filter(r => r.shouldSwitch && !pinned.has(`${h.home.id}::${r.community}`))
        .forEach(r => applyRec(r.community, h.home.id))
    )
  }

  async function applyOspfOptimization() {
    if (!ospfServerId) return
    setOspfApplying(true)
    setOspfApplyError("")
    setOspfApplyResult(null)
    try {
      const res = await apiFetch<{ serverName: string; optimizedCount: number }>(
        `/api/servers/${ospfServerId}/ospf/optimize`,
        {
          method: "POST",
          body: JSON.stringify({ pingWeight: settings.pingWeight }),
        },
      )
      setOspfApplyResult({
        serverName: res.serverName,
        optimizedCount: res.optimizedCount,
      })
      await load()
      setOspfPreview((prev) => (prev ? { ...prev, changedCount: 0, changes: [] } : prev))
    } catch (e) {
      setOspfApplyError(e instanceof Error ? e.message : "Ошибка применения OSPF-оптимизации")
    } finally {
      setOspfApplying(false)
    }
  }

  const set = <K extends keyof OptimizerSettings>(k: K, v: OptimizerSettings[K]) =>
    setSettings(prev => ({ ...prev, [k]: v }))

  useEffect(() => {
    if (!useLiveData || !ospfServerId) return
    let cancelled = false
    void apiFetch<{
      interfaces: Array<{ areaId: string; interface: string }>
      instances: Array<unknown>
      neighbors: Array<unknown>
      bfdSessions: Array<unknown>
    }>(`/api/servers/${ospfServerId}/ospf`)
      .then((data) => {
        if (cancelled) return
        const selected = liveServers.find((s) => String(s.id) === ospfServerId)
        const visibleInterfaces = data.interfaces.filter((i) => !/^\(ref\s+\*.+\)$/.test(i.interface.trim()))
        setOspfMeta({
          interfaces: visibleInterfaces.length,
          areas: new Set(visibleInterfaces.map((i) => i.areaId)).size,
          serverName: selected?.name || selected?.host || ospfServerId,
        })
      })
      .catch(() => {
        if (cancelled) return
        setOspfMeta(null)
      })
    return () => { cancelled = true }
  }, [apiFetch, liveServers, ospfServerId, useLiveData])

  useEffect(() => {
    if (!useLiveData || !ospfServerId) return
    let cancelled = false
    void apiFetch<{
      changedCount: number
      interfacesTotal: number
      interfaces: Array<{
        interface: string
        currentCost: number
        optimalCost: number
        score: number
        pingMs: number
        dlMbps: number
        ulMbps: number
      }>
      changes: Array<{
        interface: string
        currentCost: number
        optimalCost: number
        score: number
        pingMs: number
        dlMbps: number
        ulMbps: number
      }>
    }>(
      `/api/servers/${ospfServerId}/ospf/optimize/preview`,
      { method: "POST", body: JSON.stringify({ pingWeight: settings.pingWeight }) },
    )
      .then((data) => {
        if (cancelled) return
        setOspfPreviewError("")
        setOspfPreview(data)
      })
      .catch((e) => {
        if (cancelled) return
        setOspfPreview(null)
        setOspfPreviewError(e instanceof Error ? e.message : "Ошибка preview OSPF")
      })
    return () => { cancelled = true }
  }, [apiFetch, ospfServerId, settings.pingWeight, useLiveData])

  const ospfPreviewLoading = useLiveData && Boolean(ospfServerId) && !ospfPreview && !ospfPreviewError

  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Инструменты" }, { label: "Оптимизатор маршрутов" }]}
        actions={
          <>
            {totalSwitches > 0 && (
              <Button size="sm" onClick={applyAll}>
                <ZapIcon className="size-4" />Применить все ({totalSwitches})
              </Button>
            )}
            <Button variant="outline" size="sm" onClick={() => void load()} disabled={loading}>
              <RefreshCwIcon className={cn("size-4", loading && "animate-spin")} />
              {loading ? "Расчёт…" : "Обновить"}
            </Button>
          </>
        }
      />

      <div className="flex-1 overflow-y-auto p-6">
        <div className="flex flex-col gap-4">

          {/* Status row */}
          <div className="flex items-center gap-3 text-xs text-muted-foreground flex-wrap px-0.5">
            <TopologyBar />
            <span className="text-border">·</span>
            <span>Обновлено: {data?.updatedAt ?? "—"}</span>
            <span className="text-border">·</span>
            <span>Авто {useLiveData ? `${settings.probeIntervalMin} мин` : "30 с"}</span>
            <Chip>{useLiveData ? "Живые данные · API" : "Демо · mock"}</Chip>
            {useLiveData && backendStatus === false && (
              <span className="text-amber-600 dark:text-amber-400">
                бекенд не отвечает на /health — проверьте URL в настройках
              </span>
            )}
          </div>

          <KpiStatGrid
            aria-label="Сводка оптимизатора"
            items={statsChips.map((s, i) => ({
              id: `ro-${i}`,
              label: s.label,
              value: s.value,
              hint: s.sub,
              icon: s.icon,
              iconClassName: s.label === "Переключений" && totalSwitches > 0
                ? "text-warning"
                : s.label === "Exit Node"
                  ? "text-success"
                  : s.label === "JumpHost"
                    ? "text-primary"
                    : "text-muted-foreground",
              variant: s.label === "Переключений" && totalSwitches > 0 ? "warning" as const : "default" as const,
            }))}
          />

          {error && (
            <div className="flex items-center gap-2 rounded-lg border border-destructive/30 bg-destructive/10 px-4 py-2.5 text-sm text-destructive">
              <AlertCircleIcon className="size-4 shrink-0" />{error}
            </div>
          )}

          {loading && !data && (
            <div className="text-muted-foreground text-sm py-8 text-center animate-pulse">
              Расчёт оптимальных маршрутов…
            </div>
          )}

          {/* Settings */}
          <Frame dense className="w-full">
            <FramePanel className="p-0">
            <div
              className={cn("flex items-center gap-3 px-4 py-3 cursor-pointer select-none", showSettings && "border-b")}
              onClick={() => setShowSettings(v => !v)}
            >
              <SettingsIcon className="size-4 text-muted-foreground shrink-0" />
              <span className="text-sm font-medium flex-1">Настройки оптимизатора</span>
              <span className="text-[11px] text-muted-foreground mr-2">
                порог {settings.switchThreshold}% · ping {settings.pingWeight}% · зондирование {settings.probeIntervalMin} мин
              </span>
              {showSettings ? <ChevronUpIcon className="size-4 text-muted-foreground" /> : <ChevronDownIcon className="size-4 text-muted-foreground" />}
            </div>

            {showSettings && (
              <div className="py-4 px-5">
                <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
                  <div className="flex flex-col gap-3">
                    <p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Пороги переключения</p>
                    <SettingRow label="Мин. выигрыш" unit="%">
                      <NInput value={settings.switchThreshold} onChange={v => set("switchThreshold", v)} min={0} max={50} />
                    </SettingRow>
                    <SettingRow label="Гистерезис" unit="%">
                      <NInput value={settings.hysteresisThreshold} onChange={v => set("hysteresisThreshold", v)} min={0} max={50} />
                    </SettingRow>
                  </div>
                  <div className="flex flex-col gap-3">
                    <p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Веса метрик</p>
                    <SettingRow label="Вес задержки" unit="%">
                      <NInput value={settings.pingWeight} onChange={v => set("pingWeight", Math.min(100, v))} min={0} max={100} />
                    </SettingRow>
                    <div className="h-1.5 rounded-full bg-muted overflow-hidden">
                      <div className="h-full rounded-full bg-sky-500 transition-all" style={{ width: `${settings.pingWeight}%` }} />
                    </div>
                    <div className="flex justify-between text-[10px] text-muted-foreground">
                      <span>Ping {settings.pingWeight}%</span>
                      <span>BW {100 - settings.pingWeight}%</span>
                    </div>
                    <SettingRow label="Интервал зондирования">
                      <div className="flex rounded-md border border-input overflow-hidden h-7">
                        {[5, 10, 15, 30].map(v => (
                          <button key={v} onClick={() => set("probeIntervalMin", v)}
                            className={cn("px-2.5 text-xs transition-colors cursor-pointer",
                              settings.probeIntervalMin === v ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted")}>
                            {v}м
                          </button>
                        ))}
                      </div>
                    </SettingRow>
                  </div>
                  <div className="flex flex-col gap-3">
                    <p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Автоприменение</p>
                    <div className="flex items-center justify-between">
                      <span className="text-sm">Применять автоматически</span>
                      <FormToggle checked={settings.autoApply} onChange={v => set("autoApply", v)} />
                    </div>
                    {settings.autoApply && (
                      <>
                        <SettingRow label="Интервал (мин)">
                          <NInput value={settings.autoApplyIntervalMin} onChange={v => set("autoApplyIntervalMin", v)} min={5} max={1440} />
                        </SettingRow>
                        <div className="flex items-center gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-[11px] text-amber-600 dark:text-amber-400">
                          <AlertCircleIcon className="size-3.5 shrink-0" />
                          Изменения каждые {settings.autoApplyIntervalMin} мин
                        </div>
                      </>
                    )}
                  </div>
                </div>
                <div className="flex justify-end gap-2 mt-5 pt-4 border-t">
                  <Button variant="outline" size="sm" onClick={() => setSettings({ ...DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS })}>Сбросить</Button>
                  <Button size="sm" onClick={() => void load(settings)}>
                    <CheckCircleIcon className="size-4" />Применить
                  </Button>
                </div>
                <p className="text-[10px] text-muted-foreground mt-3">
                  Базовые значения совпадают с разделом{" "}
                  <Link href="/settings#route-ai" className="text-primary underline-offset-2 hover:underline">Настройки → Route AI</Link>
                  .
                </p>
              </div>
            )}
          </FramePanel>
          </Frame>

          {/* OSPF optimization from Route Optimizer */}
          <OpsPanel
            title="OSPF"
            description={`Route AI weight: ping ${settings.pingWeight}%`}
            contentClassName="px-5 py-4 flex flex-col gap-2.5"
          >

              {!useLiveData && (
                <p className="text-xs text-muted-foreground">
                  Доступно только в режиме живых данных.
                </p>
              )}

              {useLiveData && (
                <>
                  <div className="rounded-lg border bg-muted/15 overflow-hidden">
                    <div className="flex items-center gap-2 px-3 py-2 border-b bg-background/80 flex-wrap">
                      <select
                        className="text-xs bg-background text-foreground border border-input rounded-md px-2 py-1 h-7 min-w-64 focus:outline-none focus:ring-1 focus:ring-ring"
                        value={ospfServerId}
                        onChange={(e) => setOspfServerId(e.target.value)}
                      >
                        {liveServers.length === 0 && <option value="">Нет доступных серверов</option>}
                        {liveServers.map((s) => (
                          <option key={s.id} value={String(s.id)}>
                            {s.name || s.host} ({s.host})
                          </option>
                        ))}
                      </select>
                      <span className="text-[11px] text-muted-foreground">
                        {ospfMeta ? `${ospfMeta.interfaces} iface · ${ospfMeta.areas} area` : "сбор OSPF-метрик…"}
                      </span>
                      <Button
                        size="sm"
                        className="ml-auto h-7 text-xs"
                        onClick={() => void applyOspfOptimization()}
                        disabled={!ospfServerId || ospfApplying}
                      >
                        <ZapIcon className={cn("size-3.5", ospfApplying && "animate-pulse")} />
                        {ospfApplying ? "Оптимизация…" : "Оптимизировать OSPF"}
                      </Button>
                    </div>

                    <div className="px-3 py-2 text-xs grid grid-cols-1 md:grid-cols-4 gap-2">
                      <div className="text-muted-foreground">Router</div>
                      <div className="md:col-span-2 font-mono truncate">{ospfMeta?.serverName ?? "—"}</div>
                      <div className="text-right text-muted-foreground">
                        apply: {ospfApplyResult?.optimizedCount ?? 0}
                      </div>
                    </div>
                  </div>

                  <div className="rounded-lg border bg-background/70 overflow-hidden">
                    <div className="px-3 py-2 border-b text-[11px] text-muted-foreground flex items-center justify-between">
                      <span>Preview изменений OSPF cost (до применения)</span>
                      <span>
                        {ospfPreviewLoading
                          ? "расчёт…"
                          : ospfPreview
                            ? `${ospfPreview.changedCount} из ${ospfPreview.interfacesTotal} изменятся`
                            : "нет данных"}
                      </span>
                    </div>
                    <RouteOptimizerOspfPreviewDataGrid
                      rows={(ospfPreview?.interfaces ?? []).map((row) => ({
                        id: `${row.interface}-${row.currentCost}-${row.optimalCost}`,
                        ...row,
                      }))}
                      error={ospfPreviewError || null}
                      loading={ospfPreviewLoading}
                    />
                  </div>

                  {ospfApplyResult && (
                    <div className="text-xs rounded-md border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-emerald-600 dark:text-emerald-400">
                      Применено на {ospfApplyResult.serverName}: изменено интерфейсов — {ospfApplyResult.optimizedCount}.
                    </div>
                  )}

                  {ospfApplyError && (
                    <div className="text-xs rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-destructive">
                      Ошибка OSPF-оптимизации: {ospfApplyError}
                    </div>
                  )}
                </>
              )}
          </OpsPanel>

          {/* ─── ECMP / RPF / VRF section ──────────────────────────────── */}
          <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">

            {/* ECMP Card */}
            <OpsPanel
              title="ECMP"
              description="Equal-Cost Multi-Path"
              headerRight={
                    <button type="button" role="switch" aria-checked={ecmpEnabled}
                      onClick={() => setEcmpEnabled(v => !v)}
                      className={cn("relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
                        ecmpEnabled ? "bg-violet-500" : "bg-input")}>
                      <span className={cn("pointer-events-none block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
                        ecmpEnabled ? "translate-x-4" : "translate-x-0")} />
                    </button>
              }
              contentClassName="px-5 py-4 flex flex-col gap-4"
            >

                <div className={cn("flex flex-col gap-3 transition-opacity", !ecmpEnabled && "opacity-40 pointer-events-none")}>
                  <div className="flex flex-col gap-1.5">
                    <span className="text-xs text-muted-foreground">Макс. путей</span>
                    <div className="flex items-center gap-2">
                      <input type="range" min={1} max={64} step={1} value={ecmpMaxPaths}
                        onChange={e => setEcmpMaxPaths(Number(e.target.value))}
                        className="flex-1 accent-violet-500 h-1.5" />
                      <span className="text-sm font-mono w-8 text-right">{ecmpMaxPaths}</span>
                    </div>
                  </div>

                  <div className="flex flex-col gap-1.5">
                    <span className="text-xs text-muted-foreground">Алгоритм балансировки</span>
                    <div className="flex rounded-md border border-input overflow-hidden h-7 text-xs">
                      {([["per-dst","По dst"],["per-conn","По conn"],["per-packet","По пакет"]] as const).map(([v, l]) => (
                        <button key={v} onClick={() => setEcmpAlgo(v)}
                          className={cn("flex-1 transition-colors cursor-pointer",
                            ecmpAlgo === v ? "bg-violet-500 text-white" : "text-muted-foreground hover:bg-muted")}>
                          {l}
                        </button>
                      ))}
                    </div>
                  </div>

                  <div className="flex items-start gap-2 rounded-lg border border-violet-500/20 bg-violet-500/5 px-2.5 py-2 text-[11px] text-violet-600 dark:text-violet-400">
                    <InfoIcon className="size-3 shrink-0 mt-0.5" />
                    <span>RouterOS 7.x: <code className="font-mono">/routing/rule add ecmp=yes</code></span>
                  </div>
                </div>
            </OpsPanel>

            {/* RPF Card */}
            <OpsPanel title="RPF" description="Reverse Path Forwarding" contentClassName="px-5 py-4 flex flex-col gap-4">

                <div className="flex flex-col gap-2">
                  <span className="text-xs text-muted-foreground">Режим проверки источника</span>
                  <div className="flex flex-col gap-1.5">
                    {([
                      { v: "disabled", l: "Отключён",  d: "Проверка не выполняется",              cls: "text-muted-foreground" },
                      { v: "loose",    l: "Loose",      d: "Маршрут к источнику существует",       cls: "text-amber-500" },
                      { v: "strict",   l: "Strict",     d: "Пакет пришёл по лучшему маршруту",     cls: "text-emerald-500" },
                    ] as const).map(({ v, l, d, cls }) => (
                      <button key={v} type="button" onClick={() => setRpfMode(v)}
                        className={cn(
                          "flex items-start gap-2.5 px-3 py-2 rounded-lg border text-left transition-all text-xs",
                          rpfMode === v
                            ? "border-amber-500/30 bg-amber-500/5"
                            : "border-border hover:border-muted-foreground/30 hover:bg-muted/30",
                        )}>
                        <div className={cn("size-3.5 rounded-full border-2 shrink-0 mt-0.5 transition-colors",
                          rpfMode === v ? "border-amber-500 bg-amber-500" : "border-muted-foreground")} />
                        <div>
                          <div className={cn("font-medium", rpfMode === v ? cls : "text-foreground")}>{l}</div>
                          <div className="text-muted-foreground text-[10px] mt-0.5">{d}</div>
                        </div>
                      </button>
                    ))}
                  </div>
                </div>

                {rpfMode !== "disabled" && (
                  <div className="flex items-start gap-2 rounded-lg border border-amber-500/20 bg-amber-500/5 px-2.5 py-2 text-[11px] text-amber-600 dark:text-amber-400">
                    <InfoIcon className="size-3 shrink-0 mt-0.5" />
                    <span><code className="font-mono">/ip settings set rp-filter={rpfMode}</code></span>
                  </div>
                )}
            </OpsPanel>

            {/* VRF Card */}
            <OpsPanel title="VRF" description="Virtual Routing" contentClassName="px-5 py-4 flex flex-col gap-4">

                <div className="flex flex-col gap-1.5">
                  <span className="text-xs text-muted-foreground">Контекст оптимизации маршрутов</span>
                  <div className="flex flex-col gap-1.5 max-h-48 overflow-y-auto pr-0.5">
                    {VRF_NAMES.map(vrf => {
                      const isSel = selectedVrf === vrf
                      const isMain = vrf === "main"
                      return (
                        <button key={vrf} type="button" onClick={() => setSelectedVrf(vrf)}
                          className={cn(
                            "flex items-center gap-2.5 px-3 py-2 rounded-lg border text-left transition-all text-xs",
                            isSel
                              ? "border-sky-500/40 bg-sky-500/8 text-sky-400"
                              : "border-border hover:border-muted-foreground/30 hover:bg-muted/30 text-muted-foreground hover:text-foreground",
                          )}>
                          <div className={cn("size-2.5 rounded-full shrink-0",
                            isSel ? "bg-sky-500" : "bg-muted-foreground/30")} />
                          <span className="font-mono flex-1">{vrf}</span>
                          {isMain && (
                            <span className="text-[10px] border border-current rounded px-1 opacity-60">default</span>
                          )}
                        </button>
                      )
                    })}
                  </div>
                </div>

                <div className="flex items-start gap-2 rounded-lg border border-sky-500/20 bg-sky-500/5 px-2.5 py-2 text-[11px] text-sky-600 dark:text-sky-400">
                  <InfoIcon className="size-3 shrink-0 mt-0.5" />
                  <span>Оптимизатор работает в VRF <code className="font-mono">{selectedVrf}</code></span>
                </div>
            </OpsPanel>

          </div>

          {/* Per-home-router cards */}
          {data?.homes.map((entry) => (
            <HomeRouterCard
              key={entry.home.id}
              entry={entry}
              jumpHosts={jumpHostsForCards}
              settings={settings}
              pinned={pinned}
              applied={applied}
              applying={applying}
              onPin={togglePin}
              onApply={applyRec}
            />
          ))}

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