"use client"

import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useSearchParams } from "next/navigation"
import { PageHeader } from "@/components/page-header"
import {
  FirewallRulesDataGrid,
  ActionBadge,
  ChainBadge,
} from "@/components/data-grids/firewall-rules-data-grid"
import { FirewallScenarioRulesDataGrid } from "@/components/data-grids/firewall-scenario-rules-data-grid"
import { DataPageCard } from "@/components/data-page-card"
import { DataPageToolbarFrame } from "@/components/data-page-toolbar"
import { FormField, FormToggle, SectionTitle, SegmentedControl } from "@/components/form-kit"
import {
  firewallRules,
  servers as mockServers,
  type FirewallAddressListEntry,
  type FirewallRule,
  type FirewallTable,
  type Server,
} from "@/lib/data"
import { useDataSource } from "@/lib/data-source"
import { requestJson } from "@/shared/api/http-client"
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 {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@/components/ui/input-group"
import {
  Sheet, SheetContent, SheetHeader, SheetTitle,
  SheetDescription, SheetFooter, SheetClose,
} from "@/components/ui/sheet"
import {
  DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,
  DropdownMenuItem, DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu"
import { cn } from "@/lib/utils"
import {
  PlusIcon, SearchIcon, ShieldIcon, ShieldOffIcon,
  ListFilterIcon, ArrowRightLeftIcon, WrenchIcon, LayersIcon,
  PencilIcon, Trash2Icon, CodeXmlIcon,
  MoreHorizontalIcon,
  PowerIcon, CheckCircleIcon,
  PlayIcon, SquareIcon, RotateCcwIcon, ZapIcon,
  CheckCircle2Icon, XCircleIcon, MinusCircleIcon, SkipForwardIcon,
  SlidersHorizontalIcon, RefreshCwIcon,
} from "lucide-react"
import { CodeExportSheet } from "@/components/reui-kit/code-export-sheet"
import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout"
import { ALL_SERVERS_ID, type ServerTileItem } from "@/components/server-tile-rail"
import { toast } from "sonner"

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

type ChainGroup = "filter" | "nat" | "mangle" | "raw" | "address-lists" | "simulator"
type ChainFilter = "all" | string

// ─── Simulator types ──────────────────────────────────────────────────────────

interface PacketDef {
  chain: string
  proto: string
  srcAddr: string
  dstAddr: string
  srcPort: string
  dstPort: string
  inIface: string
  outIface: string
  connState: string
  srcAddrList: string
  dstAddrList: string
  tlsHost: string
}

type SimSpeed = "slow" | "normal" | "fast" | "instant"

interface SimCheck {
  field: string
  ruleVal: string
  packetVal: string
  passed: boolean
}

interface SimStep {
  rule: FirewallRule
  index: number
  status: "pending" | "evaluating" | "match" | "skip" | "disabled" | "passthrough"
  checks: SimCheck[]
  failedAt?: string
}

// ─── Scenario types ───────────────────────────────────────────────────────────

interface ScenarioRule {
  id: string
  chain: string
  action: string
  proto: string
  src: string
  dst: string
  port: string
  iface: string
  comment: string
  enabled: boolean
}

interface SimScenario {
  id: string
  name: string
  description: string
  packet: PacketDef
  rules: ScenarioRule[]
  createdAt: number
}

// ─── Packet presets ───────────────────────────────────────────────────────────

const DEFAULT_PKT: PacketDef = {
  chain: "forward", proto: "tcp",
  srcAddr: "10.10.0.100", dstAddr: "142.250.74.110",
  srcPort: "54321", dstPort: "443",
  inIface: "lan", outIface: "wan-msk",
  connState: "new", srcAddrList: "", dstAddrList: "", tlsHost: "",
}

// ─── RouterOS traversal paths ────────────────────────────────────────────────
// Maps packet "flow type" (= packet.chain value) → ordered chain names to evaluate.
// Mirrors the real RouterOS pipeline so Mangle, Raw, NAT rules are all included.

const DIR_PATHS: Record<string, string[]> = {
  // Forwarded traffic (LAN → WAN, etc.)
  forward:  ["prerouting", "forward",  "postrouting", "srcnat"],
  // Traffic destined for the router itself
  input:    ["prerouting", "input"],
  // Traffic originating from the router
  output:   ["output",     "postrouting", "srcnat"],
  // DNAT / port-forward
  dstnat:   ["prerouting", "dstnat",   "forward", "postrouting", "srcnat"],
}

// Human-readable table hint shown next to each chain header in the trace
const CHAIN_TABLE_HINT: Record<string, string> = {
  prerouting:  "Raw + Mangle",
  forward:     "Mangle + Filter",
  input:       "Mangle + Filter",
  output:      "Mangle + Filter",
  postrouting: "Mangle",
  srcnat:      "NAT",
  dstnat:      "NAT",
}

// ─── Packet presets ───────────────────────────────────────────────────────────

const SIM_PRESETS: Array<{
  id: string; label: string; desc: string; emoji: string; pkt: Partial<PacketDef>
}> = [
  { id: "https",     label: "HTTPS → интернет",   desc: "TCP 443, forward, новый",       emoji: "🌐",
    pkt: { chain: "forward", proto: "tcp", dstPort: "443", srcAddr: "10.10.0.100", dstAddr: "142.250.74.110", inIface: "lan", connState: "new" } },
  { id: "http",      label: "HTTP → интернет",     desc: "TCP 80, forward",               emoji: "📡",
    pkt: { chain: "forward", proto: "tcp", dstPort: "80",  srcAddr: "10.10.0.100", dstAddr: "93.184.216.34",  inIface: "lan", connState: "new" } },
  { id: "ssh-lan",   label: "SSH управление",      desc: "TCP 22, input, с LAN",          emoji: "🔐",
    pkt: { chain: "input",   proto: "tcp", dstPort: "22",  srcAddr: "10.10.0.5",   dstAddr: "10.0.0.1", inIface: "lan",     connState: "new" } },
  { id: "ssh-wan",   label: "SSH с WAN",           desc: "TCP 22, input, внешний",        emoji: "🚨",
    pkt: { chain: "input",   proto: "tcp", dstPort: "22",  srcAddr: "185.220.101.45", dstAddr: "10.0.0.1", inIface: "wan-msk", connState: "new" } },
  { id: "winbox",    label: "WinBox",              desc: "TCP 8291, input, с LAN",        emoji: "🖥️",
    pkt: { chain: "input",   proto: "tcp", dstPort: "8291",srcAddr: "10.10.0.5",   dstAddr: "10.0.0.1", inIface: "lan",     connState: "new" } },
  { id: "youtube",   label: "YouTube bypass",      desc: "TCP 443, dst-list youtube",     emoji: "▶️",
    pkt: { chain: "forward", proto: "tcp", dstPort: "443", srcAddr: "10.10.0.100", dstAddr: "142.250.74.110", dstAddrList: "youtube-bypass", inIface: "lan", connState: "new" } },
  { id: "cdn",       label: "CDN mark-routing",    desc: "TCP, dst-list cdn-bypass",      emoji: "🚀",
    pkt: { chain: "forward", proto: "tcp", dstPort: "443", srcAddr: "10.10.0.100", dstAddr: "104.16.132.229", dstAddrList: "cdn-bypass",     inIface: "lan", connState: "new" } },
  { id: "nat-out",   label: "Masquerade",          desc: "srcnat, LAN → WAN",             emoji: "🔄",
    pkt: { chain: "srcnat",  proto: "tcp", srcAddr: "10.10.0.100", dstAddr: "8.8.8.8", dstPort: "443", outIface: "wan-msk" } },
  { id: "icmp",      label: "Ping ICMP",           desc: "ICMP echo, forward",            emoji: "📶",
    pkt: { chain: "forward", proto: "icmp", srcAddr: "10.10.0.100", dstAddr: "8.8.8.8", inIface: "lan" } },
  { id: "established", label: "Established flow", desc: "TCP established, reverse",      emoji: "↩️",
    pkt: { chain: "forward", proto: "tcp", dstPort: "54321", srcAddr: "142.250.74.110", dstAddr: "10.10.0.100", inIface: "wan-msk", connState: "established" } },
]

// ─── Matching engine ──────────────────────────────────────────────────────────

function _ipNum(ip: string): number {
  return ip.split(".").reduce((a, o) => (a << 8) + parseInt(o, 10), 0) >>> 0
}
function _inCidr(ip: string, cidr: string): boolean {
  if (!ip) return false
  if (!cidr.includes("/")) return ip === cidr
  const [net, b] = cidr.split("/"); const bits = parseInt(b)
  if (bits === 0) return true
  const mask = bits >= 32 ? 0xFFFFFFFF : (~(0xFFFFFFFF >>> bits)) >>> 0
  return (_ipNum(ip) & mask) === (_ipNum(net) & mask)
}
function _mAddr(pAddr: string, rAddr: string): boolean {
  if (!rAddr || rAddr === "—" || rAddr === "") return true
  return rAddr.includes("/") ? _inCidr(pAddr, rAddr) : pAddr === rAddr
}
function _mPort(pPort: string, rPort: string): boolean {
  if (!rPort || rPort === "—") return true
  if (!pPort) return false
  const n = parseInt(pPort)
  for (const p of rPort.split(",")) {
    const t = p.trim()
    if (t.includes("-")) { const [lo, hi] = t.split("-").map(Number); if (n >= lo && n <= hi) return true }
    else if (Number(t) === n) return true
  }
  return false
}
function _mList(pList: string, rList: string): boolean {
  return !rList || rList === "—" || pList === rList
}
function _mIface(pI: string, rI: string): boolean {
  return !rI || rI === "—" || !pI || pI === rI || rI === "*"
}

function evalRule(rule: FirewallRule, pkt: PacketDef): {
  verdict: SimStep["status"]; checks: SimCheck[]; failedAt?: string
} {
  if (!rule.enabled) return { verdict: "disabled", checks: [] }

  const checks: SimCheck[] = []
  const fail = (c: SimCheck) => { checks.push(c); return { verdict: "skip" as const, checks, failedAt: c.field } }

  if (rule.chain !== pkt.chain) return fail({ field: "chain", ruleVal: rule.chain, packetVal: pkt.chain, passed: false })
  checks.push({ field: "chain", ruleVal: rule.chain, packetVal: pkt.chain, passed: true })

  if (rule.proto && rule.proto !== "all") {
    const ok = pkt.proto === rule.proto
    if (!ok) return fail({ field: "protocol", ruleVal: rule.proto, packetVal: pkt.proto, passed: false })
    checks.push({ field: "protocol", ruleVal: rule.proto, packetVal: pkt.proto, passed: true })
  }

  if (rule.src && rule.src !== "—") {
    const ok = _mAddr(pkt.srcAddr, rule.src) || _mList(pkt.srcAddrList, rule.src)
    if (!ok) return fail({ field: "src-address", ruleVal: rule.src, packetVal: pkt.srcAddr || "—", passed: false })
    checks.push({ field: "src-address", ruleVal: rule.src, packetVal: pkt.srcAddr || pkt.srcAddrList, passed: true })
  }

  if (rule.dst && rule.dst !== "—") {
    const ok = _mAddr(pkt.dstAddr, rule.dst) || _mList(pkt.dstAddrList, rule.dst)
    if (!ok) return fail({ field: "dst-address", ruleVal: rule.dst, packetVal: pkt.dstAddr || "—", passed: false })
    checks.push({ field: "dst-address", ruleVal: rule.dst, packetVal: pkt.dstAddr || pkt.dstAddrList, passed: true })
  }

  if (rule.port && rule.port !== "—") {
    const ok = _mPort(pkt.dstPort, rule.port)
    if (!ok) return fail({ field: "dst-port", ruleVal: rule.port, packetVal: pkt.dstPort || "—", passed: false })
    checks.push({ field: "dst-port", ruleVal: rule.port, packetVal: pkt.dstPort, passed: true })
  }

  if (rule.iface && rule.iface !== "—") {
    const ok = _mIface(pkt.inIface, rule.iface)
    if (!ok) return fail({ field: "in-interface", ruleVal: rule.iface, packetVal: pkt.inIface || "—", passed: false })
    checks.push({ field: "in-interface", ruleVal: rule.iface, packetVal: pkt.inIface, passed: true })
  }

  if (rule.tlsHost && pkt.tlsHost) {
    const pat = "^" + rule.tlsHost.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
    const ok = new RegExp(pat).test(pkt.tlsHost)
    if (!ok) return fail({ field: "tls-host", ruleVal: rule.tlsHost, packetVal: pkt.tlsHost, passed: false })
    checks.push({ field: "tls-host", ruleVal: rule.tlsHost, packetVal: pkt.tlsHost, passed: true })
  }

  return { verdict: rule.action === "passthrough" ? "passthrough" : "match", checks }
}

type AddressListEntry = FirewallAddressListEntry

// ─── Mock address-list data ───────────────────────────────────────────────────

const INIT_ADDRESS_LISTS: AddressListEntry[] = [
  { id: "al1",  list: "youtube-bypass",    address: "142.250.0.0/15",    comment: "Google/YouTube AS15169",       disabled: false, family: "ip", serverId: "srv1", rosId: "*1" },
  { id: "al2",  list: "youtube-bypass",    address: "172.217.0.0/16",    comment: "Google/YouTube AS15169",       disabled: false, family: "ip", serverId: "srv1", rosId: "*2" },
  { id: "al3",  list: "cdn-bypass",        address: "104.16.0.0/12",     comment: "Cloudflare AS13335",           disabled: false, family: "ip", serverId: "srv1", rosId: "*3" },
  { id: "al4",  list: "cdn-bypass",        address: "23.32.0.0/11",      comment: "Akamai AS20940",               disabled: false, family: "ip", serverId: "srv1", rosId: "*4" },
  { id: "al5",  list: "cdn-bypass",        address: "151.101.0.0/16",    comment: "Fastly AS54113",               disabled: false, family: "ip", serverId: "srv1", rosId: "*5" },
  { id: "al6",  list: "streaming-eu",      address: "54.246.0.0/16",     comment: "Netflix AS2906",               disabled: false, family: "ip", serverId: "srv1", rosId: "*6" },
  { id: "al7",  list: "streaming-eu",      address: "45.57.0.0/16",      comment: "Netflix AS2906 NL",            disabled: false, family: "ip", serverId: "srv1", rosId: "*7" },
  { id: "al8",  list: "social-block",      address: "157.240.0.0/17",    comment: "Facebook/Meta AS32934",        disabled: false, family: "ip", serverId: "srv1", rosId: "*8" },
  { id: "al9",  list: "social-block",      address: "31.13.64.0/18",     comment: "Facebook/Meta AS32934",        disabled: false, family: "ip", serverId: "srv1", rosId: "*9" },
  { id: "al10", list: "gaming-low-latency",address: "162.159.128.0/19",  comment: "Discord/Cloudflare AS13335",   disabled: false, family: "ip", serverId: "srv1", rosId: "*10" },
  { id: "al11", list: "management-access", address: "10.10.0.0/16",      comment: "LAN",                          disabled: false, family: "ip", serverId: "srv1", rosId: "*11" },
  { id: "al12", list: "management-access", address: "192.168.100.0/24",  comment: "MGMT VLAN",                    disabled: false, family: "ip", serverId: "srv1", rosId: "*12" },
  { id: "al13", list: "blocklist-dynamic", address: "185.220.101.45",    comment: "Tor exit — dynamic ban",       disabled: false, timeout: "01:00:00", family: "ip", serverId: "srv1", rosId: "*13" },
  { id: "al14", list: "blocklist-dynamic", address: "80.82.70.118",      comment: "Shodan scanner — dynamic ban", disabled: false, timeout: "00:42:18", family: "ip", serverId: "srv1", rosId: "*14" },
  { id: "al15", list: "v6-allow",          address: "2a01:4f8::/32",     comment: "Hetzner v6",                   disabled: false, family: "ip6", serverId: "srv1", rosId: "*1" },
]

// ─── IP family filter ─────────────────────────────────────────────────────────

type IpFamily = "ip" | "ip6"

const IP_FAMILY_LABELS: Record<IpFamily, string> = {
  ip:  "IPv4",
  ip6: "IPv6",
}

// ─── Chain groups config ─────────────────────────────────────────────────────

const CHAIN_GROUPS: {
  id: ChainGroup; label: string; icon: React.ReactNode; chains: string[]
}[] = [
  {
    id: "filter", label: "Filter", icon: <ShieldIcon className="size-3.5" />,
    chains: ["input","forward","output"],
  },
  { id: "nat",           label: "NAT",        icon: <ArrowRightLeftIcon className="size-3.5" />, chains: ["srcnat","dstnat"] },
  { id: "mangle",        label: "Mangle",     icon: <WrenchIcon className="size-3.5" />,         chains: ["prerouting","postrouting","forward","input","output"] },
  { id: "raw",           label: "Raw",        icon: <LayersIcon className="size-3.5" />,          chains: ["prerouting","output"] },
  { id: "address-lists", label: "Адр. листы", icon: <ListFilterIcon className="size-3.5" />,     chains: [] },
  { id: "simulator",     label: "Симулятор",  icon: <PlayIcon className="size-3.5" />,            chains: [] },
]

// ─── Action styles ────────────────────────────────────────────────────────────

const ACTION_STYLES: Record<string, string> = {
  accept:                  "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
  drop:                    "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
  reject:                  "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/20",
  masquerade:              "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/20",
  "mark-routing":          "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
  "mark-conn":             "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20",
  "fasttrack-connection":  "bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20",
  "dst-nat":               "bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20",
  "src-nat":               "bg-sky-500/10 text-sky-600 dark:text-sky-400 border-sky-500/20",
  "add-src-to-address-list": "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/20",
}

const CHAIN_STYLES: Record<string, string> = {
  forward:       "bg-foreground/5 text-foreground/70",
  input:         "bg-violet-500/10 text-violet-600 dark:text-violet-400",
  output:        "bg-sky-500/10 text-sky-600 dark:text-sky-400",
  srcnat:        "bg-blue-500/10 text-blue-600 dark:text-blue-400",
  dstnat:        "bg-blue-500/10 text-blue-600 dark:text-blue-400",
  prerouting:    "bg-amber-500/10 text-amber-600 dark:text-amber-400",
  postrouting:   "bg-amber-500/10 text-amber-600 dark:text-amber-400",
  "ip6-input":   "bg-violet-500/10 text-violet-600 dark:text-violet-400",
  "ip6-forward": "bg-foreground/5 text-foreground/70",
  "ip6-output":  "bg-sky-500/10 text-sky-600 dark:text-sky-400",
}

// ─── Default form ─────────────────────────────────────────────────────────────

const defaultForm = {
  chain: "forward", action: "accept", proto: "all",
  src: "", dst: "", srcPort: "", dstPort: "", iface: "",
  inIface: "", outIface: "", connState: "",
  srcAddrList: "", dstAddrList: "",
  comment: "", enabled: true,
  log: false, logPrefix: "",
}

type RuleForm = typeof defaultForm

function looksLikeAddress(value: string): boolean {
  const v = value.trim()
  if (!v || v === "—") return false
  return v.includes(".") || v.includes(":") || v.includes("/")
}

function tableOfGroup(group: ChainGroup): FirewallTable | null {
  if (group === "filter" || group === "nat" || group === "mangle" || group === "raw") return group
  return null
}

interface RuleWritePayload {
  chain: string
  action: string
  protocol?: string
  srcAddress?: string
  dstAddress?: string
  srcAddressList?: string
  dstAddressList?: string
  srcPort?: string
  dstPort?: string
  inInterface?: string
  outInterface?: string
  connectionState?: string
  comment?: string
  disabled?: boolean
  log?: boolean
  logPrefix?: string
}

function formToWritePayload(form: RuleForm): RuleWritePayload {
  const src = form.srcAddrList.trim()
  const dst = form.dstAddrList.trim()
  return {
    chain: form.chain,
    action: form.action,
    protocol: form.proto,
    srcAddress: src && looksLikeAddress(src) ? src : undefined,
    srcAddressList: src && !looksLikeAddress(src) ? src : undefined,
    dstAddress: dst && looksLikeAddress(dst) ? dst : undefined,
    dstAddressList: dst && !looksLikeAddress(dst) ? dst : undefined,
    srcPort: form.srcPort.trim() || undefined,
    dstPort: form.dstPort.trim() || undefined,
    inInterface: form.inIface.trim() || undefined,
    outInterface: form.outIface.trim() || undefined,
    connectionState: form.connState.trim() || undefined,
    comment: form.comment.trim() || undefined,
    disabled: !form.enabled,
    log: form.log,
    logPrefix: form.log && form.logPrefix.trim() ? form.logPrefix.trim() : undefined,
  }
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function fmtHits(n: number): string {
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}М`
  if (n >= 1_000)     return `${(n / 1_000).toFixed(0)}к`
  return String(n)
}

// ActionBadge, ChainBadge — из firewall-rules-data-grid

function NativeSelect({ value, onChange, children, className }: {
  value: string; onChange: (v: string) => void; children: React.ReactNode; className?: string
}) {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}
      className={cn(
        "h-8 w-full rounded-lg border border-input bg-background px-2.5 text-sm text-foreground outline-none",
        "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50",
        className,
      )}>
      {children}
    </select>
  )
}

// ─── RSC Export generator ─────────────────────────────────────────────────────

function generateRsc(rules: FirewallRule[], timestamp?: string): string {
  const lines: string[] = []
  lines.push(`# MikrotikManager Firewall Export`)
  if (timestamp) lines.push(`# Сгенерировано: ${timestamp}`)
  lines.push(`# Правил: ${rules.length}`)
  lines.push("")

  const byChain: Record<string, FirewallRule[]> = {}
  for (const r of rules) {
    if (!byChain[r.chain]) byChain[r.chain] = []
    byChain[r.chain].push(r)
  }

  const TABLE_MAP: Record<string, string> = {
    input: "filter", forward: "filter", output: "filter",
    srcnat: "nat", dstnat: "nat",
    prerouting: "mangle", postrouting: "mangle",
  }

  for (const [chain, chainRules] of Object.entries(byChain)) {
    const table = chainRules[0]?.table ?? TABLE_MAP[chain] ?? "filter"
    const familyPath = chainRules[0]?.family === "ip6" ? "/ipv6 firewall" : "/ip firewall"
    lines.push(`# ── ${familyPath} ${table} chain=${chain} ──────────────────────────────`)
    for (const r of chainRules) {
      const parts = [`${familyPath} ${table} add`]
      parts.push(`chain=${r.chain}`)
      parts.push(`action=${r.action}`)
      if (r.proto && r.proto !== "all") parts.push(`protocol=${r.proto}`)
      if (r.src && r.src !== "—") parts.push(`src-address-list=${r.src}`)
      if (r.dst && r.dst !== "—") parts.push(`dst-address-list=${r.dst}`)
      if (r.port && r.port !== "—") parts.push(`dst-port=${r.port}`)
      if (r.iface && r.iface !== "—") parts.push(`in-interface=${r.iface}`)
      if (r.comment) parts.push(`comment="${r.comment}"`)
      if (!r.enabled) parts.push(`disabled=yes`)
      lines.push(parts.join(" \\\n  "))
      lines.push("")
    }
  }

  return lines.join("\n")
}

// ─── Rule Sheet ───────────────────────────────────────────────────────────────

function RuleSheet({ open, onClose, initialRule, chainGroup, family, onSave, saving = false }: {
  open: boolean
  onClose: () => void
  initialRule: Partial<FirewallRule> | null
  chainGroup: ChainGroup
  family: IpFamily
  onSave: (payload: RuleWritePayload) => void
  saving?: boolean
}) {
  const isNew = !initialRule?.id
  const [form, setForm] = useState<RuleForm>(defaultForm)

  useEffect(() => {
    if (!open) return
    setForm({
      ...defaultForm,
      chain: initialRule?.chain ?? (CHAIN_GROUPS.find(g => g.id === chainGroup)?.chains[0] ?? "forward"),
      action: (initialRule?.action as string) ?? defaultForm.action,
      proto: initialRule?.proto ?? defaultForm.proto,
      comment: initialRule?.comment ?? "",
      enabled: initialRule?.enabled ?? true,
      srcAddrList: initialRule?.src && initialRule.src !== "—" ? initialRule.src : "",
      dstAddrList: initialRule?.dst && initialRule.dst !== "—" ? initialRule.dst : "",
      dstPort: initialRule?.port && initialRule.port !== "—" ? initialRule.port : "",
      inIface: initialRule?.iface && initialRule.iface !== "—" ? initialRule.iface : "",
      log: initialRule?.log ?? false,
      logPrefix: initialRule?.logPrefix ?? "",
    })
  }, [open, initialRule, chainGroup])

  const set = <K extends keyof RuleForm>(k: K, v: RuleForm[K]) =>
    setForm((f) => ({ ...f, [k]: v }))

  const chainsForGroup = CHAIN_GROUPS.find(g => g.id === chainGroup)?.chains ?? ["forward"]
  const tableName = tableOfGroup(chainGroup) ?? "filter"
  const familyPath = family === "ip6" ? "/ipv6 firewall" : "/ip firewall"
  const showNatActions = chainGroup === "nat"
  const actions = showNatActions
    ? ["masquerade", "dst-nat", "src-nat", "netmap", "same", "passthrough"]
    : chainGroup === "mangle"
      ? ["mark-routing", "mark-conn", "mark-packet", "strip-ipv4-options", "passthrough", "add-src-to-address-list", "add-dst-to-address-list"]
      : ["accept", "drop", "reject", "fasttrack-connection", "add-src-to-address-list", "add-dst-to-address-list", "passthrough"]

  return (
    <Sheet open={open} onOpenChange={(v) => { if (!v) onClose() }}>
      <SheetContent className="flex flex-col overflow-hidden p-0 gap-0 sm:max-w-lg">
        <SheetHeader className="shrink-0 px-6 pt-5 pb-4 border-b">
          <SheetTitle>{isNew ? "Новое правило" : "Редактировать правило"}</SheetTitle>
          <SheetDescription>
            {familyPath} {tableName} · {IP_FAMILY_LABELS[family]} · RouterOS 7
          </SheetDescription>
        </SheetHeader>

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

          {/* Chain + Action */}
          <div className="flex flex-col gap-4">
            <SectionTitle>Цепочка и действие</SectionTitle>
            <div className="grid grid-cols-2 gap-3">
              <FormField label="Цепочка" required>
                <NativeSelect value={form.chain} onChange={(v) => set("chain", v)}>
                  {chainsForGroup.map((c) => <option key={c} value={c}>{c}</option>)}
                </NativeSelect>
              </FormField>
              <FormField label="Действие" required>
                <NativeSelect value={form.action} onChange={(v) => set("action", v)}>
                  {actions.map((a) => <option key={a} value={a}>{a}</option>)}
                </NativeSelect>
              </FormField>
            </div>
          </div>

          {/* Matching */}
          <div className="flex flex-col gap-4">
            <SectionTitle>Условие совпадения</SectionTitle>
            <FormField label="Протокол">
              <NativeSelect value={form.proto} onChange={(v) => set("proto", v)}>
                {["all","tcp","udp","icmp","gre","esp","ah","ipencap","ospf"].map((p) =>
                  <option key={p} value={p}>{p}</option>
                )}
              </NativeSelect>
            </FormField>

            <div className="grid grid-cols-2 gap-3">
              <FormField label="Src-address / Address-list" hint="IP, CIDR или имя address-list">
                <Input className="font-mono h-8" placeholder="10.0.0.0/8"
                  value={form.srcAddrList} onChange={(e) => set("srcAddrList", e.target.value)} />
              </FormField>
              <FormField label="Dst-address / Address-list">
                <Input className="font-mono h-8" placeholder="0.0.0.0/0"
                  value={form.dstAddrList} onChange={(e) => set("dstAddrList", e.target.value)} />
              </FormField>
            </div>

            <div className="grid grid-cols-2 gap-3">
              <FormField label="Src-port" hint="TCP/UDP, напр. 1024-65535">
                <Input className="font-mono h-8" placeholder="—"
                  value={form.srcPort} onChange={(e) => set("srcPort", e.target.value)} />
              </FormField>
              <FormField label="Dst-port">
                <Input className="font-mono h-8" placeholder="443"
                  value={form.dstPort} onChange={(e) => set("dstPort", e.target.value)} />
              </FormField>
            </div>

            <div className="grid grid-cols-2 gap-3">
              <FormField label="In-interface" hint="Входящий интерфейс">
                <Input className="font-mono h-8" placeholder="wan-msk"
                  value={form.inIface} onChange={(e) => set("inIface", e.target.value)} />
              </FormField>
              <FormField label="Out-interface">
                <Input className="font-mono h-8" placeholder="lan"
                  value={form.outIface} onChange={(e) => set("outIface", e.target.value)} />
              </FormField>
            </div>

            <FormField label="Connection-state" hint="Через запятую: new, established, related, invalid">
              <Input className="font-mono h-8" placeholder="new,established"
                value={form.connState} onChange={(e) => set("connState", e.target.value)} />
            </FormField>
          </div>

          {/* Log + Comment */}
          <div className="flex flex-col gap-4">
            <SectionTitle>Логирование и комментарий</SectionTitle>
            <div className="flex items-center justify-between">
              <div>
                <p className="text-sm font-medium">Log</p>
                <p className="text-xs text-muted-foreground">Записывать совпадения в системный лог</p>
              </div>
              <FormToggle checked={form.log} onChange={(v) => set("log", v)} />
            </div>
            {form.log && (
              <FormField label="Log-prefix" hint="Метка в логе, например FW-DROP">
                <Input className="font-mono h-8" placeholder="FW-RULE"
                  value={form.logPrefix} onChange={(e) => set("logPrefix", e.target.value)} />
              </FormField>
            )}
            <FormField label="Комментарий">
              <Input className="h-8" placeholder="Описание правила"
                value={form.comment} onChange={(e) => set("comment", e.target.value)} />
            </FormField>
          </div>

          {/* Enabled */}
          <div className="flex items-center justify-between rounded-lg border bg-muted/30 px-4 py-3">
            <div>
              <p className="text-sm font-medium">Правило включено</p>
              <p className="text-xs text-muted-foreground">Отключённые правила сохраняются, но не применяются</p>
            </div>
            <FormToggle checked={form.enabled} onChange={(v) => set("enabled", v)} />
          </div>

          {/* CLI preview */}
          <div className="rounded-lg bg-zinc-950 dark:bg-zinc-900 border border-zinc-800 px-4 py-3">
            <p className="text-[10px] font-mono text-zinc-500 mb-2">RouterOS CLI preview</p>
            <pre className="text-[11px] font-mono text-zinc-300 whitespace-pre-wrap leading-relaxed">
              {[
                `${familyPath} ${tableName} add \\`,
                `  chain=${form.chain} \\`,
                `  action=${form.action}`,
                form.proto !== "all" ? `  protocol=${form.proto} \\` : null,
                form.srcAddrList
                  ? looksLikeAddress(form.srcAddrList)
                    ? `  src-address=${form.srcAddrList} \\`
                    : `  src-address-list=${form.srcAddrList} \\`
                  : null,
                form.dstAddrList
                  ? looksLikeAddress(form.dstAddrList)
                    ? `  dst-address=${form.dstAddrList} \\`
                    : `  dst-address-list=${form.dstAddrList} \\`
                  : null,
                form.dstPort     ? `  dst-port=${form.dstPort} \\`             : null,
                form.inIface     ? `  in-interface=${form.inIface} \\`         : null,
                form.outIface    ? `  out-interface=${form.outIface} \\`        : null,
                form.connState   ? `  connection-state=${form.connState} \\`   : null,
                form.log         ? `  log=yes \\`                              : null,
                form.log && form.logPrefix ? `  log-prefix="${form.logPrefix}" \\` : null,
                form.comment     ? `  comment="${form.comment}"`               : null,
                !form.enabled    ? `  disabled=yes`                            : null,
              ].filter(Boolean).join("\n")}
            </pre>
          </div>
        </div>

        <SheetFooter className="shrink-0 px-6 py-4 border-t flex-row gap-2">
          <SheetClose render={<Button variant="outline" className="flex-1" />}>Отмена</SheetClose>
          <Button
            className="flex-1"
            disabled={saving}
            onClick={() => onSave(formToWritePayload(form))}
          >
            {saving ? "Сохранение…" : isNew ? "Создать правило" : "Сохранить"}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

// ─── Export Sheet ─────────────────────────────────────────────────────────────

function ExportSheet({ open, onClose, rules }: {
  open: boolean; onClose: () => void; rules: FirewallRule[]
}) {
  const timestamp = useMemo(
    () => open ? new Date().toLocaleString("ru") : undefined,
    [open],
  )

  const code = useMemo(() => generateRsc(rules, timestamp), [rules, timestamp])

  return (
    <CodeExportSheet
      open={open}
      onClose={onClose}
      title="Экспорт Firewall"
      description="RouterOS .rsc · /ip firewall filter, nat, mangle"
      formats={[
        {
          id: "rsc",
          label: "MikroTik .rsc",
          filename: `firewall-${new Date().toISOString().slice(0, 10)}.rsc`,
          code,
        },
      ]}
    />
  )
}

// ─── Address List tab ─────────────────────────────────────────────────────────

function AddressListsTab({
  entries,
  onAdd,
  onAddToList,
  onToggle,
  onEdit,
  onDelete,
  showServer = false,
}: {
  entries: AddressListEntry[]
  onAdd: () => void
  onAddToList: (list: string) => void
  onToggle: (entry: AddressListEntry) => void
  onEdit: (entry: AddressListEntry) => void
  onDelete: (entry: AddressListEntry) => void
  showServer?: boolean
}) {
  const [search, setSearch] = useState("")
  const [listFilter, setListFilter] = useState("all")

  const lists = useMemo(() => {
    const s = new Set(entries.map((e) => e.list))
    return ["all", ...Array.from(s)]
  }, [entries])

  useEffect(() => {
    if (listFilter !== "all" && !lists.includes(listFilter)) setListFilter("all")
  }, [lists, listFilter])

  const filtered = useMemo(() => {
    return entries.filter((e) => {
      if (listFilter !== "all" && e.list !== listFilter) return false
      if (!search) return true
      const q = search.toLowerCase()
      return e.address.includes(q) || e.list.includes(q) || e.comment.toLowerCase().includes(q)
        || (e.serverName ?? "").toLowerCase().includes(q)
    })
  }, [entries, search, listFilter])

  const grouped = useMemo(() => {
    if (listFilter !== "all") return { [listFilter]: filtered }
    const g: Record<string, AddressListEntry[]> = {}
    for (const e of filtered) {
      if (!g[e.list]) g[e.list] = []
      g[e.list].push(e)
    }
    return g
  }, [filtered, listFilter])

  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-center gap-3 flex-wrap">
        <div className="flex items-center gap-2 h-8 px-3 border border-input rounded-md bg-background min-w-[220px]">
          <SearchIcon className="size-3.5 text-muted-foreground shrink-0" />
          <input className="flex-1 bg-transparent outline-none placeholder:text-muted-foreground text-sm"
            placeholder="Поиск по адресу, листу…" value={search} onChange={(e) => setSearch(e.target.value)} />
        </div>
        <div className="flex items-center gap-1 rounded-md border border-border bg-muted/40 p-0.5 overflow-x-auto">
          {lists.map((l) => (
            <button key={l} onClick={() => setListFilter(l)}
              className={cn("px-3 py-1 text-xs rounded whitespace-nowrap transition-colors",
                listFilter === l ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
              )}>
              {l === "all" ? "Все листы" : l}
            </button>
          ))}
        </div>
        <span className="text-sm text-muted-foreground ml-auto">{filtered.length} записей</span>
        <Button size="sm" onClick={onAdd}><PlusIcon className="size-4" />Добавить</Button>
      </div>

      <div className="flex flex-col gap-3">
        {Object.entries(grouped).map(([listName, listEntries]) => (
          <Frame key={listName} dense className="w-full">
            <FramePanel className="p-0">
            <div className="flex items-center justify-between px-4 py-2.5 border-b bg-muted/20">
              <div className="flex items-center gap-2">
                <ListFilterIcon className="size-3.5 text-muted-foreground" />
                <span className="text-sm font-mono font-medium">{listName}</span>
                <span className="text-xs text-muted-foreground">({listEntries.length})</span>
              </div>
              <Button variant="ghost" size="sm" className="h-6 text-xs gap-1" onClick={() => onAddToList(listName)}>
                <PlusIcon className="size-3" />Адрес
              </Button>
            </div>
            <div className="divide-y divide-border">
              {listEntries.map((e) => (
                <div key={e.id} className={cn(
                  "flex items-center gap-3 px-4 py-2.5",
                  e.disabled && "opacity-50",
                )}>
                  <span className={cn("size-1.5 rounded-full shrink-0", e.disabled ? "bg-muted-foreground" : "bg-emerald-500")} />
                  <span className="font-mono text-sm font-medium min-w-[150px]">{e.address}</span>
                  <span className="text-xs text-muted-foreground flex-1 truncate">{e.comment}</span>
                  {showServer && e.serverName && (
                    <span className="text-[10px] text-muted-foreground shrink-0">{e.serverName}</span>
                  )}
                  {e.timeout && (
                    <span className="text-[10px] font-mono bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20 px-2 py-0.5 rounded shrink-0">
                      ⏱ {e.timeout}
                    </span>
                  )}
                  <FormToggle checked={!e.disabled} onChange={() => onToggle(e)} />
                  <DropdownMenu>
                    <DropdownMenuTrigger render={
                      <Button variant="ghost" size="icon" className="size-7 shrink-0">
                        <MoreHorizontalIcon className="size-4" />
                      </Button>
                    } />
                    <DropdownMenuContent side="bottom" align="end">
                      <DropdownMenuItem onClick={() => onEdit(e)}><PencilIcon className="size-4" />Редактировать</DropdownMenuItem>
                      <DropdownMenuItem onClick={() => onToggle(e)}>
                        <PowerIcon className="size-4" />{e.disabled ? "Включить" : "Отключить"}
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem variant="destructive" onClick={() => onDelete(e)}>
                        <Trash2Icon className="size-4" />Удалить
                      </DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </div>
              ))}
            </div>
          </FramePanel>
          </Frame>
        ))}
      </div>
    </div>
  )
}

const defaultAddrForm = {
  list: "",
  address: "",
  comment: "",
  timeout: "",
  enabled: true,
}

function AddressListSheet({
  open, onClose, initial, family, onSave, saving = false,
}: {
  open: boolean
  onClose: () => void
  initial: Partial<AddressListEntry> | null
  family: IpFamily
  onSave: (payload: { list: string; address: string; comment?: string; timeout?: string; disabled?: boolean }) => void
  saving?: boolean
}) {
  const isNew = !initial?.id
  const [form, setForm] = useState(defaultAddrForm)

  useEffect(() => {
    if (!open) return
    setForm({
      list: initial?.list ?? "",
      address: initial?.address ?? "",
      comment: initial?.comment ?? "",
      timeout: initial?.timeout ?? "",
      enabled: !(initial?.disabled ?? false),
    })
  }, [open, initial])

  return (
    <Sheet open={open} onOpenChange={(v) => { if (!v) onClose() }}>
      <SheetContent className="flex flex-col overflow-hidden p-0 gap-0 sm:max-w-md">
        <SheetHeader className="shrink-0 px-6 pt-5 pb-4 border-b">
          <SheetTitle>{isNew ? "Запись address-list" : "Редактировать запись"}</SheetTitle>
          <SheetDescription>
            {family === "ip6" ? "/ipv6 firewall address-list" : "/ip firewall address-list"} · {IP_FAMILY_LABELS[family]}
          </SheetDescription>
        </SheetHeader>
        <div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-4">
          <FormField label="Лист" required>
            <Input className="font-mono h-8" placeholder="cdn-bypass"
              value={form.list} onChange={(e) => setForm((f) => ({ ...f, list: e.target.value }))} />
          </FormField>
          <FormField label="Адрес" required>
            <Input className="font-mono h-8" placeholder="10.0.0.0/8"
              value={form.address} onChange={(e) => setForm((f) => ({ ...f, address: e.target.value }))} />
          </FormField>
          <FormField label="Комментарий">
            <Input className="h-8" value={form.comment}
              onChange={(e) => setForm((f) => ({ ...f, comment: e.target.value }))} />
          </FormField>
          <FormField label="Timeout" hint="например 01:00:00">
            <Input className="font-mono h-8" placeholder="опционально" value={form.timeout}
              onChange={(e) => setForm((f) => ({ ...f, timeout: e.target.value }))} />
          </FormField>
          <div className="flex items-center justify-between gap-3 rounded-lg border px-3 py-2.5">
            <div>
              <p className="text-sm font-medium">Запись включена</p>
              <p className="text-xs text-muted-foreground">Отключённые адреса не матчятся</p>
            </div>
            <FormToggle checked={form.enabled} onChange={(v) => setForm((f) => ({ ...f, enabled: v }))} />
          </div>
        </div>
        <SheetFooter className="shrink-0 px-6 py-4 border-t flex-row gap-2">
          <SheetClose render={<Button variant="outline" className="flex-1" />}>Отмена</SheetClose>
          <Button
            className="flex-1"
            disabled={saving || !form.list.trim() || !form.address.trim()}
            onClick={() => onSave({
              list: form.list.trim(),
              address: form.address.trim(),
              comment: form.comment.trim() || undefined,
              timeout: form.timeout.trim() || undefined,
              disabled: !form.enabled,
            })}
          >
            {saving ? "Сохранение…" : isNew ? "Добавить" : "Сохранить"}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

// ─── Verdict helpers ──────────────────────────────────────────────────────────

function getVerdictIcon(action: string) {
  if (action === "drop" || action === "reject")  return <XCircleIcon className="size-5" />
  if (action === "fasttrack-connection")         return <ZapIcon className="size-5" />
  if (action.startsWith("mark-"))               return <SkipForwardIcon className="size-5" />
  return <CheckCircle2Icon className="size-5" />
}

function getVerdictColors(action: string) {
  if (action === "drop" || action === "reject")
    return { bg: "bg-red-500/10",     border: "border-red-500/30",     text: "text-red-600 dark:text-red-400" }
  if (action === "fasttrack-connection")
    return { bg: "bg-violet-500/10",  border: "border-violet-500/30",  text: "text-violet-600 dark:text-violet-400" }
  if (action === "masquerade" || action.endsWith("-nat"))
    return { bg: "bg-blue-500/10",    border: "border-blue-500/30",    text: "text-blue-600 dark:text-blue-400" }
  if (action.startsWith("mark-"))
    return { bg: "bg-amber-500/10",   border: "border-amber-500/30",   text: "text-amber-600 dark:text-amber-400" }
  return   { bg: "bg-emerald-500/10", border: "border-emerald-500/30", text: "text-emerald-600 dark:text-emerald-400" }
}

// ─── SimStepRow ───────────────────────────────────────────────────────────────

function SimStepRow({ step, index }: { step: SimStep; index: number }) {
  const [manualExpanded, setExpanded] = useState(false)
  // Auto-expand on match/evaluating; user can also toggle manually
  const expanded = manualExpanded || step.status === "match" || step.status === "evaluating"

  const isClickable = step.checks.length > 0

  return (
    <div className={cn(
      "border-l-2 transition-all duration-200",
      step.status === "evaluating"  && "border-amber-500 bg-amber-500/5",
      step.status === "match"       && "border-emerald-500 bg-emerald-500/5",
      step.status === "passthrough" && "border-violet-500 bg-violet-500/5",
      (step.status === "skip" || step.status === "disabled" || step.status === "pending") && "border-transparent",
    )}>
      <button
        type="button"
        onClick={() => isClickable && setExpanded(e => !e)}
        className={cn(
          "w-full flex items-center gap-3 px-5 py-2.5 text-left transition-colors",
          isClickable ? "hover:bg-muted/30 cursor-pointer" : "cursor-default",
          step.status === "pending"  && "opacity-40",
          step.status === "disabled" && "opacity-25",
        )}
      >
        <span className="text-xs font-mono text-muted-foreground w-6 shrink-0 tabular-nums">
          {index + 1}
        </span>
        <div className="shrink-0"><ChainBadge chain={step.rule.chain} /></div>
        <div className="shrink-0"><ActionBadge action={step.rule.action} /></div>
        <span className="text-xs font-mono text-muted-foreground flex-1 truncate min-w-0">
          {step.rule.src || "any"} → {step.rule.dst || "any"}
          {step.rule.proto && step.rule.proto !== "all" && (
            <span className="opacity-50"> {step.rule.proto}</span>
          )}
          {step.rule.port && step.rule.port !== "—" && (
            <span className="opacity-50">:{step.rule.port}</span>
          )}
        </span>
        {step.rule.comment && (
          <span className="hidden lg:block text-xs text-muted-foreground/50 truncate max-w-[200px]">
            {step.rule.comment}
          </span>
        )}

        {/* Status badge */}
        <div className="shrink-0 min-w-[130px] flex justify-end">
          {step.status === "pending"     && (
            <span className="text-[10px] text-muted-foreground/40">ожидание</span>
          )}
          {step.status === "evaluating"  && (
            <span className="text-[10px] text-amber-500 font-mono font-semibold flex items-center gap-1 animate-pulse">
              <ZapIcon className="size-3" />проверяется…
            </span>
          )}
          {step.status === "match"       && (
            <span className="text-[10px] text-emerald-600 dark:text-emerald-400 font-mono font-semibold flex items-center gap-1">
              <CheckCircle2Icon className="size-3" />совпадение!
            </span>
          )}
          {step.status === "passthrough" && (
            <span className="text-[10px] text-violet-500 font-mono font-semibold flex items-center gap-1">
              <SkipForwardIcon className="size-3" />passthrough
            </span>
          )}
          {step.status === "disabled"    && (
            <span className="text-[10px] text-muted-foreground/40 font-mono">⊝ выключено</span>
          )}
          {step.status === "skip"        && (
            <span className="text-[10px] text-muted-foreground font-mono flex items-center gap-1">
              <MinusCircleIcon className="size-3" />пропуск
              {step.failedAt && <span className="opacity-60">({step.failedAt})</span>}
            </span>
          )}
        </div>
      </button>

      {/* Expanded checks */}
      {expanded && step.checks.length > 0 && (
        <div className="px-5 pb-3 pl-[52px] flex flex-wrap gap-2">
          {step.checks.map((c, ci) => (
            <div key={ci} className={cn(
              "inline-flex items-center gap-1.5 rounded border px-2.5 py-1 text-[11px] font-mono",
              c.passed
                ? "bg-emerald-500/5 border-emerald-500/20 text-emerald-700 dark:text-emerald-300"
                : "bg-red-500/5 border-red-500/20 text-red-700 dark:text-red-300",
            )}>
              {c.passed
                ? <CheckCircle2Icon className="size-3 shrink-0" />
                : <XCircleIcon className="size-3 shrink-0" />}
              <span className="text-muted-foreground">{c.field}:</span>
              <span className="font-semibold">{c.ruleVal}</span>
              {!c.passed && (
                <>
                  <span className="text-muted-foreground/40 mx-0.5">≠</span>
                  <span>{c.packetVal}</span>
                </>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

// ─── Scenario constants + helpers ────────────────────────────────────────────

const SCENARIO_STORAGE_KEY = "routerlists-fw-scenarios"

const DEFAULT_SRULE: Omit<ScenarioRule, "id"> = {
  chain: "forward", action: "accept", proto: "all",
  src: "", dst: "", port: "", iface: "", comment: "", enabled: true,
}

function scenarioRuleToFw(r: ScenarioRule): FirewallRule {
  return { ...r, hits: 0, action: r.action as FirewallRule["action"] }
}

// ─── Scenario Sheet ───────────────────────────────────────────────────────────

function ScenarioSheet({ open, onClose, initial, onSave }: {
  open: boolean
  onClose: () => void
  initial: SimScenario | null
  onSave: (s: SimScenario) => void
}) {
  const isNew = !initial
  const [name,    setName]   = useState(initial?.name ?? "")
  const [desc,    setDesc]   = useState(initial?.description ?? "")
  const [pkt,     setPkt2]   = useState<PacketDef>(initial?.packet ?? DEFAULT_PKT)
  const [rules,   setRules]  = useState<ScenarioRule[]>(initial?.rules ?? [])
  const [addOpen, setAddOpen]= useState(false)
  const [addForm, setAddForm]= useState<Omit<ScenarioRule, "id">>(DEFAULT_SRULE)

  // Reset form fields each time the sheet opens — standard modal initialization pattern
   
  useEffect(() => {
    if (!open) return
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setName(initial?.name ?? ""); setDesc(initial?.description ?? "")
     
    setPkt2(initial?.packet ?? DEFAULT_PKT); setRules(initial?.rules ?? [])
     
    setAddForm(DEFAULT_SRULE); setAddOpen(false)
  }, [open, initial])

  const setP  = <K extends keyof PacketDef>(k: K, v: PacketDef[K]) =>
    setPkt2(p => ({ ...p, [k]: v }))
  const setAF = <K extends keyof Omit<ScenarioRule, "id">>(k: K, v: Omit<ScenarioRule, "id">[K]) =>
    setAddForm(f => ({ ...f, [k]: v }))

  function addRule() {
    setRules(prev => [...prev, { ...addForm, id: `sr-${Date.now()}` }])
    setAddForm(DEFAULT_SRULE)
    setAddOpen(false)
  }
  function removeRule(id: string) { setRules(prev => prev.filter(r => r.id !== id)) }
  function toggleEnabled(id: string) {
    setRules(prev => prev.map(r => r.id === id ? { ...r, enabled: !r.enabled } : r))
  }
  function moveRule(id: string, dir: -1 | 1) {
    setRules(prev => {
      const idx = prev.findIndex(r => r.id === id)
      const swap = idx + dir
      if (idx < 0 || swap < 0 || swap >= prev.length) return prev
      const next = [...prev];
      [next[idx], next[swap]] = [next[swap], next[idx]]
      return next
    })
  }
  function handleSave() {
    if (!name.trim()) return
    onSave({
      id: initial?.id ?? `scenario-${Date.now()}`,
      name: name.trim(), description: desc.trim(),
      packet: pkt, rules,
      createdAt: initial?.createdAt ?? Date.now(),
    })
    onClose()
  }

  const CHAIN_OPTS  = ["forward","input","output","dstnat","srcnat","prerouting","postrouting"]
  const PROTO_OPTS  = ["all","tcp","udp","icmp","gre","esp"]
  const ACTION_OPTS = [
    "accept","drop","reject","mark-routing","mark-conn","masquerade",
    "fasttrack-connection","passthrough","add-src-to-address-list","add-dst-to-address-list",
  ]

  return (
    <Sheet open={open} onOpenChange={v => { if (!v) onClose() }}>
      <SheetContent className="flex flex-col overflow-hidden p-0 gap-0 sm:max-w-2xl">
        <SheetHeader className="shrink-0 px-6 pt-5 pb-4 border-b">
          <SheetTitle>{isNew ? "Новый сценарий" : `Редактировать: ${initial?.name}`}</SheetTitle>
          <SheetDescription>Набор правил firewall/mangle/nat + тестовый пакет</SheetDescription>
        </SheetHeader>

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

          {/* Meta */}
          <div className="flex flex-col gap-3">
            <SectionTitle>Название</SectionTitle>
            <div className="grid grid-cols-2 gap-3">
              <FormField label="Название сценария" required>
                <Input className="h-8" placeholder="Блокировка Tor Exit"
                  value={name} onChange={e => setName(e.target.value)} />
              </FormField>
              <FormField label="Описание">
                <Input className="h-8" placeholder="Краткое описание"
                  value={desc} onChange={e => setDesc(e.target.value)} />
              </FormField>
            </div>
          </div>

          {/* Default packet */}
          <div className="flex flex-col gap-3">
            <SectionTitle>Тестовый пакет по умолчанию</SectionTitle>
            <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
              <FormField label="Направление / цепочка">
                <NativeSelect value={pkt.chain} onChange={v => setP("chain", v)}>
                  <optgroup label="Полный маршрут">
                    <option value="forward">forward — транзит</option>
                    <option value="input">input — на роутер</option>
                    <option value="output">output — от роутера</option>
                    <option value="dstnat">dstnat — port-forward</option>
                  </optgroup>
                  <optgroup label="Одна цепочка">
                    {["srcnat","prerouting","postrouting","ip6-forward","ip6-input","ip6-output"]
                      .map(c => <option key={c} value={c}>{c}</option>)}
                  </optgroup>
                </NativeSelect>
              </FormField>
              <FormField label="Протокол">
                <NativeSelect value={pkt.proto} onChange={v => setP("proto", v)}>
                  {PROTO_OPTS.map(p => <option key={p} value={p}>{p}</option>)}
                </NativeSelect>
              </FormField>
              <FormField label="Conn-state">
                <Input className="font-mono h-8" value={pkt.connState}
                  placeholder="new" onChange={e => setP("connState", e.target.value)} />
              </FormField>
              <FormField label="Src IP">
                <Input className="font-mono h-8" value={pkt.srcAddr}
                  onChange={e => setP("srcAddr", e.target.value)} />
              </FormField>
              <FormField label="Dst IP">
                <Input className="font-mono h-8" value={pkt.dstAddr}
                  onChange={e => setP("dstAddr", e.target.value)} />
              </FormField>
              <FormField label="Dst Port">
                <Input className="font-mono h-8" value={pkt.dstPort}
                  placeholder="443" onChange={e => setP("dstPort", e.target.value)} />
              </FormField>
              <FormField label="In-interface">
                <Input className="font-mono h-8" value={pkt.inIface}
                  placeholder="lan" onChange={e => setP("inIface", e.target.value)} />
              </FormField>
              <FormField label="Out-interface">
                <Input className="font-mono h-8" value={pkt.outIface}
                  placeholder="wan-msk" onChange={e => setP("outIface", e.target.value)} />
              </FormField>
              <FormField label="Dst addr-list">
                <Input className="font-mono h-8" value={pkt.dstAddrList}
                  placeholder="" onChange={e => setP("dstAddrList", e.target.value)} />
              </FormField>
            </div>
          </div>

          {/* Rules */}
          <div className="flex flex-col gap-3">
            <div className="flex items-center gap-2 py-0.5">
              <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
                Правила сценария
              </span>
              <span className="text-xs font-mono text-muted-foreground">({rules.length})</span>
              <div className="flex-1 h-px bg-border" />
              <Button size="sm" variant="outline" className="h-6 text-xs gap-1 px-2"
                onClick={() => setAddOpen(o => !o)}>
                <PlusIcon className="size-3" />
                {addOpen ? "Отмена" : "Добавить правило"}
              </Button>
            </div>

            {/* Add-rule form */}
            {addOpen && (
              <div className="rounded-lg border bg-muted/20 p-4 flex flex-col gap-3">
                <p className="text-xs font-medium text-muted-foreground">Новое правило</p>
                <div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Цепочка</label>
                    <NativeSelect value={addForm.chain} onChange={v => setAF("chain", v)}>
                      {CHAIN_OPTS.map(c => <option key={c} value={c}>{c}</option>)}
                    </NativeSelect>
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Действие</label>
                    <NativeSelect value={addForm.action} onChange={v => setAF("action", v)}>
                      {ACTION_OPTS.map(a => <option key={a} value={a}>{a}</option>)}
                    </NativeSelect>
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Протокол</label>
                    <NativeSelect value={addForm.proto} onChange={v => setAF("proto", v)}>
                      {PROTO_OPTS.map(p => <option key={p} value={p}>{p}</option>)}
                    </NativeSelect>
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Dst Port</label>
                    <Input className="font-mono h-8 text-xs" value={addForm.port}
                      placeholder="443" onChange={e => setAF("port", e.target.value)} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Src address / list</label>
                    <Input className="font-mono h-8 text-xs" value={addForm.src}
                      placeholder="10.0.0.0/8" onChange={e => setAF("src", e.target.value)} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Dst address / list</label>
                    <Input className="font-mono h-8 text-xs" value={addForm.dst}
                      placeholder="any" onChange={e => setAF("dst", e.target.value)} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">In-interface</label>
                    <Input className="font-mono h-8 text-xs" value={addForm.iface}
                      placeholder="lan" onChange={e => setAF("iface", e.target.value)} />
                  </div>
                  <div className="flex flex-col gap-1">
                    <label className="text-xs text-muted-foreground">Комментарий</label>
                    <Input className="h-8 text-xs" value={addForm.comment}
                      onChange={e => setAF("comment", e.target.value)} />
                  </div>
                </div>
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-2">
                    <FormToggle checked={addForm.enabled} onChange={v => setAF("enabled", v)} />
                    <span className="text-xs text-muted-foreground">Включено</span>
                  </div>
                  <Button size="sm" onClick={addRule}><PlusIcon className="size-4" />Добавить</Button>
                </div>
              </div>
            )}

            {/* Rules table */}
            {rules.length > 0 ? (
              <FirewallScenarioRulesDataGrid
                rules={rules}
                onToggleEnabled={toggleEnabled}
                onMoveUp={(id) => moveRule(id, -1)}
                onMoveDown={(id) => moveRule(id, 1)}
                onRemove={removeRule}
              />
            ) : (
              !addOpen && (
                <div className="text-center py-6 text-sm text-muted-foreground border rounded-lg border-dashed">
                  Нет правил — нажмите «Добавить правило»
                </div>
              )
            )}
          </div>

        </div>

        <SheetFooter className="shrink-0 px-6 py-4 border-t flex-row gap-2">
          <SheetClose render={<Button variant="outline" className="flex-1" />}>Отмена</SheetClose>
          <Button className="flex-1" onClick={handleSave} disabled={!name.trim()}>
            {isNew ? "Создать сценарий" : "Сохранить изменения"}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

// ─── Simulator Tab ────────────────────────────────────────────────────────────

const SIM_SPEED_MS: Record<SimSpeed, number> = { slow: 700, normal: 350, fast: 120, instant: 0 }
const SIM_SPEED_LABELS: Record<SimSpeed, string> = {
  slow: "Медленно", normal: "Нормально", fast: "Быстро", instant: "Мгновенно",
}

function SimulatorTab({ rules: allRules }: { rules: FirewallRule[] }) {
  // ── Core sim state ──────────────────────────────────────────────────────────
  const [packet,       setPacket]   = useState<PacketDef>(DEFAULT_PKT)
  const [activePreset, setPreset]   = useState<string | null>("https")
  const [simState,     setSimState] = useState<"idle" | "running" | "done">("idle")
  const [steps,        setSteps]    = useState<SimStep[]>([])
  const [speed,        setSpeed]    = useState<SimSpeed>("normal")
  const [verdict,      setVerdict]  = useState<{ action: string; ruleIndex: number | null } | null>(null)
  const cancelRef = useRef(false)

  // ── Scenarios state ─────────────────────────────────────────────────────────
  const [scenarios, setScenarios] = useState<SimScenario[]>(() => {
    if (typeof window === "undefined") return []
    try { return JSON.parse(localStorage.getItem(SCENARIO_STORAGE_KEY) ?? "[]") }
    catch { return [] }
  })
  const [activeScenarioId, setActiveScenarioId] = useState<string | null>(null)
  const [scenarioSheetOpen, setScenarioSheetOpen] = useState(false)
  const [editingScenario, setEditingScenario]     = useState<SimScenario | null>(null)

  useEffect(() => {
    localStorage.setItem(SCENARIO_STORAGE_KEY, JSON.stringify(scenarios))
  }, [scenarios])

  const activeScenario = scenarios.find(s => s.id === activeScenarioId) ?? null

  // Live rules vs scenario rules
  const effectiveRules: FirewallRule[] = useMemo(
    () => activeScenario ? activeScenario.rules.map(scenarioRuleToFw) : allRules,
    [activeScenario, allRules],
  )

  const setPkt = <K extends keyof PacketDef>(k: K, v: PacketDef[K]) =>
    setPacket(p => ({ ...p, [k]: v }))

  // All rules in traversal order across every chain the packet touches
  const simRules = useMemo(() => {
    const path = DIR_PATHS[packet.chain] ?? [packet.chain]
    return path.flatMap(chain => effectiveRules.filter(r => r.chain === chain))
  }, [effectiveRules, packet.chain])

  // ── Handlers ────────────────────────────────────────────────────────────────
  function resetSim() {
    cancelRef.current = true
    setSimState("idle")
    setSteps([])
    setVerdict(null)
  }

  function applyPreset(p: typeof SIM_PRESETS[number]) {
    setActiveScenarioId(null)
    setPreset(p.id)
    setPacket({ ...DEFAULT_PKT, ...p.pkt })
    cancelRef.current = true
    setSimState("idle")
    setSteps([])
    setVerdict(null)
  }

  function activateScenario(id: string | null) {
    setActiveScenarioId(id)
    setPreset(null)
    cancelRef.current = true
    setSimState("idle")
    setSteps([])
    setVerdict(null)
    if (id) {
      const sc = scenarios.find(s => s.id === id)
      if (sc) setPacket(sc.packet)
    }
  }

  function saveScenario(sc: SimScenario) {
    setScenarios(prev => {
      const idx = prev.findIndex(x => x.id === sc.id)
      if (idx >= 0) { const next = [...prev]; next[idx] = sc; return next }
      return [...prev, sc]
    })
    activateScenario(sc.id)
  }

  function deleteScenario(id: string) {
    setScenarios(prev => prev.filter(s => s.id !== id))
    if (activeScenarioId === id) { setActiveScenarioId(null); resetSim() }
  }

  async function runSimulation() {
    const path = DIR_PATHS[packet.chain] ?? [packet.chain]
    const rules = path.flatMap(chain => effectiveRules.filter(r => r.chain === chain))
    if (rules.length === 0) {
      setVerdict({ action: "accept (default)", ruleIndex: null })
      setSimState("done")
      return
    }

    cancelRef.current = false
    setSimState("running")
    setVerdict(null)
    setSteps(rules.map((r, i) => ({ rule: r, index: i, status: "pending", checks: [] })))

    const delay = SIM_SPEED_MS[speed]
    let finalVerdict: { action: string; ruleIndex: number | null } | null = null

    for (let i = 0; i < rules.length; i++) {
      if (cancelRef.current) return
      setSteps(prev => prev.map((s, idx) => idx === i ? { ...s, status: "evaluating" } : s))
      if (delay > 0) await new Promise<void>(res => setTimeout(res, delay))
      if (cancelRef.current) return

      const result = evalRule(rules[i], packet)
      setSteps(prev => prev.map((s, idx) =>
        idx === i ? { ...s, status: result.verdict, checks: result.checks, failedAt: result.failedAt } : s,
      ))

      if (result.verdict === "match") {
        finalVerdict = { action: rules[i].action, ruleIndex: i }
        if (delay > 0) await new Promise<void>(res => setTimeout(res, Math.max(delay * 0.5, 200)))
        break
      }
      if (delay > 0) await new Promise<void>(res => setTimeout(res, Math.max(delay * 0.25, 40)))
    }

    if (!cancelRef.current) {
      setVerdict(finalVerdict ?? { action: "accept (default)", ruleIndex: null })
      setSimState("done")
    }
  }

  const verdictColors = verdict ? getVerdictColors(verdict.action) : null

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

      {/* ── Presets + Scenarios card ─────────────────────────────────────────── */}
      <OpsPanel title="Пресеты трафика" contentClassName="px-5 py-4 flex flex-col gap-4">
            <div className="flex flex-wrap gap-2">
              {SIM_PRESETS.map(p => (
                <button key={p.id} type="button" onClick={() => applyPreset(p)}
                  className={cn(
                    "flex items-center gap-2 rounded-lg border px-3 py-1.5 text-sm transition-colors",
                    activePreset === p.id && !activeScenarioId
                      ? "bg-primary/90 text-primary-foreground border-primary"
                      : "border-border bg-background hover:bg-muted/60 text-foreground",
                  )}>
                  <span className="leading-none">{p.emoji}</span>
                  <span className="font-medium leading-none">{p.label}</span>
                  <span className={cn("text-xs leading-none",
                    activePreset === p.id && !activeScenarioId
                      ? "text-primary-foreground/60" : "text-muted-foreground",
                  )}>{p.desc}</span>
                </button>
              ))}
            </div>

          <div className="h-px bg-border" />

          {/* Saved scenarios */}
          <div>
            <div className="flex items-center justify-between mb-2.5">
              <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
                Мои сценарии
              </p>
              <Button size="sm" variant="outline" className="h-6 text-xs gap-1 px-2"
                onClick={() => { setEditingScenario(null); setScenarioSheetOpen(true) }}>
                <PlusIcon className="size-3" />Новый сценарий
              </Button>
            </div>

            {scenarios.length === 0 ? (
              <p className="text-xs text-muted-foreground/60 py-1">
                Нет сохранённых сценариев — создайте набор правил firewall/mangle/nat для тестирования
              </p>
            ) : (
              <div className="flex flex-wrap gap-2">
                {scenarios.map(sc => {
                  const isActive = activeScenarioId === sc.id
                  return (
                    <div key={sc.id} className={cn(
                      "flex items-center gap-2.5 rounded-lg border px-3 py-2 transition-colors",
                      isActive
                        ? "bg-violet-500/10 border-violet-500/40"
                        : "border-border bg-background hover:bg-muted/40",
                    )}>
                      <div className="flex flex-col min-w-0 leading-tight">
                        <span className="text-sm font-medium">{sc.name}</span>
                        <span className="text-[11px] text-muted-foreground font-mono">
                          {sc.rules.length} правил · {sc.packet.chain}
                          {sc.description ? ` · ${sc.description}` : ""}
                        </span>
                      </div>
                      <div className="flex items-center gap-1 shrink-0">
                        <button type="button"
                          onClick={() => activateScenario(isActive ? null : sc.id)}
                          className={cn(
                            "text-[11px] font-mono px-2 py-0.5 rounded border transition-colors",
                            isActive
                              ? "bg-violet-500 text-white border-violet-500"
                              : "border-border hover:bg-muted",
                          )}>
                          {isActive ? "Активен" : "Выбрать"}
                        </button>
                        <button type="button"
                          onClick={() => { setEditingScenario(sc); setScenarioSheetOpen(true) }}
                          className="p-1 text-muted-foreground/50 hover:text-foreground transition-colors">
                          <PencilIcon className="size-3.5" />
                        </button>
                        <button type="button" onClick={() => deleteScenario(sc.id)}
                          className="p-1 text-muted-foreground/50 hover:text-red-500 transition-colors">
                          <Trash2Icon className="size-3.5" />
                        </button>
                      </div>
                    </div>
                  )
                })}
              </div>
            )}
          </div>
      </OpsPanel>

      {/* ── Packet editor ───────────────────────────────────────────────────── */}
      <OpsPanel title="Параметры пакета" contentClassName="px-5 py-4 flex flex-col gap-4">
          <div className="flex items-center justify-end flex-wrap gap-2">
            <div className="flex items-center gap-3">
              <span className={cn(
                "text-[11px] font-mono px-2 py-0.5 rounded border",
                activeScenario
                  ? "bg-violet-500/10 border-violet-500/30 text-violet-600 dark:text-violet-400"
                  : "bg-muted border-border text-muted-foreground",
              )}>
                {activeScenario ? `🎭 ${activeScenario.name}` : "🔴 Живые правила"}
              </span>
              <div className="flex flex-col items-end gap-0.5">
                <span className="text-xs text-muted-foreground font-mono">
                  {simRules.length} правил ·{" "}
                  <span className="text-foreground font-semibold">{packet.chain}</span>
                </span>
                {DIR_PATHS[packet.chain] && (
                  <span className="text-[10px] text-muted-foreground/50 font-mono">
                    {DIR_PATHS[packet.chain].join(" → ")}
                  </span>
                )}
              </div>
            </div>
          </div>

          {/* Row 1 */}
          <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Направление / цепочка</label>
              <NativeSelect value={packet.chain} onChange={v => { setPkt("chain", v); resetSim() }}>
                <optgroup label="Полный маршрут (все таблицы)">
                  <option value="forward">forward — транзит LAN→WAN</option>
                  <option value="input">input — на роутер</option>
                  <option value="output">output — от роутера</option>
                  <option value="dstnat">dstnat — port-forward</option>
                </optgroup>
                <optgroup label="Одна цепочка">
                  <option value="prerouting">prerouting</option>
                  <option value="postrouting">postrouting</option>
                  <option value="srcnat">srcnat</option>
                  <option value="ip6-forward">ip6-forward</option>
                  <option value="ip6-input">ip6-input</option>
                  <option value="ip6-output">ip6-output</option>
                </optgroup>
              </NativeSelect>
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Протокол</label>
              <NativeSelect value={packet.proto} onChange={v => setPkt("proto", v)}>
                {["tcp","udp","icmp","gre","esp","ah"].map(p => <option key={p} value={p}>{p}</option>)}
              </NativeSelect>
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Src IP</label>
              <Input className="font-mono h-8 text-xs" value={packet.srcAddr}
                onChange={e => setPkt("srcAddr", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Dst IP</label>
              <Input className="font-mono h-8 text-xs" value={packet.dstAddr}
                onChange={e => setPkt("dstAddr", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Src Port</label>
              <Input className="font-mono h-8 text-xs" value={packet.srcPort}
                placeholder="—" onChange={e => setPkt("srcPort", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Dst Port</label>
              <Input className="font-mono h-8 text-xs" value={packet.dstPort}
                placeholder="—" onChange={e => setPkt("dstPort", e.target.value)} />
            </div>
          </div>

          {/* Row 2 */}
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">In-interface</label>
              <Input className="font-mono h-8 text-xs" value={packet.inIface}
                placeholder="lan" onChange={e => setPkt("inIface", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Out-interface</label>
              <Input className="font-mono h-8 text-xs" value={packet.outIface}
                placeholder="wan-msk" onChange={e => setPkt("outIface", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Conn-state</label>
              <Input className="font-mono h-8 text-xs" value={packet.connState}
                placeholder="new" onChange={e => setPkt("connState", e.target.value)} />
            </div>
            <div className="flex flex-col gap-1">
              <label className="text-xs text-muted-foreground">Dst addr-list</label>
              <Input className="font-mono h-8 text-xs" value={packet.dstAddrList}
                placeholder="youtube-bypass" onChange={e => setPkt("dstAddrList", e.target.value)} />
            </div>
          </div>

          {/* Speed + Run controls */}
          <div className="flex items-center gap-3 pt-2 border-t border-border flex-wrap">
            <SlidersHorizontalIcon className="size-3.5 text-muted-foreground shrink-0" />
            <div className="flex items-center gap-1 rounded-md border border-border bg-muted/40 p-0.5">
              {(["slow","normal","fast","instant"] as SimSpeed[]).map(s => (
                <button key={s} type="button"
                  onClick={() => setSpeed(s)} disabled={simState === "running"}
                  className={cn(
                    "px-3 py-1 text-xs rounded transition-colors disabled:opacity-50",
                    speed === s ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
                  )}>
                  {SIM_SPEED_LABELS[s]}
                </button>
              ))}
            </div>
            <div className="flex items-center gap-2 ml-auto">
              <Button variant="outline" size="sm" onClick={resetSim} disabled={simState === "idle"}>
                <RotateCcwIcon className="size-4" />Сбросить
              </Button>
              {simState === "running" ? (
                <Button size="sm" variant="destructive"
                  onClick={() => { cancelRef.current = true; setSimState("done") }}>
                  <SquareIcon className="size-4" />Стоп
                </Button>
              ) : (
                <Button size="sm" onClick={runSimulation} disabled={simRules.length === 0}>
                  <PlayIcon className="size-4" />
                  {simState === "done" ? "Снова" : "Запустить"}
                </Button>
              )}
            </div>
          </div>
      </OpsPanel>

      {/* ── Verdict banner ──────────────────────────────────────────────────── */}
      {verdict && simState === "done" && verdictColors && (
        <div className={cn(
          "rounded-xl border px-5 py-4 flex items-center gap-4",
          verdictColors.bg, verdictColors.border,
        )}>
          <div className={verdictColors.text}>{getVerdictIcon(verdict.action)}</div>
          <div className="flex-1 min-w-0">
            <p className={cn("font-semibold text-base", verdictColors.text)}>
              {verdict.action === "drop" || verdict.action === "reject"
                ? "Пакет заблокирован"
                : verdict.ruleIndex === null ? "Пакет принят (по умолчанию)" : "Пакет принят"}
              {verdict.ruleIndex !== null && (
                <span className="font-normal text-sm ml-2 opacity-70">— правило #{verdict.ruleIndex + 1}</span>
              )}
            </p>
            <p className="text-sm text-muted-foreground mt-0.5">
              Действие: <span className={cn("font-mono font-medium", verdictColors.text)}>{verdict.action}</span>
              {verdict.ruleIndex === null && <span className="ml-2 opacity-70">· ни одно правило не совпало</span>}
            </p>
          </div>
        </div>
      )}

      {/* ── Trace list ──────────────────────────────────────────────────────── */}
      {steps.length > 0 && (
        <Frame dense className="w-full">
          <FramePanel className="p-0">
          <div className="px-5 py-3 border-b flex items-center justify-between">
            <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
              Трассировка · цепочка: <span className="text-foreground normal-case font-mono">{packet.chain}</span>
              {activeScenario && (
                <span className="ml-2 text-violet-500 font-sans normal-case">· {activeScenario.name}</span>
              )}
            </p>
            <span className="text-xs text-muted-foreground">
              <span className="text-emerald-500">{steps.filter(s => s.status === "match").length} совпало</span>
              {" · "}{steps.filter(s => s.status === "skip").length} пропущено
              {" · "}{steps.filter(s => s.status === "disabled").length} выкл
            </span>
          </div>
          <div className="divide-y divide-border/60">
            {steps.map((step, i) => {
              const prevChain = i > 0 ? steps[i - 1].rule.chain : null
              const showHeader = step.rule.chain !== prevChain
              return (
                <div key={step.rule.id}>
                  {showHeader && (
                    <div className="px-5 py-1.5 bg-muted/40 border-b border-border/50 flex items-center gap-2">
                      <span className="text-[10px] font-mono font-semibold text-foreground/70 uppercase tracking-wider">
                        {step.rule.chain}
                      </span>
                      {CHAIN_TABLE_HINT[step.rule.chain] && (
                        <span className="text-[10px] text-muted-foreground/50">
                          · {CHAIN_TABLE_HINT[step.rule.chain]}
                        </span>
                      )}
                      <span className="text-[10px] text-muted-foreground/40 ml-auto">
                        {steps.filter(s => s.rule.chain === step.rule.chain).length} правил
                      </span>
                    </div>
                  )}
                  <SimStepRow step={step} index={i} />
                </div>
              )
            })}
          </div>
          </FramePanel>
        </Frame>
      )}

      {/* ── Empty state ─────────────────────────────────────────────────────── */}
      {simState === "idle" && simRules.length === 0 && (
        <div className="flex flex-col items-center justify-center py-16 text-center text-muted-foreground">
          <ShieldOffIcon className="size-10 mb-3 opacity-30" />
          <p className="text-sm font-medium">Нет правил в цепочке «{packet.chain}»</p>
          <p className="text-xs mt-1 opacity-60">
            {activeScenario
              ? "Добавьте правила в сценарий через кнопку «Редактировать»"
              : "Выберите другой пресет или измените цепочку"}
          </p>
        </div>
      )}

      {/* ── Scenario Sheet ───────────────────────────────────────────────────── */}
      <ScenarioSheet
        open={scenarioSheetOpen}
        onClose={() => setScenarioSheetOpen(false)}
        initial={editingScenario}
        onSave={saveScenario}
      />

    </div>
  )
}

// ─── Live API ─────────────────────────────────────────────────────────────────

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

interface FirewallAllResponse {
  rules: FirewallRule[]
  addressLists: AddressListEntry[]
}

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

function mapBackendToServer(s: BackendServer): Server {
  return {
    id: String(s.id),
    name: s.name || s.host,
    host: s.host,
    model: "—",
    os: "—",
    site: s.site,
    country: s.country || "UN",
    asn: s.asn,
    type: s.type,
    enabled: s.enabled,
    status: (s.status ?? "offline") as Server["status"],
    latency: s.latency != null ? Math.round(s.latency) : null,
    sessions: 0,
  }
}

function ruleFamily(r: Pick<FirewallRule, "family">): IpFamily {
  return r.family === "ip6" ? "ip6" : "ip"
}

function addressFamily(e: Pick<AddressListEntry, "family">): IpFamily {
  return e.family === "ip6" ? "ip6" : "ip"
}

function ruleKey(r: FirewallRule) {
  if (!r.serverId || !r.rosId || !r.table) return null
  return { serverId: r.serverId, family: ruleFamily(r), table: r.table, rosId: r.rosId }
}

function sameRuleGroup(a: FirewallRule, b: FirewallRule): boolean {
  return (
    a.serverId === b.serverId
    && ruleFamily(a) === ruleFamily(b)
    && (a.table ?? "filter") === (b.table ?? "filter")
  )
}

function moveInArray<T>(arr: T[], from: number, to: number): T[] {
  if (from < 0 || to < 0 || from >= arr.length || to >= arr.length) return arr
  const copy = [...arr]
  const [moved] = copy.splice(from, 1)
  if (moved === undefined) return arr
  copy.splice(to, 0, moved)
  return copy
}

function reorderWithinGroup(list: FirewallRule[], activeId: string, overId: string): FirewallRule[] | null {
  const active = list.find((r) => r.id === activeId)
  const over = list.find((r) => r.id === overId)
  if (!active || !over || !sameRuleGroup(active, over)) return null
  const same = (r: FirewallRule) => sameRuleGroup(r, active)
  const group = list.filter(same)
  const from = group.findIndex((r) => r.id === activeId)
  const to = group.findIndex((r) => r.id === overId)
  if (from < 0 || to < 0 || from === to) return null
  const nextGroup = moveInArray(group, from, to)
  let i = 0
  return list.map((r) => (same(r) ? nextGroup[i++]! : r))
}

/** RouterOS move: insert-before destination. Omit dest = конец таблицы. */
function rosMoveDestination(list: FirewallRule[], activeId: string, overId: string): { dest?: string } | null {
  const active = list.find((r) => r.id === activeId)
  const over = list.find((r) => r.id === overId)
  if (!active || !over || !sameRuleGroup(active, over)) return null
  const group = list.filter((r) => sameRuleGroup(r, active))
  const from = group.findIndex((r) => r.id === activeId)
  const to = group.findIndex((r) => r.id === overId)
  if (from < 0 || to < 0 || from === to) return null
  if (from < to) {
    const dest = group[to + 1]?.rosId
    return dest ? { dest } : {}
  }
  const dest = group[to]?.rosId
  return dest ? { dest } : {}
}

function addressKey(e: AddressListEntry) {
  if (!e.serverId || !e.rosId) return null
  return { serverId: e.serverId, family: addressFamily(e), rosId: e.rosId }
}

function payloadToMockRule(
  payload: RuleWritePayload,
  opts: { id: string; table: FirewallTable; family: IpFamily; serverId: string; serverName: string; rosId: string },
): FirewallRule {
  return {
    id: opts.id,
    table: opts.table,
    family: opts.family,
    serverId: opts.serverId,
    serverName: opts.serverName,
    rosId: opts.rosId,
    chain: payload.chain,
    action: payload.action,
    proto: payload.protocol ?? "all",
    src: payload.srcAddress ?? payload.srcAddressList ?? "—",
    dst: payload.dstAddress ?? payload.dstAddressList ?? "—",
    port: payload.dstPort ?? payload.srcPort ?? "—",
    iface: payload.inInterface ?? payload.outInterface ?? "—",
    comment: payload.comment ?? "",
    enabled: !payload.disabled,
    hits: 0,
    log: payload.log,
    logPrefix: payload.logPrefix,
  }
}

// ════════════════════════════════════════════════════════════════════════════
function FirewallPageInner() {
  const { mode, backendUrl } = useDataSource()
  const isLive = mode === "live"
  const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])
  const searchParams = useSearchParams()
  const serverFromQuery = searchParams.get("server")

  const [liveServers, setLiveServers] = useState<Server[]>([])
  const [liveRules, setLiveRules] = useState<FirewallRule[]>([])
  const [liveAddrLists, setLiveAddrLists] = useState<AddressListEntry[]>([])
  const [mockRules, setMockRules] = useState<FirewallRule[]>(firewallRules)
  const [mockAddrLists, setMockAddrLists] = useState<AddressListEntry[]>(INIT_ADDRESS_LISTS)
  const [dataLoading, setDataLoading] = useState(false)
  const [dataError, setDataError] = useState<string | null>(null)
  const [busy, setBusy] = useState(false)

  const [selectedServerId, setSelectedServerId] = useState(serverFromQuery ?? ALL_SERVERS_ID)
  const appliedQueryRef = useRef(false)

  const [chainGroup, setChainGroup] = useState<ChainGroup>("filter")
  const [chainFilter, setChainFilter] = useState<ChainFilter>("all")
  const [ipFamily, setIpFamily] = useState<IpFamily>("ip")
  const [search, setSearch] = useState("")

  const [editingRule, setEditingRule] = useState<Partial<FirewallRule> | null>(null)
  const [ruleSheetOpen, setRuleSheetOpen] = useState(false)
  const [exportOpen, setExportOpen] = useState(false)
  const [editingAddr, setEditingAddr] = useState<Partial<AddressListEntry> | null>(null)
  const [addrSheetOpen, setAddrSheetOpen] = useState(false)

  const loadLive = useCallback(async () => {
    if (!isLive) return
    setDataLoading(true)
    setDataError(null)
    try {
      const [backendServers, fw] = await Promise.all([
        apiFetch<BackendServer[]>("/api/servers"),
        apiFetch<FirewallAllResponse>("/api/firewall/all"),
      ])
      setLiveServers(backendServers.map(mapBackendToServer))
      setLiveRules(fw.rules)
      setLiveAddrLists(fw.addressLists)
    } catch (e) {
      setDataError(e instanceof Error ? e.message : "Ошибка загрузки")
      setLiveServers([])
      setLiveRules([])
      setLiveAddrLists([])
    } finally {
      setDataLoading(false)
    }
  }, [isLive, apiFetch])

  useEffect(() => {
    if (!isLive) {
      queueMicrotask(() => {
        setLiveServers([])
        setLiveRules([])
        setLiveAddrLists([])
        setDataError(null)
      })
      return
    }
    queueMicrotask(() => {
      void loadLive()
    })
  }, [isLive, loadLive])

  useEffect(() => {
    if (dataError) toast.error(dataError)
  }, [dataError])

  const displayRules = isLive ? liveRules : mockRules
  const displayAddrLists = isLive ? liveAddrLists : mockAddrLists
  const displayServers = isLive ? liveServers : mockServers

  useEffect(() => {
    if (appliedQueryRef.current || !serverFromQuery) return
    if (displayServers.length === 0) return
    if (displayServers.some((s) => s.id === serverFromQuery)) {
      setSelectedServerId(serverFromQuery)
    }
    appliedQueryRef.current = true
  }, [displayServers, serverFromQuery])

  const scopedRules = useMemo(() => {
    if (selectedServerId === ALL_SERVERS_ID) return displayRules
    return displayRules.filter((r) => r.serverId === selectedServerId)
  }, [displayRules, selectedServerId])

  const scopedAddrLists = useMemo(() => {
    if (selectedServerId === ALL_SERVERS_ID) return displayAddrLists
    return displayAddrLists.filter((e) => e.serverId === selectedServerId)
  }, [displayAddrLists, selectedServerId])

  const familyRules = useMemo(
    () => scopedRules.filter((r) => ruleFamily(r) === ipFamily),
    [scopedRules, ipFamily],
  )
  const familyAddrLists = useMemo(
    () => scopedAddrLists.filter((e) => addressFamily(e) === ipFamily),
    [scopedAddrLists, ipFamily],
  )

  const firewallRailItems = useMemo<ServerTileItem[]>(() => (
    displayServers.map((s) => ({
      id: s.id,
      name: s.name,
      host: s.host,
      site: s.site,
      country: s.country,
      status: s.status,
      type: s.type,
      enabled: s.enabled,
      meta: String(displayRules.filter((r) => r.serverId === s.id).length),
    }))
  ), [displayServers, displayRules])

  const writeServerId = selectedServerId === ALL_SERVERS_ID ? null : selectedServerId
  const writeServer = displayServers.find((s) => s.id === writeServerId)

  const chainsInGroup = useMemo(() => {
    if (chainGroup === "address-lists" || chainGroup === "simulator") return []
    return CHAIN_GROUPS.find((g) => g.id === chainGroup)?.chains ?? []
  }, [chainGroup])

  const groupRules = useMemo(() => {
    const table = tableOfGroup(chainGroup)
    if (!table) return []
    return familyRules.filter((r) => r.table === table)
  }, [familyRules, chainGroup])

  const filteredRules = useMemo(() => {
    return groupRules.filter((r) => {
      if (chainFilter !== "all" && r.chain !== chainFilter) return false
      if (!search) return true
      const q = search.toLowerCase()
      return (
        r.chain.toLowerCase().includes(q) ||
        r.action.toLowerCase().includes(q) ||
        r.src.toLowerCase().includes(q) ||
        r.dst.toLowerCase().includes(q) ||
        r.comment.toLowerCase().includes(q) ||
        (r.serverName ?? "").toLowerCase().includes(q)
      )
    })
  }, [groupRules, chainFilter, search])

  const chainCounts = useMemo(() => {
    const counts: Record<string, number> = { all: groupRules.length }
    for (const r of groupRules) {
      counts[r.chain] = (counts[r.chain] ?? 0) + 1
    }
    return counts
  }, [groupRules])

  const totalEnabled = familyRules.filter((r) => r.enabled).length
  const totalHits = familyRules.reduce((s, r) => s + r.hits, 0)
  const dropRules = familyRules.filter((r) => r.action === "drop" || r.action === "reject").length
  const showServerCol = selectedServerId === ALL_SERVERS_ID

  function requireWriteServer(): string | null {
    if (writeServerId) return writeServerId
    toast.info("Выберите сервер в панели слева")
    return null
  }

  async function toggleRule(id: string) {
    const r = displayRules.find((x) => x.id === id)
    if (!r) return
    if (isLive) {
      const key = ruleKey(r)
      if (!key) return
      try {
        await apiFetch("/api/firewall/rules", {
          method: "PATCH",
          body: JSON.stringify({ ...key, disabled: r.enabled }),
        })
        await loadLive()
      } catch (e) {
        toast.error(e instanceof Error ? e.message : "Не удалось переключить правило")
      }
      return
    }
    setMockRules((rs) => rs.map((x) => x.id === id ? { ...x, enabled: !x.enabled } : x))
  }

  async function deleteRule(r: FirewallRule) {
    if (isLive) {
      const key = ruleKey(r)
      if (!key) return
      try {
        await apiFetch("/api/firewall/rules", { method: "DELETE", body: JSON.stringify(key) })
        toast.success("Правило удалено")
        await loadLive()
      } catch (e) {
        toast.error(e instanceof Error ? e.message : "Не удалось удалить правило")
      }
      return
    }
    setMockRules((rs) => rs.filter((x) => x.id !== r.id))
  }

  const canReorder = useMemo(() => {
    if (filteredRules.length < 2) return false
    const servers = new Set(filteredRules.map((r) => r.serverId).filter(Boolean))
    return servers.size === 1
  }, [filteredRules])

  async function reorderRules(activeId: string, overId: string) {
    const source = isLive ? liveRules : mockRules
    const next = reorderWithinGroup(source, activeId, overId)
    if (!next) {
      toast.info("Правила можно переставлять только в пределах одного сервера и таблицы")
      return
    }
    if (!isLive) {
      setMockRules(next)
      return
    }
    const active = source.find((r) => r.id === activeId)
    const key = active ? ruleKey(active) : null
    if (!key) {
      toast.error("У правила нет идентификатора RouterOS")
      return
    }
    const dest = rosMoveDestination(source, activeId, overId)
    if (!dest) return
    const prev = source
    setLiveRules(next)
    try {
      await apiFetch("/api/firewall/rules/move", {
        method: "POST",
        body: JSON.stringify({
          ...key,
          ...(dest.dest ? { destinationRosId: dest.dest } : {}),
        }),
      })
      await loadLive()
    } catch (e) {
      setLiveRules(prev)
      toast.error(e instanceof Error ? e.message : "Не удалось переместить правило")
    }
  }

  function openAdd() {
    if (!requireWriteServer()) return
    const table = tableOfGroup(chainGroup)
    if (!table) {
      setChainGroup("filter")
    }
    setEditingRule(null)
    setRuleSheetOpen(true)
  }

  function openEdit(r: FirewallRule) {
    setEditingRule(r)
    setRuleSheetOpen(true)
  }

  async function saveRule(payload: RuleWritePayload) {
    const table = (editingRule?.table ?? tableOfGroup(chainGroup) ?? "filter") as FirewallTable
    const family = editingRule?.id ? ruleFamily(editingRule as FirewallRule) : ipFamily
    const serverId = editingRule?.serverId ?? requireWriteServer()
    if (!serverId) return
    const serverName = editingRule?.serverName
      ?? displayServers.find((s) => s.id === serverId)?.name
      ?? writeServer?.name
      ?? serverId

    if (isLive) {
      setBusy(true)
      try {
        if (editingRule?.rosId) {
          await apiFetch("/api/firewall/rules", {
            method: "PUT",
            body: JSON.stringify({ serverId, family, table, rosId: editingRule.rosId, ...payload }),
          })
          toast.success("Правило сохранено")
        } else {
          await apiFetch("/api/firewall/rules", {
            method: "POST",
            body: JSON.stringify({ serverId, family, table, ...payload }),
          })
          toast.success("Правило создано")
        }
        setRuleSheetOpen(false)
        await loadLive()
      } catch (e) {
        toast.error(e instanceof Error ? e.message : "Не удалось сохранить правило")
      } finally {
        setBusy(false)
      }
      return
    }

    if (editingRule?.id) {
      setMockRules((rs) => rs.map((x) => x.id === editingRule.id
        ? { ...x, ...payloadToMockRule(payload, {
            id: x.id,
            table,
            family,
            serverId,
            serverName,
            rosId: x.rosId ?? "*0",
          }) }
        : x))
    } else {
      const id = `fw-${Date.now()}`
      setMockRules((rs) => [...rs, payloadToMockRule(payload, {
        id,
        table,
        family,
        serverId,
        serverName,
        rosId: `*${Date.now()}`,
      })])
    }
    setRuleSheetOpen(false)
  }

  function openAddAddr(list?: string) {
    if (!requireWriteServer()) return
    setEditingAddr(list ? { list } : null)
    setAddrSheetOpen(true)
  }

  function openEditAddr(e: AddressListEntry) {
    setEditingAddr(e)
    setAddrSheetOpen(true)
  }

  async function toggleAddr(e: AddressListEntry) {
    if (isLive) {
      const key = addressKey(e)
      if (!key) return
      try {
        await apiFetch("/api/firewall/address-lists", {
          method: "PATCH",
          body: JSON.stringify({ ...key, disabled: !e.disabled }),
        })
        await loadLive()
      } catch (err) {
        toast.error(err instanceof Error ? err.message : "Не удалось переключить запись")
      }
      return
    }
    setMockAddrLists((rows) => rows.map((x) => x.id === e.id ? { ...x, disabled: !x.disabled } : x))
  }

  async function deleteAddr(e: AddressListEntry) {
    if (isLive) {
      const key = addressKey(e)
      if (!key) return
      try {
        await apiFetch("/api/firewall/address-lists", { method: "DELETE", body: JSON.stringify(key) })
        toast.success("Запись удалена")
        await loadLive()
      } catch (err) {
        toast.error(err instanceof Error ? err.message : "Не удалось удалить запись")
      }
      return
    }
    setMockAddrLists((rows) => rows.filter((x) => x.id !== e.id))
  }

  async function saveAddr(payload: {
    list: string
    address: string
    comment?: string
    timeout?: string
    disabled?: boolean
  }) {
    const family = editingAddr?.id ? addressFamily(editingAddr as AddressListEntry) : ipFamily
    const serverId = editingAddr?.serverId ?? requireWriteServer()
    if (!serverId) return
    const serverName = editingAddr?.serverName
      ?? displayServers.find((s) => s.id === serverId)?.name
      ?? writeServer?.name
      ?? serverId

    if (isLive) {
      setBusy(true)
      try {
        if (editingAddr?.rosId) {
          await apiFetch("/api/firewall/address-lists", {
            method: "PUT",
            body: JSON.stringify({ serverId, family, rosId: editingAddr.rosId, ...payload }),
          })
          toast.success("Запись сохранена")
        } else {
          await apiFetch("/api/firewall/address-lists", {
            method: "POST",
            body: JSON.stringify({ serverId, family, ...payload }),
          })
          toast.success("Запись создана")
        }
        setAddrSheetOpen(false)
        await loadLive()
      } catch (err) {
        toast.error(err instanceof Error ? err.message : "Не удалось сохранить запись")
      } finally {
        setBusy(false)
      }
      return
    }

    if (editingAddr?.id) {
      setMockAddrLists((rows) => rows.map((x) => x.id === editingAddr.id
        ? { ...x, ...payload, family, serverId, serverName }
        : x))
    } else {
      setMockAddrLists((rows) => [...rows, {
        id: `al-${Date.now()}`,
        list: payload.list,
        address: payload.address,
        comment: payload.comment ?? "",
        timeout: payload.timeout,
        disabled: payload.disabled ?? false,
        family,
        serverId,
        serverName,
        rosId: `*${Date.now()}`,
      }])
    }
    setAddrSheetOpen(false)
  }

  const sheetChainGroup: ChainGroup =
    chainGroup === "address-lists" || chainGroup === "simulator" ? "filter" : chainGroup

  return (
    <>
    <ServerRailLayout
      items={firewallRailItems}
      selectedId={selectedServerId}
      onSelect={setSelectedServerId}
      showAll
      allCount={displayServers.length}
      loading={isLive && dataLoading && displayServers.length === 0}
      header={
        <PageHeader
          crumbs={[{ label: "Управление" }, { label: "Firewall" }]}
          actions={
            <>
              <ServerRailMobileButton />
              <Button
                variant="outline"
                size="sm"
                onClick={() => { void loadLive() }}
                disabled={!isLive || dataLoading}
                title={!isLive ? "Включите Live в источнике данных" : "Обновить правила с устройств"}
              >
                <RefreshCwIcon className={cn("size-4", dataLoading && "animate-spin")} />
                Обновить
              </Button>
              <Button variant="outline" size="sm" onClick={() => setExportOpen(true)}>
                <CodeXmlIcon className="size-4" />Экспорт .rsc
              </Button>
              <Button size="sm" onClick={openAdd}>
                <PlusIcon className="size-4" />Новое правило
              </Button>
            </>
          }
        />
      }
    >
        <div className="flex flex-col gap-5">
          {dataError && isLive && (
            <div className="rounded-lg border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive">
              {dataError}
            </div>
          )}

          <div className="flex items-center gap-3 flex-wrap">
            <SegmentedControl
              value={ipFamily}
              onChange={(v) => { setIpFamily(v); setChainFilter("all") }}
              options={(["ip", "ip6"] as IpFamily[]).map((f) => ({
                value: f,
                label: IP_FAMILY_LABELS[f],
              }))}
            />
            <span className="text-xs text-muted-foreground">
              {ipFamily === "ip6" ? "/ipv6 firewall" : "/ip firewall"}
            </span>
          </div>

          <KpiStatGrid
            aria-label="Сводка Firewall"
            items={[
              {
                id: "rules",
                label: "Всего правил",
                value: familyRules.length,
                icon: <ShieldIcon className="size-4" />,
                iconClassName: "text-muted-foreground",
              },
              {
                id: "enabled",
                label: "Активных",
                value: totalEnabled,
                icon: <CheckCircleIcon className="size-4" />,
                iconClassName: "text-success",
              },
              {
                id: "drop",
                label: "Блокирующих",
                value: dropRules,
                icon: <ShieldOffIcon className="size-4" />,
                iconClassName: "text-destructive",
              },
              {
                id: "hits",
                label: "Срабатываний",
                value: fmtHits(totalHits),
                icon: <ListFilterIcon className="size-4" />,
                iconClassName: "text-info",
              },
            ]}
          />

          <div className="flex items-center gap-1 border-b">
            {CHAIN_GROUPS.map((g) => (
              <button key={g.id}
                onClick={() => { setChainGroup(g.id); setChainFilter("all"); setSearch("") }}
                className={cn(
                  "flex items-center gap-1.5 px-4 py-2 text-sm font-medium border-b-2 transition-colors -mb-px",
                  chainGroup === g.id
                    ? "border-foreground text-foreground"
                    : "border-transparent text-muted-foreground hover:text-foreground",
                )}>
                {g.icon}
                {g.label}
                {g.id !== "address-lists" && g.id !== "simulator" && (
                  <span className="text-[10px] font-mono opacity-50">
                    {familyRules.filter((r) => r.table === g.id).length}
                  </span>
                )}
                {g.id === "address-lists" && (
                  <span className="text-[10px] font-mono opacity-50">{familyAddrLists.length}</span>
                )}
              </button>
            ))}
          </div>

          {chainGroup === "address-lists" ? (
            <AddressListsTab
              entries={familyAddrLists}
              onAdd={() => openAddAddr()}
              onAddToList={(list) => openAddAddr(list)}
              onToggle={(e) => { void toggleAddr(e) }}
              onEdit={openEditAddr}
              onDelete={(e) => { void deleteAddr(e) }}
              showServer={showServerCol}
            />
          ) : chainGroup === "simulator" ? (
            <SimulatorTab rules={familyRules} />
          ) : (
            <DataPageCard>
              <DataPageToolbarFrame>
                <SegmentedControl
                  value={chainFilter}
                  onChange={setChainFilter}
                  options={[
                    { value: "all", label: "Все", count: chainCounts.all ?? 0 },
                    ...chainsInGroup.map((c) => ({
                      value: c,
                      label: c,
                      count: chainCounts[c] ?? 0,
                    })),
                  ]}
                />
                <InputGroup className="min-w-[220px] max-w-sm">
                  <InputGroupAddon>
                    <SearchIcon className="size-3.5" />
                  </InputGroupAddon>
                  <InputGroupInput
                    placeholder="Поиск по адресу, действию…"
                    value={search}
                    onChange={(e) => setSearch(e.target.value)}
                  />
                </InputGroup>
                <span className="text-sm text-muted-foreground ml-auto">
                  {filteredRules.length} правил
                </span>
              </DataPageToolbarFrame>

              <FirewallRulesDataGrid
                rules={filteredRules}
                onToggle={(id) => { void toggleRule(id) }}
                onEdit={openEdit}
                onDelete={(r) => { void deleteRule(r) }}
                onReorder={canReorder ? (activeId, overId) => { void reorderRules(activeId, overId) } : undefined}
                showServer={showServerCol}
              />
            </DataPageCard>
          )}

          <OpsPanel title="RouterOS 7.20+ · /ip firewall + /ipv6 firewall — цепочки и новые матчеры" contentClassName="px-5 py-4">
              <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 text-xs">
                {[
                  {
                    title: "/ip firewall filter",
                    lines: ["input — трафик на роутер", "forward — транзит", "output — от роутера"],
                  },
                  {
                    title: "/ipv6 firewall filter",
                    lines: ["input / forward / output", "без префикса ip6-", "семейство — отдельный сегмент"],
                  },
                  {
                    title: "Новые матчеры 7.x",
                    lines: [
                      "tls-host=*.google.com",
                      "connection-rate=100/s",
                      "layer7-protocol=...",
                    ],
                  },
                  {
                    title: "Новые действия 7.x",
                    lines: [
                      "fasttrack-connection",
                      "nfqueue (queue-num=0)",
                      "return",
                      "passthrough",
                    ],
                  },
                ].map((t) => (
                  <div key={t.title}>
                    <p className="font-mono font-semibold text-foreground/80 mb-1.5 text-[11px]">{t.title}</p>
                    <ul className="flex flex-col gap-1">
                      {t.lines.map((c) => (
                        <li key={c} className="text-muted-foreground font-mono flex items-start gap-1.5">
                          <span className="text-muted-foreground/40 mt-0.5">›</span>{c}
                        </li>
                      ))}
                    </ul>
                  </div>
                ))}
              </div>
          </OpsPanel>

        </div>
    </ServerRailLayout>

      <RuleSheet
        open={ruleSheetOpen}
        onClose={() => setRuleSheetOpen(false)}
        initialRule={editingRule}
        chainGroup={sheetChainGroup}
        family={editingRule?.id ? ruleFamily(editingRule as FirewallRule) : ipFamily}
        onSave={(payload) => { void saveRule(payload) }}
        saving={busy}
      />

      <AddressListSheet
        open={addrSheetOpen}
        onClose={() => setAddrSheetOpen(false)}
        initial={editingAddr}
        family={editingAddr?.id ? addressFamily(editingAddr as AddressListEntry) : ipFamily}
        onSave={(payload) => { void saveAddr(payload) }}
        saving={busy}
      />

      <ExportSheet
        open={exportOpen}
        onClose={() => setExportOpen(false)}
        rules={familyRules}
      />
    </>
  )
}

export default function FirewallPage() {
  return (
    <Suspense fallback={null}>
      <FirewallPageInner />
    </Suspense>
  )
}
