"use client"

import { useEffect, useMemo, useRef, useState } from "react"
import { PageHeader } from "@/components/page-header"
import { firewallRules, type FirewallRule } from "@/lib/data"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
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,
  MoreHorizontalIcon, PencilIcon, Trash2Icon, CopyIcon, CodeXmlIcon,
  CheckIcon, PowerIcon, CheckCircleIcon,
  PlayIcon, SquareIcon, RotateCcwIcon, ZapIcon,
  CheckCircle2Icon, XCircleIcon, MinusCircleIcon, SkipForwardIcon,
  PackageIcon, SlidersHorizontalIcon,
} from "lucide-react"

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

interface AddressListEntry {
  id: string
  list: string
  address: string
  comment: string
  disabled: boolean
  timeout?: string
}

// ─── 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 },
  { id: "al2",  list: "youtube-bypass",    address: "172.217.0.0/16",    comment: "Google/YouTube AS15169",       disabled: false },
  { id: "al3",  list: "cdn-bypass",        address: "104.16.0.0/12",     comment: "Cloudflare AS13335",           disabled: false },
  { id: "al4",  list: "cdn-bypass",        address: "23.32.0.0/11",      comment: "Akamai AS20940",               disabled: false },
  { id: "al5",  list: "cdn-bypass",        address: "151.101.0.0/16",    comment: "Fastly AS54113",               disabled: false },
  { id: "al6",  list: "streaming-eu",      address: "54.246.0.0/16",     comment: "Netflix AS2906",               disabled: false },
  { id: "al7",  list: "streaming-eu",      address: "45.57.0.0/16",      comment: "Netflix AS2906 NL",            disabled: false },
  { id: "al8",  list: "social-block",      address: "157.240.0.0/17",    comment: "Facebook/Meta AS32934",        disabled: false },
  { id: "al9",  list: "social-block",      address: "31.13.64.0/18",     comment: "Facebook/Meta AS32934",        disabled: false },
  { id: "al10", list: "gaming-low-latency",address: "162.159.128.0/19",  comment: "Discord/Cloudflare AS13335",   disabled: false },
  { id: "al11", list: "management-access", address: "10.10.0.0/16",      comment: "LAN",                          disabled: false },
  { id: "al12", list: "management-access", address: "192.168.100.0/24",  comment: "MGMT VLAN",                    disabled: false },
  { id: "al13", list: "blocklist-dynamic", address: "185.220.101.45",    comment: "Tor exit — dynamic ban",       disabled: false, timeout: "01:00:00" },
  { id: "al14", list: "blocklist-dynamic", address: "80.82.70.118",      comment: "Shodan scanner — dynamic ban", disabled: false, timeout: "00:42:18" },
]

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

type IpFamily = "all" | "ip" | "ip6"

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

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

const CHAIN_GROUPS: {
  id: ChainGroup; label: string; icon: React.ReactNode; chains: string[]; ip6chains?: string[]
}[] = [
  {
    id: "filter", label: "Filter", icon: <ShieldIcon className="size-3.5" />,
    chains: ["input","forward","output"],
    ip6chains: ["ip6-input","ip6-forward","ip6-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

// ─── 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)
}

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

function ActionBadge({ action }: { action: string }) {
  const cls = ACTION_STYLES[action] ?? "bg-muted text-muted-foreground border-border"
  return (
    <span className={cn("text-[11px] font-mono font-medium px-2 py-0.5 rounded border whitespace-nowrap", cls)}>
      {action}
    </span>
  )
}

function ChainBadge({ chain }: { chain: string }) {
  const cls = CHAIN_STYLES[chain] ?? "bg-muted text-muted-foreground"
  return (
    <span className={cn("text-[11px] font-mono px-2 py-0.5 rounded", cls)}>
      {chain}
    </span>
  )
}

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

function SectionTitle({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex items-center gap-2 py-0.5">
      <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">{children}</span>
      <div className="flex-1 h-px bg-border" />
    </div>
  )
}

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(`# RouterLists 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 = TABLE_MAP[chain] ?? "filter"
    lines.push(`# ── /ip firewall ${table} chain=${chain} ──────────────────────────────`)
    for (const r of chainRules) {
      const parts = [`/ip firewall ${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 }: {
  open: boolean
  onClose: () => void
  initialRule: Partial<FirewallRule> | null
  chainGroup: ChainGroup
}) {
  const isNew = !initialRule?.id
  const [form, setForm] = useState<RuleForm>(() => ({
    ...defaultForm,
    chain: CHAIN_GROUPS.find(g => g.id === chainGroup)?.chains[0] ?? "forward",
    ...(initialRule ?? {}),
    srcPort: "",
    dstPort: initialRule?.port ?? "",
    inIface: initialRule?.iface ?? "",
    outIface: "",
    connState: "",
    srcAddrList: initialRule?.src ?? "",
    dstAddrList: initialRule?.dst ?? "",
    log: false,
    logPrefix: "",
  }))

  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 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>
            /ip firewall {chainGroup === "nat" ? "nat" : chainGroup === "mangle" ? "mangle" : "filter"} · 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">
              <Field label="Цепочка" required>
                <NativeSelect value={form.chain} onChange={(v) => set("chain", v)}>
                  {chainsForGroup.map((c) => <option key={c} value={c}>{c}</option>)}
                </NativeSelect>
              </Field>
              <Field label="Действие" required>
                <NativeSelect value={form.action} onChange={(v) => set("action", v)}>
                  {actions.map((a) => <option key={a} value={a}>{a}</option>)}
                </NativeSelect>
              </Field>
            </div>
          </div>

          {/* Matching */}
          <div className="flex flex-col gap-4">
            <SectionTitle>Условие совпадения</SectionTitle>
            <Field 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>
            </Field>

            <div className="grid grid-cols-2 gap-3">
              <Field 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)} />
              </Field>
              <Field 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)} />
              </Field>
            </div>

            <div className="grid grid-cols-2 gap-3">
              <Field 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)} />
              </Field>
              <Field label="Dst-port">
                <Input className="font-mono h-8" placeholder="443"
                  value={form.dstPort} onChange={(e) => set("dstPort", e.target.value)} />
              </Field>
            </div>

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

            <Field 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)} />
            </Field>
          </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>
              <Toggle checked={form.log} onChange={(v) => set("log", v)} />
            </div>
            {form.log && (
              <Field 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)} />
              </Field>
            )}
            <Field label="Комментарий">
              <Input className="h-8" placeholder="Описание правила"
                value={form.comment} onChange={(e) => set("comment", e.target.value)} />
            </Field>
          </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>
            <Toggle 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">
              {[
                `/ip firewall filter add \\`,
                `  chain=${form.chain} \\`,
                `  action=${form.action}`,
                form.proto !== "all" ? `  protocol=${form.proto} \\` : null,
                form.srcAddrList ? `  src-address-list=${form.srcAddrList} \\` : null,
                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" onClick={onClose}>
            {isNew ? "Добавить правило" : "Сохранить"}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

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

function ExportSheet({ open, onClose, rules }: {
  open: boolean; onClose: () => void; rules: FirewallRule[]
}) {
  const [copied, setCopied] = useState(false)

  // Derived from open — new timestamp each time modal opens, undefined when closed
  const timestamp = useMemo(
    () => open ? new Date().toLocaleString("ru") : undefined,
    [open],  
  )

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

  function handleCopy() {
    const full = generateRsc(rules, new Date().toLocaleString("ru"))
    navigator.clipboard.writeText(full).then(() => {
      setCopied(true)
      setTimeout(() => setCopied(false), 2000)
    })
  }

  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">
          <div className="flex items-start justify-between gap-4">
            <div>
              <SheetTitle>Экспорт Firewall</SheetTitle>
              <SheetDescription>RouterOS .rsc · /ip firewall filter, nat, mangle</SheetDescription>
            </div>
            <Button variant="outline" size="sm" className="shrink-0" onClick={handleCopy}>
              {copied
                ? <><CheckIcon className="size-3.5 text-emerald-500" />Скопировано</>
                : <><CopyIcon className="size-3.5" />Копировать</>}
            </Button>
          </div>
        </SheetHeader>
        <div className="flex-1 overflow-y-auto">
          <pre className="px-6 py-5 text-[12px] font-mono leading-relaxed text-foreground/85 whitespace-pre select-all">
            {code.split("\n").map((line, i) => {
              const isComment = line.startsWith("#")
              const isSection = isComment && line.includes("──")
              const isKey     = /^\s+[a-z]/.test(line)
              return (
                <span key={i} className={
                  isSection ? "text-muted-foreground/50"
                  : isComment ? "text-muted-foreground"
                  : isKey    ? "text-sky-400/90"
                  : "text-foreground"
                }>
                  {line}{"\n"}
                </span>
              )
            })}
          </pre>
        </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={handleCopy}>
            {copied ? <CheckIcon className="size-4" /> : <CopyIcon className="size-4" />}
            {copied ? "Скопировано" : "Копировать .rsc"}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

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

function AddressListsTab({ entries, onAdd }: {
  entries: AddressListEntry[]
  onAdd: () => void
}) {
  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])

  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)
    })
  }, [entries, search, listFilter])

  // Group by list name for display
  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">
      {/* toolbar */}
      <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>

      {/* groups */}
      <div className="flex flex-col gap-3">
        {Object.entries(grouped).map(([listName, listEntries]) => (
          <Card key={listName}>
            <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">
                <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>
                  {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>
                  )}
                  <DropdownMenu>
                    <DropdownMenuTrigger render={
                      <Button variant="ghost" size="icon" className="size-7 shrink-0">
                        <MoreHorizontalIcon className="size-4" />
                      </Button>
                    } />
                    <DropdownMenuContent side="bottom" align="end">
                      <DropdownMenuItem><PencilIcon className="size-4" />Редактировать</DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem variant="destructive"><Trash2Icon className="size-4" />Удалить</DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </div>
              ))}
            </div>
          </Card>
        ))}
      </div>
    </div>
  )
}

// ─── 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">
              <Field label="Название сценария" required>
                <Input className="h-8" placeholder="Блокировка Tor Exit"
                  value={name} onChange={e => setName(e.target.value)} />
              </Field>
              <Field label="Описание">
                <Input className="h-8" placeholder="Краткое описание"
                  value={desc} onChange={e => setDesc(e.target.value)} />
              </Field>
            </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">
              <Field 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>
              </Field>
              <Field label="Протокол">
                <NativeSelect value={pkt.proto} onChange={v => setP("proto", v)}>
                  {PROTO_OPTS.map(p => <option key={p} value={p}>{p}</option>)}
                </NativeSelect>
              </Field>
              <Field label="Conn-state">
                <Input className="font-mono h-8" value={pkt.connState}
                  placeholder="new" onChange={e => setP("connState", e.target.value)} />
              </Field>
              <Field label="Src IP">
                <Input className="font-mono h-8" value={pkt.srcAddr}
                  onChange={e => setP("srcAddr", e.target.value)} />
              </Field>
              <Field label="Dst IP">
                <Input className="font-mono h-8" value={pkt.dstAddr}
                  onChange={e => setP("dstAddr", e.target.value)} />
              </Field>
              <Field label="Dst Port">
                <Input className="font-mono h-8" value={pkt.dstPort}
                  placeholder="443" onChange={e => setP("dstPort", e.target.value)} />
              </Field>
              <Field label="In-interface">
                <Input className="font-mono h-8" value={pkt.inIface}
                  placeholder="lan" onChange={e => setP("inIface", e.target.value)} />
              </Field>
              <Field label="Out-interface">
                <Input className="font-mono h-8" value={pkt.outIface}
                  placeholder="wan-msk" onChange={e => setP("outIface", e.target.value)} />
              </Field>
              <Field label="Dst addr-list">
                <Input className="font-mono h-8" value={pkt.dstAddrList}
                  placeholder="" onChange={e => setP("dstAddrList", e.target.value)} />
              </Field>
            </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">
                    <Toggle 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 ? (
              <div className="rounded-lg border overflow-hidden">
                <table className="w-full text-xs">
                  <thead>
                    <tr className="border-b bg-muted/30 text-muted-foreground">
                      <th className="text-left px-3 py-2 w-6">#</th>
                      <th className="text-left px-3 py-2">Цепочка</th>
                      <th className="text-left px-3 py-2">Действие</th>
                      <th className="text-left px-3 py-2">Src</th>
                      <th className="text-left px-3 py-2">Dst</th>
                      <th className="text-left px-3 py-2">Порт</th>
                      <th className="text-left px-3 py-2">Iface</th>
                      <th className="text-left px-3 py-2">Комментарий</th>
                      <th className="w-24 px-2 py-2" />
                    </tr>
                  </thead>
                  <tbody className="divide-y divide-border">
                    {rules.map((r, i) => (
                      <tr key={r.id} className={cn(
                        "hover:bg-muted/20 transition-colors",
                        !r.enabled && "opacity-40",
                      )}>
                        <td className="px-3 py-1.5 text-muted-foreground tabular-nums">{i + 1}</td>
                        <td className="px-3 py-1.5"><ChainBadge chain={r.chain} /></td>
                        <td className="px-3 py-1.5"><ActionBadge action={r.action} /></td>
                        <td className="px-3 py-1.5 font-mono text-muted-foreground max-w-[90px] truncate">{r.src || "any"}</td>
                        <td className="px-3 py-1.5 font-mono text-muted-foreground max-w-[90px] truncate">{r.dst || "any"}</td>
                        <td className="px-3 py-1.5 font-mono text-muted-foreground">{r.port || "—"}</td>
                        <td className="px-3 py-1.5 font-mono text-muted-foreground">{r.iface || "—"}</td>
                        <td className="px-3 py-1.5 text-muted-foreground/70 max-w-[110px] truncate">{r.comment || "—"}</td>
                        <td className="px-2 py-1.5">
                          <div className="flex items-center gap-0.5 justify-end">
                            <button type="button" onClick={() => toggleEnabled(r.id)}
                              title={r.enabled ? "Отключить" : "Включить"}
                              className="p-0.5 text-muted-foreground/40 hover:text-foreground transition-colors">
                              <PowerIcon className="size-3.5" />
                            </button>
                            <button type="button" onClick={() => moveRule(r.id, -1)} disabled={i === 0}
                              className="p-0.5 text-muted-foreground/40 hover:text-foreground disabled:opacity-20 transition-colors">▲</button>
                            <button type="button" onClick={() => moveRule(r.id, 1)} disabled={i === rules.length - 1}
                              className="p-0.5 text-muted-foreground/40 hover:text-foreground disabled:opacity-20 transition-colors">▼</button>
                            <button type="button" onClick={() => removeRule(r.id)}
                              className="p-0.5 ml-0.5 text-muted-foreground/40 hover:text-red-500 transition-colors">
                              <Trash2Icon className="size-3.5" />
                            </button>
                          </div>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            ) : (
              !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 ─────────────────────────────────────────── */}
      <Card>
        <CardContent className="px-5 py-4 flex flex-col gap-4">

          {/* Built-in traffic presets */}
          <div>
            <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2.5">
              Пресеты трафика
            </p>
            <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>

          <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>
        </CardContent>
      </Card>

      {/* ── Packet editor ───────────────────────────────────────────────────── */}
      <Card>
        <CardContent className="px-5 py-4 flex flex-col gap-4">

          <div className="flex items-center justify-between flex-wrap gap-2">
            <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground flex items-center gap-2">
              <PackageIcon className="size-3.5" />Параметры пакета
            </p>
            <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>
        </CardContent>
      </Card>

      {/* ── 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 && (
        <Card>
          <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>
        </Card>
      )}

      {/* ── 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>
  )
}

// ─── Rules Table ──────────────────────────────────────────────────────────────

function RulesTable({ rules, onToggle, onEdit }: {
  rules: FirewallRule[]
  onToggle: (id: string) => void
  onEdit: (r: FirewallRule) => void
}) {
  if (rules.length === 0) {
    return (
      <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">Правила не найдены</p>
        <p className="text-xs mt-1">Попробуйте изменить фильтр или добавьте новое правило</p>
      </div>
    )
  }

  return (
    <div className="overflow-x-auto">
      <table className="w-full text-sm">
        <thead>
          <tr className="border-b border-border text-xs text-muted-foreground">
            <th className="text-left font-medium px-5 py-3 w-8">#</th>
            <th className="text-left font-medium px-4 py-3">Цепочка</th>
            <th className="text-left font-medium px-4 py-3">Действие</th>
            <th className="text-left font-medium px-4 py-3">Источник</th>
            <th className="text-left font-medium px-4 py-3">Назначение</th>
            <th className="text-left font-medium px-4 py-3">Протокол</th>
            <th className="text-left font-medium px-4 py-3">Порт</th>
            <th className="text-left font-medium px-4 py-3">Интерфейс</th>
            <th className="text-right font-medium px-4 py-3">Пакетов</th>
            <th className="text-left font-medium px-4 py-3 w-12">Вкл</th>
            <th className="w-10 px-3 py-3" />
          </tr>
        </thead>
        <tbody className="divide-y divide-border">
          {rules.map((r, i) => (
            <tr key={r.id}
              className={cn("hover:bg-muted/40 transition-colors", !r.enabled && "opacity-40")}>
              <td className="px-5 py-2.5 font-mono text-xs text-muted-foreground">{i + 1}</td>
              <td className="px-4 py-2.5"><ChainBadge chain={r.chain} /></td>
              <td className="px-4 py-2.5"><ActionBadge action={r.action} /></td>
              <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground max-w-[140px] truncate">
                {r.src || "any"}
              </td>
              <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground max-w-[140px] truncate">
                {r.dst || "any"}
              </td>
              <td className="px-4 py-2.5 text-xs font-mono">{r.proto}</td>
              <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{r.port || "—"}</td>
              <td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{r.iface || "—"}</td>
              <td className="px-4 py-2.5 text-right">
                <span className={cn(
                  "text-xs font-mono tabular-nums",
                  r.hits > 1_000_000 ? "text-emerald-600 dark:text-emerald-400 font-semibold"
                  : r.hits > 10_000 ? "text-foreground"
                  : "text-muted-foreground",
                )}>
                  {fmtHits(r.hits)}
                </span>
              </td>
              <td className="px-4 py-2.5">
                <Toggle checked={r.enabled} onChange={() => onToggle(r.id)} />
              </td>
              <td className="px-3 py-2.5">
                <DropdownMenu>
                  <DropdownMenuTrigger render={
                    <Button variant="ghost" size="icon" className="size-7">
                      <MoreHorizontalIcon className="size-4" />
                    </Button>
                  } />
                  <DropdownMenuContent side="bottom" align="end">
                    <DropdownMenuItem onClick={() => onEdit(r)}>
                      <PencilIcon className="size-4" />Редактировать
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                      <CopyIcon className="size-4" />Дублировать
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem onClick={() => onToggle(r.id)}>
                      <PowerIcon className="size-4" />
                      {r.enabled ? "Отключить" : "Включить"}
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem variant="destructive">
                      <Trash2Icon className="size-4" />Удалить правило
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

// ════════════════════════════════════════════════════════════════════════════
export default function FirewallPage() {
  const [rules, setRules] = useState<FirewallRule[]>(firewallRules)
  const [addrLists] = useState<AddressListEntry[]>(INIT_ADDRESS_LISTS)

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

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

  // ── derived ──────────────────────────────────────────────────────────────

  const chainsInGroup = useMemo(() => {
    if (chainGroup === "address-lists") return []
    const grp = CHAIN_GROUPS.find((g) => g.id === chainGroup)
    if (!grp) return []
    const v4 = grp.chains
    const v6 = grp.ip6chains ?? []
    if (ipFamily === "ip")  return v4
    if (ipFamily === "ip6") return v6
    return [...v4, ...v6]
  }, [chainGroup, ipFamily])

  const groupRules = useMemo(() => {
    if (chainGroup === "address-lists") return []
    return rules.filter((r) => chainsInGroup.includes(r.chain))
  }, [rules, chainGroup, chainsInGroup])

  const filteredRules = useMemo(() => {
    return groupRules.filter((r) => {
      if (chainFilter !== "all" && r.chain !== chainFilter) return false
      // ipFamily filtering also respects rules with explicit family field
      if (ipFamily === "ip"  && r.family === "ip6") return false
      if (ipFamily === "ip6" && r.family === "ip")  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)
      )
    })
  }, [groupRules, chainFilter, ipFamily, 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])

  // ── stats ────────────────────────────────────────────────────────────────

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

  // ── handlers ─────────────────────────────────────────────────────────────

  function toggleRule(id: string) {
    setRules((rs) => rs.map((r) => r.id === id ? { ...r, enabled: !r.enabled } : r))
  }

  function openAdd() {
    setEditingRule(null)
    setRuleSheetOpen(true)
  }

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

  // ─────────────────────────────────────────────────────────────────────────

  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Управление" }, { label: "Firewall" }]}
        actions={
          <>
            <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-1 overflow-y-auto p-6">
        <div className="flex flex-col gap-5">

          {/* Stats */}
          <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
            {[
              { label: "Всего правил",  value: rules.length,  icon: <ShieldIcon className="size-4 text-muted-foreground" /> },
              { label: "Активных",      value: totalEnabled,  icon: <CheckCircleIcon className="size-4 text-emerald-500" /> },
              { label: "Блокирующих",   value: dropRules,     icon: <ShieldOffIcon   className="size-4 text-red-500" /> },
              { label: "Срабатываний",  value: fmtHits(totalHits), icon: <ListFilterIcon className="size-4 text-sky-400" /> },
            ].map((s) => (
              <Card key={s.label}>
                <CardContent className="px-5 py-4 flex items-start justify-between">
                  <div>
                    <p className="text-sm text-muted-foreground">{s.label}</p>
                    <p className="text-2xl font-semibold tabular-nums mt-0.5">{s.value}</p>
                  </div>
                  <div className="mt-0.5">{s.icon}</div>
                </CardContent>
              </Card>
            ))}
          </div>

          {/* Table group tabs */}
          <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">
                    {rules.filter((r) => g.chains.includes(r.chain)).length}
                  </span>
                )}
                {g.id === "address-lists" && (
                  <span className="text-[10px] font-mono opacity-50">{addrLists.length}</span>
                )}
              </button>
            ))}
          </div>

          {chainGroup === "address-lists" ? (
            <AddressListsTab entries={addrLists} onAdd={() => {}} />
          ) : chainGroup === "simulator" ? (
            <SimulatorTab rules={rules} />
          ) : (
            <Card>
              {/* toolbar */}
              <div className="flex items-center gap-3 px-5 py-3 border-b flex-wrap">
                {/* IP family selector */}
                <div className="flex items-center gap-1 rounded-md border border-border bg-muted/40 p-0.5">
                  {(["all", "ip", "ip6"] as IpFamily[]).map((f) => (
                    <button key={f} onClick={() => setIpFamily(f)}
                      className={cn(
                        "flex items-center gap-1.5 rounded px-3 py-1 text-xs transition-colors whitespace-nowrap",
                        ipFamily === f
                          ? "bg-background text-foreground shadow-sm"
                          : "text-muted-foreground hover:text-foreground",
                      )}>
                      {IP_FAMILY_LABELS[f]}
                    </button>
                  ))}
                </div>

                {/* chain sub-tabs */}
                <div className="flex items-center gap-1 rounded-md border border-border bg-muted/40 p-0.5">
                  {[{ value: "all", label: "Все" }, ...chainsInGroup.map((c) => ({ value: c, label: c }))].map((t) => (
                    <button key={t.value} onClick={() => setChainFilter(t.value)}
                      className={cn(
                        "flex items-center gap-1.5 rounded px-3 py-1 text-xs transition-colors",
                        chainFilter === t.value
                          ? "bg-background text-foreground shadow-sm"
                          : "text-muted-foreground hover:text-foreground",
                      )}>
                      {t.label}
                      <span className="opacity-50">{chainCounts[t.value] ?? 0}</span>
                    </button>
                  ))}
                </div>

                {/* search */}
                <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>

                <span className="text-sm text-muted-foreground ml-auto">{filteredRules.length} правил</span>
              </div>

              <RulesTable rules={filteredRules} onToggle={toggleRule} onEdit={openEdit} />
            </Card>
          )}

          {/* RouterOS reference */}
          <Card>
            <CardContent className="px-5 py-4">
              <p className="text-xs font-medium text-muted-foreground mb-3">
                RouterOS 7.20+ · /ip firewall + /ipv6 firewall — цепочки и новые матчеры
              </p>
              <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: ["ip6-input", "ip6-forward", "ip6-output"],
                  },
                  {
                    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>
            </CardContent>
          </Card>

        </div>
      </div>

      {/* Sheet: Add/Edit Rule */}
      <RuleSheet
        open={ruleSheetOpen}
        onClose={() => setRuleSheetOpen(false)}
        initialRule={editingRule}
        chainGroup={chainGroup === "address-lists" ? "filter" : chainGroup}
      />

      {/* Sheet: Export */}
      <ExportSheet
        open={exportOpen}
        onClose={() => setExportOpen(false)}
        rules={rules}
      />
    </div>
  )
}
