use crate::firewall::{
    AdvisorSeverity, CandidateFilter, CandidateStatus, FirewallController, FirewallProfile,
    ReviewGateStatus, RuleAction,
};
use crossterm::event::{Event, KeyCode, KeyEventKind};
use scrin::command_palette::{Command, CommandPalette};
use scrin::core::{buffer::Buffer, color::Color, rect::Rect};
use scrin::effects::{EffectKind, EffectPlayer};
use scrin::overlays::modal::ModalStyle;
use scrin::overlays::toast::ToastKind;
use scrin::overlays::{Modal, Overlay, OverlayPosition, Toast, Transition};
use scrin::status_bar::{StatusBar, StatusBarPosition};
use scrin::style::Style;
use scrin::terminal::{Frame, FrameTiming, Terminal};
use scrin::widgets::popup::{PopupPosition, PopupStyle};
use scrin::widgets::table::{Cell as TableCell, Row as TableRow};
use scrin::widgets::{Block, Line, List, ListItem, Paragraph, Popup, Span, Table, Tabs, Widget};
use std::env;
use std::fs;
use std::time::Duration;

const BG: Color = Color::rgb(4, 8, 14);
const PANEL: Color = Color::rgb(12, 19, 36);
const DIM: Color = Color::rgb(91, 107, 130);
const TEXT: Color = Color::rgb(226, 232, 240);
const ACCENT: Color = Color::rgb(56, 189, 170);
const WARN: Color = Color::rgb(226, 88, 88);
const OK: Color = Color::rgb(56, 189, 170);
const FOCUS: Color = Color::rgb(34, 211, 238);
const SELECTED_BG: Color = Color::rgb(20, 63, 58);
const TAB_TITLES: [&str; 5] = ["Dashboard", "Rules", "Ports", "Plan", "Advisor"];
const REPORT_PATH: &str = "kwall-dry-run-report.txt";
const SCRIPT_PATH: &str = "kwall-plan-preview.sh";
const MANIFEST_PATH: &str = "kwall-dry-run-manifest.json";
const FRAME_METRICS_ENV: &str = "KWALL_FRAME_METRICS";
const SHOW_FRAME_METRICS_DEFAULT: bool = false;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Focus {
    Rules,
    Ports,
    Plan,
    Advisor,
}

impl Focus {
    fn next(self) -> Self {
        match self {
            Self::Rules => Self::Ports,
            Self::Ports => Self::Plan,
            Self::Plan => Self::Advisor,
            Self::Advisor => Self::Rules,
        }
    }
}

#[derive(Debug)]
enum PendingAction {
    ApplyProfile(FirewallProfile),
    ApplyRecommendedProfile,
    ApplySafeRecommendedProfile,
    BlockSelectedGroup { index: usize, group: &'static str },
    BlockSelectedRisk { index: usize, risk: &'static str },
    BlockVisibleCandidates(Vec<usize>),
    AllowVisibleCandidates(Vec<usize>),
    BlockPriorityQueue(Vec<usize>),
    HardenHighRisk,
    ClearRules(usize),
    ResetStarterPlan,
}

pub struct FirewallTui {
    controller: FirewallController,
    focus: Focus,
    selected_tab: usize,
    selected_rule: usize,
    selected_candidate: usize,
    candidate_filter: CandidateFilter,
    selected_group_filter: Option<usize>,
    selected_risk_filter: Option<usize>,
    selected_finding: usize,
    banner: EffectPlayer,
    help_modal: Modal,
    safety_popup: Popup,
    command_palette: CommandPalette,
    confirmation_modal: Modal,
    pending_confirmation: Option<PendingAction>,
    toasts: Vec<Toast>,
    last_frame_timing: Option<FrameTiming>,
    show_frame_metrics: bool,
}

impl FirewallTui {
    pub fn new(controller: FirewallController) -> Self {
        Self {
            controller,
            focus: Focus::Rules,
            selected_tab: 0,
            selected_rule: 0,
            selected_candidate: 0,
            candidate_filter: CandidateFilter::All,
            selected_group_filter: None,
            selected_risk_filter: None,
            selected_finding: 0,
            banner: EffectPlayer::new(EffectKind::Decrypt, "KWALL")
                .with_size(72, 3)
                .with_duration(64)
                .with_accent(ACCENT),
            help_modal: Modal::new("Help", "Dry-run firewall control surface")
                .with_style(ModalStyle::Dialog)
                .with_options(vec![
                    "Tab: switch pages".into(),
                    ": or /: open command palette".into(),
                    "Up/Down: move current selection".into(),
                    "Home/End/PageUp/PageDown: jump or jump by pages".into(),
                    "Enter or d: block/unblock selected known port".into(),
                    "N: jump to priority at-risk port".into(),
                    "n: block priority at-risk port".into(),
                    "M: block visible priority queue".into(),
                    "X: mark/remove reviewed exception for allowed risky port".into(),
                    "F/G/K: cycle status/name, group, and risk filters".into(),
                    "g/B: block selected group or risk class".into(),
                    "V/A: block or allow visible filter".into(),
                    "h: block all high-risk known ports".into(),
                    "Y/y: apply strongest or management-preserving profile".into(),
                    "w/e/D/k: workstation, web, developer, lockdown profiles".into(),
                    "r: advisor quick fix, u: undo".into(),
                    "o/S/m/E: export report, script, manifest, bundle".into(),
                    "I/O: toggle default incoming/outgoing policy".into(),
                    "c/R: clear rules or restore starter plan".into(),
                    "Enter/y/Y: confirm | n/Esc: cancel in confirmation modal".into(),
                    "b/a: force block or allow selected service port".into(),
                    "f: dashboard focus, p: backend, l: logging".into(),
                    "s: safety popup".into(),
                    "Esc or ?: close this modal".into(),
            ])
                .with_border_color(ACCENT)
                .with_transition(Transition::Scale),
            safety_popup: Popup::new(
                "Safety Lock",
                "No live executor is compiled in.\nNo ufw, iptables, nft, or /etc writes happen.\nThe plan panel is preview-only.\nCLI and live apply can be added later behind explicit guards.",
            )
            .with_style(PopupStyle::Panel)
            .with_size(58, 9)
            .with_min_size(32, 7)
            .with_position(PopupPosition::BottomRight)
            .with_border_color(WARN)
            .with_auto_close_on_resize(false),
            command_palette: build_command_palette(),
            confirmation_modal: Modal::new("Confirm", "")
                .with_style(ModalStyle::Confirm)
                .with_border_color(WARN)
                .with_transition(Transition::Scale),
            pending_confirmation: None,
            last_frame_timing: None,
            show_frame_metrics: read_frame_metrics_flag(),
            toasts: vec![Toast::new("Dry-run safe mode active", ToastKind::Success)
                .with_position(OverlayPosition::TopRight)
                .with_lifetime(Duration::from_secs(4))
                .with_max_width(44)],
        }
    }

    pub fn run(&mut self) -> std::io::Result<()> {
        let mut terminal = Terminal::init()?;

        loop {
            let frame_timing = terminal.draw_timed(|frame| self.render(frame))?;
            self.last_frame_timing = Some(frame_timing);

            if let Some(event) = terminal.poll_event()? {
                if self.handle_event(event) {
                    break;
                }
            }

            let frame_step = Duration::from_millis(45);
            self.banner.advance();
            self.update_overlays(frame_step);
            std::thread::sleep(frame_step);
        }

        terminal.restore()
    }

    fn handle_event(&mut self, event: Event) -> bool {
        let Event::Key(key) = event else {
            return false;
        };

        if !matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
            return false;
        }

        if self.command_palette.is_open() {
            match key.code {
                KeyCode::Esc => self.command_palette.close(),
                KeyCode::Enter => {
                    if let Some(action) = self.command_palette.execute_selected().map(str::to_owned)
                    {
                        self.command_palette.close();
                        self.run_palette_action(&action);
                    }
                }
                KeyCode::Up => self.command_palette.select_prev(),
                KeyCode::Down => self.command_palette.select_next(),
                KeyCode::Backspace => self.command_palette.backspace(),
                KeyCode::Char(c) => self.command_palette.input_char(c),
                _ => {}
            }
            return false;
        }

        if self.confirmation_modal.is_visible() {
            match key.code {
                KeyCode::Esc | KeyCode::Char('n') => {
                    self.resolve_confirmation(Some(false));
                }
                KeyCode::Enter => {
                    self.resolve_confirmation(None);
                }
                KeyCode::Char('y') | KeyCode::Char('Y') => {
                    self.resolve_confirmation(Some(true));
                }
                KeyCode::Left | KeyCode::Up => self.confirmation_modal.select_prev(),
                KeyCode::Right | KeyCode::Down => self.confirmation_modal.select_next(),
                _ => {}
            }
            return false;
        }

        if self.help_modal.is_visible() {
            match key.code {
                KeyCode::Esc | KeyCode::Enter | KeyCode::Char('?') => self.help_modal.hide(),
                KeyCode::Up => self.help_modal.select_prev(),
                KeyCode::Down => self.help_modal.select_next(),
                _ => {}
            }
            return false;
        }

        if self.safety_popup.is_visible() {
            if matches!(
                key.code,
                KeyCode::Esc | KeyCode::Char('s') | KeyCode::Char('q')
            ) {
                self.safety_popup.hide();
                return false;
            }
            return false;
        }

        match key.code {
            KeyCode::Char('q') | KeyCode::Esc => true,
            KeyCode::Tab => {
                self.select_tab((self.selected_tab + 1) % TAB_TITLES.len());
                self.toast("Switched tab", ToastKind::Info);
                false
            }
            KeyCode::BackTab => {
                let next = if self.selected_tab == 0 {
                    TAB_TITLES.len() - 1
                } else {
                    self.selected_tab - 1
                };
                self.select_tab(next);
                false
            }
            KeyCode::Char('1') => self.select_tab_and_continue(0),
            KeyCode::Char('2') => self.select_tab_and_continue(1),
            KeyCode::Char('3') => self.select_tab_and_continue(2),
            KeyCode::Char('4') => self.select_tab_and_continue(3),
            KeyCode::Char('5') => self.select_tab_and_continue(4),
            KeyCode::Up => {
                self.move_selection(-1);
                false
            }
            KeyCode::Down => {
                self.move_selection(1);
                false
            }
            KeyCode::PageUp => {
                self.move_selection(-10);
                false
            }
            KeyCode::PageDown => {
                self.move_selection(10);
                false
            }
            KeyCode::Home => {
                self.move_selection_to_boundary(true);
                false
            }
            KeyCode::End => {
                self.move_selection_to_boundary(false);
                false
            }
            KeyCode::Enter => {
                self.activate_focused_item();
                false
            }
            KeyCode::Char('f') => {
                if self.selected_tab == 0 {
                    self.focus = self.focus.next();
                    self.toast("Dashboard focus moved", ToastKind::Info);
                }
                false
            }
            KeyCode::Char('d') => {
                self.toggle_selected_port_block();
                false
            }
            KeyCode::Char('F') => {
                self.cycle_candidate_filter();
                false
            }
            KeyCode::Char('G') => {
                self.cycle_candidate_group_filter();
                false
            }
            KeyCode::Char('K') => {
                self.cycle_candidate_risk_filter();
                false
            }
            KeyCode::Char('N') => {
                self.jump_to_priority_candidate();
                false
            }
            KeyCode::Char('n') => {
                self.block_priority_candidate();
                false
            }
            KeyCode::Char('M') => {
                self.request_block_priority_queue();
                false
            }
            KeyCode::Char('X') => {
                self.toggle_selected_exception();
                false
            }
            KeyCode::Char('h') => {
                self.request_harden_high_risk();
                false
            }
            KeyCode::Char('g') => {
                self.request_block_selected_group();
                false
            }
            KeyCode::Char('B') => {
                self.request_block_selected_risk();
                false
            }
            KeyCode::Char('V') => {
                self.request_block_visible_candidates();
                false
            }
            KeyCode::Char('A') => {
                self.request_allow_visible_candidates();
                false
            }
            KeyCode::Char('Y') => {
                self.request_apply_recommended_profile();
                false
            }
            KeyCode::Char('y') => {
                self.request_apply_safe_recommended_profile();
                false
            }
            KeyCode::Char('w') => {
                self.request_apply_profile(FirewallProfile::Workstation);
                false
            }
            KeyCode::Char('e') => {
                self.request_apply_profile(FirewallProfile::WebServer);
                false
            }
            KeyCode::Char('D') => {
                self.request_apply_profile(FirewallProfile::Developer);
                false
            }
            KeyCode::Char('k') => {
                self.request_apply_profile(FirewallProfile::Lockdown);
                false
            }
            KeyCode::Char('r') => {
                self.apply_advisor_quick_fix();
                false
            }
            KeyCode::Char('u') => {
                if self.controller.undo() {
                    self.selected_rule = self
                        .selected_rule
                        .min(self.controller.rules().len().saturating_sub(1));
                    self.align_selected_candidate_with_filter();
                    self.toast("Undid last staged change", ToastKind::Info);
                } else {
                    self.toast("Nothing to undo", ToastKind::Info);
                }
                false
            }
            KeyCode::Char('b') => {
                self.block_selected_candidate();
                false
            }
            KeyCode::Char('a') => {
                self.allow_selected_candidate();
                false
            }
            KeyCode::Char(' ') => {
                self.controller.toggle_rule(self.selected_rule);
                self.align_selected_candidate_with_filter();
                self.toast("Rule toggled", ToastKind::Info);
                false
            }
            KeyCode::Char('x') => {
                self.controller.remove_rule(self.selected_rule);
                self.selected_rule = self
                    .selected_rule
                    .min(self.controller.rules().len().saturating_sub(1));
                self.align_selected_candidate_with_filter();
                self.toast("Rule removed", ToastKind::Warning);
                false
            }
            KeyCode::Char('p') => {
                self.controller.cycle_backend();
                self.toast("Preview backend changed", ToastKind::Info);
                false
            }
            KeyCode::Char('l') => {
                self.controller.toggle_logging();
                self.toast("Logging plan toggled", ToastKind::Info);
                false
            }
            KeyCode::Char('I') => {
                self.controller.toggle_default_incoming();
                self.toast("Default incoming policy toggled", ToastKind::Info);
                false
            }
            KeyCode::Char('O') => {
                self.controller.toggle_default_outgoing();
                self.toast("Default outgoing policy toggled", ToastKind::Info);
                false
            }
            KeyCode::Char('c') => {
                self.request_clear_rules();
                false
            }
            KeyCode::Char('R') => {
                self.request_reset_to_starter_plan();
                false
            }
            KeyCode::Char('o') => {
                self.export_report();
                false
            }
            KeyCode::Char('S') => {
                self.export_script();
                false
            }
            KeyCode::Char('m') => {
                self.export_manifest();
                false
            }
            KeyCode::Char('E') => {
                self.export_bundle();
                false
            }
            KeyCode::Char('?') => {
                self.help_modal.show();
                false
            }
            KeyCode::Char('s') => {
                self.safety_popup.toggle();
                false
            }
            KeyCode::Char(':') | KeyCode::Char('/') => {
                self.command_palette.open();
                false
            }
            _ => false,
        }
    }

    fn run_palette_action(&mut self, action: &str) {
        match action {
            "dashboard" => self.select_tab(0),
            "rules" => self.select_tab(1),
            "ports" => self.select_tab(2),
            "plan" => self.select_tab(3),
            "block-selected" => {
                self.block_selected_candidate();
                return;
            }
            "toggle-port-block" => self.toggle_selected_port_block(),
            "cycle-port-filter" => {
                self.cycle_candidate_filter();
                return;
            }
            "cycle-group-filter" => {
                self.cycle_candidate_group_filter();
                return;
            }
            "cycle-risk-filter" => {
                self.cycle_candidate_risk_filter();
                return;
            }
            "jump-priority-port" => {
                self.jump_to_priority_candidate();
                return;
            }
            "block-priority-port" => {
                self.block_priority_candidate();
                return;
            }
            "block-priority-queue" => {
                self.request_block_priority_queue();
                return;
            }
            "toggle-exception" => {
                self.toggle_selected_exception();
                return;
            }
            "block-selected-group" => {
                self.request_block_selected_group();
                return;
            }
            "block-selected-risk" => {
                self.request_block_selected_risk();
                return;
            }
            "block-visible-ports" => {
                self.request_block_visible_candidates();
                return;
            }
            "allow-visible-ports" => {
                self.request_allow_visible_candidates();
                return;
            }
            "harden-known" => {
                self.request_harden_high_risk();
                return;
            }
            "profile-recommended" => {
                self.request_apply_recommended_profile();
                return;
            }
            "profile-safe-recommended" => {
                self.request_apply_safe_recommended_profile();
                return;
            }
            "profile-workstation" => {
                self.request_apply_profile(FirewallProfile::Workstation);
                return;
            }
            "profile-web" => {
                self.request_apply_profile(FirewallProfile::WebServer);
                return;
            }
            "profile-developer" => {
                self.request_apply_profile(FirewallProfile::Developer);
                return;
            }
            "profile-lockdown" => {
                self.request_apply_profile(FirewallProfile::Lockdown);
                return;
            }
            "clear-rules" => {
                self.request_clear_rules();
                return;
            }
            "reset-starter" => {
                self.request_reset_to_starter_plan();
                return;
            }
            "allow-selected" => {
                self.allow_selected_candidate();
                return;
            }
            "advisor" => self.select_tab(4),
            "advisor-fix" => self.apply_advisor_quick_fix(),
            "undo" => {
                self.controller.undo();
                self.selected_rule = self
                    .selected_rule
                    .min(self.controller.rules().len().saturating_sub(1));
                self.align_selected_candidate_with_filter();
            }
            "toggle-rule" => {
                self.controller.toggle_rule(self.selected_rule);
                self.align_selected_candidate_with_filter();
            }
            "remove-rule" => {
                self.controller.remove_rule(self.selected_rule);
                self.selected_rule = self
                    .selected_rule
                    .min(self.controller.rules().len().saturating_sub(1));
                self.align_selected_candidate_with_filter();
            }
            "cycle-backend" => self.controller.cycle_backend(),
            "toggle-logging" => self.controller.toggle_logging(),
            "toggle-default-incoming" => self.controller.toggle_default_incoming(),
            "toggle-default-outgoing" => self.controller.toggle_default_outgoing(),
            "export-report" => {
                self.export_report();
                return;
            }
            "export-script" => {
                self.export_script();
                return;
            }
            "export-manifest" => {
                self.export_manifest();
                return;
            }
            "export-bundle" => {
                self.export_bundle();
                return;
            }
            "safety" => self.safety_popup.toggle(),
            "help" => self.help_modal.show(),
            _ => {}
        }
        self.toast("Command palette action applied", ToastKind::Success);
    }

    fn activate_focused_item(&mut self) {
        match self.focus {
            Focus::Ports => self.toggle_selected_port_block(),
            Focus::Rules => {
                self.controller.toggle_rule(self.selected_rule);
                self.align_selected_candidate_with_filter();
                self.toast("Rule toggled", ToastKind::Info);
            }
            Focus::Plan | Focus::Advisor => self.command_palette.open(),
        }
    }

    fn toggle_selected_port_block(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        match self.controller.toggle_candidate_block(index) {
            Some(true) => {
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                self.toast("Known port blocked", ToastKind::Success);
            }
            Some(false) => {
                self.selected_rule = self
                    .selected_rule
                    .min(self.controller.rules().len().saturating_sub(1));
                self.align_selected_candidate_with_filter();
                self.toast("Known port unblocked", ToastKind::Info);
            }
            None => self.toast("No known port selected", ToastKind::Warning),
        }
    }

    fn request_confirmation(&mut self, title: &str, message: &str, action: PendingAction) {
        self.pending_confirmation = Some(action);
        self.confirmation_modal = Modal::new(title, message)
            .with_style(ModalStyle::Confirm)
            .with_border_color(WARN)
            .with_transition(Transition::Scale)
            .with_options(vec!["Confirm".into(), "Cancel".into()]);
        self.confirmation_modal.show();
    }

    fn request_block_selected_group(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        let Some(group) = self.controller.candidate_group(index) else {
            self.toast("Selected candidate is unavailable", ToastKind::Warning);
            return;
        };

        let total = self
            .controller
            .candidates()
            .iter()
            .enumerate()
            .filter(|(candidate_index, candidate)| {
                candidate.group == group
                    && self
                        .controller
                        .candidate_status(*candidate_index)
                        .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .count();

        if total == 0 {
            self.toast(
                &format!("{group} group already fully blocked or not eligible"),
                ToastKind::Info,
            );
            return;
        }

        self.request_confirmation(
            "Confirm group stage",
            &format!("Stage block rules for all {total} ports in {group}?"),
            PendingAction::BlockSelectedGroup { index, group },
        );
    }

    fn request_block_selected_risk(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        let Some(risk) = self.controller.candidate_risk(index) else {
            self.toast("Selected candidate is unavailable", ToastKind::Warning);
            return;
        };

        let total = self
            .controller
            .candidates()
            .iter()
            .enumerate()
            .filter(|(candidate_index, candidate)| {
                candidate.risk == risk
                    && self
                        .controller
                        .candidate_status(*candidate_index)
                        .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .count();

        if total == 0 {
            self.toast(
                &format!("{risk} risk already fully blocked or not eligible"),
                ToastKind::Info,
            );
            return;
        }

        self.request_confirmation(
            "Confirm risk stage",
            &format!("Stage block rules for all {total} {risk} risk ports?"),
            PendingAction::BlockSelectedRisk { index, risk },
        );
    }

    fn request_block_visible_candidates(&mut self) {
        let visible = self.visible_candidate_indexes();
        if visible.is_empty() {
            self.toast("No known ports in active filter", ToastKind::Warning);
            return;
        }

        let open_indexes = visible
            .into_iter()
            .filter(|index| {
                self.controller
                    .candidate_status(*index)
                    .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .collect::<Vec<_>>();

        if open_indexes.is_empty() {
            self.toast("No visible ports need blocking", ToastKind::Info);
            return;
        }

        self.request_confirmation(
            "Confirm visible blocks",
            &format!(
                "Stage blocks for all {} currently visible ports?",
                open_indexes.len()
            ),
            PendingAction::BlockVisibleCandidates(open_indexes),
        );
    }

    fn request_allow_visible_candidates(&mut self) {
        let visible = self.visible_candidate_indexes();
        if visible.is_empty() {
            self.toast("No known ports in active filter", ToastKind::Warning);
            return;
        }

        let allow_indexes = visible
            .into_iter()
            .filter(|index| {
                self.controller
                    .candidate_status(*index)
                    .is_some_and(|status| status != CandidateStatus::Allowed)
            })
            .collect::<Vec<_>>();

        if allow_indexes.is_empty() {
            self.toast("No visible ports need allowing", ToastKind::Info);
            return;
        }

        self.request_confirmation(
            "Confirm visible allow",
            &format!(
                "Stage allows for all {} currently visible ports?",
                allow_indexes.len()
            ),
            PendingAction::AllowVisibleCandidates(allow_indexes),
        );
    }

    fn request_apply_profile(&mut self, profile: FirewallProfile) {
        let pending_changes = self.controller.profile_change_preview(profile).len();
        if pending_changes == 0 {
            self.toast(
                &format!(
                    "{} profile already matches the current plan",
                    profile.label()
                ),
                ToastKind::Info,
            );
            return;
        }

        self.request_confirmation(
            "Apply profile",
            &format!(
                "Apply {} profile ({} changes)?",
                profile.label(),
                pending_changes
            ),
            PendingAction::ApplyProfile(profile),
        );
    }

    fn request_apply_recommended_profile(&mut self) {
        let recommendation = self.controller.recommended_profile();
        let pending_changes = self
            .controller
            .profile_change_preview(recommendation.profile)
            .len();
        if pending_changes == 0 {
            self.toast("Strongest recommendation already applied", ToastKind::Info);
            return;
        }

        self.request_confirmation(
            "Apply strongest recommendation",
            &format!(
                "Apply strongest recommended {} profile (score {})?",
                recommendation.profile.label(),
                recommendation.score
            ),
            PendingAction::ApplyRecommendedProfile,
        );
    }

    fn request_apply_safe_recommended_profile(&mut self) {
        let Some(recommendation) = self.controller.safe_recommended_profile() else {
            self.toast(
                "No management-preserving profile is available",
                ToastKind::Warning,
            );
            return;
        };

        let pending_changes = self
            .controller
            .profile_change_preview(recommendation.profile)
            .len();
        if pending_changes == 0 {
            self.toast("Safe recommendation already applied", ToastKind::Info);
            return;
        }

        self.request_confirmation(
            "Apply safe recommendation",
            &format!(
                "Apply management-preserving {} profile ({} change(s))?",
                recommendation.profile.label(),
                recommendation.score
            ),
            PendingAction::ApplySafeRecommendedProfile,
        );
    }

    fn request_harden_high_risk(&mut self) {
        let high_risk_candidates = self
            .controller
            .candidates()
            .iter()
            .enumerate()
            .filter(|(index, candidate)| {
                matches!(candidate.risk, "admin" | "high" | "data")
                    && self
                        .controller
                        .candidate_status(*index)
                        .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .count();

        if high_risk_candidates == 0 {
            self.toast(
                "No high-risk known ports currently need blocking",
                ToastKind::Info,
            );
            return;
        }

        self.request_confirmation(
            "Harden high-risk ports",
            &format!("Stage blocks for all {high_risk_candidates} known high-risk ports?",),
            PendingAction::HardenHighRisk,
        );
    }

    fn request_clear_rules(&mut self) {
        let staged = self.controller.rules().len();
        if staged == 0 {
            self.toast("No staged rules to clear", ToastKind::Warning);
            return;
        }

        self.request_confirmation(
            "Clear staged rules",
            &format!("Remove all {} staged rules from the dry-run plan?", staged),
            PendingAction::ClearRules(staged),
        );
    }

    fn request_reset_to_starter_plan(&mut self) {
        self.request_confirmation(
            "Reset starter plan",
            "Restore the safe starter dry-run plan baseline?",
            PendingAction::ResetStarterPlan,
        );
    }

    fn request_block_priority_queue(&mut self) {
        let queue = self.controller.priority_ports(3);
        if queue.is_empty() {
            self.toast("No at-risk priority ports remain", ToastKind::Success);
            return;
        }

        let indexes = queue
            .iter()
            .map(|priority| priority.index)
            .collect::<Vec<_>>();
        self.request_confirmation(
            "Block priority queue",
            &format!("Stage blocks for top {} priority ports?", indexes.len()),
            PendingAction::BlockPriorityQueue(indexes),
        );
    }

    fn resolve_confirmation(&mut self, selected: Option<bool>) {
        let Some(action) = self.pending_confirmation.take() else {
            self.confirmation_modal.hide();
            return;
        };

        let proceed = selected.unwrap_or_else(|| {
            self.confirmation_modal
                .selected_option()
                .is_some_and(|option| option == "Confirm")
        });
        self.confirmation_modal.hide();

        if !proceed {
            self.toast("Action cancelled", ToastKind::Info);
            return;
        }

        match action {
            PendingAction::ApplyProfile(profile) => self.apply_profile(profile),
            PendingAction::ApplyRecommendedProfile => self.apply_recommended_profile(),
            PendingAction::ApplySafeRecommendedProfile => self.apply_safe_recommended_profile(),
            PendingAction::BlockSelectedGroup { index, group } => {
                let staged = self.controller.block_candidate_group(index);
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                let message = if staged == 0 {
                    format!("{group} group already blocked")
                } else {
                    format!("{group} group blocked")
                };
                self.toast(&message, ToastKind::Success);
            }
            PendingAction::BlockSelectedRisk { index, risk } => {
                let staged = self.controller.block_candidate_risk(index);
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                let message = if staged == 0 {
                    format!("{risk} risk already blocked")
                } else {
                    format!("{risk} risk blocked")
                };
                self.toast(&message, ToastKind::Success);
            }
            PendingAction::BlockVisibleCandidates(indexes) => {
                let staged = self.controller.block_candidate_indexes(&indexes);
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                self.toast(
                    if staged == 0 {
                        "Visible ports already blocked"
                    } else {
                        "Visible ports blocked"
                    },
                    ToastKind::Success,
                );
            }
            PendingAction::AllowVisibleCandidates(indexes) => {
                let staged = self.controller.allow_candidate_indexes(&indexes);
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                self.toast(
                    if staged == 0 {
                        "Visible ports already allowed"
                    } else {
                        "Visible ports allowed"
                    },
                    ToastKind::Success,
                );
            }
            PendingAction::BlockPriorityQueue(indexes) => {
                let staged = self.controller.block_candidate_indexes(&indexes);
                self.selected_candidate = indexes.first().copied().unwrap_or(0);
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.selected_tab = 2;
                self.focus = Focus::Ports;
                self.candidate_filter = CandidateFilter::AtRisk;
                self.selected_group_filter = None;
                self.selected_risk_filter = None;
                self.toast(
                    if staged == 0 {
                        "Priority queue already blocked"
                    } else {
                        "Priority queue blocked"
                    },
                    ToastKind::Success,
                );
            }
            PendingAction::HardenHighRisk => {
                let staged = self.controller.block_high_risk_candidates();
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.align_selected_candidate_with_filter();
                self.toast(
                    if staged == 0 {
                        "High-risk ports already blocked"
                    } else {
                        "High-risk ports blocked"
                    },
                    ToastKind::Success,
                );
            }
            PendingAction::ClearRules(staged_count) => {
                let removed = self.controller.clear_rules();
                self.selected_rule = 0;
                self.align_selected_candidate_with_filter();
                self.toast(
                    if staged_count == 0 {
                        "No staged rules to clear"
                    } else if removed == 0 {
                        "No staged rules to clear"
                    } else {
                        "Staged rules cleared"
                    },
                    ToastKind::Warning,
                );
            }
            PendingAction::ResetStarterPlan => {
                self.controller.reset_to_starter_plan();
                self.selected_rule = self.controller.rules().len().saturating_sub(1);
                self.candidate_filter = CandidateFilter::All;
                self.selected_group_filter = None;
                self.selected_risk_filter = None;
                self.align_selected_candidate_with_filter();
                self.toast("Starter dry-run plan restored", ToastKind::Success);
            }
        }
    }

    fn apply_profile(&mut self, profile: FirewallProfile) {
        let staged = self.controller.apply_profile(profile);
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        self.toast(
            if staged == 0 {
                "Profile already reflected"
            } else {
                "Dry-run profile applied"
            },
            ToastKind::Success,
        );
    }

    fn apply_recommended_profile(&mut self) {
        let (profile, staged) = self.controller.apply_recommended_profile();
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        let message = if staged == 0 {
            format!("Recommended {} profile already reflected", profile.label())
        } else {
            format!("Recommended {} profile applied", profile.label())
        };
        self.toast(&message, ToastKind::Success);
    }

    fn apply_safe_recommended_profile(&mut self) {
        let Some((profile, staged)) = self.controller.apply_safe_recommended_profile() else {
            self.toast(
                "No management-preserving profile is available",
                ToastKind::Warning,
            );
            return;
        };

        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        let message = if staged == 0 {
            format!(
                "Management-preserving {} profile already reflected",
                profile.label()
            )
        } else {
            format!("Management-preserving {} profile applied", profile.label())
        };
        self.toast(&message, ToastKind::Success);
    }

    fn apply_advisor_quick_fix(&mut self) {
        let changes = self.controller.apply_advisor_quick_fix();
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        self.toast(
            if changes == 0 {
                "Advisor found no quick changes"
            } else {
                "Advisor quick fix staged"
            },
            ToastKind::Success,
        );
    }

    fn export_report(&mut self) {
        match fs::write(REPORT_PATH, self.controller.dry_run_report()) {
            Ok(()) => {
                self.controller.record_report_export(REPORT_PATH);
                self.toast("Dry-run report exported", ToastKind::Success);
            }
            Err(_) => self.toast("Dry-run report export failed", ToastKind::Warning),
        }
    }

    fn export_script(&mut self) {
        match fs::write(SCRIPT_PATH, self.controller.dry_run_script()) {
            Ok(()) => {
                self.controller.record_script_export(SCRIPT_PATH);
                self.toast("Safe command script exported", ToastKind::Success);
            }
            Err(_) => self.toast("Command script export failed", ToastKind::Warning),
        }
    }

    fn export_manifest(&mut self) {
        match fs::write(MANIFEST_PATH, self.controller.dry_run_manifest()) {
            Ok(()) => {
                self.controller.record_manifest_export(MANIFEST_PATH);
                self.toast("Dry-run manifest exported", ToastKind::Success);
            }
            Err(_) => self.toast("Manifest export failed", ToastKind::Warning),
        }
    }

    fn export_bundle(&mut self) {
        let report = self.controller.dry_run_report();
        let script = self.controller.dry_run_script();
        let manifest = self.controller.dry_run_manifest();

        let result = fs::write(REPORT_PATH, report)
            .and_then(|()| fs::write(SCRIPT_PATH, script))
            .and_then(|()| fs::write(MANIFEST_PATH, manifest));

        match result {
            Ok(()) => {
                self.controller.record_bundle_export();
                self.toast("Review bundle exported", ToastKind::Success);
            }
            Err(_) => self.toast("Review bundle export failed", ToastKind::Warning),
        }
    }

    fn cycle_candidate_filter(&mut self) {
        self.candidate_filter = self.candidate_filter.next();
        self.align_selected_candidate_with_filter();
        self.toast("Known-port filter changed", ToastKind::Info);
    }

    fn cycle_candidate_group_filter(&mut self) {
        let groups = self.controller.candidate_groups();
        self.selected_group_filter = match self.selected_group_filter {
            None if !groups.is_empty() => Some(0),
            Some(index) if index + 1 < groups.len() => Some(index + 1),
            _ => None,
        };
        self.align_selected_candidate_with_filter();
        self.toast("Known-port group filter changed", ToastKind::Info);
    }

    fn cycle_candidate_risk_filter(&mut self) {
        let risks = self.controller.candidate_risks();
        self.selected_risk_filter = match self.selected_risk_filter {
            None if !risks.is_empty() => Some(0),
            Some(index) if index + 1 < risks.len() => Some(index + 1),
            _ => None,
        };
        self.align_selected_candidate_with_filter();
        self.toast("Known-port risk filter changed", ToastKind::Info);
    }

    fn jump_to_priority_candidate(&mut self) {
        let priority = self.controller.priority_ports(1).into_iter().next();

        let Some(priority) = priority else {
            self.toast("No at-risk priority port remains", ToastKind::Success);
            return;
        };

        self.selected_candidate = priority.index;
        self.selected_tab = 2;
        self.focus = Focus::Ports;
        self.candidate_filter = CandidateFilter::AtRisk;
        self.selected_group_filter = None;
        self.selected_risk_filter = None;
        self.toast(
            &format!(
                "Priority port selected: {} (score {})",
                priority.name, priority.score
            ),
            if priority.status == CandidateStatus::Allowed {
                ToastKind::Warning
            } else {
                ToastKind::Info
            },
        );
    }

    fn block_priority_candidate(&mut self) {
        let priority = self.controller.priority_ports(1).into_iter().next();

        let Some(priority) = priority else {
            self.toast("No at-risk priority port remains", ToastKind::Success);
            return;
        };

        self.controller.block_candidate(priority.index);
        self.selected_candidate = priority.index;
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.selected_tab = 2;
        self.focus = Focus::Ports;
        self.candidate_filter = CandidateFilter::AtRisk;
        self.selected_group_filter = None;
        self.selected_risk_filter = None;
        self.toast(
            &format!(
                "Blocked priority port: {} (score {})",
                priority.name, priority.score
            ),
            ToastKind::Success,
        );
    }

    fn align_selected_candidate_with_filter(&mut self) {
        let visible = self.visible_candidate_indexes();
        if visible.contains(&self.selected_candidate) {
            return;
        }
        self.selected_candidate = visible.first().copied().unwrap_or(0);
    }

    fn selected_visible_candidate(&self) -> Option<usize> {
        self.visible_candidate_indexes()
            .into_iter()
            .find(|index| *index == self.selected_candidate)
    }

    fn visible_candidate_indexes(&self) -> Vec<usize> {
        self.controller.filtered_candidate_indexes(
            self.candidate_filter,
            self.current_group_filter(),
            self.current_risk_filter(),
        )
    }

    fn current_group_filter(&self) -> Option<&'static str> {
        let index = self.selected_group_filter?;
        self.controller.candidate_groups().get(index).copied()
    }

    fn current_group_label(&self) -> &'static str {
        self.current_group_filter().unwrap_or("all-groups")
    }

    fn current_risk_filter(&self) -> Option<&'static str> {
        let index = self.selected_risk_filter?;
        self.controller.candidate_risks().get(index).copied()
    }

    fn current_risk_label(&self) -> &'static str {
        self.current_risk_filter().unwrap_or("all-risks")
    }

    fn block_selected_candidate(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        self.controller.block_candidate(index);
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        self.toast("Block rule staged", ToastKind::Warning);
    }

    fn allow_selected_candidate(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        self.controller.allow_candidate(index);
        self.selected_rule = self.controller.rules().len().saturating_sub(1);
        self.align_selected_candidate_with_filter();
        self.toast("Allow rule staged", ToastKind::Success);
    }

    fn toggle_selected_exception(&mut self) {
        let Some(index) = self.selected_visible_candidate() else {
            self.toast("No known port in active filter", ToastKind::Warning);
            return;
        };

        match self.controller.toggle_candidate_exception(index) {
            Some(true) => self.toast("Reviewed exception recorded", ToastKind::Warning),
            Some(false) => self.toast("Reviewed exception removed", ToastKind::Info),
            None => self.toast(
                "Allow an at-risk port before exception review",
                ToastKind::Warning,
            ),
        }
    }

    fn select_tab_and_continue(&mut self, index: usize) -> bool {
        self.select_tab(index);
        false
    }

    fn select_tab(&mut self, index: usize) {
        self.selected_tab = index.min(TAB_TITLES.len().saturating_sub(1));
        self.focus = match self.selected_tab {
            1 => Focus::Rules,
            2 => Focus::Ports,
            3 => Focus::Plan,
            4 => Focus::Advisor,
            _ => self.focus,
        };
    }

    fn toast(&mut self, message: &str, kind: ToastKind) {
        self.toasts.push(
            Toast::new(message, kind)
                .with_position(OverlayPosition::TopRight)
                .with_lifetime(Duration::from_secs(3))
                .with_transition(Transition::Fade)
                .with_max_width(44),
        );
        if self.toasts.len() > 3 {
            self.toasts.remove(0);
        }
    }

    fn update_overlays(&mut self, delta: Duration) {
        self.help_modal.update(delta);
        self.safety_popup.update(delta);
        self.confirmation_modal.update(delta);
        for toast in &mut self.toasts {
            toast.update(delta);
        }
        self.toasts.retain(|toast| !toast.is_expired());
    }

    fn move_selection(&mut self, delta: isize) {
        match self.focus {
            Focus::Rules => {
                self.selected_rule =
                    move_index(self.selected_rule, self.controller.rules().len(), delta);
            }
            Focus::Ports => {
                let visible = self.visible_candidate_indexes();
                if visible.is_empty() {
                    self.selected_candidate = 0;
                    return;
                }
                let current = visible
                    .iter()
                    .position(|index| *index == self.selected_candidate)
                    .unwrap_or(0);
                self.selected_candidate = visible[move_index(current, visible.len(), delta)];
            }
            Focus::Plan => {}
            Focus::Advisor => {
                self.selected_finding = move_index(
                    self.selected_finding,
                    self.controller.advisor_findings().len(),
                    delta,
                );
            }
        }
    }

    fn move_selection_to_boundary(&mut self, to_start: bool) {
        match self.focus {
            Focus::Rules => {
                self.selected_rule = if to_start {
                    0
                } else {
                    self.controller.rules().len().saturating_sub(1)
                };
            }
            Focus::Ports => {
                let visible = self.visible_candidate_indexes();
                self.selected_candidate = if to_start {
                    visible.first().copied().unwrap_or(0)
                } else {
                    visible.last().copied().unwrap_or(0)
                };
            }
            Focus::Advisor => {
                let findings = self.controller.advisor_findings();
                self.selected_finding = if to_start {
                    0
                } else {
                    findings.len().saturating_sub(1)
                };
            }
            Focus::Plan => {}
        }
    }

    fn render(&mut self, frame: &mut Frame<'_>) {
        let area = frame.area();
        let buffer = frame.buffer();
        buffer.fill(area, ' ', TEXT, Some(BG));

        if area.width < 24 || area.height < 8 {
            Paragraph::new("Kwall needs at least 24x8 terminal space.")
                .with_style(Style::new().fg(WARN))
                .with_word_wrap(true)
                .render(buffer, area);
            return;
        }

        let header_height = if area.height >= 12 { 5 } else { 3 };
        let tabs_height = if area.height >= 10 { 3 } else { 1 };
        let footer_height = if area.height >= 14 { 2 } else { 1 };
        let body_height = area
            .height
            .saturating_sub(header_height + tabs_height + footer_height);
        let header = Rect::new(0, 0, area.width, header_height);
        let tabs = Rect::new(0, header_height, area.width, tabs_height);
        let body = Rect::new(0, header_height + tabs_height, area.width, body_height);
        let footer = Rect::new(
            0,
            header_height + tabs_height + body_height,
            area.width,
            footer_height,
        );

        self.render_header(buffer, header);
        self.render_tabs(buffer, tabs);

        match self.selected_tab {
            1 => self.render_rules(buffer, body),
            2 => self.render_ports(buffer, body),
            3 => self.render_plan(buffer, body),
            4 => self.render_advisor(buffer, body),
            _ => self.render_dashboard(buffer, body),
        }

        self.render_footer(buffer, footer);
        self.render_overlays(buffer, area);
    }

    fn render_dashboard(&self, buffer: &mut Buffer, area: Rect) {
        if area.height >= 11 {
            let overview_height = if area.height >= 18 { 6 } else { 4 };
            let overview = Rect::new(area.x, area.y, area.width, overview_height);
            let panels = Rect::new(
                area.x,
                area.y + overview_height,
                area.width,
                area.height.saturating_sub(overview_height),
            );
            self.render_overview(buffer, overview);
            let (rules, ports, plan) = dashboard_panel_layout(panels, self.focus);
            self.render_rules(buffer, rules);
            self.render_ports(buffer, ports);
            self.render_plan(buffer, plan);
            return;
        }

        let (rules, ports, plan) = dashboard_panel_layout(area, self.focus);
        self.render_rules(buffer, rules);
        self.render_ports(buffer, ports);
        self.render_plan(buffer, plan);
    }

    fn render_overview(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let block = Block::new("Overview")
            .with_title_right("f cycles pane focus")
            .with_bg(PANEL)
            .border_style(Style::new().fg(ACCENT));
        let inner = block.inner(area);
        block.render(buffer, area);
        if inner.width == 0 || inner.height == 0 {
            return;
        }

        let stats = self.controller.stats();
        let advisor_summary = self.controller.advisor_summary();
        let gate_summary = self.controller.review_gate_summary();
        let recommended_profile = self.controller.recommended_profile();
        let safe_profile_label = self
            .controller
            .safe_recommended_profile()
            .map(|recommendation| recommendation.profile.label())
            .unwrap_or("none");
        let management_access = self.controller.management_access_summary();
        let left_width = if inner.width >= 86 {
            inner.width / 2
        } else {
            inner.width
        };
        let left = Rect::new(inner.x, inner.y, left_width, inner.height);
        let right = Rect::new(
            inner.x + left_width,
            inner.y,
            inner.width.saturating_sub(left_width),
            inner.height,
        );

        let summary = format!(
            "readiness {}% {} | rec {} | gates {}B/{}W/{}P | advisor {}C/{}W | undo {}",
            stats.risk_percent,
            stats.readiness_grade(),
            recommended_profile.profile.label(),
            gate_summary.block,
            gate_summary.warn,
            gate_summary.pass,
            advisor_summary.critical,
            advisor_summary.warning,
            self.controller.history_depth()
        );
        buffer.set_str(
            left.x as usize + 1,
            left.y as usize,
            &summary,
            TEXT,
            Some(PANEL),
        );

        if left.height > 1 {
            draw_meter(
                buffer,
                Rect::new(left.x + 1, left.y + 1, left.width.saturating_sub(2), 1),
                stats.coverage_percent,
                "known high-risk blocked",
            );
        }

        if left.height > 2 {
            let port_summary = format!(
                "known ports: {} blocked | {} open | {} at-risk | mgmt {} blocked/{} open",
                stats.known_blocked,
                stats.known_open,
                stats.at_risk_open,
                management_access.blocked,
                management_access.open
            );
            buffer.set_str(
                left.x as usize + 1,
                left.y as usize + 2,
                &port_summary,
                DIM,
                Some(PANEL),
            );
        }

        if left.height > 3 {
            let hint = format!(
                "profiles: Y strongest {} | y mgmt-safe {} | ports F/G/K",
                recommended_profile.profile.label(),
                safe_profile_label
            );
            let hint: String = hint
                .chars()
                .take(left.width.saturating_sub(2) as usize)
                .collect();
            buffer.set_str(
                left.x as usize + 1,
                left.y as usize + 3,
                &hint,
                DIM,
                Some(PANEL),
            );
        }

        if left.height > 4 {
            buffer.set_str(
                left.x as usize + 1,
                left.y as usize + 4,
                "live executor: absent | I/O policies | c clear rules | r fix | u undo",
                ACCENT,
                Some(PANEL),
            );
        }

        if right.width == 0 {
            return;
        }

        let mut audit_start = right.y;
        if let Some(candidate) = self.controller.candidates().get(self.selected_candidate) {
            let status = self
                .controller
                .candidate_status(self.selected_candidate)
                .unwrap_or(CandidateStatus::Open);
            let exception = if self
                .controller
                .candidate_has_review_exception(self.selected_candidate)
            {
                " | reviewed exception"
            } else {
                ""
            };
            let selected = format!(
                "selected: {} {} {}/{} group {} risk {}{} | filters {}/{}/{}",
                candidate.name,
                status.label(),
                candidate.port,
                candidate.protocol,
                candidate.group,
                candidate.risk,
                exception,
                self.candidate_filter.label(),
                self.current_group_label(),
                self.current_risk_label()
            );
            buffer.set_str(
                right.x as usize + 1,
                right.y as usize,
                &selected,
                status_color(status),
                Some(PANEL),
            );
            audit_start = right.y + 1;
        }

        if audit_start < right.bottom() {
            let next_action = self.controller.advisor_next_action();
            let next = format!(
                "next: [{}] {}",
                next_action.severity.label(),
                next_action.title
            );
            let display: String = next
                .chars()
                .take(right.width.saturating_sub(2) as usize)
                .collect();
            buffer.set_str(
                right.x as usize + 1,
                audit_start as usize,
                &display,
                severity_color(next_action.severity),
                Some(PANEL),
            );
            audit_start += 1;
        }

        if audit_start < right.bottom() {
            let (priority, color) = self.dashboard_priority_line();
            let display: String = priority
                .chars()
                .take(right.width.saturating_sub(2) as usize)
                .collect();
            buffer.set_str(
                right.x as usize + 1,
                audit_start as usize,
                &display,
                color,
                Some(PANEL),
            );
            audit_start += 1;
        }

        if audit_start < right.bottom() {
            let gate = self.controller.next_review_gate();
            let line = format!("gate: [{}] {}", gate.status.label(), gate.title);
            let display: String = line
                .chars()
                .take(right.width.saturating_sub(2) as usize)
                .collect();
            buffer.set_str(
                right.x as usize + 1,
                audit_start as usize,
                &display,
                review_gate_color(gate.status),
                Some(PANEL),
            );
            audit_start += 1;
        }

        if audit_start < right.bottom() {
            let queue = self.controller.priority_ports(3);
            if !queue.is_empty() {
                let title = format!(
                    "queue: {} scored priority port(s) | n blocks next | N jumps",
                    queue.len()
                );
                let display: String = title
                    .chars()
                    .take(right.width.saturating_sub(2) as usize)
                    .collect();
                buffer.set_str(
                    right.x as usize + 1,
                    audit_start as usize,
                    &display,
                    ACCENT,
                    Some(PANEL),
                );
                audit_start += 1;

                for (row, priority) in queue.iter().enumerate() {
                    if audit_start >= right.bottom() {
                        break;
                    }
                    let line = format!(
                        "{}. {} score {} {} {}/{} {}",
                        row + 1,
                        priority.status.label(),
                        priority.score,
                        priority.name,
                        priority.protocol,
                        priority.port,
                        priority.risk
                    );
                    let display: String = line
                        .chars()
                        .take(right.width.saturating_sub(2) as usize)
                        .collect();
                    buffer.set_str(
                        right.x as usize + 1,
                        audit_start as usize,
                        &display,
                        status_color(priority.status),
                        Some(PANEL),
                    );
                    audit_start += 1;
                }
            }
        }

        let audit_rows = right.bottom().saturating_sub(audit_start) as usize;
        for (row, message) in self
            .controller
            .audit_log()
            .iter()
            .rev()
            .take(audit_rows)
            .enumerate()
        {
            let y = audit_start + row as u16;
            if y >= right.bottom() {
                break;
            }
            let display: String = message
                .chars()
                .take(right.width.saturating_sub(2) as usize)
                .collect();
            buffer.set_str(right.x as usize + 1, y as usize, &display, DIM, Some(PANEL));
        }
    }

    fn dashboard_priority_line(&self) -> (String, Color) {
        let stats = self.controller.stats();
        if let Some(priority) = self.controller.priority_ports(1).into_iter().next() {
            let color = if priority.status == CandidateStatus::Allowed {
                WARN
            } else {
                status_color(priority.status)
            };
            return (
                format!(
                    "priority: {} score {} {} {}/{} {} - {}",
                    priority.status.label(),
                    priority.score,
                    priority.name,
                    priority.protocol,
                    priority.port,
                    priority.risk,
                    priority.reason
                ),
                color,
            );
        }

        (
            format!(
                "priority: {} {}% {} - review export bundle with E",
                stats.readiness_grade(),
                stats.risk_percent,
                stats.readiness_label()
            ),
            OK,
        )
    }

    fn render_header(&self, buffer: &mut Buffer, area: Rect) {
        let block = Block::new("Kwall")
            .with_title_right("scrin + Aisling")
            .with_bg(PANEL)
            .border_style(Style::new().fg(ACCENT));
        let inner = block.inner(area);
        block.render(buffer, area);

        if inner.height >= 2 {
            self.banner.render_to_buffer(buffer, inner);
            let y = inner.y as usize;
            buffer.set_str_bold(inner.x as usize + 1, y, "Kwall", ACCENT, Some(PANEL));
            buffer.set_str(
                inner.x as usize + 1,
                y + 1,
                "ufw-style firewall control, dry-run planning only",
                TEXT,
                Some(PANEL),
            );
        } else {
            buffer.set_str(
                inner.x as usize,
                inner.y as usize,
                "dry-run firewall planner",
                TEXT,
                Some(PANEL),
            );
        }

        if inner.width > 54 && inner.height >= 3 {
            let note = "dry-run only | staged rules | no system writes";
            buffer.set_str(
                inner.x as usize + inner.width.saturating_sub(note.len() as u16 + 2) as usize,
                inner.y as usize + inner.height.saturating_sub(1) as usize,
                note,
                DIM,
                Some(PANEL),
            );
        }
    }

    fn render_tabs(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        buffer.fill(area, ' ', TEXT, Some(BG));
        let inner = if area.height >= 3 {
            let block = Block::new("Pages")
                .with_bg(BG)
                .border_style(Style::new().fg(Color::rgb(30, 41, 59)));
            let inner = block.inner(area);
            block.render(buffer, area);
            inner
        } else {
            area
        };

        Tabs::new(&TAB_TITLES)
            .with_selected(self.selected_tab)
            .with_style(Style::new().fg(DIM))
            .with_highlight_style(Style::new().fg(ACCENT).bold().underlined())
            .render(
                buffer,
                Rect::new(inner.x + 1, inner.y, inner.width.saturating_sub(2), 1),
            );
    }

    fn render_rules(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let total_rules = self.controller.rules().len();
        let rule_position = if total_rules == 0 {
            "0/0".to_string()
        } else {
            format!(
                "{}/{}",
                self.selected_rule.min(total_rules.saturating_sub(1)) + 1,
                total_rules
            )
        };
        let title = format!(
            "Rules {} {}",
            focus_marker(self.focus == Focus::Rules),
            rule_position
        );
        let block = panel_block(&title, self.focus == Focus::Rules);
        let inner = block.inner(area);
        block.render(buffer, area);

        if inner.width == 0 || inner.height == 0 {
            return;
        }

        if self.controller.rules().is_empty() {
            Paragraph::new(
                "No staged rules yet. Select a service port and press b to block or a to allow.",
            )
            .with_word_wrap(true)
            .with_style(Style::new().fg(DIM))
            .render(buffer, inner);
            return;
        }

        let items = self
            .controller
            .rules()
            .iter()
            .map(|rule| {
                let action_color = match rule.action {
                    RuleAction::Allow => OK,
                    RuleAction::Deny | RuleAction::Reject => WARN,
                };
                ListItem::with_line(Line::new(vec![
                    Span::styled(&format!("#{:02} ", rule.id), Style::new().fg(DIM)),
                    Span::styled(rule.action.label(), Style::new().fg(action_color).bold()),
                    Span::styled(
                        &format!(
                            " {} {} {} ",
                            rule.direction.label(),
                            rule.protocol,
                            rule.port
                        ),
                        Style::new().fg(TEXT),
                    ),
                    Span::styled(
                        if rule.enabled { "on " } else { "off " },
                        Style::new().fg(if rule.enabled { ACCENT } else { DIM }),
                    ),
                    Span::styled(&rule.note, Style::new().fg(DIM)),
                ]))
            })
            .collect::<Vec<_>>();

        let selected = self.selected_rule.min(items.len().saturating_sub(1));
        List::new(&items)
            .with_selected(selected)
            .with_highlight_symbol("> ")
            .with_highlight_style(Style::new().fg(Color::WHITE).bg(SELECTED_BG))
            .render(buffer, inner);
    }

    fn render_ports(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let visible = self.visible_candidate_indexes();
        let selector = if visible.is_empty() {
            "0/0".to_string()
        } else {
            let selected = visible
                .iter()
                .position(|index| *index == self.selected_candidate)
                .unwrap_or(0);
            format!("{}/{}", selected + 1, visible.len())
        };
        let title = format!(
            "Known Ports {} [{}] filter {} | group {} | risk {} | F/G/K | X exception",
            focus_marker(self.focus == Focus::Ports),
            selector,
            self.candidate_filter.label(),
            self.current_group_label(),
            self.current_risk_label()
        );
        let block = panel_block(&title, self.focus == Focus::Ports);
        let inner = block.inner(area);
        block.render(buffer, area);

        if inner.width == 0 || inner.height == 0 {
            return;
        }

        if visible.is_empty() {
            Paragraph::new(
                "No known ports match these filters. Press F, G, or K to cycle filters.",
            )
            .with_word_wrap(true)
            .with_style(Style::new().fg(DIM))
            .render(buffer, inner);
            return;
        }

        let list_area = if inner.height > 4 {
            let summary = self.group_coverage_line(inner.width as usize);
            buffer.set_str(
                inner.x as usize,
                inner.y as usize,
                &summary,
                DIM,
                Some(PANEL),
            );
            let risk_summary = self.risk_coverage_line(inner.width as usize);
            buffer.set_str(
                inner.x as usize,
                inner.y as usize + 1,
                &risk_summary,
                DIM,
                Some(PANEL),
            );
            Rect::new(
                inner.x,
                inner.y + 3,
                inner.width,
                inner.height.saturating_sub(3),
            )
        } else if inner.height > 3 {
            let summary = self.group_coverage_line(inner.width as usize);
            buffer.set_str(
                inner.x as usize,
                inner.y as usize,
                &summary,
                DIM,
                Some(PANEL),
            );
            Rect::new(
                inner.x,
                inner.y + 2,
                inner.width,
                inner.height.saturating_sub(2),
            )
        } else {
            inner
        };

        if list_area.height == 0 {
            return;
        }

        let items = visible
            .iter()
            .map(|index| {
                let candidate = &self.controller.candidates()[*index];
                let status = self
                    .controller
                    .candidate_status(*index)
                    .unwrap_or(CandidateStatus::Open);
                let action = if status == CandidateStatus::Blocked {
                    "unblock"
                } else if self.controller.candidate_has_review_exception(*index) {
                    "reviewed"
                } else if status == CandidateStatus::Allowed
                    && matches!(candidate.risk, "high" | "data" | "admin")
                {
                    "needs X"
                } else {
                    "block"
                };
                ListItem::with_line(Line::new(vec![
                    Span::styled(
                        &format!("{:<8}", status.label()),
                        Style::new().fg(status_color(status)).bold(),
                    ),
                    Span::styled(
                        &format!("{:<12}", candidate.name),
                        Style::new().fg(TEXT).bold(),
                    ),
                    Span::styled(
                        &format!(" {:>7}/{:<3} ", candidate.port, candidate.protocol),
                        Style::new().fg(ACCENT),
                    ),
                    Span::styled(&format!("{:<13}", candidate.group), Style::new().fg(DIM)),
                    Span::styled(&format!("{:<9}", action), Style::new().fg(ACCENT)),
                    Span::styled(candidate.detail, Style::new().fg(DIM)),
                ]))
            })
            .collect::<Vec<_>>();

        let selected = visible
            .iter()
            .position(|index| *index == self.selected_candidate)
            .unwrap_or(0);
        List::new(&items)
            .with_selected(selected)
            .with_highlight_symbol("> ")
            .with_highlight_style(Style::new().fg(Color::WHITE).bg(SELECTED_BG))
            .render(buffer, list_area);
    }

    fn group_coverage_line(&self, width: usize) -> String {
        let coverage = self.controller.group_coverage_summaries();
        let text = if let Some(group) = self.current_group_filter() {
            coverage
                .iter()
                .find(|summary| summary.group == group)
                .map(|summary| {
                    format!(
                        "group coverage: {} {}/{} blocked, {} allowed, {} open, {} at-risk open",
                        summary.group,
                        summary.blocked,
                        summary.total,
                        summary.allowed,
                        summary.open,
                        summary.at_risk_open
                    )
                })
                .unwrap_or_else(|| "group coverage: no selected group".into())
        } else {
            let groups = coverage
                .iter()
                .take(4)
                .map(|summary| format!("{} {}/{}", summary.group, summary.blocked, summary.total))
                .collect::<Vec<_>>()
                .join(" | ");
            format!("group coverage: {groups}")
        };

        text.chars().take(width).collect()
    }

    fn risk_coverage_line(&self, width: usize) -> String {
        let coverage = self.controller.risk_coverage_summaries();
        let text = if let Some(risk) = self.current_risk_filter() {
            coverage
                .iter()
                .find(|summary| summary.risk == risk)
                .map(|summary| {
                    format!(
                        "risk exposure: {} {}/{} blocked, {} allowed, {} open",
                        summary.risk, summary.blocked, summary.total, summary.allowed, summary.open
                    )
                })
                .unwrap_or_else(|| "risk exposure: no selected risk".into())
        } else {
            let risks = coverage
                .iter()
                .take(5)
                .map(|summary| format!("{} {} open", summary.risk, summary.open))
                .collect::<Vec<_>>()
                .join(" | ");
            format!("risk exposure: {risks}")
        };

        text.chars().take(width).collect()
    }

    fn render_plan(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let plan = self.controller.plan();
        let title = format!(
            "Plan {} [{} commands] o exports report",
            focus_marker(self.focus == Focus::Plan),
            plan.len()
        );
        let block = panel_block(&title, self.focus == Focus::Plan);
        let inner = block.inner(area);
        block.render(buffer, area);

        if inner.width == 0 || inner.height == 0 {
            return;
        }

        let fingerprint = self.controller.plan_fingerprint();
        let backend_summary = self
            .controller
            .backend_plan_summaries()
            .iter()
            .map(|summary| format!("{} {}", summary.backend, summary.commands))
            .collect::<Vec<_>>()
            .join(" | ");
        let profile_summary = self
            .controller
            .profile_impact_summaries()
            .iter()
            .map(|impact| {
                format!(
                    "{} +{} mgmt {}{}",
                    impact.profile.label(),
                    impact.staged_changes,
                    impact.management_allowed + impact.management_open,
                    if impact.management_all_blocked {
                        "!"
                    } else {
                        ""
                    }
                )
            })
            .collect::<Vec<_>>()
            .join(" | ");
        let recommended_profile = self.controller.recommended_profile();
        let recommended_warning = recommended_profile
            .warning
            .as_deref()
            .unwrap_or("management continuity ok");
        let safe_recommended_profile = self.controller.safe_recommended_profile();
        let safe_profile_summary = safe_recommended_profile
            .as_ref()
            .map(|recommendation| {
                format!(
                    "safe alternative: {} score {} | {} management path(s) stay open/allowed",
                    recommendation.profile.label(),
                    recommendation.score,
                    recommendation.management_allowed + recommendation.management_open
                )
            })
            .unwrap_or_else(|| "safe alternative: none preserves management access".into());
        let recommended_changes = self.controller.recommended_profile_changes();
        let safe_recommended_changes = self.controller.safe_recommended_profile_changes();
        let recommended_change_summary = recommended_changes
            .iter()
            .take(3)
            .map(|change| {
                format!(
                    "{} {}->{}",
                    change.name,
                    change.before.label(),
                    change.after.label()
                )
            })
            .collect::<Vec<_>>()
            .join(" | ");
        let safe_change_summary = safe_recommended_changes
            .iter()
            .take(3)
            .map(|change| {
                format!(
                    "{} {}->{}",
                    change.name,
                    change.before.label(),
                    change.after.label()
                )
            })
            .collect::<Vec<_>>()
            .join(" | ");
        if inner.width >= 58 && inner.height >= 10 {
            let stats = self.controller.stats();
            let meta = format!(
                "id {} | backend {} | defaults in {} out {} | logging {} | commands {}",
                fingerprint,
                self.controller.backend(),
                self.controller.default_incoming().label(),
                self.controller.default_outgoing().label(),
                if self.controller.logging() {
                    "on"
                } else {
                    "off"
                },
                stats.plan_commands
            );
            buffer.set_str(inner.x as usize, inner.y as usize, &meta, TEXT, Some(PANEL));
            buffer.set_str(
                inner.x as usize,
                inner.y as usize + 2,
                "Generated commands are review-only. They are not executed.",
                ACCENT,
                Some(PANEL),
            );
            buffer.set_str(
                inner.x as usize,
                inner.y as usize + 3,
                &format!("backend comparison: {backend_summary}"),
                DIM,
                Some(PANEL),
            );
            buffer.set_str(
                inner.x as usize,
                inner.y as usize + 4,
                &format!(
                    "recommended profile: {} score {} | {}",
                    recommended_profile.profile.label(),
                    recommended_profile.score,
                    recommended_warning
                ),
                if recommended_profile.management_all_blocked {
                    WARN
                } else {
                    DIM
                },
                Some(PANEL),
            );
            if inner.height >= 11 {
                buffer.set_str(
                    inner.x as usize,
                    inner.y as usize + 5,
                    &safe_profile_summary,
                    if safe_recommended_profile.is_some() {
                        DIM
                    } else {
                        WARN
                    },
                    Some(PANEL),
                );
            }
            if inner.height >= 12 {
                buffer.set_str(
                    inner.x as usize,
                    inner.y as usize + 6,
                    &format!(
                        "recommended changes: {} | {}",
                        recommended_changes.len(),
                        recommended_change_summary
                    ),
                    DIM,
                    Some(PANEL),
                );
            }
            if inner.height >= 13 {
                buffer.set_str(
                    inner.x as usize,
                    inner.y as usize + 7,
                    &format!("profile impact: {profile_summary}"),
                    DIM,
                    Some(PANEL),
                );
            }

            let reason_width = if inner.width >= 92 { 24 } else { 16 };
            let index_width = 4;
            let backend_width = 9;
            let command_width = inner
                .width
                .saturating_sub(index_width + backend_width + reason_width + 4)
                .max(12);
            let widths = [index_width, backend_width, command_width, reason_width];
            let header = TableRow::new(vec![
                TableCell::new("#").with_style(Style::new().fg(ACCENT).bold()),
                TableCell::new("backend").with_style(Style::new().fg(ACCENT).bold()),
                TableCell::new("command").with_style(Style::new().fg(ACCENT).bold()),
                TableCell::new("reason").with_style(Style::new().fg(ACCENT).bold()),
            ]);
            let rows = plan
                .iter()
                .enumerate()
                .map(|(index, command)| {
                    TableRow::new(vec![
                        TableCell::new(&format!("{:02}", index + 1))
                            .with_style(Style::new().fg(DIM)),
                        TableCell::new(&command.backend.to_string())
                            .with_style(Style::new().fg(OK)),
                        TableCell::new(&command.shell_line()).with_style(Style::new().fg(TEXT)),
                        TableCell::new(&command.reason).with_style(Style::new().fg(DIM)),
                    ])
                })
                .collect::<Vec<_>>();
            Table::new(&rows, &widths).with_header(&header).render(
                buffer,
                Rect::new(
                    inner.x,
                    inner.y + 9,
                    inner.width,
                    inner.height.saturating_sub(9),
                ),
            );
            return;
        }

        let mut lines = vec![
            format!("review id: {fingerprint}"),
            format!("mode: {}", self.controller.mode()),
            format!("backend: {}", self.controller.backend()),
            format!("backend comparison: {backend_summary}"),
            format!(
                "recommended profile: {} score {} - {} | {} change(s)",
                recommended_profile.profile.label(),
                recommended_profile.score,
                recommended_warning,
                recommended_changes.len()
            ),
            safe_profile_summary,
            format!("recommended changes: {recommended_change_summary}"),
            format!(
                "safe changes: {} | {}",
                safe_recommended_changes.len(),
                safe_change_summary
            ),
            format!("profile impact: {profile_summary}"),
            format!(
                "defaults: incoming {}, outgoing {}, logging {}",
                self.controller.default_incoming().label(),
                self.controller.default_outgoing().label(),
                if self.controller.logging() {
                    "on"
                } else {
                    "off"
                }
            ),
            "".into(),
            "Generated commands are review-only. They are not executed.".into(),
            "".into(),
        ];

        let available_command_rows = inner.height.saturating_sub(lines.len() as u16) as usize;
        let command_limit = (available_command_rows / 2).max(1);
        for command in plan.iter().take(command_limit) {
            lines.push(format!("$ {}", command.shell_line()));
            lines.push(format!("  # {}", command.reason));
        }

        if plan.len() > command_limit {
            lines.push(format!(
                "... {} more planned commands",
                plan.len() - command_limit
            ));
        }

        let text = lines.join("\n");
        Paragraph::new(&text)
            .with_word_wrap(true)
            .with_style(Style::new().fg(TEXT))
            .render(buffer, inner);
    }

    fn render_advisor(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let findings = self.controller.advisor_findings();
        let finding_position = if findings.is_empty() {
            "0/0".to_string()
        } else {
            format!(
                "{}/{}",
                self.selected_finding.min(findings.len().saturating_sub(1)) + 1,
                findings.len()
            )
        };
        let title = format!(
            "Advisor {} [{}] r quick fix | u undo",
            focus_marker(self.focus == Focus::Advisor),
            finding_position
        );
        let block = panel_block(&title, self.focus == Focus::Advisor);
        let inner = block.inner(area);
        block.render(buffer, area);

        if inner.width == 0 || inner.height == 0 {
            return;
        }
        if inner.width >= 72 && inner.height >= 8 {
            let header = TableRow::new(vec![
                TableCell::new("level").with_style(Style::new().fg(ACCENT).bold()),
                TableCell::new("finding").with_style(Style::new().fg(ACCENT).bold()),
                TableCell::new("recommendation").with_style(Style::new().fg(ACCENT).bold()),
            ]);
            let level_width = 10;
            let rec_width = if inner.width >= 110 { 38 } else { 28 };
            let title_width = inner
                .width
                .saturating_sub(level_width + rec_width + 3)
                .max(18);
            let widths = [level_width, title_width, rec_width];
            let rows = findings
                .iter()
                .map(|finding| {
                    TableRow::new(vec![
                        TableCell::new(finding.severity.label())
                            .with_style(Style::new().fg(severity_color(finding.severity)).bold()),
                        TableCell::new(&finding.title).with_style(Style::new().fg(TEXT)),
                        TableCell::new(&finding.recommendation).with_style(Style::new().fg(DIM)),
                    ])
                })
                .collect::<Vec<_>>();
            let selected = self.selected_finding.min(rows.len().saturating_sub(1));
            Table::new(&rows, &widths)
                .with_header(&header)
                .with_selected(selected)
                .render(buffer, inner);
            return;
        }

        let items = findings
            .iter()
            .map(|finding| {
                ListItem::with_line(Line::new(vec![
                    Span::styled(
                        &format!("{:<8}", finding.severity.label()),
                        Style::new().fg(severity_color(finding.severity)).bold(),
                    ),
                    Span::styled(&finding.title, Style::new().fg(TEXT)),
                ]))
            })
            .collect::<Vec<_>>();
        let selected = self.selected_finding.min(items.len().saturating_sub(1));
        List::new(&items)
            .with_selected(selected)
            .with_highlight_symbol("> ")
            .with_highlight_style(Style::new().fg(Color::WHITE).bg(SELECTED_BG))
            .render(buffer, inner);
    }

    fn render_footer(&self, buffer: &mut Buffer, area: Rect) {
        if area.is_empty() {
            return;
        }

        let mut status = StatusBar::new()
            .with_position(StatusBarPosition::Bottom)
            .with_bg(Color::rgb(3, 7, 18));
        status.height = area.height;
        status.set_left("DRY RUN LOCK", ACCENT);
        status.set_center(self.controller.last_message(), DIM);
        let right = if self.confirmation_modal.is_visible() {
            "Confirmation: Enter selects option, y confirm, n/Esc cancel | Arrow keys move"
        } else {
            "N priority | F/G/K filters | V block | A allow | E bundle | : palette"
        };
        if self.show_frame_metrics {
            let timing = self.last_frame_timing.unwrap_or(FrameTiming {
                elapsed: Duration::from_nanos(0),
                bytes_written: 0,
                dirty_cells: 0,
                areas: 0,
                full_repaint: false,
            });
            let metrics = format!(
                "{} | frame {}µs | {}B/cells {}",
                right,
                timing.elapsed.as_micros(),
                timing.bytes_written,
                timing.dirty_cells
            );
            status.set_right(&metrics, OK);
        } else {
            status.set_right(right, OK);
        }
        status.render(buffer, area);
    }

    fn render_overlays(&mut self, buffer: &mut Buffer, area: Rect) {
        self.safety_popup.adapt_to_terminal(area.width, area.height);
        self.safety_popup.render(buffer, area);
        self.help_modal.render(buffer, area);
        self.command_palette.render(buffer, area);
        self.confirmation_modal.render(buffer, area);
        for toast in self.toasts.iter().rev().take(1) {
            toast.render(buffer, area);
        }
    }
}

fn dashboard_panel_layout(area: Rect, focus: Focus) -> (Rect, Rect, Rect) {
    if area.width >= 90 {
        let focused = area.width * 48 / 100;
        let side_total = area.width.saturating_sub(focused);
        let side = side_total / 2;
        let last = side_total.saturating_sub(side);

        return match focus {
            Focus::Rules => (
                Rect::new(area.x, area.y, focused, area.height),
                Rect::new(area.x + focused, area.y, side, area.height),
                Rect::new(area.x + focused + side, area.y, last, area.height),
            ),
            Focus::Ports => (
                Rect::new(area.x, area.y, side, area.height),
                Rect::new(area.x + side, area.y, focused, area.height),
                Rect::new(area.x + side + focused, area.y, last, area.height),
            ),
            Focus::Plan | Focus::Advisor => (
                Rect::new(area.x, area.y, side, area.height),
                Rect::new(area.x + side, area.y, last, area.height),
                Rect::new(area.x + side + last, area.y, focused, area.height),
            ),
        };
    }

    let focused = area.height * 50 / 100;
    let side_total = area.height.saturating_sub(focused);
    let side = side_total / 2;
    let last = side_total.saturating_sub(side);

    match focus {
        Focus::Rules => (
            Rect::new(area.x, area.y, area.width, focused),
            Rect::new(area.x, area.y + focused, area.width, side),
            Rect::new(area.x, area.y + focused + side, area.width, last),
        ),
        Focus::Ports => (
            Rect::new(area.x, area.y, area.width, side),
            Rect::new(area.x, area.y + side, area.width, focused),
            Rect::new(area.x, area.y + side + focused, area.width, last),
        ),
        Focus::Plan | Focus::Advisor => (
            Rect::new(area.x, area.y, area.width, side),
            Rect::new(area.x, area.y + side, area.width, last),
            Rect::new(area.x, area.y + side + last, area.width, focused),
        ),
    }
}

fn panel_block<'a>(title: &'a str, focused: bool) -> Block<'a> {
    let color = if focused {
        FOCUS
    } else {
        Color::rgb(51, 65, 85)
    };
    Block::new(title)
        .with_bg(PANEL)
        .border_style(Style::new().fg(color))
}

fn focus_marker(focused: bool) -> &'static str {
    if focused {
        "[active]"
    } else {
        ""
    }
}

fn read_frame_metrics_flag() -> bool {
    env::var(FRAME_METRICS_ENV)
        .ok()
        .map(|value| {
            value.eq_ignore_ascii_case("1")
                || value.eq_ignore_ascii_case("true")
                || value.eq_ignore_ascii_case("yes")
                || value.eq_ignore_ascii_case("on")
        })
        .unwrap_or(SHOW_FRAME_METRICS_DEFAULT)
}

fn move_index(current: usize, len: usize, delta: isize) -> usize {
    if len == 0 {
        return 0;
    }

    let max = len - 1;
    if delta < 0 {
        current.saturating_sub(delta.unsigned_abs()).min(max)
    } else {
        current.saturating_add(delta as usize).min(max)
    }
}

fn status_color(status: CandidateStatus) -> Color {
    match status {
        CandidateStatus::Blocked => ACCENT,
        CandidateStatus::Allowed => TEXT,
        CandidateStatus::Open => DIM,
    }
}

fn severity_color(severity: AdvisorSeverity) -> Color {
    match severity {
        AdvisorSeverity::Critical => WARN,
        AdvisorSeverity::Warning => Color::rgb(214, 160, 72),
        AdvisorSeverity::Info => DIM,
        AdvisorSeverity::Good => ACCENT,
    }
}

fn review_gate_color(status: ReviewGateStatus) -> Color {
    match status {
        ReviewGateStatus::Pass => ACCENT,
        ReviewGateStatus::Warn => Color::rgb(214, 160, 72),
        ReviewGateStatus::Block => WARN,
    }
}

fn draw_meter(buffer: &mut Buffer, area: Rect, percent: u8, label: &str) {
    if area.is_empty() {
        return;
    }

    let width = area.width as usize;
    let suffix = format!(" {:>3}%", percent.min(100));
    let reserve = label.len().saturating_add(suffix.len()).saturating_add(4);
    let bar_width = width.saturating_sub(reserve).max(8);
    let fill = bar_width * percent.min(100) as usize / 100;
    let bar = format!("[{}{}]", "#".repeat(fill), "-".repeat(bar_width - fill));
    let text = format!("{} {}{}", label, bar, suffix);
    let display: String = text.chars().take(width).collect();
    buffer.set_str(
        area.x as usize,
        area.y as usize,
        &display,
        ACCENT,
        Some(PANEL),
    );
}

fn build_command_palette() -> CommandPalette {
    let mut palette = CommandPalette::new().with_commands(palette_commands());
    palette.border_color = ACCENT;
    palette.bg = PANEL;
    palette.selected_bg = Color::rgb(20, 63, 58);
    palette
}

fn palette_commands() -> Vec<Command> {
    vec![
        command("Dashboard", "1", "Open dashboard page", "dashboard"),
        command("Rules", "2", "Open staged rules page", "rules"),
        command("Ports", "3", "Open service port page", "ports"),
        command("Plan", "4", "Open generated plan page", "plan"),
        command("Advisor", "5", "Open dry-run advisor review", "advisor"),
        command(
            "Block selected port",
            "b",
            "Stage a deny rule for the selected service port",
            "block-selected",
        ),
        command(
            "Toggle known port block",
            "d/Enter",
            "Block or unblock the selected known service port",
            "toggle-port-block",
        ),
        command(
            "Cycle known-port filter",
            "F",
            "Show all, named, at-risk, open, blocked, or allowed ports",
            "cycle-port-filter",
        ),
        command(
            "Cycle group filter",
            "G",
            "Narrow known ports by service group",
            "cycle-group-filter",
        ),
        command(
            "Cycle risk filter",
            "K",
            "Narrow known ports by risk class",
            "cycle-risk-filter",
        ),
        command(
            "Jump to priority port",
            "N",
            "Select the highest-priority at-risk known port",
            "jump-priority-port",
        ),
        command(
            "Block priority port",
            "n",
            "Stage a block for the highest-priority at-risk known port",
            "block-priority-port",
        ),
        command(
            "Block priority queue",
            "M",
            "Stage blocks for the top visible priority ports",
            "block-priority-queue",
        ),
        command(
            "Toggle reviewed exception",
            "X",
            "Record or remove exception review for an allowed risky port",
            "toggle-exception",
        ),
        command(
            "Harden high-risk ports",
            "h",
            "Stage blocks for known admin, high, and data ports",
            "harden-known",
        ),
        command(
            "Block selected group",
            "g",
            "Stage blocks for every known port in the selected group",
            "block-selected-group",
        ),
        command(
            "Block selected risk class",
            "B",
            "Stage blocks for every known port in the selected risk class",
            "block-selected-risk",
        ),
        command(
            "Block visible ports",
            "V",
            "Stage blocks for every known port in the active filters",
            "block-visible-ports",
        ),
        command(
            "Allow visible ports",
            "A",
            "Stage allows for every known port in the active filters",
            "allow-visible-ports",
        ),
        command(
            "Apply recommended profile",
            "Y",
            "Stage the strongest currently recommended dry-run profile",
            "profile-recommended",
        ),
        command(
            "Apply safe recommended profile",
            "y",
            "Stage the management-preserving recommended dry-run profile",
            "profile-safe-recommended",
        ),
        command(
            "Apply workstation profile",
            "w",
            "Block legacy, file, data, admin, dev, and orchestration services",
            "profile-workstation",
        ),
        command(
            "Apply web profile",
            "e",
            "Allow HTTP/HTTPS and block data/dev/control-plane services",
            "profile-web",
        ),
        command(
            "Apply developer profile",
            "D",
            "Block legacy, file, data, and orchestration services",
            "profile-developer",
        ),
        command(
            "Apply lockdown profile",
            "k",
            "Block every known service port in the catalog",
            "profile-lockdown",
        ),
        command(
            "Advisor quick fix",
            "r",
            "Stage obvious safe hardening recommendations",
            "advisor-fix",
        ),
        command(
            "Undo last staged change",
            "u",
            "Restore the previous in-memory dry-run state",
            "undo",
        ),
        command(
            "Allow selected port",
            "a",
            "Stage an allow rule for the selected service port",
            "allow-selected",
        ),
        command(
            "Toggle selected rule",
            "Space",
            "Enable or disable the selected staged rule",
            "toggle-rule",
        ),
        command(
            "Remove selected rule",
            "x",
            "Remove the selected staged rule",
            "remove-rule",
        ),
        command(
            "Cycle backend preview",
            "p",
            "Switch ufw, iptables, and nftables command previews",
            "cycle-backend",
        ),
        command(
            "Toggle logging plan",
            "l",
            "Toggle planned logging commands",
            "toggle-logging",
        ),
        command(
            "Toggle default incoming",
            "I",
            "Switch planned default incoming policy between allow and deny",
            "toggle-default-incoming",
        ),
        command(
            "Toggle default outgoing",
            "O",
            "Switch planned default outgoing policy between allow and deny",
            "toggle-default-outgoing",
        ),
        command(
            "Clear staged rules",
            "c",
            "Remove all staged rules while keeping undo available",
            "clear-rules",
        ),
        command(
            "Restore starter plan",
            "R",
            "Return to the safe starter dry-run baseline",
            "reset-starter",
        ),
        command(
            "Export dry-run report",
            "o",
            "Write a local review-only report file",
            "export-report",
        ),
        command(
            "Export safe command script",
            "S",
            "Write a commented shell preview that exits before commands",
            "export-script",
        ),
        command(
            "Export JSON manifest",
            "m",
            "Write a review-only machine-readable dry-run plan",
            "export-manifest",
        ),
        command(
            "Export review bundle",
            "E",
            "Write report, safe script, and JSON manifest together",
            "export-bundle",
        ),
        command("Safety popup", "s", "Show dry-run safety lock", "safety"),
        command("Help", "?", "Show keyboard help", "help"),
    ]
}

fn command(name: &str, shortcut: &str, description: &str, action_id: &str) -> Command {
    Command {
        name: name.to_string(),
        shortcut: Some(shortcut.to_string()),
        description: description.to_string(),
        action_id: action_id.to_string(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crossterm::event::{KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};

    fn select_palette_action(tui: &mut FirewallTui, action_id: &str) {
        let target = tui
            .command_palette
            .commands
            .iter()
            .position(|command| command.action_id == action_id)
            .expect("command palette should expose action");
        let filtered_pos = tui
            .command_palette
            .filtered
            .iter()
            .position(|&index| index == target)
            .expect("command should be in visible palette list");
        tui.command_palette.selected_index = filtered_pos;
    }

    fn execute_palette_action(tui: &mut FirewallTui, action_id: &str) {
        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        select_palette_action(tui, action_id);
        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Enter,
            KeyModifiers::empty(),
        )));
    }

    fn apply_profile_palette_action(
        tui: &mut FirewallTui,
        action_id: &str,
        expected: FirewallProfile,
    ) {
        execute_palette_action(tui, action_id);

        match tui.pending_confirmation.as_ref() {
            Some(PendingAction::ApplyProfile(profile)) => {
                assert_eq!(*profile, expected);
                tui.resolve_confirmation(Some(true));
            }
            Some(_) => panic!("expected ApplyProfile action for {action_id}"),
            None => {}
        }

        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());
    }

    fn visible_open_count(tui: &FirewallTui) -> usize {
        tui.visible_candidate_indexes()
            .into_iter()
            .filter(|index| {
                tui.controller
                    .candidate_status(*index)
                    .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .count()
    }

    fn ui_snapshot(
        tui: &FirewallTui,
    ) -> (
        usize,
        usize,
        usize,
        CandidateFilter,
        Option<usize>,
        Option<usize>,
        Focus,
    ) {
        (
            tui.selected_tab,
            tui.selected_rule,
            tui.selected_candidate,
            tui.candidate_filter,
            tui.selected_group_filter,
            tui.selected_risk_filter,
            tui.focus,
        )
    }

    fn assert_ui_snapshot_eq(
        first: &(
            usize,
            usize,
            usize,
            CandidateFilter,
            Option<usize>,
            Option<usize>,
            Focus,
        ),
        second: &(
            usize,
            usize,
            usize,
            CandidateFilter,
            Option<usize>,
            Option<usize>,
            Focus,
        ),
    ) {
        assert_eq!(first, second);
    }

    #[test]
    fn request_block_visible_candidates_can_cancel_without_changes() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let open_count = visible_open_count(&tui);
        assert!(
            open_count > 0,
            "demo should include unblocked visible ports"
        );

        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());

        match tui.pending_confirmation.as_ref() {
            Some(PendingAction::BlockVisibleCandidates(indexes)) => {
                assert_eq!(indexes.len(), open_count);
            }
            Some(_) => panic!("expected pending BlockVisibleCandidates action"),
            None => panic!("expected a pending confirmation action"),
        }

        let before = tui.controller.rules().len();
        tui.resolve_confirmation(Some(false));
        assert_eq!(before, tui.controller.rules().len());
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn request_block_visible_candidates_confirms_with_enter() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let open_count = visible_open_count(&tui);
        assert!(
            open_count > 0,
            "demo should include unblocked visible ports"
        );

        tui.request_block_visible_candidates();
        let before = tui.controller.rules().len();
        tui.resolve_confirmation(None);

        assert!(tui.controller.rules().len() > before);
        assert_eq!(visible_open_count(&tui), 0);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn confirmation_modal_confirms_with_y_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before = tui.controller.rules().len();
        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());
        assert!(matches!(
            tui.pending_confirmation.as_ref(),
            Some(PendingAction::BlockVisibleCandidates(_))
        ));

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('y'),
            KeyModifiers::empty(),
        )));
        assert!(tui.controller.rules().len() > before);
        assert_eq!(visible_open_count(&tui), 0);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn confirmation_modal_confirms_with_uppercase_y_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before = tui.controller.rules().len();
        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('Y'),
            KeyModifiers::empty(),
        )));
        assert!(tui.controller.rules().len() > before);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn confirmation_modal_cancels_with_n_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before = tui.controller.rules().len();
        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('n'),
            KeyModifiers::empty(),
        )));
        assert_eq!(before, tui.controller.rules().len());
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn confirmation_modal_cancels_with_esc_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before = tui.controller.rules().len();
        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert_eq!(before, tui.controller.rules().len());
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn help_modal_toggles_with_question_mark() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.help_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(tui.help_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(!tui.help_modal.is_visible());
    }

    #[test]
    fn help_modal_dismisses_with_enter_and_escape() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(tui.help_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Enter,
            KeyModifiers::empty(),
        )));
        assert!(!tui.help_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(tui.help_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!tui.help_modal.is_visible());
    }

    #[test]
    fn safety_popup_toggles_with_s_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.safety_popup.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(tui.safety_popup.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn safety_popup_dismisses_with_escape() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(tui.safety_popup.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn command_palette_opens_with_colon() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.command_palette.is_open());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());
    }

    #[test]
    fn command_palette_opens_with_slash() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.command_palette.is_open());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('/'),
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());
    }

    #[test]
    fn command_palette_closes_with_escape() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!tui.command_palette.is_open());
    }

    #[test]
    fn command_palette_enter_executes_clear_rules_action() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let before = tui.controller.rules().len();
        assert!(before > 0, "demo should start with staged rules");

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        select_palette_action(&mut tui, "clear-rules");
        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Enter,
            KeyModifiers::empty(),
        )));

        assert!(!tui.command_palette.is_open());
        assert!(tui.confirmation_modal.is_visible());
        assert!(matches!(
            tui.pending_confirmation,
            Some(PendingAction::ClearRules(staged_count)) if staged_count == before
        ));
    }

    #[test]
    fn command_palette_enter_with_no_matches_keeps_open() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());

        for c in "qzzq".chars() {
            tui.command_palette.input_char(c);
        }
        assert!(tui.command_palette.filtered.is_empty());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Enter,
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());
    }

    #[test]
    fn confirmation_modal_preempts_help_modal() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());
        assert!(!tui.help_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.confirmation_modal.is_visible());
        assert!(!tui.help_modal.is_visible());
    }

    #[test]
    fn confirmation_modal_preempts_safety_popup() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());
        assert!(!tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.confirmation_modal.is_visible());
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn command_palette_action_matrix_routes_common_actions() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let before_rules = tui.controller.rules().len();
        assert!(before_rules > 0, "demo should start with staged rules");

        execute_palette_action(&mut tui, "plan");
        assert_eq!(tui.selected_tab, 3);

        execute_palette_action(&mut tui, "cycle-risk-filter");
        if tui.controller.candidate_risks().is_empty() {
            assert!(tui.selected_risk_filter.is_none());
        } else {
            assert!(tui.selected_risk_filter.is_some());
        }

        execute_palette_action(&mut tui, "help");
        assert!(tui.help_modal.is_visible());
        tui.help_modal.hide();

        execute_palette_action(&mut tui, "clear-rules");
        assert!(matches!(
            tui.pending_confirmation,
            Some(PendingAction::ClearRules(staged_count)) if staged_count == before_rules
        ));
    }

    #[test]
    fn command_palette_state_actions_update_controller() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before_backend = tui.controller.backend();
        let before_incoming = tui.controller.default_incoming();
        let before_outgoing = tui.controller.default_outgoing();
        let before_logging = tui.controller.logging();

        execute_palette_action(&mut tui, "cycle-backend");
        assert_eq!(tui.controller.backend(), before_backend.next());

        execute_palette_action(&mut tui, "toggle-default-incoming");
        assert_eq!(tui.controller.default_incoming(), before_incoming.next());

        execute_palette_action(&mut tui, "toggle-default-outgoing");
        assert_eq!(tui.controller.default_outgoing(), before_outgoing.next());

        execute_palette_action(&mut tui, "toggle-logging");
        assert_eq!(tui.controller.logging(), !before_logging);

        execute_palette_action(&mut tui, "clear-rules");
        assert!(matches!(
            tui.pending_confirmation,
            Some(PendingAction::ClearRules(_))
        ));
    }

    #[test]
    fn command_palette_prevents_quit_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.command_palette.is_open());
        assert_eq!(tui.command_palette.query, "q");
    }

    #[test]
    fn command_palette_prevents_safety_toggle() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.command_palette.is_open());
        assert_eq!(tui.command_palette.query, "s");
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn quit_shortcuts_exit_when_no_modal_is_open() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.confirmation_modal.is_visible());
        assert!(!tui.command_palette.is_open());
        assert!(!tui.help_modal.is_visible());
        assert!(!tui.safety_popup.is_visible());

        assert!(tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        ))));

        assert!(tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        ))));
    }

    #[test]
    fn confirmation_modal_prevents_quit_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_clear_rules();
        assert!(tui.confirmation_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_some());
    }

    #[test]
    fn help_modal_prevents_quit_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(tui.help_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.help_modal.is_visible());
    }

    #[test]
    fn safety_popup_prevents_quit_key() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn request_clear_rules_enter_confirms_by_default() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let before = tui.controller.rules().len();
        assert!(before > 0, "demo should start with staged rules");

        tui.request_clear_rules();
        match tui.pending_confirmation.as_ref() {
            Some(PendingAction::ClearRules(staged_count)) => {
                assert_eq!(*staged_count, before);
            }
            Some(_) => panic!("expected pending ClearRules action"),
            None => panic!("expected a pending confirmation action"),
        }

        tui.resolve_confirmation(None);
        assert_eq!(tui.controller.rules().len(), 0);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn command_palette_escape_is_layer_priority() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(tui.command_palette.is_open());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.command_palette.is_open());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(should_quit);
    }

    #[test]
    fn confirmation_modal_escape_is_layer_priority() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_clear_rules();
        assert!(tui.confirmation_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(should_quit);
    }

    #[test]
    fn help_modal_escape_is_layer_priority() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(tui.help_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.help_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(should_quit);
    }

    #[test]
    fn safety_popup_escape_is_layer_priority() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Esc,
            KeyModifiers::empty(),
        )));
        assert!(should_quit);
    }

    #[test]
    fn request_block_selected_group_skips_when_fully_blocked() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let staged = tui.controller.block_candidate_group(0);
        assert!(
            staged > 0,
            "selected candidate should belong to a valid group"
        );

        tui.request_block_selected_group();
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn request_block_selected_risk_skips_when_fully_blocked() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let Some(index) = tui.selected_visible_candidate() else {
            panic!("demo should have at least one visible candidate");
        };

        let risk = tui
            .controller
            .candidate_risk(index)
            .expect("candidate should have risk");
        let staged = tui.controller.block_candidate_risk(index);
        assert!(
            staged > 0,
            "selected risk class should block at least one candidate"
        );

        tui.request_block_selected_risk();

        let open_count = tui
            .controller
            .candidates()
            .iter()
            .enumerate()
            .filter(|(candidate_index, candidate)| {
                candidate.risk == risk
                    && tui
                        .controller
                        .candidate_status(*candidate_index)
                        .is_some_and(|status| status != CandidateStatus::Blocked)
            })
            .count();

        assert_eq!(open_count, 0);
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn request_allow_visible_candidates_noop_when_already_allowed() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let visible = tui.visible_candidate_indexes();

        let _ = tui.controller.allow_candidate_indexes(&visible);
        assert!(
            tui.visible_candidate_indexes()
                .into_iter()
                .all(|index| tui.controller.candidate_status(index)
                    == Some(CandidateStatus::Allowed)),
            "every visible candidate should be allowed after pre-staging"
        );

        tui.request_allow_visible_candidates();

        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn request_harden_high_risk_skips_when_no_candidates() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let staged = tui.controller.block_high_risk_candidates();
        assert!(staged > 0, "demo should include high-risk candidates");

        tui.request_harden_high_risk();
        assert!(!tui.confirmation_modal.is_visible());
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn block_priority_queue_action_requests_confirmation() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_block_priority_queue();
        match tui.pending_confirmation.as_ref() {
            Some(PendingAction::BlockPriorityQueue(indexes)) => {
                assert!(!indexes.is_empty());
            }
            Some(_) => panic!("expected pending BlockPriorityQueue action"),
            None => panic!("expected a pending confirmation action"),
        }
        assert!(tui.confirmation_modal.is_visible());
    }

    #[test]
    fn run_palette_action_routes_hardening_to_confirmation() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.run_palette_action("harden-known");
        assert!(matches!(
            tui.pending_confirmation.as_ref(),
            Some(PendingAction::HardenHighRisk)
        ));
    }

    #[test]
    fn run_palette_action_routes_profile_actions_to_confirmation_or_noop() {
        let profile_actions = [
            ("profile-workstation", FirewallProfile::Workstation),
            ("profile-web", FirewallProfile::WebServer),
            ("profile-developer", FirewallProfile::Developer),
            ("profile-lockdown", FirewallProfile::Lockdown),
        ];

        for (action_id, profile) in profile_actions {
            let mut tui = FirewallTui::new(FirewallController::demo());
            let pending_changes = tui.controller.profile_change_preview(profile).len();

            tui.run_palette_action(action_id);

            if pending_changes == 0 {
                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());
            } else {
                assert!(matches!(
                    tui.pending_confirmation.as_ref(),
                    Some(PendingAction::ApplyProfile(actual)) if *actual == profile
                ));

                tui.resolve_confirmation(Some(true));

                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());
            }
        }
    }

    #[test]
    fn run_palette_action_profile_workstation_is_noop_when_already_applied() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        apply_profile_palette_action(
            &mut tui,
            "profile-workstation",
            FirewallProfile::Workstation,
        );
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Workstation)
                .len(),
            0
        );

        apply_profile_palette_action(
            &mut tui,
            "profile-workstation",
            FirewallProfile::Workstation,
        );
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Workstation)
                .len(),
            0
        );
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn run_palette_action_profile_web_is_noop_when_already_applied() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        apply_profile_palette_action(&mut tui, "profile-web", FirewallProfile::WebServer);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::WebServer)
                .len(),
            0
        );

        apply_profile_palette_action(&mut tui, "profile-web", FirewallProfile::WebServer);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::WebServer)
                .len(),
            0
        );
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn run_palette_action_profile_developer_is_noop_when_already_applied() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        apply_profile_palette_action(&mut tui, "profile-developer", FirewallProfile::Developer);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Developer)
                .len(),
            0
        );

        apply_profile_palette_action(&mut tui, "profile-developer", FirewallProfile::Developer);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Developer)
                .len(),
            0
        );
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn run_palette_action_profile_lockdown_is_noop_when_already_applied() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        apply_profile_palette_action(&mut tui, "profile-lockdown", FirewallProfile::Lockdown);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Lockdown)
                .len(),
            0
        );

        apply_profile_palette_action(&mut tui, "profile-lockdown", FirewallProfile::Lockdown);
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Lockdown)
                .len(),
            0
        );
        assert!(tui.pending_confirmation.is_none());
    }

    #[test]
    fn run_palette_action_profile_recommended_is_noop_after_apply() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let recommendation = tui.controller.recommended_profile();

        execute_palette_action(&mut tui, "profile-recommended");
        if tui
            .controller
            .profile_change_preview(recommendation.profile)
            .len()
            == 0
        {
            assert!(tui.pending_confirmation.is_none());
            assert!(!tui.confirmation_modal.is_visible());
        } else {
            assert!(matches!(
                tui.pending_confirmation.as_ref(),
                Some(PendingAction::ApplyRecommendedProfile)
            ));
            tui.resolve_confirmation(Some(true));
            assert!(tui.pending_confirmation.is_none());
            assert!(!tui.confirmation_modal.is_visible());
        }

        let before_changes = tui
            .controller
            .profile_change_preview(recommendation.profile)
            .len();
        execute_palette_action(&mut tui, "profile-recommended");
        assert_eq!(before_changes, 0);
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());
    }

    #[test]
    fn run_palette_action_profile_safe_recommended_is_noop_after_apply_or_unavailable() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let safe_recommendation = tui.controller.safe_recommended_profile();
        match safe_recommendation {
            Some(recommendation) => {
                execute_palette_action(&mut tui, "profile-safe-recommended");
                if tui
                    .controller
                    .profile_change_preview(recommendation.profile)
                    .len()
                    == 0
                {
                    assert!(tui.pending_confirmation.is_none());
                    assert!(!tui.confirmation_modal.is_visible());
                } else {
                    assert!(matches!(
                        tui.pending_confirmation.as_ref(),
                        Some(PendingAction::ApplySafeRecommendedProfile)
                    ));
                    tui.resolve_confirmation(Some(true));
                    assert!(tui.pending_confirmation.is_none());
                    assert!(!tui.confirmation_modal.is_visible());
                }

                let before_changes = tui
                    .controller
                    .profile_change_preview(recommendation.profile)
                    .len();
                execute_palette_action(&mut tui, "profile-safe-recommended");
                assert_eq!(before_changes, 0);
                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());
            }
            None => {
                execute_palette_action(&mut tui, "profile-safe-recommended");
                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());

                execute_palette_action(&mut tui, "profile-safe-recommended");
                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());
            }
        }
    }

    #[test]
    fn run_palette_action_routes_recommended_actions_to_confirmation_or_noop() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let recommendation = tui.controller.recommended_profile();
        let pending_changes = tui
            .controller
            .profile_change_preview(recommendation.profile)
            .len();

        tui.run_palette_action("profile-recommended");
        if pending_changes == 0 {
            assert!(tui.pending_confirmation.is_none());
            assert!(!tui.confirmation_modal.is_visible());
        } else {
            assert!(matches!(
                tui.pending_confirmation.as_ref(),
                Some(PendingAction::ApplyRecommendedProfile)
            ));
            tui.resolve_confirmation(Some(true));
            assert!(tui.pending_confirmation.is_none());
            assert!(!tui.confirmation_modal.is_visible());
        }

        tui = FirewallTui::new(FirewallController::demo());
        match tui.controller.safe_recommended_profile() {
            Some(recommendation) => {
                let pending_changes = tui
                    .controller
                    .profile_change_preview(recommendation.profile)
                    .len();

                tui.run_palette_action("profile-safe-recommended");
                if pending_changes == 0 {
                    assert!(tui.pending_confirmation.is_none());
                    assert!(!tui.confirmation_modal.is_visible());
                } else {
                    assert!(matches!(
                        tui.pending_confirmation.as_ref(),
                        Some(PendingAction::ApplySafeRecommendedProfile)
                    ));
                    tui.resolve_confirmation(Some(true));
                    assert!(tui.pending_confirmation.is_none());
                    assert!(!tui.confirmation_modal.is_visible());
                }
            }
            None => {
                tui.run_palette_action("profile-safe-recommended");
                assert!(tui.pending_confirmation.is_none());
                assert!(!tui.confirmation_modal.is_visible());
            }
        }
    }

    #[test]
    fn run_palette_action_overlay_toggles() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.run_palette_action("help");
        assert!(tui.help_modal.is_visible());

        tui.help_modal.hide();
        tui.run_palette_action("safety");
        assert!(tui.safety_popup.is_visible());
    }

    #[test]
    fn run_palette_action_navigation_commands_are_idempotent_by_action_id() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let tabs = [
            ("dashboard", 0usize),
            ("rules", 1usize),
            ("ports", 2usize),
            ("plan", 3usize),
            ("advisor", 4usize),
        ];

        for (action_id, expected_tab) in tabs {
            tui.run_palette_action(action_id);
            assert_eq!(tui.selected_tab, expected_tab);
            assert!(tui.pending_confirmation.is_none());
            assert!(!tui.confirmation_modal.is_visible());

            let before = ui_snapshot(&tui);
            tui.run_palette_action(action_id);

            let after = ui_snapshot(&tui);
            assert_eq!(tui.selected_tab, expected_tab);
            assert_ui_snapshot_eq(&before, &after);
        }
    }

    #[test]
    fn run_palette_action_clear_rules_is_noop_when_nothing_to_clear() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(
            tui.controller.clear_rules() > 0,
            "demo should have starter rules"
        );
        tui.run_palette_action("clear-rules");
        assert_eq!(tui.controller.rules().len(), 0);
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());

        let before = ui_snapshot(&tui);
        tui.run_palette_action("clear-rules");

        assert_eq!(tui.controller.rules().len(), 0);
        assert_eq!(ui_snapshot(&tui), before);
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());
    }

    #[test]
    fn run_palette_action_safety_idempotent_toggle_path() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let baseline = ui_snapshot(&tui);

        tui.run_palette_action("safety");
        assert!(tui.safety_popup.is_visible());
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.help_modal.is_visible());

        tui.run_palette_action("safety");
        assert!(!tui.safety_popup.is_visible());
        assert_eq!(ui_snapshot(&tui), baseline);

        tui.run_palette_action("safety");
        assert!(tui.safety_popup.is_visible());
    }

    #[test]
    fn run_palette_action_reset_starter_and_undo_restore_previous_rules() {
        let mut tui = FirewallTui::new(FirewallController::demo());
        let starter_rule_count = tui.controller.rules().len();

        execute_palette_action(&mut tui, "clear-rules");
        let clear_rules = tui
            .pending_confirmation
            .as_ref()
            .and_then(|action| match action {
                PendingAction::ClearRules(staged) => Some(*staged),
                _ => None,
            });
        assert!(clear_rules.is_some_and(|count| count > 0));

        tui.resolve_confirmation(Some(true));
        assert_eq!(tui.controller.rules().len(), 0);

        tui.run_palette_action("reset-starter");
        assert!(matches!(
            tui.pending_confirmation.as_ref(),
            Some(PendingAction::ResetStarterPlan)
        ));

        tui.resolve_confirmation(Some(true));
        assert_eq!(tui.controller.rules().len(), starter_rule_count);

        tui.run_palette_action("undo");
        assert_eq!(tui.controller.rules().len(), 0);
    }

    #[test]
    fn handle_event_ignores_resize_events() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before_tab = tui.selected_tab;
        let before_rule = tui.selected_rule;
        let before_candidate = tui.selected_candidate;
        let before_filter = tui.candidate_filter;
        let before_group = tui.selected_group_filter;
        let before_risk = tui.selected_risk_filter;
        let before_focus = tui.focus;
        let before_pending = tui.pending_confirmation.is_some();

        let should_quit = tui.handle_event(Event::Resize(120, 30));

        assert!(!should_quit);
        assert_eq!(tui.selected_tab, before_tab);
        assert_eq!(tui.selected_rule, before_rule);
        assert_eq!(tui.selected_candidate, before_candidate);
        assert_eq!(tui.candidate_filter, before_filter);
        assert_eq!(tui.selected_group_filter, before_group);
        assert_eq!(tui.selected_risk_filter, before_risk);
        assert_eq!(tui.focus, before_focus);
        assert_eq!(tui.pending_confirmation.is_some(), before_pending);
        assert!(!tui.command_palette.is_open());
        assert!(!tui.confirmation_modal.is_visible());
        assert!(!tui.help_modal.is_visible());
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn handle_event_ignores_non_press_key_events() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.select_tab(2);

        let before_tab = tui.selected_tab;
        let before_rule = tui.selected_rule;
        let before_candidate = tui.selected_candidate;
        let before_focus = tui.focus;

        let should_quit = tui.handle_event(Event::Key(KeyEvent {
            code: KeyCode::Char('q'),
            modifiers: KeyModifiers::empty(),
            kind: KeyEventKind::Release,
            state: KeyEventState::empty(),
        }));

        assert!(!should_quit);
        assert_eq!(tui.selected_tab, before_tab);
        assert_eq!(tui.selected_rule, before_rule);
        assert_eq!(tui.selected_candidate, before_candidate);
        assert_eq!(tui.focus, before_focus);
        assert!(!tui.command_palette.is_open());
        assert!(!tui.confirmation_modal.is_visible());
        assert!(!tui.help_modal.is_visible());
        assert!(!tui.safety_popup.is_visible());
    }

    #[test]
    fn handle_event_accepts_key_repeat_events() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert_eq!(tui.selected_tab, 0);

        let should_quit = tui.handle_event(Event::Key(KeyEvent {
            code: KeyCode::Tab,
            modifiers: KeyModifiers::empty(),
            kind: KeyEventKind::Repeat,
            state: KeyEventState::empty(),
        }));

        assert!(!should_quit);
        assert_eq!(tui.selected_tab, 1);
    }

    #[test]
    fn run_palette_action_unknown_action_is_noop_for_state() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let before_tab = tui.selected_tab;
        let before_rule = tui.selected_rule;
        let before_candidate = tui.selected_candidate;
        let before_focus = tui.focus;
        let before_filter = tui.candidate_filter;
        let before_group = tui.selected_group_filter;
        let before_risk = tui.selected_risk_filter;
        let before_rules = tui.controller.rules().len();
        let before_pending = tui.pending_confirmation.is_some();

        tui.run_palette_action("does-not-exist");

        assert_eq!(tui.selected_tab, before_tab);
        assert_eq!(tui.selected_rule, before_rule);
        assert_eq!(tui.selected_candidate, before_candidate);
        assert_eq!(tui.focus, before_focus);
        assert_eq!(tui.candidate_filter, before_filter);
        assert_eq!(tui.selected_group_filter, before_group);
        assert_eq!(tui.selected_risk_filter, before_risk);
        assert_eq!(tui.controller.rules().len(), before_rules);
        assert_eq!(tui.pending_confirmation.is_some(), before_pending);
        assert!(!tui.confirmation_modal.is_visible());
    }

    #[test]
    fn request_clear_rules_is_noop_with_empty_rules() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        let cleared = tui.controller.clear_rules();
        assert!(cleared > 0, "demo should have staged rules to clear first");
        assert_eq!(tui.controller.rules().len(), 0);

        tui.request_clear_rules();
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('c'),
            KeyModifiers::empty(),
        )));
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());
    }

    #[test]
    fn safety_popup_prevents_palette_and_help_keybinds() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        )));
        assert!(tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.command_palette.is_open());
        assert!(tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.help_modal.is_visible());
        assert!(tui.safety_popup.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.safety_popup.is_visible());

        assert!(tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('q'),
            KeyModifiers::empty(),
        ))));
    }

    #[test]
    fn confirmation_modal_blocks_profile_hotkey_without_state_change() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        tui.request_block_visible_candidates();
        assert!(tui.confirmation_modal.is_visible());
        assert!(matches!(
            tui.pending_confirmation.as_ref(),
            Some(PendingAction::BlockVisibleCandidates(_))
        ));

        let before_changes = tui.controller.rules().len();

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('w'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.confirmation_modal.is_visible());
        assert_eq!(tui.controller.rules().len(), before_changes);

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));

        assert!(!should_quit);
        assert!(tui.confirmation_modal.is_visible());
        assert!(!tui.command_palette.is_open());
    }

    #[test]
    fn tab_shortcuts_cycle_tabs_and_jump_by_number() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert_eq!(tui.selected_tab, 0);

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Tab,
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.selected_tab, 1);

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::BackTab,
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.selected_tab, 0);

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('4'),
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.selected_tab, 3);

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('5'),
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.selected_tab, 4);
    }

    #[test]
    fn dashboard_focus_key_cycles_focus_only_on_dashboard() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert_eq!(tui.selected_tab, 0);
        assert_eq!(tui.focus, Focus::Rules);

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('f'),
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.focus, Focus::Ports);

        tui.select_tab(1);
        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('f'),
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.focus, Focus::Rules);
    }

    #[test]
    fn handle_event_direct_profile_keys_are_noops_when_already_applied() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        apply_profile_palette_action(
            &mut tui,
            "profile-workstation",
            FirewallProfile::Workstation,
        );
        assert_eq!(
            tui.controller
                .profile_change_preview(FirewallProfile::Workstation)
                .len(),
            0
        );

        let before_rules = tui.controller.rules().len();
        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('w'),
            KeyModifiers::empty(),
        ))));
        assert_eq!(tui.controller.rules().len(), before_rules);
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('Y'),
            KeyModifiers::empty(),
        ))));
        let recommended = tui.controller.recommended_profile().profile;
        assert_eq!(tui.controller.profile_change_preview(recommended).len(), 0);
        assert!(tui.pending_confirmation.is_none());
        assert!(!tui.confirmation_modal.is_visible());
    }

    #[test]
    fn help_modal_does_not_open_other_overlays_while_visible() {
        let mut tui = FirewallTui::new(FirewallController::demo());

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        ))));
        assert!(tui.help_modal.is_visible());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char(':'),
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.command_palette.is_open());

        let should_quit = tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('?'),
            KeyModifiers::empty(),
        )));
        assert!(!should_quit);
        assert!(!tui.help_modal.is_visible());

        assert!(!tui.handle_event(Event::Key(KeyEvent::new(
            KeyCode::Char('s'),
            KeyModifiers::empty(),
        ))));
        assert!(tui.safety_popup.is_visible());
    }
}
