use crate::io::IoHandler;
use crate::monitoring::{MetricsDrillPanel, MonitoringSection, MonitoringState};
use crate::screens::{NavigationState, Screen};
use crate::storage::Cache;
use crate::ui::agent_builder::AgentBuilderState;
use crate::ui::run_manager::RunManagerState;
use crate::ui::sentinel_config::SentinelConfigState;
use crate::ui::shield_dashboard::ShieldDashboardState;
use crate::ui::settings::SettingsState;
use crate::websocket::WebSocketClient;
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ratatui::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};

/// `HH:MM:SS` for Connection Portal activity log (UTC clock; avoids pulling in `chrono`).
fn wall_clock_hms() -> String {
    let secs = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    let h = (secs / 3600) % 24;
    let m = (secs / 60) % 60;
    let s = secs % 60;
    format!("{h:02}:{m:02}:{s:02}")
}

mod render_scheduler;
pub use render_scheduler::{RenderScheduler, RunMode};

/// Upper bound for the operations log (`VecDeque`). Keeps TUI RAM stable under heavy traffic.
/// See `docs/REDUCING-MEMORY-AND-DISK-USAGE.md` §8.
const MAX_OPERATION_LOG_LINES: usize = 250;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Message {
    Log { message: String, ts: String },
    Posture { status: String },
    Metrics { cpu: f64, mem: f64 },
    Network { status: String },
    Capabilities { items: Vec<String> },
}

#[derive(Debug, Clone, PartialEq)]
pub enum AppMode {
    Boot,
    Main,
}

#[derive(Debug, Clone, PartialEq)]
pub enum OperationMode {
    Local,     // No Gateway connection
    Connected, // Connected to Gateway
}

impl OperationMode {
    pub fn as_str(&self) -> &str {
        match self {
            OperationMode::Local => "LOCAL",
            OperationMode::Connected => "CONNECTED",
        }
    }
}

#[cfg(test)]
mod portal_monitoring_tests {
    use super::*;

    #[test]
    fn metric_history_totals_contract_keys() {
        let totals = serde_json::json!({
            "httpRequests": 10,
            "httpRequestErrors": 1,
            "queueJobsWaiting": 2,
            "queueJobsActive": 3,
            "queueJobsFailed": 4,
            "runsActive": 5,
            "sseActive": 6
        });
        let entry = metric_history_entry_from_totals(
            "2026-01-01T00:00:00.000Z".to_string(),
            totals.as_object().expect("totals object"),
        );

        assert_eq!(entry.http_requests, 10);
        assert_eq!(entry.http_errors, 1);
        assert_eq!(entry.queue_waiting, 2);
        assert_eq!(entry.queue_active, 3);
        assert_eq!(entry.queue_failed, 4);
        assert_eq!(entry.runs_active, 5);
        assert_eq!(entry.sse_active, 6);
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum PortalField {
    GatewayUrl,
    Username,
}

#[derive(Debug, Clone, PartialEq)]
pub enum GatewayType {
    FourRunrServer, // External 4Runr cloud server
    LocalBundle,    // Local Gateway bundle (included with Full OS)
    CustomUrl,      // User's own Gateway server
}

impl GatewayType {
    pub fn as_str(&self) -> &str {
        match self {
            GatewayType::FourRunrServer => "4Runr Server",
            GatewayType::LocalBundle => "Local Bundle",
            GatewayType::CustomUrl => "Custom URL",
        }
    }

    pub fn default_url(&self) -> &str {
        match self {
            GatewayType::FourRunrServer => "https://gateway.4runr.com",
            GatewayType::LocalBundle => "http://localhost:3001",
            GatewayType::CustomUrl => "",
        }
    }
}

// Activity Log for Connection Portal
#[derive(Debug, Clone)]
pub struct ActivityLogEntry {
    pub timestamp: String, // HH:MM:SS format
    pub level: LogLevel,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq)]
pub enum LogLevel {
    Info,
    Success,
    Warning,
    Error,
}

#[derive(Debug, Clone)]
pub struct ConnectionPortalState {
    pub gateway_url: String,
    pub username: String,
    pub focused_field: PortalField,
    pub connecting: bool,
    /// When `connecting` became true (for elapsed timer in the portal UI).
    pub connecting_started: Option<Instant>,
    pub connection_success: bool,
    pub error: Option<String>,
    pub last_successful_url: Option<String>,
    pub activity_log: Vec<ActivityLogEntry>,
}

impl Default for ConnectionPortalState {
    fn default() -> Self {
        Self {
            gateway_url: "http://localhost:3001".to_string(),
            username: String::new(),
            focused_field: PortalField::GatewayUrl,
            connecting: false,
            connecting_started: None,
            connection_success: false,
            error: None,
            last_successful_url: None,
            activity_log: Vec::new(),
        }
    }
}

impl ConnectionPortalState {
    pub fn reset(&mut self) {
        self.gateway_url = self
            .last_successful_url
            .clone()
            .unwrap_or_else(|| "http://localhost:3001".to_string());
        self.username = String::new();
        self.focused_field = PortalField::GatewayUrl;
        self.connecting = false;
        self.connecting_started = None;
        self.connection_success = false;
        self.error = None;
        self.activity_log.clear();
    }

    pub fn begin_connecting(&mut self) {
        self.connecting = true;
        self.connecting_started = Some(Instant::now());
    }

    pub fn finish_connecting(&mut self) {
        self.connecting = false;
        self.connecting_started = None;
    }

    pub fn toggle_field(&mut self) {
        self.focused_field = match self.focused_field {
            PortalField::GatewayUrl => PortalField::Username,
            PortalField::Username => PortalField::GatewayUrl,
        };
    }

    pub fn add_log_entry(&mut self, timestamp: String, level: LogLevel, message: String) {
        self.activity_log.push(ActivityLogEntry {
            timestamp,
            level,
            message,
        });

        // Keep last N lines so the portal can show a full connect trace on small terminals
        const MAX_ACTIVITY: usize = 80;
        if self.activity_log.len() > MAX_ACTIVITY {
            self.activity_log.remove(0);
        }
    }
}

/// Gateway traffic snapshot (Prometheus `/metrics` via CLI WebSocket handler).
#[derive(Debug, Clone, Serialize)]
pub struct MetricHistoryEntry {
    pub timestamp: String,
    pub http_requests: u64,
    pub http_errors: u64,
    pub queue_waiting: u64,
    pub queue_active: u64,
    pub queue_failed: u64,
    pub runs_active: u64,
    pub sse_active: u64,
}

/// Phase 5 contract with `packages/os-cli/src/gateway-observability.ts` `totals`.
/// If CLI keys change, update this mapping and `metric_history_totals_contract_keys`.
pub fn metric_history_entry_from_totals(
    timestamp: String,
    totals: &serde_json::Map<String, serde_json::Value>,
) -> MetricHistoryEntry {
    fn read_u64(totals: &serde_json::Map<String, serde_json::Value>, key: &str) -> u64 {
        totals
            .get(key)
            .and_then(|v| v.as_u64().or_else(|| v.as_f64().map(|f| f.max(0.0) as u64)))
            .unwrap_or(0)
    }

    MetricHistoryEntry {
        timestamp,
        http_requests: read_u64(totals, "httpRequests"),
        http_errors: read_u64(totals, "httpRequestErrors"),
        queue_waiting: read_u64(totals, "queueJobsWaiting"),
        queue_active: read_u64(totals, "queueJobsActive"),
        queue_failed: read_u64(totals, "queueJobsFailed"),
        runs_active: read_u64(totals, "runsActive"),
        sse_active: read_u64(totals, "sseActive"),
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct DependencyAlertEntry {
    pub timestamp: String,
    pub previous: String,
    pub current: String,
}

#[derive(Debug, Clone)]
pub struct PortalMonitoringState {
    pub loading: bool,
    pub error: Option<String>,
    pub last_updated: Option<String>,
    pub content_lines: Vec<String>,
    pub scroll_offset: usize,
    /// Width-based clip for section summaries; updated each render (Phase 1).
    pub summary_clip_width: usize,
    /// Body inner height from last render (for scroll bounds).
    pub viewport_lines: usize,
    /// Auto-refresh timer
    pub last_refresh: Option<Instant>,
    pub auto_refresh_interval: Duration,
    pub auto_refresh_enabled: bool,
    /// Previous HTTP total (from `totals.httpRequests`) for a per-refresh "live" delta line.
    pub last_http_for_delta: Option<u64>,
    /// Wall clock at last successful observability poll (for rate estimate).
    pub last_instant_for_delta: Option<Instant>,
    /// Phase 2: per-section lines from `monitoring.refresh` / `monitoring.logs` (merged over snapshot).
    pub section_overrides: HashMap<MonitoringSection, Vec<String>>,
    /// Which section is waiting on `monitoring.refresh`.
    pub section_refresh_loading: Option<MonitoringSection>,
    pub logs_fetch_loading: bool,
    /// Phase 3: drill-down inside Metrics (`→` / `←`); which prom-client slice is shown.
    pub metrics_drill: Option<MetricsDrillPanel>,
    pub metrics_drill_lines: Vec<String>,
    pub metrics_drill_loading: bool,
    /// Last N HTTP request totals from full snapshots (for ASCII sparkline).
    pub http_total_sparkline: VecDeque<u64>,
    /// Phase 5: bounded sample history (720 samples; wall-clock span depends on refresh cadence).
    pub metric_history: VecDeque<MetricHistoryEntry>,
    pub dependency_alerts: VecDeque<DependencyAlertEntry>,
    pub last_dependency_status: Option<String>,
    pub trend_view: bool,
    pub last_export_path: Option<String>,
    pub help_visible: bool,
}

impl Default for PortalMonitoringState {
    fn default() -> Self {
        Self {
            loading: false,
            error: None,
            last_updated: None,
            content_lines: Vec::new(),
            scroll_offset: 0,
            summary_clip_width: 80,
            viewport_lines: 18,
            last_refresh: None,
            auto_refresh_interval: Duration::from_secs(5),
            auto_refresh_enabled: false,
            last_http_for_delta: None,
            last_instant_for_delta: None,
            section_overrides: HashMap::new(),
            section_refresh_loading: None,
            logs_fetch_loading: false,
            metrics_drill: None,
            metrics_drill_lines: Vec::new(),
            metrics_drill_loading: false,
            http_total_sparkline: VecDeque::new(),
            metric_history: VecDeque::new(),
            dependency_alerts: VecDeque::new(),
            last_dependency_status: None,
            trend_view: false,
            last_export_path: None,
            help_visible: false,
        }
    }
}

/// Section-based monitoring UI state (Phase 1).
#[derive(Debug, Clone)]
pub struct AdvancedMonitoringState {
    pub monitoring_state: MonitoringState,
    /// Optional mirror of the connected Gateway URL at construction time. **Live URL for UI is always
    /// `AppState.gateway_url`** (and `MonitoringState::gateway_url` is synced from there in Portal Monitoring render).
    #[allow(dead_code)]
    pub gateway_url: Option<String>,
}

impl Default for AdvancedMonitoringState {
    fn default() -> Self {
        Self {
            monitoring_state: MonitoringState::default(),
            gateway_url: None,
        }
    }
}

impl AdvancedMonitoringState {
    pub fn new(gateway_url: Option<String>) -> Self {
        Self {
            monitoring_state: MonitoringState::new(gateway_url.clone()),
            gateway_url,
        }
    }
}

#[derive(Debug, Clone)]
pub struct AppState {
    // Navigation state (NEW - replaces simple mode)
    pub navigation: NavigationState,

    // App mode (DEPRECATED - keeping for backward compatibility during transition)
    pub mode: AppMode,

    // Boot state
    pub boot_progress: u16, // 0-100
    pub boot_lines: VecDeque<String>,
    pub boot_done: bool,
    pub boot_started_at: Instant,
    // Connection state
    pub connected: bool,
    #[allow(dead_code)]
    pub gateway_url: Option<String>,

    // Real component status
    #[allow(dead_code)]
    pub gateway_healthy: bool,
    /// Last `gateway.health` hint for 4Runr DB (from `/health` → `fourRunrDb`).
    pub gateway_db_summary: Option<String>,
    pub posture_status: String,
    pub shield_mode: String,           // "off", "monitor", "enforce"
    pub shield_detectors: Vec<String>, // ["pii", "injection", "hallucination"]
    pub shield_blocks_total: u64,
    pub shield_masks_total: u64,
    pub sentinel_state: String,        // "idle", "watching", "triggered"
    pub sentinel_active_runs: usize,

    // Real metrics
    pub total_runs: u64,
    #[allow(dead_code)]
    pub active_sse_connections: u64,
    #[allow(dead_code)]
    pub idempotency_store_size: u64,

    // System resources
    pub cpu: f64,
    pub mem: f64,
    pub network_status: String,

    // Real logs
    pub logs: VecDeque<String>,

    // Capabilities
    pub capabilities: Vec<String>,

    // UI state
    pub command_input: String,
    pub command_focused: bool,
    pub log_scroll: usize,

    // Animation state
    pub tick: u64,
    pub spinner_frame: usize,
    pub uptime_secs: u64, // Updated from real clock in main loop, not from tick()

    // Perf overlay
    pub perf_overlay: bool,
    pub render_count: u64,
    pub last_render_time: std::time::Instant,
    pub render_durations: VecDeque<u64>, // ms
    pub render_scheduled_count: u64,
    pub log_write_count: u64,

    // Screen-specific state
    pub agent_builder: AgentBuilderState,
    pub run_manager: RunManagerState,
    pub sentinel_config: SentinelConfigState,
    pub shield_dashboard: ShieldDashboardState,
    pub settings: SettingsState,
    pub agent_list: AgentListState,
    pub connection_portal: ConnectionPortalState,
    pub setup_portal: SetupPortalState,
    pub portal_monitoring: PortalMonitoringState,
    /// Last successful observability pull (for auto-refresh interval).
    pub portal_observability_last_poll: Option<Instant>,

    /// Section selection / expand state for Portal Monitoring (Phase 1).
    pub advanced_monitoring: AdvancedMonitoringState,

    // Command tracking for response handling (Step 5.1)
    pub pending_agent_create_id: Option<String>,
    pub pending_agent_list_id: Option<String>,
    pub pending_agent_delete_id: Option<String>,
    pub pending_gateway_connect_id: Option<String>,
    pub pending_gateway_health_id: Option<String>, // Phase 2.1: Track health checks
    pub pending_gateway_observability_id: Option<String>,
    pub pending_monitoring_refresh_id: Option<String>,
    pub pending_monitoring_logs_id: Option<String>,
    pub pending_monitoring_drill_id: Option<String>,
    pub pending_setup_detect_id: Option<String>, // Track setup.detect command

    // Step 6: Gateway runs
    pub pending_run_list_id: Option<String>,
    pub pending_run_list_silent: bool,
    pub pending_run_get_id: Option<String>,
    pub pending_run_cancel_id: Option<String>,
    pub pending_run_quick_id: Option<String>,

    pub pending_sentinel_load_id: Option<String>,
    pub pending_sentinel_apply_id: Option<String>,
    pub pending_shield_load_id: Option<String>,

    // Deletion confirmation (Step 5.5)
    pub agent_delete_requested: bool,

    // Mode tracking (Step 7)
    pub operation_mode: OperationMode,

    // Local cache
    pub cache: Option<Cache>,
    pub cache_loaded: bool,
}

impl Default for AppState {
    fn default() -> Self {
        Self {
            navigation: NavigationState::new(),
            mode: AppMode::Boot,
            boot_progress: 0,
            boot_lines: VecDeque::new(),
            boot_done: false,
            boot_started_at: Instant::now(),
            connected: false,
            gateway_url: None,
            gateway_healthy: false,
            gateway_db_summary: None,
            posture_status: "Demo Mode".to_string(),
            shield_mode: "enforce".to_string(),
            shield_detectors: vec!["pii".into(), "injection".into(), "hallucination".into()],
            shield_blocks_total: 0,
            shield_masks_total: 0,
            sentinel_state: "idle".to_string(),
            sentinel_active_runs: 0,
            total_runs: 0,
            active_sse_connections: 0,
            idempotency_store_size: 0,
            cpu: 0.15,
            mem: 0.25,
            network_status: "Not Connected".to_string(),
            logs: VecDeque::from([
                "[SYSTEM] 4Runr AI Agent OS initialized".to_string(),
                "[MODE] Starting in LOCAL mode (offline)".to_string(),
                "[SHIELD] Safety layer active (enforce mode)".to_string(),
                "[SHIELD] Detectors: PII, Injection, Hallucination".to_string(),
                "[SENTINEL] Real-time monitoring ready".to_string(),
                "[HELP] Type 'connect portal' to connect to Gateway server".to_string(),
                "[HELP] Type 'help' for available commands".to_string(),
            ]),
            capabilities: vec![
                "text-analysis".to_string(),
                "code-review".to_string(),
                "data-extraction".to_string(),
            ],
            command_input: String::new(),
            command_focused: false,
            log_scroll: 0,
            tick: 0,
            spinner_frame: 0,
            uptime_secs: 0,
            perf_overlay: false,
            render_count: 0,
            last_render_time: std::time::Instant::now(),
            render_durations: VecDeque::with_capacity(60),
            render_scheduled_count: 0,
            log_write_count: 0,
            agent_builder: AgentBuilderState::default(),
            run_manager: RunManagerState::default(),
            sentinel_config: SentinelConfigState::default(),
            shield_dashboard: ShieldDashboardState::default(),
            settings: SettingsState::default(),
            agent_list: AgentListState::default(),
            connection_portal: ConnectionPortalState::default(),
            setup_portal: SetupPortalState::default(),
            portal_monitoring: PortalMonitoringState::default(),
            portal_observability_last_poll: None,
            advanced_monitoring: AdvancedMonitoringState::default(),
            pending_agent_create_id: None,
            pending_agent_list_id: None,
            pending_agent_delete_id: None,
            pending_gateway_connect_id: None,
            pending_gateway_health_id: None, // Phase 2.1: Initialize health check tracking
            pending_gateway_observability_id: None,
            pending_monitoring_refresh_id: None,
            pending_monitoring_logs_id: None,
            pending_monitoring_drill_id: None,
            pending_setup_detect_id: None,
            pending_run_list_id: None,
            pending_run_list_silent: false,
            pending_run_get_id: None,
            pending_run_cancel_id: None,
            pending_run_quick_id: None,
            pending_sentinel_load_id: None,
            pending_sentinel_apply_id: None,
            pending_shield_load_id: None,
            agent_delete_requested: false,
            operation_mode: OperationMode::Local,
            cache: Cache::new().ok(),
            cache_loaded: false,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum GatewayOption {
    LocalBundle,
    CloudServer,
    CustomUrl,
}

impl GatewayOption {
    pub fn as_str(&self) -> &str {
        match self {
            GatewayOption::LocalBundle => "Local Bundle",
            GatewayOption::CloudServer => "4Runr Server",
            GatewayOption::CustomUrl => "Custom URL",
        }
    }

    pub fn default_url(&self) -> &str {
        match self {
            GatewayOption::LocalBundle => "http://localhost:3001",
            GatewayOption::CloudServer => "https://api.4runr.com",
            GatewayOption::CustomUrl => "",
        }
    }
}

#[derive(Debug, Clone)]
pub struct SetupPortalState {
    pub selected_option: GatewayOption,
    pub detecting: bool,
    /// When detection started (for timeout)
    pub detecting_since: Option<std::time::Instant>,
    pub detection_result: Option<DetectionResult>,
    pub error: Option<String>,
}

#[derive(Debug, Clone)]
pub struct DetectionResult {
    pub local_bundle: BundleStatus,
    pub cloud_server: CloudStatus,
}

#[derive(Debug, Clone)]
pub struct BundleStatus {
    pub available: bool,
    pub running: bool,
    pub path: Option<String>,
}

#[derive(Debug, Clone)]
pub struct CloudStatus {
    pub available: bool,
    pub status: String,
}

impl Default for SetupPortalState {
    fn default() -> Self {
        Self {
            selected_option: GatewayOption::LocalBundle,
            detecting: false,
            detecting_since: None,
            detection_result: None,
            error: None,
        }
    }
}

impl SetupPortalState {
    /// Re-open Setup without wiping last detection (so the screen still looks “warm” while a refresh runs).
    pub fn soft_open(&mut self) {
        self.error = None;
    }

    /// Cycle to next/previous option
    /// direction: 1 for down/next, -1 for up/previous
    pub fn cycle_option(&mut self, direction: i8) {
        use GatewayOption::*;
        self.selected_option = match (&self.selected_option, direction) {
            // Forward (Down key, Scroll Down)
            (LocalBundle, 1) => CloudServer,
            (CloudServer, 1) => CustomUrl,
            (CustomUrl, 1) => LocalBundle, // Wrap to top
            // Backward (Up key, Scroll Up)
            (LocalBundle, -1) => CustomUrl, // Wrap to bottom
            (CloudServer, -1) => LocalBundle,
            (CustomUrl, -1) => CloudServer,
            _ => return, // No change for invalid direction
        };
    }

    /// Clear detection state and errors (full reset; prefer [`soft_open`](Self::soft_open) when reopening Setup).
    #[allow(dead_code)]
    pub fn reset(&mut self) {
        self.detecting = false;
        self.detecting_since = None;
        self.detection_result = None;
        self.error = None;
    }
}

#[derive(Debug, Clone)]
pub struct AgentListState {
    pub agents: Vec<AgentInfo>,
    pub selected_index: usize,
    pub detail_view: Option<usize>, // None = list view, Some(index) = detail popup
}

impl Default for AgentListState {
    fn default() -> Self {
        Self {
            agents: Vec::new(),
            selected_index: 0,
            detail_view: None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct AgentInfo {
    pub name: String,
    pub description: Option<String>,
    pub model: String,
    pub provider: String,
    pub system_prompt: Option<String>,
    pub temperature: Option<f32>,
    pub max_tokens: Option<u32>,
    pub tools: Vec<String>,
}

pub struct App {
    pub(crate) state: AppState,
    render_scheduler: RenderScheduler,
    pub(crate) input_debounce: Option<Instant>,
    pub(crate) input_debounce_duration: Duration,
}

impl App {
    pub fn new() -> Self {
        let render_scheduler = RenderScheduler::new();
        let run_mode = render_scheduler.run_mode();

        // Input debounce: browser 10ms, local 5ms (minimal delay for smooth typing)
        let input_debounce_duration = match run_mode {
            RunMode::Browser => Duration::from_millis(10),
            RunMode::Local => Duration::from_millis(5),
        };

        let mut state = AppState::default();
        Self::load_cached_data(&mut state);

        Self {
            state,
            render_scheduler,
            input_debounce: None,
            input_debounce_duration,
        }
    }

    /// True when the session still has a Gateway URL / connected mode (portal may show linked UI).
    pub fn gateway_session_linked(&self) -> bool {
        self.state.connection_portal.connection_success
            || (matches!(self.state.operation_mode, OperationMode::Connected)
                && self.state.gateway_url.is_some())
    }

    fn request_gateway_health_check(&mut self, ws_client: Option<&WebSocketClient>) {
        if let Some(ws) = ws_client {
            if let Ok(id) = ws.send_command("gateway.health", None) {
                self.state.pending_gateway_health_id = Some(id);
                self.add_log("[GATEWAY] Re-verifying link (/health + /ready)...".to_string());
            }
        }
    }

    fn request_gateway_disconnect(&mut self, ws_client: Option<&WebSocketClient>) {
        if let Some(ws) = ws_client {
            match ws.send_command("gateway.disconnect", None) {
                Ok(id) => {
                    let short_id = &id[id.len().saturating_sub(8)..];
                    self.state
                        .logs
                        .push_back(format!("[GATEWAY] Disconnecting (id: {})", short_id));
                }
                Err(e) => {
                    self.state
                        .logs
                        .push_back(format!("[ERROR] Failed to disconnect: {}", e));
                }
            }
        } else {
            self.state
                .logs
                .push_back("[ERROR] WebSocket not connected".into());
        }
    }

    /// Ask CLI to pull Gateway `GET /metrics` and return a condensed snapshot for Portal Monitoring.
    ///
    /// **Intentional command split:** the TUI uses this `gateway.observability` path for the
    /// **Overview** row (full health + Prometheus snapshot in one response). Per-section refresh
    /// uses `begin_monitoring_refresh_request` → `monitoring.refresh` instead. The CLI also maps
    /// `monitoring.refresh` with `section: "overview"` to the same handler for non-TUI callers.
    pub fn begin_portal_observability_request(&mut self, ws: &WebSocketClient) {
        if self.state.pending_gateway_observability_id.is_some() {
            return;
        }
        if self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        self.state.portal_monitoring.loading = true;
        self.state.portal_monitoring.error = None;
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let data = serde_json::json!({ "nonce": nonce });
        match ws.send_command("gateway.observability", Some(data)) {
            Ok(id) => {
                self.state.pending_gateway_observability_id = Some(id);
            }
            Err(e) => {
                self.state.portal_monitoring.loading = false;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("portal_obs_send_err");
            }
        }
    }

    /// Fetch run list from Gateway (`run.list`). `silent` = background poll (no loading spinner).
    pub fn begin_run_list_request(&mut self, ws: &WebSocketClient, silent: bool) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if self.state.pending_run_list_id.is_some() {
            return;
        }
        if !silent && self.state.run_manager.loading {
            return;
        }
        let data = serde_json::json!({ "limit": 50 });
        match ws.send_command("run.list", Some(data)) {
            Ok(id) => {
                self.state.pending_run_list_id = Some(id);
                self.state.pending_run_list_silent = silent;
                if !silent {
                    self.state.run_manager.loading = true;
                }
            }
            Err(e) => {
                self.add_log(format!("[RUN] run.list failed: {}", e));
            }
        }
    }

    /// Refresh selected run detail (`run.get`) during live polling.
    pub fn begin_run_get_request(&mut self, ws: &WebSocketClient, run_id: &str) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if self.state.pending_run_get_id.is_some() {
            return;
        }
        let data = serde_json::json!({ "runId": run_id });
        if let Ok(id) = ws.send_command("run.get", Some(data)) {
            self.state.pending_run_get_id = Some(id);
        }
    }

    pub fn open_shield_dashboard(&mut self, ws: Option<&WebSocketClient>) {
        self.push_overlay(Screen::ShieldDashboard);
        self.state
            .logs
            .push_back("[NAV] Opening Shield (production safety layer)...".into());
        if self.state.operation_mode == OperationMode::Connected {
            if let Some(ws) = ws {
                self.begin_shield_load_request(ws, false);
            }
        } else {
            self.state.shield_dashboard.loading = false;
            self.state.shield_dashboard.error = Some(
                "Connect to Gateway first (connect portal).".to_string(),
            );
        }
        self.request_immediate_render("open_shield_dashboard");
    }

    pub fn begin_shield_load_request(&mut self, ws: &WebSocketClient, force: bool) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if !force && self.state.pending_shield_load_id.is_some() {
            return;
        }
        if force {
            self.state.pending_shield_load_id = None;
        }
        self.state.shield_dashboard.mark_loading();
        self.state.shield_dashboard.error = None;
        if let Ok(id) = ws.send_command("shield.load", None) {
            self.state.pending_shield_load_id = Some(id);
        } else {
            self.state.shield_dashboard.fail_loading(
                "Failed to send shield.load to CLI.".to_string(),
            );
        }
    }

    pub fn open_run_from_shield_block(&mut self, ws: Option<&WebSocketClient>, run_id: &str) {
        self.push_overlay(Screen::RunManager);
        self.state.run_manager.detail_run_id = Some(run_id.to_string());
        self.add_log(format!(
            "[SHIELD] Opening blocked run {} in Run Manager",
            &run_id[run_id.len().saturating_sub(8)..]
        ));
        if let Some(ws) = ws {
            self.begin_run_list_request(ws, false);
            self.begin_run_get_request(ws, run_id);
        }
        self.request_immediate_render("shield_open_run");
    }

    pub fn open_sentinel_config(&mut self, ws: Option<&WebSocketClient>) {
        self.state.pending_sentinel_load_id = None;
        self.state.pending_sentinel_apply_id = None;
        self.push_overlay(Screen::SentinelConfig);
        self.state
            .logs
            .push_back("[NAV] Opening Sentinel Configuration...".into());
        if self.state.operation_mode == OperationMode::Connected {
            if let Some(ws) = ws {
                self.begin_sentinel_load_request(ws);
            }
        } else {
            self.state.sentinel_config.error = Some(
                "Connect to Gateway first (connect portal).".to_string(),
            );
        }
        self.request_immediate_render("open_sentinel_config");
    }

    pub fn begin_sentinel_load_request(&mut self, ws: &WebSocketClient) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if self.state.pending_sentinel_load_id.is_some() {
            return;
        }
        self.state.sentinel_config.loading = true;
        self.state.sentinel_config.error = None;
        self.state.sentinel_config.status_message = None;
        if let Ok(id) = ws.send_command("sentinel.load", None) {
            self.state.pending_sentinel_load_id = Some(id);
        } else {
            self.state.sentinel_config.loading = false;
        }
    }

    pub fn begin_sentinel_apply_request(&mut self, ws: &WebSocketClient, template_key: &str) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if self.state.pending_sentinel_load_id.is_some()
            || self.state.pending_sentinel_apply_id.is_some()
        {
            return;
        }
        self.state.sentinel_config.applying = true;
        self.state.sentinel_config.error = None;
        self.state.sentinel_config.status_message = None;
        let data = serde_json::json!({ "template": template_key });
        if let Ok(id) = ws.send_command("sentinel.apply", Some(data)) {
            self.state.pending_sentinel_apply_id = Some(id);
        } else {
            self.state.sentinel_config.applying = false;
        }
    }

    pub fn begin_sentinel_apply_custom_request(&mut self, ws: &WebSocketClient, draft: &crate::ui::sentinel_config::SentinelCurrentConfig) {
        if self.state.operation_mode != OperationMode::Connected {
            return;
        }
        if self.state.pending_sentinel_load_id.is_some()
            || self.state.pending_sentinel_apply_id.is_some()
        {
            return;
        }
        self.state.sentinel_config.applying = true;
        self.state.sentinel_config.error = None;
        self.state.sentinel_config.status_message = None;
        let data = serde_json::json!({
            "enabled": draft.enabled,
            "runIdleMs": draft.idle_ms,
            "runMaxDurationMs": draft.duration_ms,
            "runMaxTokens": draft.max_tokens,
            "runMaxCost": draft.max_cost,
            "loopWindow": draft.loop_window,
            "loopMax": draft.loop_max,
        });
        if let Ok(id) = ws.send_command("sentinel.applyCustom", Some(data)) {
            self.state.pending_sentinel_apply_id = Some(id);
        } else {
            self.state.sentinel_config.applying = false;
        }
    }

    /// Phase 2: refresh one Portal Monitoring section via CLI (`monitoring.refresh` → Gateway APIs).
    pub fn begin_monitoring_refresh_request(
        &mut self,
        ws: &WebSocketClient,
        section: MonitoringSection,
    ) {
        if self.state.pending_gateway_observability_id.is_some()
            || self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let slug = section.cli_slug();
        let data = serde_json::json!({ "section": slug, "nonce": nonce });
        match ws.send_command("monitoring.refresh", Some(data)) {
            Ok(id) => {
                self.state.pending_monitoring_refresh_id = Some(id);
                self.state.portal_monitoring.section_refresh_loading = Some(section);
                self.state.portal_monitoring.error = None;
            }
            Err(e) => {
                self.state.portal_monitoring.section_refresh_loading = None;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("monitoring_refresh_send_err");
            }
        }
    }

    /// Phase 2: fetch Gateway log buffer (`monitoring.logs`).
    pub fn begin_monitoring_logs_request(&mut self, ws: &WebSocketClient) {
        if self.state.pending_gateway_observability_id.is_some()
            || self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let data = serde_json::json!({ "limit": 100u32, "nonce": nonce });
        match ws.send_command("monitoring.logs", Some(data)) {
            Ok(id) => {
                self.state.pending_monitoring_logs_id = Some(id);
                self.state.portal_monitoring.logs_fetch_loading = true;
                self.state.portal_monitoring.error = None;
            }
            Err(e) => {
                self.state.portal_monitoring.logs_fetch_loading = false;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("monitoring_logs_send_err");
            }
        }
    }

    /// Phase 3: Gateway drill-down (`monitoring.drill`) for Metrics sub-panels (HTTP / Runs / Queue / SSE).
    pub fn begin_monitoring_drill_request(
        &mut self,
        ws: &WebSocketClient,
        panel: MetricsDrillPanel,
    ) {
        if self.state.pending_gateway_observability_id.is_some()
            || self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let target = panel.cli_target();
        let data = serde_json::json!({ "target": target, "nonce": nonce });
        match ws.send_command("monitoring.drill", Some(data)) {
            Ok(id) => {
                self.state.pending_monitoring_drill_id = Some(id);
                self.state.portal_monitoring.metrics_drill = Some(panel);
                self.state.portal_monitoring.metrics_drill_loading = true;
                self.state.portal_monitoring.metrics_drill_lines.clear();
                self.state.portal_monitoring.error = None;
            }
            Err(e) => {
                self.state.portal_monitoring.metrics_drill_loading = false;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("monitoring_drill_send_err");
            }
        }
    }

    /// Phase 3: dependency pools + queue counts (`dependencies/detail`).
    pub fn begin_dependencies_detail_drill(&mut self, ws: &WebSocketClient) {
        if self.state.pending_gateway_observability_id.is_some()
            || self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let data = serde_json::json!({ "target": "dependencies_detail", "nonce": nonce });
        match ws.send_command("monitoring.drill", Some(data)) {
            Ok(id) => {
                self.state.pending_monitoring_drill_id = Some(id);
                self.state.portal_monitoring.section_refresh_loading =
                    Some(MonitoringSection::Dependencies);
                self.state.portal_monitoring.error = None;
            }
            Err(e) => {
                self.state.portal_monitoring.section_refresh_loading = None;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("deps_drill_send_err");
            }
        }
    }

    /// Phase 4: run local CLI/host diagnostics for the System section.
    pub fn begin_system_diagnostics_request(&mut self, ws: &WebSocketClient) {
        if self.state.pending_gateway_observability_id.is_some()
            || self.state.pending_monitoring_refresh_id.is_some()
            || self.state.pending_monitoring_logs_id.is_some()
            || self.state.pending_monitoring_drill_id.is_some()
        {
            return;
        }
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis())
            .unwrap_or(0);
        let data = serde_json::json!({ "target": "system_diagnostics", "nonce": nonce });
        match ws.send_command("monitoring.drill", Some(data)) {
            Ok(id) => {
                self.state.pending_monitoring_drill_id = Some(id);
                self.state.portal_monitoring.section_refresh_loading =
                    Some(MonitoringSection::System);
                self.state.portal_monitoring.error = None;
            }
            Err(e) => {
                self.state.portal_monitoring.section_refresh_loading = None;
                self.state.portal_monitoring.error = Some(format!("{}", e));
                self.request_immediate_render("system_diagnostics_send_err");
            }
        }
    }

    pub fn export_portal_monitoring_snapshot(&mut self) -> anyhow::Result<String> {
        let mut overrides = serde_json::Map::new();
        for (section, lines) in &self.state.portal_monitoring.section_overrides {
            overrides.insert(section.cli_slug().to_string(), serde_json::json!(lines));
        }
        let payload = serde_json::json!({
            "exportedAt": wall_clock_hms(),
            "gatewayUrl": self.state.gateway_url.clone(),
            "lastUpdated": self.state.portal_monitoring.last_updated.clone(),
            "contentLines": self.state.portal_monitoring.content_lines.clone(),
            "sectionOverrides": overrides,
            "metricHistory": self.state.portal_monitoring.metric_history.clone(),
            "dependencyAlerts": self.state.portal_monitoring.dependency_alerts.clone(),
            "trendView": self.state.portal_monitoring.trend_view,
        });
        let unix = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);
        let mut path = std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir());
        path.push(format!("4runr-monitoring-export-{unix}.json"));
        let body = serde_json::to_string_pretty(&payload)?;
        std::fs::write(&path, body)?;
        let display = path.display().to_string();
        self.state.portal_monitoring.last_export_path = Some(display.clone());
        Ok(display)
    }

    pub fn scroll_portal_monitoring_by(&mut self, delta: isize) {
        use crate::ui::portal_monitoring::portal_monitoring_scroll_max;
        let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
        let sw = self.state.portal_monitoring.summary_clip_width.max(24);
        let max_scroll = portal_monitoring_scroll_max(&self.state, sw, inner_h);
        let current = self.state.portal_monitoring.scroll_offset;
        self.state.portal_monitoring.scroll_offset = if delta < 0 {
            current.saturating_sub(delta.unsigned_abs())
        } else {
            current.saturating_add(delta as usize).min(max_scroll)
        };
        self.request_immediate_render("portal_obs_scroll");
    }

    fn cancel_portal_monitoring_requests(&mut self) {
        self.state.pending_gateway_observability_id = None;
        self.state.pending_monitoring_refresh_id = None;
        self.state.pending_monitoring_logs_id = None;
        self.state.pending_monitoring_drill_id = None;
        self.state.portal_monitoring.loading = false;
        self.state.portal_monitoring.section_refresh_loading = None;
        self.state.portal_monitoring.logs_fetch_loading = false;
        self.state.portal_monitoring.metrics_drill_loading = false;
    }

    /// CLI ↔ TUI WebSocket lost: drop Gateway link UI and leave Portal Monitoring / Connection Portal so the session does not appear still "connected".
    pub fn on_cli_backend_disconnect(&mut self) {
        use crate::screens::Screen;
        self.state.operation_mode = OperationMode::Local;
        self.state.gateway_url = None;
        self.state.connected = false;
        self.state.gateway_healthy = false;
        self.state.gateway_db_summary = None;
        self.state.pending_gateway_connect_id = None;
        self.state.pending_gateway_health_id = None;
        self.state.pending_gateway_observability_id = None;
        self.state.pending_monitoring_refresh_id = None;
        self.state.pending_monitoring_logs_id = None;
        self.state.pending_monitoring_drill_id = None;
        self.state.portal_observability_last_poll = None;
        self.state.portal_monitoring = PortalMonitoringState::default();
        self.state.advanced_monitoring = AdvancedMonitoringState::default();
        self.state.connection_portal.finish_connecting();
        self.state.connection_portal.connection_success = false;
        self.state.connection_portal.connecting = false;
        self.state.run_manager.clear_runs();

        let screen = self.state.navigation.current_screen();
        if matches!(
            screen,
            &Screen::PortalMonitoring | &Screen::ConnectionPortal
        ) {
            self.state.navigation.navigate_to_base(Screen::Main);
            self.request_immediate_render("cli_backend_disconnect_nav_main");
        } else {
            self.request_immediate_render("cli_backend_disconnect");
        }
    }

    /// Load cached data on startup
    fn load_cached_data(state: &mut AppState) {
        if let Some(cache) = &state.cache {
            // Load cached agents
            let agents = cache.get_agents();
            if !agents.is_empty() {
                state.capabilities = agents.iter().map(|a| a.name.clone()).collect();
                state.cache_loaded = true;
            }

            // Load cached system status
            if let Some(status) = cache.get_system_status() {
                state.network_status = if status.connected {
                    "Connected (cached)".to_string()
                } else {
                    "Disconnected (cached)".to_string()
                };
                state.posture_status = format!("{} (cached)", status.posture);
            }
        }
    }

    pub fn request_render(&mut self, reason: &str) -> bool {
        self.render_scheduler.request_render(reason)
    }

    /// Request immediate render (bypass throttling) - for critical input events
    pub fn request_immediate_render(&mut self, reason: &str) -> bool {
        self.render_scheduler.request_immediate_render(reason)
    }

    pub fn should_render(&mut self) -> bool {
        self.render_scheduler.should_render()
    }

    pub fn record_render(&mut self, duration_ms: u64) {
        self.state.render_count += 1;
        self.state.render_durations.push_back(duration_ms);
        if self.state.render_durations.len() > 60 {
            self.state.render_durations.pop_front();
        }
        self.state.last_render_time = Instant::now();
    }

    pub fn run_mode(&self) -> RunMode {
        self.render_scheduler.run_mode()
    }

    pub fn render_scheduler_stats(&self) -> (u64, u64) {
        (
            self.render_scheduler.render_scheduled_count(),
            self.render_scheduler.min_render_interval_ms(),
        )
    }

    /// IMPORTANT: Only call tick() when poll() times out (NO keyboard input)!
    /// Otherwise animations will flash weirdly when typing.
    ///
    /// NOTE: Uptime is now tracked via Instant::now() in main loop,
    /// so we don't increment it here anymore.
    pub fn tick(&mut self) {
        self.state.tick = self.state.tick.wrapping_add(1);

        // Update spinner frame (for "Processing..." indicator only)
        // Cycle through 8 braille spinner frames
        self.state.spinner_frame = (self.state.spinner_frame + 1) % 8;

        // Boot timeline: update progress every ~150-250ms
        if self.state.mode == AppMode::Boot && !self.state.boot_done {
            let elapsed = self.state.boot_started_at.elapsed();
            let boot_steps = vec![
                (Duration::from_millis(200), "Initializing gateway…"),
                (Duration::from_millis(500), "Loading policies…"),
                (Duration::from_millis(800), "Agent registry online…"),
                (Duration::from_millis(1100), "Telemetry connected…"),
                (Duration::from_millis(1400), "System ready."),
            ];

            let mut current_progress = 0;
            let mut step_idx = 0;

            for (i, (delay, msg)) in boot_steps.iter().enumerate() {
                if elapsed >= *delay {
                    step_idx = i + 1;
                    current_progress = ((i + 1) * 100 / boot_steps.len()) as u16;

                    // Add line if not already added
                    if self.state.boot_lines.len() <= i {
                        self.state.boot_lines.push_back(msg.to_string());
                        self.request_render("boot_line");
                    }
                }
            }

            self.state.boot_progress = current_progress.min(100);

            // Mark boot done after last step
            if step_idx >= boot_steps.len() && self.state.boot_progress >= 100 {
                self.state.boot_done = true;
                self.request_render("boot_done");
            }
        }

        // NOTE: pulse_frame is NOT updated here - we use static dots for status indicators
        // to prevent flashing when typing. Only use animated pulse for non-status elements.
    }

    pub fn handle_input(
        &mut self,
        key: KeyEvent,
        _io: &mut IoHandler,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // === EXIT SHORTCUTS (always checked first) ===
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') | KeyCode::Char('d') => {
                    return Ok(true); // Exit
                }
                _ => return Ok(false), // Ignore other Ctrl combos
            }
        }

        if key.code == KeyCode::F(10) {
            return Ok(true); // Exit
        }

        // === BOOT MODE: Any key to continue ===
        if self.state.mode == AppMode::Boot && self.state.boot_done {
            // Any key press switches to main dashboard
            self.navigate_to(Screen::Main);
            return Ok(false);
        }

        // In boot mode, ignore other input until boot is done
        if self.state.mode == AppMode::Boot {
            return Ok(false);
        }

        // F12 - Toggle perf overlay
        if key.code == KeyCode::F(12) {
            self.state.perf_overlay = !self.state.perf_overlay;
            self.request_render("perf_toggle");
            return Ok(false);
        }

        // === AGENT BUILDER INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::AgentBuilder {
            return self.handle_agent_builder_input(key, ws_client);
        }

        // === RUN MANAGER INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::RunManager {
            return self.handle_run_manager_input(key, ws_client);
        }

        // === SENTINEL CONFIG INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::SentinelConfig {
            return self.handle_sentinel_config_input(key, ws_client);
        }

        // === SHIELD DASHBOARD INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::ShieldDashboard {
            return self.handle_shield_dashboard_input(key, ws_client);
        }

        // === SETTINGS INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::Settings {
            return self.handle_settings_input(key);
        }

        // === CONNECTION PORTAL INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::ConnectionPortal {
            return self.handle_connection_portal_input(key, ws_client);
        }

        // === PORTAL MONITORING (Gateway traffic view) ===
        if self.state.navigation.current_screen() == &Screen::PortalMonitoring {
            return self.handle_portal_monitoring_input(key, ws_client);
        }

        // === SETUP PORTAL INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::SetupPortal {
            return self.handle_setup_portal_input(key, ws_client);
        }

        // === AGENT LIST INPUT HANDLING ===
        if self.state.navigation.current_screen() == &Screen::AgentList {
            return self.handle_agent_list_input(key, ws_client);
        }

        // === MAIN INPUT HANDLING ===
        match key.code {
            // F2 - Open Setup Portal (Gateway options) from main screen
            KeyCode::F(2) => {
                self.state.navigation.navigate_to_base(Screen::SetupPortal);
                self.state.setup_portal.soft_open();
                if let Some(ws) = ws_client {
                    self.state.setup_portal.detecting = true;
                    self.state.setup_portal.detecting_since = Some(std::time::Instant::now());
                    if let Ok(id) = ws.send_command("setup.detect", None) {
                        self.state.pending_setup_detect_id = Some(id);
                        self.add_log("[SETUP] Detecting Gateway options...".to_string());
                    } else {
                        self.state.setup_portal.detecting = false;
                        self.state.setup_portal.detecting_since = None;
                        self.state.setup_portal.error =
                            Some("Failed to start detection".to_string());
                    }
                } else {
                    self.state.setup_portal.error = Some(
                        "CLI backend not connected — auto-detect skipped. Choose an option and press Enter; set GATEWAY_URL in the shell if needed."
                            .into(),
                    );
                }
                self.state
                    .logs
                    .push_back("[NAV] Opening Setup Portal (F2)...".into());
                self.request_immediate_render("open_setup_portal");
            }
            // Typing - ALL characters go to command input (immediate render for responsiveness)
            KeyCode::Char(c) => {
                // CRITICAL: Always set focused when typing to show cursor
                self.state.command_focused = true;
                self.state.command_input.push(c);
                // OPTIMIZATION: Immediate render for instant typing feedback
                self.request_immediate_render("typing_input");
            }

            // Submit command
            KeyCode::Enter => {
                if !self.state.command_input.is_empty() {
                    let cmd: String = self.state.command_input.drain(..).collect();
                    self.state.command_focused = false;
                    self.input_debounce = None;

                    match cmd.to_lowercase().as_str() {
                        "quit" | "exit" => return Ok(true),
                        "clear" => self.state.logs.clear(),
                        "help" => {
                            // Display help in operations log - clean and organized
                            // NOTE: VecDeque displays newest items first, so we push in REVERSE order
                            // (Last item pushed appears at top of log)

                            // Push Keyboard Shortcuts FIRST (will appear at BOTTOM)
                            self.state.logs.push_back(
                                "  Settings:      ↑/↓=Navigate, Space=Toggle, Enter=Save"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  Run Manager:   ↑/↓=Navigate, F=Filter, S=Sort, R=Refresh"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  Agent Builder: Enter=Next, Backspace=Back, ESC=Cancel"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "────────────────────────────────────────────────────────────────"
                                    .to_string(),
                            );
                            self.state
                                .logs
                                .push_back("⌨️  KEYBOARD SHORTCUTS".to_string());
                            self.state.logs.push_back("".to_string());

                            // WebSocket Commands
                            self.state.logs.push_back(
                                "  tool.list          - List available tools".to_string(),
                            );
                            self.state.logs.push_back(
                                "  run.list           - List runs (requires gateway)".to_string(),
                            );
                            self.state
                                .logs
                                .push_back("  system.status      - Get system status".to_string());
                            self.state.logs.push_back(
                                "  agent.delete       - Delete agent (data: {name})".to_string(),
                            );
                            self.state.logs.push_back(
                                "  agent.create       - Create new agent (use Agent Builder)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  agent.get          - Get agent details (data: {name})"
                                    .to_string(),
                            );
                            self.state
                                .logs
                                .push_back("  agent.list         - List all agents".to_string());
                            self.state.logs.push_back(
                                "────────────────────────────────────────────────────────────────"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "🌐 WEBSOCKET COMMANDS (requires connection)".to_string(),
                            );
                            self.state.logs.push_back("".to_string());

                            // Local Commands
                            self.state.logs.push_back(
                                "  :perf              - Show performance stats".to_string(),
                            );
                            self.state
                                .logs
                                .push_back("  help               - Show this help".to_string());
                            self.state.logs.push_back(
                                "  clear              - Clear operations log".to_string(),
                            );
                            self.state
                                .logs
                                .push_back("  quit, exit         - Exit application".to_string());
                            self.state.logs.push_back(
                                "────────────────────────────────────────────────────────────────"
                                    .to_string(),
                            );
                            self.state.logs.push_back("💻 LOCAL COMMANDS".to_string());
                            self.state.logs.push_back("".to_string());

                            // Navigation Commands
                            self.state.logs.push_back("  disconnect         - Disconnect from Gateway (return to LOCAL mode)".to_string());
                            self.state.logs.push_back("  portal monitoring  - Gateway /metrics traffic (R refresh, ↑↓ scroll)".to_string());
                            self.state.logs.push_back("  connect portal     - Open Connection Portal (connect to Gateway)".to_string());
                            self.state.logs.push_back(
                                "  a, agents          - Open Agent List (view, select, delete)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  config, settings   - Open Settings (mode, AI provider)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  runs               - Open Run Manager (list, filter, sort)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  sentinel           - Configure Sentinel policies (templates)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  shield             - Shield safety dashboard (live metrics + enforcement)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "  build              - Open Agent Builder (6-step wizard)"
                                    .to_string(),
                            );
                            self.state.logs.push_back(
                                "────────────────────────────────────────────────────────────────"
                                    .to_string(),
                            );
                            self.state
                                .logs
                                .push_back("🧭 NAVIGATION COMMANDS".to_string());
                            self.state.logs.push_back("".to_string());

                            // Push Header LAST (will appear at TOP)
                            self.state.logs.push_back(
                                "════════════════════════════════════════════════════════════════"
                                    .to_string(),
                            );
                            self.state
                                .logs
                                .push_back("📖 4RUNR AI AGENT OS - COMMAND REFERENCE".to_string());
                            self.state.logs.push_back(
                                "════════════════════════════════════════════════════════════════"
                                    .to_string(),
                            );

                            self.request_render("help_command");
                        }
                        ":perf" => {
                            // Perf self-check command
                            let rps = if !self.state.render_durations.is_empty() {
                                let avg_ms: f64 = self.state.render_durations.iter().sum::<u64>()
                                    as f64
                                    / self.state.render_durations.len() as f64;
                                if avg_ms > 0.0 {
                                    (1000.0 / avg_ms) as u64
                                } else {
                                    0
                                }
                            } else {
                                0
                            };

                            let mode_str = match self.run_mode() {
                                RunMode::Browser => "browser",
                                RunMode::Local => "local",
                            };

                            let (scheduled_count, interval_ms) = self.render_scheduler_stats();

                            self.state.logs.push_back(format!(
                                "[PERF] Mode: {} | RPS: {} | Render interval: {}ms | Scheduled: {}",
                                mode_str, rps, interval_ms, scheduled_count
                            ));
                        }
                        // Navigation commands
                        "build" | "build new" | "agent new" => {
                            self.push_overlay(Screen::AgentBuilder);
                            self.state
                                .logs
                                .push_back("[NAV] Opening Agent Builder...".into());
                        }
                        "runs" | "run list" | "run manager" => {
                            self.push_overlay(Screen::RunManager);
                            self.state
                                .logs
                                .push_back("[NAV] Opening Run Manager...".into());
                            if self.state.operation_mode == OperationMode::Connected {
                                if let Some(ws) = ws_client {
                                    self.begin_run_list_request(ws, false);
                                }
                            } else {
                                self.add_log(
                                    "[RUN] Connect to Gateway (connect portal) to load live runs."
                                        .to_string(),
                                );
                            }
                        }
                        "sentinel" | "sentinel config" | "sentinel policies" => {
                            self.open_sentinel_config(ws_client);
                        }
                        "shield" | "shield status" | "shield dashboard" => {
                            self.open_shield_dashboard(ws_client);
                        }
                        "config" | "settings" => {
                            self.push_overlay(Screen::Settings);
                            self.state
                                .logs
                                .push_back("[NAV] Opening Settings...".into());
                        }
                        "a" | "agents" | "agent list" => {
                            self.push_overlay(Screen::AgentList);
                            self.state
                                .logs
                                .push_back("[NAV] Opening Agent List...".into());
                            // Request agent list from backend
                            if let Some(ws) = ws_client {
                                if let Ok(list_id) = ws.send_command("agent.list", None) {
                                    self.state.pending_agent_list_id = Some(list_id);
                                }
                            }
                        }
                        "portal monitoring" | "portal monitor" => {
                            if self.state.operation_mode == OperationMode::Connected {
                                self.state
                                    .navigation
                                    .navigate_to_base(Screen::PortalMonitoring);
                                self.state.advanced_monitoring =
                                    AdvancedMonitoringState::new(self.state.gateway_url.clone());
                                self.state.portal_monitoring.scroll_offset = 0;
                                self.state
                                    .logs
                                    .push_back("[NAV] Opening Portal Monitoring...".into());
                                if let Some(ws) = ws_client {
                                    self.begin_portal_observability_request(ws);
                                }
                                self.request_immediate_render("open_portal_monitoring");
                            } else {
                                self.state.logs.push_back(
                                    "[NAV] Connect to a Gateway first (connect portal), then open Portal Monitoring."
                                        .into(),
                                );
                                self.request_render("portal_monitoring_need_gateway");
                            }
                        }
                        "connect portal" | "portal" => {
                            let relink = self.gateway_session_linked();
                            self.state.connection_portal.reset();
                            self.state
                                .navigation
                                .navigate_to_base(Screen::ConnectionPortal);
                            self.state
                                .logs
                                .push_back("[NAV] Opening Connection Portal...".into());
                            if relink {
                                self.request_gateway_health_check(ws_client);
                            }
                            self.request_immediate_render("open_connection_portal");
                        }
                        "setup" | "setup gateway" | "setup portal" => {
                            // Navigate to portal as base screen (standalone, not overlay)
                            self.state.navigation.navigate_to_base(Screen::SetupPortal);
                            self.state.setup_portal.soft_open();

                            // Auto-detect Gateway options when portal opens
                            if let Some(ws) = ws_client {
                                self.state.setup_portal.detecting = true;
                                self.state.setup_portal.detecting_since =
                                    Some(std::time::Instant::now());
                                if let Ok(id) = ws.send_command("setup.detect", None) {
                                    self.state.pending_setup_detect_id = Some(id);
                                    self.add_log(
                                        "[SETUP] Detecting Gateway options...".to_string(),
                                    );
                                } else {
                                    self.state.setup_portal.detecting = false;
                                    self.state.setup_portal.detecting_since = None;
                                    self.state.setup_portal.error =
                                        Some("Failed to start detection".to_string());
                                }
                            } else {
                                self.state.setup_portal.error = Some(
                                    "CLI backend not connected — auto-detect skipped. Choose an option and press Enter."
                                        .into(),
                                );
                            }

                            self.state
                                .logs
                                .push_back("[NAV] Opening Setup Portal...".into());
                            self.request_immediate_render("open_setup_portal");
                        }
                        "disconnect" => {
                            // Disconnect from Gateway
                            if let Some(ws) = ws_client {
                                match ws.send_command("gateway.disconnect", None) {
                                    Ok(id) => {
                                        let short_id = &id[id.len().saturating_sub(8)..];
                                        self.state.logs.push_back(format!(
                                            "[GATEWAY] Disconnecting (id: {})",
                                            short_id
                                        ));
                                        // Mode will be updated when response is received
                                    }
                                    Err(e) => {
                                        self.state.logs.push_back(format!(
                                            "[ERROR] Failed to disconnect: {}",
                                            e
                                        ));
                                    }
                                }
                            } else {
                                self.state
                                    .logs
                                    .push_back("[ERROR] WebSocket not connected".into());
                            }
                        }
                        _ => {
                            // Try to parse as WebSocket command
                            if let Some(ws) = ws_client {
                                // Check if command looks like a WebSocket command (contains '.')
                                if cmd.contains('.') {
                                    // Send as WebSocket command
                                    match ws.send_command(&cmd, None) {
                                        Ok(id) => {
                                            let short_id = &id[id.len().saturating_sub(8)..];
                                            self.state
                                                .logs
                                                .push_back(format!("> {} [{}]", cmd, short_id));
                                        }
                                        Err(e) => {
                                            self.state.logs.push_back(format!(
                                                "[ERROR] Failed to send '{}': {}",
                                                cmd, e
                                            ));
                                        }
                                    }
                                } else {
                                    // Unknown local command - provide helpful guidance
                                    self.state
                                        .logs
                                        .push_back(format!("> {} (unknown command)", cmd));
                                    self.state.logs.push_back("[HELP] Try: help, build, runs, config, agent.list, system.status".into());
                                }
                            } else {
                                // No WebSocket connection - guide to local commands
                                self.state
                                    .logs
                                    .push_back(format!("> {} (WebSocket not connected)", cmd));
                                self.state.logs.push_back("[WARN] Backend not running. Start backend or use local commands.".into());
                                self.state.logs.push_back(
                                    "[HELP] Local: help, build, runs, config, clear".into(),
                                );
                            }
                        }
                    }
                    self.request_render("command_submit");
                }
            }

            // Delete character
            KeyCode::Backspace => {
                self.state.command_input.pop();
                if self.state.command_input.is_empty() {
                    self.state.command_focused = false;
                }
                // OPTIMIZATION: Immediate render for instant feedback
                self.request_immediate_render("backspace_input");
            }

            // Clear input or close overlay/popup
            KeyCode::Esc => {
                // Priority: close popup > close overlay > clear input
                if self.state.navigation.has_popup() {
                    self.pop_popup();
                } else if self.state.navigation.has_overlay() {
                    self.pop_overlay();
                } else if !self.state.command_input.is_empty() {
                    self.state.command_input.clear();
                    self.state.command_focused = false;
                    self.input_debounce = None;
                    self.request_render("clear_input");
                }
            }

            // === SCROLL: Arrow keys (only when not typing) ===
            // Calculate max valid scroll based on visible height
            KeyCode::Up if self.state.command_input.is_empty() => {
                let visible_height = 15; // Approximate visible log height (adjust based on actual panel size)
                let total_logs = self.state.logs.len();
                let max_scroll = total_logs.saturating_sub(visible_height.max(1));

                // Clamp scroll to valid range [0, max_scroll]
                self.state.log_scroll = (self.state.log_scroll + 1).min(max_scroll);
                self.request_render("scroll_up");
            }

            KeyCode::Down if self.state.command_input.is_empty() => {
                self.state.log_scroll = self.state.log_scroll.saturating_sub(1);
                self.request_render("scroll_down");
            }

            KeyCode::PageUp if self.state.command_input.is_empty() => {
                let visible_height = 15;
                let total_logs = self.state.logs.len();
                let max_scroll = total_logs.saturating_sub(visible_height.max(1));

                self.state.log_scroll = (self.state.log_scroll + 10).min(max_scroll);
                self.request_render("scroll_page_up");
            }

            KeyCode::PageDown if self.state.command_input.is_empty() => {
                self.state.log_scroll = self.state.log_scroll.saturating_sub(10);
                self.request_render("scroll_page_down");
            }

            _ => {}
        }

        Ok(false)
    }

    pub fn update(&mut self, message: Message) {
        match message {
            Message::Log { message, ts } => {
                let log_line = format!("[{}] {}", ts, message);
                self.state.logs.push_back(log_line);
                while self.state.logs.len() > MAX_OPERATION_LOG_LINES {
                    self.state.logs.pop_front();
                }
            }
            Message::Posture { status } => {
                self.state.posture_status = status;
            }
            Message::Metrics { cpu, mem } => {
                self.state.cpu = cpu;
                self.state.mem = mem;
            }
            Message::Network { status } => {
                self.state.network_status = status;
            }
            Message::Capabilities { items } => {
                self.state.capabilities = items;
            }
        }
    }

    pub fn render(&mut self, f: &mut Frame) {
        // Use new navigation system - render based on current screen
        let current = self.state.navigation.current_screen();

        // DEBUG: Log render decisions
        #[cfg(debug_assertions)]
        {
            eprintln!("[RENDER] Current screen: {:?}", current);
            eprintln!(
                "[RENDER] Base screen: {:?}",
                self.state.navigation.base_screen
            );
            eprintln!(
                "[RENDER] Overlay stack: {:?}",
                self.state.navigation.overlay_stack
            );
        }

        match current {
            Screen::Boot => {
                use crate::ui::boot;
                boot::render_boot(f, f.size(), &self.state);
            }
            Screen::Main => {
                #[cfg(debug_assertions)]
                eprintln!("[RENDER] Rendering Main screen (layout)");
                use crate::ui::layout;
                layout::render(f, &self.state);
            }
            Screen::AgentBuilder => {
                use crate::ui::agent_builder;
                agent_builder::render(f, &self.state);
            }
            Screen::RunManager => {
                use crate::ui::run_manager;
                run_manager::render(f, &self.state);
            }
            Screen::SentinelConfig => {
                use crate::ui::sentinel_config;
                sentinel_config::render(f, &self.state);
            }
            Screen::ShieldDashboard => {
                use crate::ui::shield_dashboard;
                shield_dashboard::render(f, &self.state);
            }
            Screen::Settings => {
                use crate::ui::settings;
                settings::render(f, &self.state);
            }
            Screen::AgentList => {
                use crate::ui::agent_list;
                agent_list::render(f, &self.state);
            }
            Screen::ConnectionPortal => {
                #[cfg(debug_assertions)]
                eprintln!("[RENDER] Rendering Connection Portal (standalone)");
                // Render Connection Portal as standalone screen (not overlay)
                // This eliminates double-rendering and improves performance
                use crate::ui::connection_portal;
                connection_portal::render(f, &self.state);
            }
            Screen::SetupPortal => {
                #[cfg(debug_assertions)]
                eprintln!("[RENDER] Rendering Setup Portal (standalone)");
                // Render Setup Portal as standalone screen (not overlay)
                // This eliminates double-rendering and improves performance
                use crate::ui::setup_portal;
                setup_portal::render(f, &mut self.state);
            }
            Screen::PortalMonitoring => {
                use crate::ui::portal_monitoring;
                portal_monitoring::render(f, &mut self.state);
            }
            Screen::Confirmation { message, action } => {
                // TODO: Implement confirmation popup
                // For now, just render the base screen
                use crate::ui::layout;
                layout::render(f, &self.state);
                let _ = (message, action); // Suppress warnings
            }
            Screen::Alert { message } => {
                // TODO: Implement alert popup
                // For now, just render the base screen
                use crate::ui::layout;
                layout::render(f, &self.state);
                let _ = message; // Suppress warning
            }
        }
    }

    /// Add a log message
    pub fn add_log(&mut self, message: String) {
        self.state.logs.push_back(message);
        while self.state.logs.len() > MAX_OPERATION_LOG_LINES {
            self.state.logs.pop_front();
        }
        self.state.log_write_count += 1;
    }

    // ============================================================
    // NAVIGATION METHODS (NEW - Step 4.1)
    // ============================================================

    /// Navigate to a base screen (Boot or Main)
    pub fn navigate_to(&mut self, screen: Screen) {
        if screen.is_base() {
            self.state.navigation.navigate_to_base(screen.clone());

            // Update legacy mode field for backward compatibility
            self.state.mode = match screen {
                Screen::Boot => AppMode::Boot,
                Screen::Main => AppMode::Main,
                _ => self.state.mode.clone(),
            };

            self.request_render("navigate_to");
        }
    }

    /// Push an overlay screen (Agent Builder, Run Manager, Settings)
    pub fn push_overlay(&mut self, screen: Screen) {
        if screen.is_overlay() {
            self.state.navigation.push_overlay(screen);
            self.request_render("push_overlay");
        }
    }

    /// Pop the current overlay and return to previous screen
    pub fn pop_overlay(&mut self) -> Option<Screen> {
        let result = self.state.navigation.pop_overlay();
        if result.is_some() {
            self.request_render("pop_overlay");
        }
        result
    }

    /// Push a popup screen (Confirmation, Alert, Help)
    pub fn push_popup(&mut self, screen: Screen) {
        if screen.is_popup() {
            self.state.navigation.push_popup(screen);
            self.request_render("push_popup");
        }
    }

    /// Pop the current popup
    pub fn pop_popup(&mut self) -> Option<Screen> {
        let result = self.state.navigation.pop_popup();
        if result.is_some() {
            self.request_render("pop_popup");
        }
        result
    }

    /// Close all overlays and popups (return to base screen)
    #[allow(dead_code)]
    pub fn close_all_overlays(&mut self) {
        self.state.navigation.close_all();
        self.request_render("close_all");
    }

    /// Get the currently visible screen
    #[allow(dead_code)]
    pub fn current_screen(&self) -> &Screen {
        self.state.navigation.current_screen()
    }

    /// Check if we're on the base screen (no overlays/popups)
    #[allow(dead_code)]
    pub fn is_on_base_screen(&self) -> bool {
        self.state.navigation.is_on_base()
    }

    // ============================================================
    // AGENT BUILDER INPUT HANDLING (Step 4.5)
    // ============================================================

    /// Handle input when Agent Builder screen is active
    fn handle_agent_builder_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // Ctrl+C/Q to exit
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => {}
            }
        }

        match key.code {
            // Character input - route to appropriate field
            KeyCode::Char(c) => {
                self.handle_agent_builder_char_input(c);
                self.request_render("agent_builder_input");
            }
            // Backspace - delete character from current field
            KeyCode::Backspace => {
                self.handle_agent_builder_backspace();
                self.request_render("agent_builder_backspace");
            }
            // Left Arrow - go back to previous step
            KeyCode::Left => {
                if self.state.agent_builder.current_step > 1 {
                    self.state.agent_builder.prev_step();
                    self.add_log("Agent Builder: Moved to previous step".to_string());
                    self.request_render("agent_builder_prev_step");
                }
            }
            // Right Arrow - move to next step (same as Enter)
            KeyCode::Right => {
                if self.state.agent_builder.current_step == 6 {
                    // Final step - create agent
                    self.create_agent_from_builder(ws_client);
                } else {
                    // Move to next step
                    if self.state.agent_builder.next_step() {
                        self.request_render("agent_builder_next");
                    } else {
                        // Validation failed - errors are in state
                        self.request_render("agent_builder_validation");
                    }
                }
            }
            // Tab - move to next field (within step)
            KeyCode::Tab => {
                let shift = key.modifiers.contains(KeyModifiers::SHIFT);
                if shift {
                    self.state.agent_builder.prev_field();
                } else {
                    self.state.agent_builder.next_field();
                }
                self.request_render("agent_builder_field_nav");
            }
            // Enter - move to next step
            KeyCode::Enter => {
                if self.state.agent_builder.current_step == 6 {
                    // Final step - create agent
                    self.create_agent_from_builder(ws_client);
                } else {
                    // Move to next step
                    if self.state.agent_builder.next_step() {
                        self.request_render("agent_builder_next");
                    } else {
                        // Validation failed - errors are in state
                        self.request_render("agent_builder_validation");
                    }
                }
            }
            // Esc - cancel and return to main
            KeyCode::Esc => {
                self.pop_overlay();
                // Reset wizard state
                self.state.agent_builder = AgentBuilderState::default();
            }
            // Arrow keys - navigate fields
            KeyCode::Up => {
                self.state.agent_builder.prev_field();
                self.request_render("agent_builder_field_nav");
            }
            KeyCode::Down => {
                self.state.agent_builder.next_field();
                self.request_render("agent_builder_field_nav");
            }
            _ => {}
        }

        Ok(false)
    }

    /// Handle character input for Agent Builder fields
    fn handle_agent_builder_char_input(&mut self, c: char) {
        let step = self.state.agent_builder.current_step;
        let field = self.state.agent_builder.focused_field;

        match step {
            1 => {
                // Step 1: Basic Info
                match field {
                    0 => self.state.agent_builder.name.push(c),
                    1 => self.state.agent_builder.description.push(c),
                    _ => {}
                }
            }
            2 => {
                // Step 2: Model Selection
                match field {
                    0 => self.state.agent_builder.provider.push(c),
                    1 => self.state.agent_builder.model.push(c),
                    2 => self.state.agent_builder.local_provider.push(c),
                    3 => self.state.agent_builder.local_model.push(c),
                    4 => self.state.agent_builder.local_url.push(c),
                    _ => {}
                }
            }
            3 => {
                // Step 3: System Prompt
                self.state.agent_builder.system_prompt.push(c);
            }
            4 => {
                // Step 4: Parameters (numeric input)
                match field {
                    0 => {
                        // Temperature - allow digits and decimal point
                        if c.is_ascii_digit() || c == '.' {
                            let mut temp_str = format!("{}", self.state.agent_builder.temperature);
                            temp_str.push(c);
                            if let Ok(val) = temp_str.parse::<f32>() {
                                self.state.agent_builder.temperature = val;
                            }
                        }
                    }
                    1 => {
                        // Max tokens - digits only
                        if c.is_ascii_digit() {
                            let mut tokens_str = format!("{}", self.state.agent_builder.max_tokens);
                            tokens_str.push(c);
                            if let Ok(val) = tokens_str.parse::<u32>() {
                                self.state.agent_builder.max_tokens = val;
                            }
                        }
                    }
                    _ => {}
                }
            }
            5 => {
                // Step 5: Tools - toggle with space
                if c == ' ' {
                    let tools = &self.state.agent_builder.available_tools;
                    if field < tools.len() {
                        let tool = tools[field].clone();
                        if self.state.agent_builder.selected_tools.contains(&tool) {
                            self.state
                                .agent_builder
                                .selected_tools
                                .retain(|t| t != &tool);
                        } else {
                            self.state.agent_builder.selected_tools.push(tool);
                        }
                    }
                }
            }
            _ => {}
        }
    }

    /// Handle backspace for Agent Builder fields
    fn handle_agent_builder_backspace(&mut self) {
        let step = self.state.agent_builder.current_step;
        let field = self.state.agent_builder.focused_field;

        match step {
            1 => match field {
                0 => {
                    self.state.agent_builder.name.pop();
                }
                1 => {
                    self.state.agent_builder.description.pop();
                }
                _ => {}
            },
            2 => match field {
                0 => {
                    self.state.agent_builder.provider.pop();
                }
                1 => {
                    self.state.agent_builder.model.pop();
                }
                2 => {
                    self.state.agent_builder.local_provider.pop();
                }
                3 => {
                    self.state.agent_builder.local_model.pop();
                }
                4 => {
                    self.state.agent_builder.local_url.pop();
                }
                _ => {}
            },
            3 => {
                self.state.agent_builder.system_prompt.pop();
            }
            4 => {
                // Parameters - backspace on numeric fields
                match field {
                    0 => {
                        let mut temp_str = format!("{}", self.state.agent_builder.temperature);
                        temp_str.pop();
                        if let Ok(val) = temp_str.parse::<f32>() {
                            self.state.agent_builder.temperature = val;
                        } else if temp_str.is_empty() {
                            self.state.agent_builder.temperature = 0.0;
                        }
                    }
                    1 => {
                        let mut tokens_str = format!("{}", self.state.agent_builder.max_tokens);
                        tokens_str.pop();
                        if let Ok(val) = tokens_str.parse::<u32>() {
                            self.state.agent_builder.max_tokens = val;
                        } else if tokens_str.is_empty() {
                            self.state.agent_builder.max_tokens = 0;
                        }
                    }
                    _ => {}
                }
            }
            _ => {}
        }
    }

    /// Create agent from builder state and send to backend
    fn create_agent_from_builder(&mut self, ws_client: Option<&WebSocketClient>) {
        // Validate one final time
        if !self.state.agent_builder.validate_step() {
            self.add_log(
                "[ERROR] Agent validation failed. Please fix errors and try again.".to_string(),
            );
            // Clone errors to avoid borrow checker issues
            let errors = self.state.agent_builder.validation_errors.clone();
            for error in &errors {
                self.add_log(format!("  - {}", error));
            }
            return;
        }

        // Clone name before borrowing mutably
        let agent_name = self.state.agent_builder.name.clone();

        // Validate name is not empty
        if agent_name.trim().is_empty() {
            self.add_log("[ERROR] Agent name cannot be empty".to_string());
            return;
        }

        // Build agent data
        let builder = &self.state.agent_builder;
        let agent_data = serde_json::json!({
            "name": builder.name,
            "description": builder.description,
            "baseAgent": if builder.use_local_model {
                builder.local_model.clone()
            } else {
                builder.model.clone()
            },
            "systemPrompt": builder.system_prompt,
            "temperature": builder.temperature,
            "maxTokens": builder.max_tokens,
            "useLocalModel": builder.use_local_model,
            "localModelProvider": if builder.use_local_model {
                Some(builder.local_provider.clone())
            } else {
                None
            },
            "localModelName": if builder.use_local_model {
                Some(builder.local_model.clone())
            } else {
                None
            },
            "localModelUrl": if builder.use_local_model {
                Some(builder.local_url.clone())
            } else {
                None
            },
            "topP": builder.top_p,
            "frequencyPenalty": builder.frequency_penalty,
            "presencePenalty": builder.presence_penalty,
            "tools": builder.selected_tools,
        });

        // Log the creation attempt
        self.add_log(format!("[AGENT] Creating agent '{}'...", agent_name));

        // Send agent.create command via WebSocket
        if let Some(ws) = ws_client {
            match ws.send_command("agent.create", Some(agent_data)) {
                Ok(id) => {
                    let short_id = &id[id.len().saturating_sub(8)..];
                    self.add_log(format!("[AGENT] Sent agent.create (id: {})", short_id));
                    // Store command ID for response tracking (Step 5.1)
                    self.state.pending_agent_create_id = Some(id);
                }
                Err(e) => {
                    self.add_log(format!("[ERROR] Failed to create agent: {}", e));
                    return;
                }
            }
        } else {
            self.add_log("[ERROR] WebSocket not connected. Cannot create agent.".to_string());
            self.add_log("[HELP] Start the backend server and reconnect.".to_string());
            return;
        }

        // Close the builder
        self.pop_overlay();

        // Reset wizard state
        self.state.agent_builder = AgentBuilderState::default();

        // Suppress unused variable warning
        let _ = agent_data;
    }

    // ============================================================
    // RUN MANAGER INPUT HANDLING (Step 4.7)
    // ============================================================

    /// Handle input when Run Manager screen is active
    fn handle_run_manager_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // Ctrl+C/Q to exit
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        match key.code {
            // Navigation — list only; detail view locks to pinned run
            KeyCode::Up => {
                if self.state.run_manager.is_detail_view() {
                    self.state.run_manager.detail_log_scroll_up();
                } else {
                    self.state.run_manager.select_previous();
                }
                self.request_render("run_manager_up");
            }
            KeyCode::Down => {
                if self.state.run_manager.is_detail_view() {
                    self.state.run_manager.detail_log_scroll_down();
                } else {
                    self.state.run_manager.select_next();
                }
                self.request_render("run_manager_down");
            }

            // Filter (list view only)
            KeyCode::Char('f') | KeyCode::Char('F') => {
                if !self.state.run_manager.is_detail_view() {
                    self.state.run_manager.next_filter();
                    self.request_render("run_manager_filter");
                }
            }

            // Sort (list view only)
            KeyCode::Char('s') | KeyCode::Char('S') => {
                if !self.state.run_manager.is_detail_view() {
                    self.state.run_manager.next_sort();
                    self.request_render("run_manager_sort");
                }
            }

            // Refresh (list or pinned detail)
            KeyCode::Char('r') | KeyCode::Char('R') => {
                if self.state.operation_mode == OperationMode::Connected {
                    if let Some(ws) = ws_client {
                        if self.state.run_manager.is_detail_view() {
                            if let Some(run) = self.state.run_manager.detail_run() {
                                let run_id = run.id.clone();
                                self.begin_run_get_request(ws, &run_id);
                                self.add_log("[RUN] Refreshing run detail...".to_string());
                            }
                        } else {
                            self.begin_run_list_request(ws, false);
                            self.add_log("[RUN] Refreshing run list from Gateway...".to_string());
                        }
                    } else {
                        self.add_log("[RUN] WebSocket not connected".to_string());
                    }
                } else {
                    self.add_log("[RUN] Connect to Gateway to refresh runs".to_string());
                }
                self.request_render("run_manager_refresh");
            }

            // Toggle live auto-refresh (poll every ~3s)
            KeyCode::Char('a') | KeyCode::Char('A') => {
                self.state.run_manager.auto_refresh_enabled =
                    !self.state.run_manager.auto_refresh_enabled;
                let status = if self.state.run_manager.auto_refresh_enabled {
                    "on"
                } else {
                    "off"
                };
                self.add_log(format!("[RUN] Live auto-refresh {}", status));
                self.request_render("run_manager_live_toggle");
            }

            // View details / Close detail view
            KeyCode::Enter => {
                if self.state.run_manager.is_detail_view() {
                    self.state.run_manager.close_detail_view();
                    self.add_log("[RUN] Closed detail view".to_string());
                } else if let Some(run_id) = self.state.run_manager.open_detail_view() {
                    if let Some(run) = self.state.run_manager.detail_run() {
                        self.add_log(format!("[RUN] Viewing run: {}", run.name));
                    }
                    if self.state.operation_mode == OperationMode::Connected {
                        if let Some(ws) = ws_client {
                            self.begin_run_get_request(ws, &run_id);
                        }
                    }
                } else {
                    self.add_log("[RUN] No run selected".to_string());
                }
                self.request_render("run_manager_view");
            }

            // Cancel run (list selection or pinned detail run)
            KeyCode::Char('c') | KeyCode::Char('C') => {
                let run = if self.state.run_manager.is_detail_view() {
                    self.state.run_manager.detail_run()
                } else {
                    self.state.run_manager.selected_run()
                };
                if let Some(run) = run {
                    if matches!(
                        run.status,
                        crate::ui::run_manager::RunStatus::Running
                            | crate::ui::run_manager::RunStatus::Pending
                    ) {
                        if self.state.operation_mode == OperationMode::Connected {
                            if let Some(ws) = ws_client {
                                let data = serde_json::json!({ "runId": run.id.clone() });
                                if let Ok(id) = ws.send_command("run.cancel", Some(data)) {
                                    self.state.pending_run_cancel_id = Some(id);
                                    self.add_log(format!("[RUN] Cancelling run: {}", run.name));
                                }
                            } else {
                                self.add_log("[RUN] WebSocket not connected".to_string());
                            }
                        } else {
                            self.add_log("[RUN] Connect to Gateway to cancel runs".to_string());
                        }
                    } else {
                        self.add_log("[RUN] Cannot cancel completed/failed run".to_string());
                    }
                }
                self.request_render("run_manager_cancel");
            }

            // Delete run
            KeyCode::Char('d') | KeyCode::Char('D') => {
                self.add_log("[RUN] Delete run is not supported by the Gateway API (use cancel for active runs).".to_string());
                self.request_render("run_manager_delete");
            }

            // Close detail view or Run Manager
            KeyCode::Esc => {
                if self.state.run_manager.is_detail_view() {
                    // If we have other overlays beneath RunManager (e.g. Shield), 
                    // close RunManager entirely instead of just closing detail view
                    if self.state.navigation.has_overlay() {
                        let current_count = self.state.navigation.overlay_stack.len();
                        if current_count > 1 {
                            // Multiple overlays - pop RunManager entirely
                            self.pop_overlay();
                            self.add_log("[RUN] Returned to previous screen".to_string());
                            self.request_render("run_manager_close");
                        } else {
                            // Just RunManager open - close detail view first
                            self.state.run_manager.close_detail_view();
                            self.add_log("[RUN] Closed detail view".to_string());
                            self.request_render("run_manager_close_detail");
                        }
                    } else {
                        // No other overlays - close detail view first
                        self.state.run_manager.close_detail_view();
                        self.add_log("[RUN] Closed detail view".to_string());
                        self.request_render("run_manager_close_detail");
                    }
                } else {
                    // Close Run Manager (list view)
                    self.pop_overlay();
                }
            }

            _ => {}
        }

        Ok(false)
    }

    pub fn sentinel_scroll_custom_value(&mut self, increase: bool) {
        self.sentinel_adjust_custom_field(increase);
    }

    fn sentinel_adjust_custom_field(&mut self, increase: bool) {
        use crate::ui::sentinel_config::SentinelViewMode;
        if self.state.sentinel_config.view_mode != SentinelViewMode::Custom {
            return;
        }
        self.state.sentinel_config.adjust_custom_field(increase);
        // Clear stale apply banner so edits are obvious; shortcuts return in footer
        self.state.sentinel_config.status_message = None;
        self.request_immediate_render(if increase {
            "sentinel_inc"
        } else {
            "sentinel_dec"
        });
    }

    /// Handle input when Sentinel Config screen is active
    fn handle_sentinel_config_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crate::ui::sentinel_config::SentinelViewMode;
        use crossterm::event::KeyModifiers;

        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        match key.code {
            KeyCode::Tab => {
                self.state.sentinel_config.cycle_view_mode();
                self.request_immediate_render("sentinel_tab");
            }
            KeyCode::Up => {
                self.state.sentinel_config.select_previous();
                self.request_immediate_render("sentinel_up");
            }
            KeyCode::Down => {
                self.state.sentinel_config.select_next();
                self.request_immediate_render("sentinel_down");
            }
            KeyCode::Left => {
                if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
                    self.sentinel_adjust_custom_field(false);
                }
            }
            KeyCode::Right => {
                if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
                    self.sentinel_adjust_custom_field(true);
                }
            }
            KeyCode::Char('+') | KeyCode::Char('=') => {
                if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
                    self.sentinel_adjust_custom_field(true);
                }
            }
            KeyCode::Char('-') | KeyCode::Char('_') => {
                if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
                    self.sentinel_adjust_custom_field(false);
                }
            }
            KeyCode::Char(' ') => {
                if self.state.sentinel_config.view_mode == SentinelViewMode::Custom {
                    use crate::ui::sentinel_config::CustomField;
                    if self.state.sentinel_config.custom_field == CustomField::Enabled {
                        self.sentinel_adjust_custom_field(true);
                    }
                }
            }
            KeyCode::Char('r') | KeyCode::Char('R') => {
                if self.state.operation_mode == OperationMode::Connected {
                    if let Some(ws) = ws_client {
                        self.begin_sentinel_load_request(ws);
                    }
                }
                self.request_render("sentinel_refresh");
            }
            KeyCode::Enter => {
                if self.state.operation_mode != OperationMode::Connected {
                    self.state.sentinel_config.error =
                        Some("Connect to Gateway first.".to_string());
                } else if let Some(ws) = ws_client {
                    // Apply custom configuration
                    let draft = self.state.sentinel_config.custom_draft.clone();
                    self.begin_sentinel_apply_custom_request(ws, &draft);
                    self.add_log("[SENTINEL] Applying custom limits...".to_string());
                }
                self.request_render("sentinel_apply");
            }
            KeyCode::Esc => {
                self.pop_overlay();
                self.request_render("sentinel_close");
            }
            _ => {}
        }

        Ok(false)
    }

    fn handle_shield_dashboard_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crate::ui::shield_dashboard::ShieldTab;
        use crossterm::event::KeyModifiers;

        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        match key.code {
            KeyCode::Tab => {
                self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
                self.request_render("shield_tab");
            }
            KeyCode::BackTab => {
                self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
                self.request_render("shield_tab_back");
            }
            KeyCode::Left => {
                self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.prev();
                self.request_render("shield_tab_left");
            }
            KeyCode::Right => {
                self.state.shield_dashboard.tab = self.state.shield_dashboard.tab.next();
                self.request_render("shield_tab_right");
            }
            KeyCode::Up => {
                if self.state.shield_dashboard.tab == ShieldTab::Activity
                    && self.state.shield_dashboard.selected_activity_index > 0
                {
                    self.state.shield_dashboard.selected_activity_index -= 1;
                    self.request_render("shield_activity_up");
                } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement
                    && self.state.shield_dashboard.selected_block_index > 0
                {
                    self.state.shield_dashboard.selected_block_index -= 1;
                    self.request_render("shield_block_up");
                }
            }
            KeyCode::Down => {
                if self.state.shield_dashboard.tab == ShieldTab::Activity {
                    let max = self.state.shield_dashboard.activity_events.len();
                    if max > 0 && self.state.shield_dashboard.selected_activity_index + 1 < max {
                        self.state.shield_dashboard.selected_activity_index += 1;
                        self.request_render("shield_activity_down");
                    }
                } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
                    let max = self.state.shield_dashboard.recent_blocks.len();
                    if max > 0 && self.state.shield_dashboard.selected_block_index + 1 < max {
                        self.state.shield_dashboard.selected_block_index += 1;
                        self.request_render("shield_block_down");
                    }
                }
            }
            KeyCode::Enter => {
                if self.state.shield_dashboard.tab == ShieldTab::Activity {
                    let run_id = self
                        .state
                        .shield_dashboard
                        .activity_events
                        .get(self.state.shield_dashboard.selected_activity_index)
                        .and_then(|row| row.run_id.clone());
                    if let Some(run_id) = run_id {
                        self.open_run_from_shield_block(ws_client, &run_id);
                    }
                } else if self.state.shield_dashboard.tab == ShieldTab::Enforcement {
                    if let Some(row) = self
                        .state
                        .shield_dashboard
                        .recent_blocks
                        .get(self.state.shield_dashboard.selected_block_index)
                    {
                        let run_id = row.id.clone();
                        self.open_run_from_shield_block(ws_client, &run_id);
                    }
                }
            }
            KeyCode::Char('r') | KeyCode::Char('R') => {
                if self.state.operation_mode == OperationMode::Connected {
                    if let Some(ws) = ws_client {
                        self.begin_shield_load_request(ws, true);
                    }
                } else {
                    self.state.shield_dashboard.fail_loading(
                        "Connect to Gateway first (connect portal).".to_string(),
                    );
                }
                self.request_render("shield_refresh");
            }
            KeyCode::Char('o') | KeyCode::Char('O') => {
                self.push_overlay(Screen::RunManager);
                if let Some(ws) = ws_client {
                    self.begin_run_list_request(ws, false);
                }
                self.add_log("[SHIELD] Opened Run Manager".to_string());
                self.request_render("shield_open_runs");
            }
            KeyCode::Char('p') | KeyCode::Char('P') => {
                if self.state.operation_mode == OperationMode::Connected {
                    self.state.navigation.navigate_to_base(Screen::PortalMonitoring);
                    if let Some(ws) = ws_client {
                        self.begin_portal_observability_request(ws);
                    }
                    self.add_log("[SHIELD] Opened Portal Monitoring".to_string());
                } else {
                    self.add_log("[SHIELD] Connect to Gateway first.".to_string());
                }
                self.request_render("shield_open_monitoring");
            }
            KeyCode::Char('a') | KeyCode::Char('A') => {
                self.state.shield_dashboard.auto_refresh_enabled =
                    !self.state.shield_dashboard.auto_refresh_enabled;
                let status = if self.state.shield_dashboard.auto_refresh_enabled {
                    "ON"
                } else {
                    "OFF"
                };
                self.add_log(format!("[SHIELD] Live refresh {}", status));
                self.request_render("shield_live_toggle");
            }
            KeyCode::Esc => {
                self.pop_overlay();
                self.request_render("shield_close");
            }
            _ => {}
        }

        Ok(false)
    }

    // ============================================================
    // SETTINGS INPUT HANDLING (Step 4.8)
    // ============================================================

    /// Handle input when Settings screen is active
    fn handle_settings_input(&mut self, key: KeyEvent) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // Ctrl+C/Q to exit
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        match key.code {
            // Navigation
            KeyCode::Up => {
                self.state.settings.prev_section();
                self.request_render("settings_up");
            }
            KeyCode::Down => {
                self.state.settings.next_section();
                self.request_render("settings_down");
            }

            // Toggle current setting
            KeyCode::Char(' ') => {
                match self.state.settings.focused_section {
                    crate::ui::settings::SettingsSection::Mode => {
                        self.state.settings.toggle_mode();
                        self.add_log(format!(
                            "[SETTINGS] Mode: {}",
                            self.state.settings.mode.as_str()
                        ));
                    }
                    crate::ui::settings::SettingsSection::AIProvider => {
                        self.state.settings.next_provider();
                        self.add_log(format!(
                            "[SETTINGS] Provider: {}",
                            self.state.settings.ai_provider
                        ));
                    }
                    crate::ui::settings::SettingsSection::AutoUpdate => {
                        self.state.settings.toggle_auto_update();
                        self.add_log(format!(
                            "[SETTINGS] Auto-update: {}",
                            self.state.settings.auto_update
                        ));
                    }
                    _ => {}
                }
                self.request_render("settings_toggle");
            }

            // Save settings
            KeyCode::Enter => {
                if self.state.settings.has_changes {
                    self.state.settings.save();
                    self.add_log("[SETTINGS] Settings saved successfully".to_string());
                    // TODO: Send config.update command via WebSocket
                    self.request_render("settings_save");
                }
            }

            // Close (with discard warning if changes)
            KeyCode::Esc => {
                if self.state.settings.has_changes {
                    self.add_log("[SETTINGS] Discarding unsaved changes".to_string());
                    self.state.settings.discard();
                }
                self.pop_overlay();
            }

            _ => {}
        }

        Ok(false)
    }

    /// Handle input when Agent List screen is active
    fn handle_agent_list_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // Ctrl+C/Q to exit
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        match key.code {
            // Navigation
            KeyCode::Up => {
                if self.state.agent_list.selected_index > 0 {
                    self.state.agent_list.selected_index -= 1;
                }
                self.request_render("agent_list_up");
            }
            KeyCode::Down => {
                let max = self.state.agent_list.agents.len().saturating_sub(1);
                if self.state.agent_list.selected_index < max {
                    self.state.agent_list.selected_index += 1;
                }
                self.request_render("agent_list_down");
            }

            // View details / Close detail view
            KeyCode::Enter => {
                if self.state.agent_list.detail_view.is_some() {
                    // Close detail view
                    self.state.agent_list.detail_view = None;
                } else {
                    // Open detail view for selected agent
                    self.state.agent_list.detail_view = Some(self.state.agent_list.selected_index);
                }
                self.request_render("agent_list_toggle_detail");
            }

            // Close
            KeyCode::Esc => {
                if self.state.agent_list.detail_view.is_some() {
                    // Close detail popup first
                    self.state.agent_list.detail_view = None;
                    self.request_render("agent_list_close_detail");
                } else {
                    // Close agent list
                    self.pop_overlay();
                    self.request_render("agent_list_close");
                }
            }

            // Delete agent (Step 5.5)
            KeyCode::Delete | KeyCode::Char('d') | KeyCode::Char('D') => {
                // Only allow deletion in list view (not in detail view)
                if self.state.agent_list.detail_view.is_none()
                    && !self.state.agent_list.agents.is_empty()
                {
                    self.state.agent_delete_requested = true;
                    self.request_render("agent_delete_requested");
                }
            }

            // Step 6: queue a Gateway run (uses built-in `test` agent executor on the server)
            KeyCode::Char('x') | KeyCode::Char('X') => {
                if self.state.operation_mode != OperationMode::Connected {
                    self.add_log(
                        "[AGENT] Connect to Gateway first to run on the server (key x)."
                            .to_string(),
                    );
                } else if self.state.agent_list.agents.is_empty() {
                    self.add_log("[AGENT] No agents in list.".to_string());
                } else if let Some((agent_name, agent_model)) = self
                    .get_selected_agent()
                    .map(|a| (a.name.clone(), a.model.clone()))
                {
                    if let Some(ws) = ws_client {
                        let name = format!("TUI: {}", agent_name);
                        let input = serde_json::json!({
                            "agent_id": "test",
                            "data": {
                                "prompt": format!("Quick run for local agent profile '{}' (model {}).", agent_name, agent_model)
                            }
                        });
                        let data = serde_json::json!({ "name": name, "input": input });
                        match ws.send_command("run.quick", Some(data)) {
                            Ok(id) => {
                                self.state.pending_run_quick_id = Some(id);
                                self.add_log(format!(
                                    "[AGENT] Queued Gateway run for profile '{}' (x)",
                                    agent_name
                                ));
                            }
                            Err(e) => self.add_log(format!("[ERROR] run.quick: {}", e)),
                        }
                    }
                }
                self.request_render("agent_list_quick_run");
            }

            _ => {}
        }

        Ok(false)
    }

    /// Delete selected agent (Step 5.5)
    pub fn delete_selected_agent(&mut self, ws_client: &Option<crate::websocket::WebSocketClient>) {
        if self.state.agent_list.agents.is_empty() {
            return;
        }

        let agent_name = self.state.agent_list.agents[self.state.agent_list.selected_index]
            .name
            .clone();

        // Send agent.delete command via WebSocket
        if let Some(ws) = ws_client {
            let delete_data = serde_json::json!({
                "name": agent_name
            });

            match ws.send_command("agent.delete", Some(delete_data)) {
                Ok(id) => {
                    let short_id = &id[id.len().saturating_sub(8)..];
                    self.add_log(format!(
                        "[AGENT] Sent agent.delete for '{}' (id: {})",
                        agent_name, short_id
                    ));
                    // Store command ID for response tracking
                    self.state.pending_agent_delete_id = Some(id);
                }
                Err(e) => {
                    self.add_log(format!("[ERROR] Failed to delete agent: {}", e));
                }
            }
        } else {
            self.add_log("[ERROR] WebSocket not connected. Cannot delete agent.".to_string());
        }
    }

    /// Get selected agent (Step 5.6)
    pub fn get_selected_agent(&self) -> Option<&AgentInfo> {
        if self.state.agent_list.agents.is_empty() {
            return None;
        }
        self.state
            .agent_list
            .agents
            .get(self.state.agent_list.selected_index)
    }

    /// Get selected agent name (Step 5.6)
    pub fn get_selected_agent_name(&self) -> Option<String> {
        self.get_selected_agent().map(|agent| agent.name.clone())
    }

    /// Handle input when Connection Portal screen is active (Step 7)
    fn handle_connection_portal_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crossterm::event::KeyModifiers;

        // Ctrl+C/Q to exit
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }

        // Disable input if connecting
        if self.state.connection_portal.connecting {
            return Ok(false);
        }

        match key.code {
            // ESC - Close portal and return to Main
            KeyCode::Esc => {
                // Drop stale connect attempt + UI state so the next open is not "pre-broken".
                self.state.pending_gateway_connect_id = None;
                self.state.connection_portal.finish_connecting();
                self.state.connection_portal.error = None;
                self.state.connection_portal.activity_log.clear();
                self.state.navigation.navigate_to_base(Screen::Main);
                self.add_log("[NAV] Connection Portal closed".to_string());
                self.request_immediate_render("portal_close");
            }

            // M — Portal Monitoring (after successful connect)
            KeyCode::Char('m') | KeyCode::Char('M')
                if self.state.connection_portal.connection_success
                    || (matches!(self.state.operation_mode, OperationMode::Connected)
                        && self.state.gateway_url.is_some()) =>
            {
                self.state
                    .navigation
                    .navigate_to_base(Screen::PortalMonitoring);
                self.state.advanced_monitoring =
                    AdvancedMonitoringState::new(self.state.gateway_url.clone());
                self.state.portal_monitoring.scroll_offset = 0;
                self.add_log("[NAV] Opening Portal Monitoring".to_string());
                if let Some(ws) = ws_client {
                    self.begin_portal_observability_request(ws);
                }
                self.request_immediate_render("open_portal_monitoring");
            }

            // D — Disconnect Gateway (drop CLI link; reconnect with Enter after)
            KeyCode::Char('d') | KeyCode::Char('D') if self.gateway_session_linked() => {
                self.request_gateway_disconnect(ws_client);
                self.request_immediate_render("portal_disconnect");
            }

            // R — Re-verify /health + /ready (detect stale link when Docker/Gateway stopped)
            KeyCode::Char('r') | KeyCode::Char('R') if self.gateway_session_linked() => {
                self.state.gateway_healthy = false;
                self.request_gateway_health_check(ws_client);
                self.request_immediate_render("portal_reverify");
            }

            // L - Continue in local-only mode (no Gateway connection)
            KeyCode::Char('l') | KeyCode::Char('L') => {
                if self.gateway_session_linked() {
                    self.request_gateway_disconnect(ws_client);
                }
                use crate::app::OperationMode;
                self.state.operation_mode = OperationMode::Local;
                self.state.gateway_url = None;
                self.state.connected = false;
                self.state.gateway_healthy = false;
                self.state.gateway_db_summary = None;
                self.state.connection_portal.finish_connecting();
                self.state.connection_portal.connection_success = false;
                self.state.connection_portal.error = None;
                self.state.pending_gateway_connect_id = None;
                self.state.pending_gateway_health_id = None;
                self.state.pending_gateway_observability_id = None;
                self.state.pending_monitoring_refresh_id = None;
                self.state.pending_monitoring_logs_id = None;
                self.state.pending_monitoring_drill_id = None;
                self.state.portal_monitoring = PortalMonitoringState::default();
                self.state.advanced_monitoring = AdvancedMonitoringState::default();
                self.state.portal_observability_last_poll = None;
                self.state.navigation.navigate_to_base(Screen::Main);
                self.add_log("[MODE] Continuing in LOCAL mode (no Gateway connection)".to_string());
                self.request_immediate_render("portal_local_mode");
            }

            // Tab - Switch between URL and Username fields
            KeyCode::Tab | KeyCode::BackTab => {
                if self.state.connection_portal.connection_success {
                    return Ok(false);
                }
                self.state.connection_portal.toggle_field();
                self.request_immediate_render("portal_field_toggle");
            }

            // Up/Down - Navigate between fields
            KeyCode::Up | KeyCode::Down => {
                if self.state.connection_portal.connection_success {
                    return Ok(false);
                }
                self.state.connection_portal.toggle_field();
                self.request_immediate_render("portal_navigate");
            }

            // Enter - Connect to Gateway
            KeyCode::Enter => {
                let raw = self.state.connection_portal.gateway_url.clone();
                let url = raw
                    .lines()
                    .map(str::trim)
                    .find(|l| !l.is_empty())
                    .unwrap_or("")
                    .to_string();
                self.state.connection_portal.gateway_url = url.clone();

                // Validate URL
                if url.is_empty() {
                    self.state.connection_portal.error =
                        Some("Gateway URL is required".to_string());
                    self.request_immediate_render("portal_error");
                    return Ok(false);
                }

                if !url.starts_with("http://") && !url.starts_with("https://") {
                    self.state.connection_portal.error =
                        Some("URL must start with http:// or https://".to_string());
                    self.request_immediate_render("portal_error");
                    return Ok(false);
                }

                // Connect to Gateway
                self.state.connection_portal.error = None;
                self.state.connection_portal.begin_connecting();

                if let Some(ws) = ws_client {
                    let connect_data = serde_json::json!({
                        "url": url,
                        "username": self.state.connection_portal.username
                    });

                    match ws.send_command("gateway.connect", Some(connect_data)) {
                        Ok(id) => {
                            let short_id = &id[id.len().saturating_sub(8)..];
                            self.add_log(format!(
                                "[GATEWAY] Connecting to {} (id: {})",
                                url, short_id
                            ));
                            self.state.pending_gateway_connect_id = Some(id);
                        }
                        Err(e) => {
                            self.state.connection_portal.finish_connecting();
                            self.state.connection_portal.error =
                                Some(format!("Failed to connect: {}", e));
                            self.add_log(format!("[ERROR] Gateway connection failed: {}", e));
                        }
                    }
                } else {
                    self.state.connection_portal.finish_connecting();
                    self.state.connection_portal.error =
                        Some("WebSocket not connected".to_string());
                    self.add_log("[ERROR] WebSocket not connected".to_string());
                }

                // CRITICAL: Use immediate render for instant status update
                self.request_immediate_render("portal_connect");
            }

            // Typing - edit focused field (immediate render for responsiveness)
            KeyCode::Char(c) => {
                if self.state.connection_portal.connection_success {
                    // After connect, URL field is read-only; use M for monitoring, ESC to leave.
                    return Ok(false);
                }
                match self.state.connection_portal.focused_field {
                    PortalField::GatewayUrl => {
                        self.state.connection_portal.gateway_url.push(c);
                        self.state.connection_portal.error = None;
                        // OPTIMIZATION: Immediate render for instant typing feedback
                        self.request_immediate_render("portal_input");
                    }
                    PortalField::Username => {
                        self.state.connection_portal.username.push(c);
                        self.state.connection_portal.error = None;
                        // OPTIMIZATION: Immediate render for instant typing feedback
                        self.request_immediate_render("portal_input");
                    }
                }
            }

            // Backspace - delete character (immediate render for responsiveness)
            KeyCode::Backspace => {
                if self.state.connection_portal.connection_success {
                    return Ok(false);
                }
                match self.state.connection_portal.focused_field {
                    PortalField::GatewayUrl => {
                        self.state.connection_portal.gateway_url.pop();
                    }
                    PortalField::Username => {
                        self.state.connection_portal.username.pop();
                    }
                }
                self.state.connection_portal.error = None;
                // OPTIMIZATION: Immediate render for instant feedback
                self.request_immediate_render("portal_delete");
            }

            _ => {}
        }

        Ok(false)
    }

    fn handle_portal_monitoring_input(
        &mut self,
        key: KeyEvent,
        ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        use crate::ui::portal_monitoring::{
            portal_monitoring_scroll_max, portal_monitoring_section_offset,
        };
        use crossterm::event::KeyModifiers;
        if key.modifiers.contains(KeyModifiers::CONTROL) {
            match key.code {
                KeyCode::Char('c') | KeyCode::Char('q') => return Ok(true),
                _ => return Ok(false),
            }
        }
        if key.code == KeyCode::F(10) {
            return Ok(true);
        }
        if key.kind != KeyEventKind::Press {
            return Ok(false);
        }
        if self.state.portal_monitoring.help_visible {
            if key.code == KeyCode::Esc || key.code == KeyCode::Char('?') {
                self.state.portal_monitoring.help_visible = false;
                self.request_immediate_render("portal_help_close");
            }
            return Ok(false);
        }
        if key.code == KeyCode::Esc {
            self.state.pending_gateway_observability_id = None;
            self.state.pending_monitoring_refresh_id = None;
            self.state.pending_monitoring_logs_id = None;
            self.state.pending_monitoring_drill_id = None;
            self.state.portal_monitoring.section_refresh_loading = None;
            self.state.portal_monitoring.logs_fetch_loading = false;
            self.state.portal_monitoring.metrics_drill = None;
            self.state.portal_monitoring.metrics_drill_lines.clear();
            self.state.portal_monitoring.metrics_drill_loading = false;
            self.state.navigation.navigate_to_base(Screen::Main);
            self.add_log("[NAV] Portal Monitoring closed".to_string());
            self.request_immediate_render("portal_monitoring_close");
            return Ok(false);
        }
        if key.code == KeyCode::Char('?') {
            self.state.portal_monitoring.help_visible = true;
            self.request_immediate_render("portal_help_open");
            return Ok(false);
        }
        // R is deliberately global: refresh the full snapshot without changing the current view.
        if key.code == KeyCode::Char('r') || key.code == KeyCode::Char('R') {
            if let Some(ws) = ws_client {
                self.state.portal_observability_last_poll = None;
                self.cancel_portal_monitoring_requests();
                self.state.portal_monitoring.last_refresh = Some(std::time::Instant::now());
                self.begin_portal_observability_request(ws);
                self.request_immediate_render("portal_obs_refresh");
            } else {
                self.state.portal_monitoring.error =
                    Some("WebSocket to CLI is not connected.".to_string());
                self.request_immediate_render("portal_obs_no_ws");
            }
            return Ok(false);
        }
        if key.code == KeyCode::Char('l') || key.code == KeyCode::Char('L') {
            let logs_open = self
                .state
                .advanced_monitoring
                .monitoring_state
                .selected_section
                == Some(MonitoringSection::Logs)
                && self
                    .state
                    .advanced_monitoring
                    .monitoring_state
                    .sections
                    .get(&MonitoringSection::Logs)
                    .map(|section| section.expanded)
                    .unwrap_or(false);
            if logs_open && !self.state.portal_monitoring.logs_fetch_loading {
                self.state
                    .advanced_monitoring
                    .monitoring_state
                    .collapse_section(MonitoringSection::Logs);
                self.state.portal_monitoring.scroll_offset = 0;
                self.request_immediate_render("portal_monitoring_logs_close");
                return Ok(false);
            }
            self.state
                .advanced_monitoring
                .monitoring_state
                .select_section(MonitoringSection::Logs);
            self.state
                .advanced_monitoring
                .monitoring_state
                .expand_section(MonitoringSection::Logs);
            if let Some(ws) = ws_client {
                self.cancel_portal_monitoring_requests();
                self.begin_monitoring_logs_request(ws);
                let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
                let sw = self.state.portal_monitoring.summary_clip_width.max(24);
                self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
                    &self.state,
                    sw,
                    inner_h,
                    MonitoringSection::Logs,
                );
                self.request_immediate_render("portal_monitoring_logs");
            } else {
                self.state.portal_monitoring.error =
                    Some("WebSocket to CLI is not connected.".to_string());
                self.request_immediate_render("portal_obs_no_ws");
            }
            return Ok(false);
        }

        if key.code == KeyCode::Char('h') || key.code == KeyCode::Char('H') {
            self.state
                .advanced_monitoring
                .monitoring_state
                .select_section(MonitoringSection::Metrics);
            self.state
                .advanced_monitoring
                .monitoring_state
                .expand_section(MonitoringSection::Metrics);
            self.state.portal_monitoring.trend_view = !self.state.portal_monitoring.trend_view;
            let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
            let sw = self.state.portal_monitoring.summary_clip_width.max(24);
            self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
                &self.state,
                sw,
                inner_h,
                MonitoringSection::Metrics,
            );
            let status = if self.state.portal_monitoring.trend_view {
                "enabled"
            } else {
                "disabled"
            };
            self.add_log(format!("[PORTAL] Trend/history view {}", status));
            self.request_immediate_render("portal_trend_toggle");
            return Ok(false);
        }

        if key.code == KeyCode::Char('e') || key.code == KeyCode::Char('E') {
            match self.export_portal_monitoring_snapshot() {
                Ok(path) => {
                    self.add_log(format!("[PORTAL] Exported monitoring snapshot: {}", path));
                    self.state.portal_monitoring.error = None;
                }
                Err(e) => {
                    self.state.portal_monitoring.error = Some(format!("Export failed: {}", e));
                }
            }
            self.request_immediate_render("portal_export");
            return Ok(false);
        }

        // Phase 3: extended dependency view (GET /api/monitoring/dependencies/detail)
        if key.code == KeyCode::Char('t') || key.code == KeyCode::Char('T') {
            self.state
                .advanced_monitoring
                .monitoring_state
                .select_section(MonitoringSection::Dependencies);
            self.state
                .advanced_monitoring
                .monitoring_state
                .expand_section(MonitoringSection::Dependencies);
            let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
            let sw = self.state.portal_monitoring.summary_clip_width.max(24);
            self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
                &self.state,
                sw,
                inner_h,
                MonitoringSection::Dependencies,
            );
            if let Some(ws) = ws_client {
                self.cancel_portal_monitoring_requests();
                self.begin_dependencies_detail_drill(ws);
                self.request_immediate_render("portal_deps_drill");
            } else {
                self.state.portal_monitoring.error =
                    Some("WebSocket to CLI is not connected.".to_string());
                self.request_immediate_render("portal_obs_no_ws");
            }
            return Ok(false);
        }

        // Phase 4: diagnostics for the local CLI/TUI bridge + Gateway reachability.
        // S toggles: show diagnostics → hide (remove override). While loading, S aborts the
        // in-flight drill so a later S can fetch again (avoids orphaned request ids vs cancel+refetch).
        if key.code == KeyCode::Char('s') || key.code == KeyCode::Char('S') {
            self.state
                .advanced_monitoring
                .monitoring_state
                .select_section(MonitoringSection::System);
            self.state
                .advanced_monitoring
                .monitoring_state
                .expand_section(MonitoringSection::System);
            let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
            let sw = self.state.portal_monitoring.summary_clip_width.max(24);
            self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
                &self.state,
                sw,
                inner_h,
                MonitoringSection::System,
            );

            let pm = &mut self.state.portal_monitoring;
            let has_system_diag =
                pm.section_overrides.contains_key(&MonitoringSection::System);
            let system_loading = pm.section_refresh_loading == Some(MonitoringSection::System);

            if has_system_diag && !system_loading {
                pm.section_overrides.remove(&MonitoringSection::System);
                self.request_immediate_render("portal_system_dismiss");
                return Ok(false);
            }
            if system_loading {
                self.state.pending_monitoring_drill_id = None;
                self.state.portal_monitoring.section_refresh_loading = None;
                self.request_immediate_render("portal_system_abort_fetch");
                return Ok(false);
            }

            if let Some(ws) = ws_client {
                self.cancel_portal_monitoring_requests();
                self.begin_system_diagnostics_request(ws);
                self.request_immediate_render("portal_system_diagnostics");
            } else {
                self.state.portal_monitoring.error =
                    Some("WebSocket to CLI is not connected.".to_string());
                self.request_immediate_render("portal_obs_no_ws");
            }
            return Ok(false);
        }

        if key.code == KeyCode::Right {
            self.state
                .advanced_monitoring
                .monitoring_state
                .select_section(MonitoringSection::Metrics);
            self.state
                .advanced_monitoring
                .monitoring_state
                .expand_section(MonitoringSection::Metrics);
            let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
            let sw = self.state.portal_monitoring.summary_clip_width.max(24);
            self.state.portal_monitoring.scroll_offset = portal_monitoring_section_offset(
                &self.state,
                sw,
                inner_h,
                MonitoringSection::Metrics,
            );
            if let Some(ws) = ws_client {
                self.cancel_portal_monitoring_requests();
                let panel = self
                    .state
                    .portal_monitoring
                    .metrics_drill
                    .map(|p| p.next())
                    .unwrap_or(MetricsDrillPanel::Http);
                self.begin_monitoring_drill_request(ws, panel);
                self.request_immediate_render("portal_metrics_drill");
            } else {
                self.state.portal_monitoring.error =
                    Some("WebSocket to CLI is not connected.".to_string());
                self.request_immediate_render("portal_obs_no_ws");
            }
            return Ok(false);
        }

        if key.code == KeyCode::Left {
            if self.state.portal_monitoring.metrics_drill.is_some() {
                self.state.portal_monitoring.metrics_drill = None;
                self.state.portal_monitoring.metrics_drill_lines.clear();
                self.state.portal_monitoring.metrics_drill_loading = false;
                self.request_immediate_render("portal_metrics_drill_exit");
                return Ok(false);
            }
        }

        // Toggle auto-refresh with 'A' key
        if key.code == KeyCode::Char('a') || key.code == KeyCode::Char('A') {
            self.state.portal_monitoring.auto_refresh_enabled =
                !self.state.portal_monitoring.auto_refresh_enabled;
            self.state.advanced_monitoring.monitoring_state.live_mode =
                self.state.portal_monitoring.auto_refresh_enabled;
            let status = if self.state.portal_monitoring.auto_refresh_enabled {
                "enabled"
            } else {
                "disabled"
            };
            self.add_log(format!("[PORTAL] Auto-refresh {}", status));
            self.request_immediate_render("portal_obs_auto_toggle");
            return Ok(false);
        }

        // Phase 1: Enter / Space — expand or collapse selected section
        if key.code == KeyCode::Enter || key.code == KeyCode::Char(' ') {
            let idx = self
                .state
                .advanced_monitoring
                .monitoring_state
                .selected_index;
            self.state
                .advanced_monitoring
                .monitoring_state
                .toggle_section_at_index(idx);
            self.request_immediate_render("portal_monitoring_toggle_section");
            return Ok(false);
        }

        let inner_h = self.state.portal_monitoring.viewport_lines.max(5);
        let sw = self.state.portal_monitoring.summary_clip_width.max(24);
        let max_scroll = portal_monitoring_scroll_max(&self.state, sw, inner_h);
        let logs_selected_and_open = self
            .state
            .advanced_monitoring
            .monitoring_state
            .selected_section
            == Some(MonitoringSection::Logs)
            && self
                .state
                .advanced_monitoring
                .monitoring_state
                .sections
                .get(&MonitoringSection::Logs)
                .map(|section| section.expanded)
                .unwrap_or(false);

        let system_selected_and_diagnostics = self
            .state
            .advanced_monitoring
            .monitoring_state
            .selected_section
            == Some(MonitoringSection::System)
            && self
                .state
                .advanced_monitoring
                .monitoring_state
                .sections
                .get(&MonitoringSection::System)
                .map(|section| section.expanded)
                .unwrap_or(false)
            && self
                .state
                .portal_monitoring
                .section_overrides
                .contains_key(&MonitoringSection::System);

        match key.code {
            KeyCode::Up => {
                if logs_selected_and_open || system_selected_and_diagnostics {
                    self.state.portal_monitoring.scroll_offset =
                        self.state.portal_monitoring.scroll_offset.saturating_sub(1);
                    self.request_immediate_render("portal_obs_scroll");
                } else {
                    self.state
                        .advanced_monitoring
                        .monitoring_state
                        .select_previous_wrapped();
                    self.state.portal_monitoring.scroll_offset = 0;
                    self.request_immediate_render("portal_monitoring_select");
                }
            }
            KeyCode::Down => {
                if logs_selected_and_open || system_selected_and_diagnostics {
                    self.state.portal_monitoring.scroll_offset =
                        (self.state.portal_monitoring.scroll_offset + 1).min(max_scroll);
                    self.request_immediate_render("portal_obs_scroll");
                } else {
                    self.state
                        .advanced_monitoring
                        .monitoring_state
                        .select_next_wrapped();
                    self.state.portal_monitoring.scroll_offset = 0;
                    self.request_immediate_render("portal_monitoring_select");
                }
            }
            KeyCode::PageUp => {
                let step = inner_h.saturating_sub(3).max(1);
                self.state.portal_monitoring.scroll_offset = self
                    .state
                    .portal_monitoring
                    .scroll_offset
                    .saturating_sub(step);
                self.request_immediate_render("portal_obs_scroll");
            }
            KeyCode::PageDown => {
                let step = inner_h.saturating_sub(3).max(1);
                self.state.portal_monitoring.scroll_offset =
                    (self.state.portal_monitoring.scroll_offset + step).min(max_scroll);
                self.request_immediate_render("portal_obs_scroll");
            }
            _ => {}
        }
        Ok(false)
    }

    /// After Setup Portal: reset Connection Portal so stale success/logs/errors are gone, pre-fill
    /// URL from the selected row, and copy detection hints onto the Connection screen.
    fn open_connection_portal_from_setup(&mut self) {
        use GatewayOption::*;
        self.state.connection_portal.reset();
        let url = self.state.setup_portal.selected_option.default_url();
        self.state.connection_portal.gateway_url = url.to_string();
        self.state.connection_portal.username.clear();
        self.state.connection_portal.focused_field = PortalField::GatewayUrl;
        self.state.connection_portal.connection_success = false;
        self.state.connection_portal.finish_connecting();

        if let Some(ref det) = self.state.setup_portal.detection_result {
            let now = wall_clock_hms();
            match self.state.setup_portal.selected_option {
                LocalBundle if !det.local_bundle.available => {
                    self.state.connection_portal.add_log_entry(
                        now,
                        LogLevel::Warning,
                        "Setup did not find a vendored local Gateway. Install 4runr-os globally, run from the repo, or set your Gateway URL manually.".to_string()
                    );
                }
                LocalBundle if det.local_bundle.available && !det.local_bundle.running => {
                    self.state.connection_portal.add_log_entry(
                        now,
                        LogLevel::Info,
                        "Local Gateway bundle detected. Start the Gateway (cd apps/gateway && npm start), then press Enter to connect.".to_string()
                    );
                }
                CloudServer if !det.cloud_server.available => {
                    self.state.connection_portal.add_log_entry(
                        now,
                        LogLevel::Warning,
                        "4Runr Cloud is not available in this build (Coming soon). Use Local Bundle or enter a Custom / self-hosted Gateway URL.".to_string()
                    );
                }
                CustomUrl if url.is_empty() => {
                    self.state.connection_portal.add_log_entry(
                        now,
                        LogLevel::Info,
                        "Type your Gateway URL (https://…), then press Enter to connect."
                            .to_string(),
                    );
                }
                _ => {}
            }
        } else if matches!(self.state.setup_portal.selected_option, CustomUrl) && url.is_empty() {
            let now = wall_clock_hms();
            self.state.connection_portal.add_log_entry(
                now,
                LogLevel::Info,
                "Type your Gateway URL, then press Enter to connect.".to_string(),
            );
        }

        self.state
            .navigation
            .navigate_to_base(Screen::ConnectionPortal);
    }

    // ============================================================
    // SETUP PORTAL INPUT HANDLING
    // ============================================================

    fn handle_setup_portal_input(
        &mut self,
        key: KeyEvent,
        _ws_client: Option<&WebSocketClient>,
    ) -> anyhow::Result<bool> {
        // ESC always closes the portal (even when detecting), so user can always get out
        if key.code == KeyCode::Esc {
            self.state.setup_portal.detecting = false;
            self.state.setup_portal.detecting_since = None;
            self.state.navigation.navigate_to_base(Screen::Main);
            self.add_log("[NAV] Setup Portal closed".to_string());
            self.request_immediate_render("setup_portal_close");
            return Ok(false);
        }

        // Belt-and-suspenders: only Press for navigation (ESC Release handled in main loop + above).
        if key.kind != KeyEventKind::Press {
            return Ok(false);
        }

        // First scan (no cached result): wait before Enter; still allow ↑↓ so the UI matches behavior.
        if self.state.setup_portal.detecting && self.state.setup_portal.detection_result.is_none() {
            if matches!(key.code, KeyCode::Enter) {
                return Ok(false);
            }
        }

        match key.code {
            // F2 - Go directly to Connection Portal (same pre-fill + reset as Enter)
            KeyCode::F(2) => {
                self.open_connection_portal_from_setup();
                self.add_log("[NAV] Opening Connection Portal (F2)...".to_string());
                self.request_immediate_render("open_connection_portal");
            }

            // Up/Down - Navigate options (immediate render so windowed terminals always redraw)
            KeyCode::Up => {
                self.state.setup_portal.cycle_option(-1);
                self.request_immediate_render("setup_nav_up");
            }

            KeyCode::Down => {
                self.state.setup_portal.cycle_option(1);
                self.request_immediate_render("setup_nav_down");
            }

            // Enter - Connection Portal with pre-filled URL and fresh portal state
            KeyCode::Enter => {
                self.state.setup_portal.error = None;
                self.open_connection_portal_from_setup();

                self.add_log(format!(
                    "[SETUP] Selected {} - Opening Connection Portal",
                    self.state.setup_portal.selected_option.as_str()
                ));
                self.request_immediate_render("setup_enter");
            }

            _ => {}
        }

        Ok(false)
    }
}
