use anyhow::Result;
use crossterm::cursor;
use crossterm::event::{self, Event, KeyCode, KeyEventKind, MouseEventKind};
use crossterm::execute;
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
    LeaveAlternateScreen, SetTitle,
};
use ratatui::prelude::*;
use std::time::{Duration, Instant};

mod app;
mod debug_log;
mod io;
mod monitoring;
mod screens;
mod storage;
mod ui;
mod websocket;

use app::{App, OperationMode};
use io::IoHandler;
use websocket::{WebSocketClient, WsClientMessage};

/// Default local WebSocket port for TUI ↔ Node (keep in sync with `DEFAULT_TUI_WS_PORT` in `packages/os-cli/src/tui-defaults.ts`).
const DEFAULT_TUI_WS_PORT: u16 = 8081;

fn sanitize_monitoring_line(raw: &str) -> String {
    let mut line: String = raw
        .chars()
        .map(|ch| match ch {
            '\r' | '\n' => ' ',
            ch if ch.is_control() && ch != '\t' => ' ',
            ch => ch,
        })
        .collect();
    if line.chars().count() > 500 {
        line = line.chars().take(500).collect();
        line.push('…');
    }
    line
}

fn main() -> Result<()> {
    // Window / tab title (otherwise hosts often show the executable name, e.g. mk3-tui)
    let window_title = match std::env::var("4RUNR_CLI_VERSION") {
        Ok(v) if !v.trim().is_empty() => format!("4Runr OS v{}", v.trim()),
        _ => "4Runr OS".to_string(),
    };

    // Setup terminal (boot screen is now part of TUI)
    enable_raw_mode()?;
    let mut stdout = std::io::stdout();

    // Enter alternate screen, hide cursor, clear screen, move to top-left
    execute!(
        stdout,
        EnterAlternateScreen,
        SetTitle(window_title),
        cursor::Hide,
        Clear(ClearType::All),
        cursor::MoveTo(0, 0)
    )?;

    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    // Clear the terminal buffer to ensure no artifacts
    terminal.clear()?;

    // Create app
    let mut app = App::new();
    let mut io_handler = IoHandler::new()?;

    // Create runtime for async WebSocket client
    let rt = tokio::runtime::Runtime::new()?;

    // Get TUI port from environment variable or default (see DEFAULT_TUI_WS_PORT)
    let tui_port = std::env::var("TUI_PORT")
        .ok()
        .and_then(|p| p.parse::<u16>().ok())
        .unwrap_or(DEFAULT_TUI_WS_PORT);

    // Try to connect to WebSocket server
    let ws_url = format!("ws://localhost:{}", tui_port);
    let ws_client = rt.block_on(async {
        match WebSocketClient::connect(&ws_url).await {
            Ok(client) => {
                eprintln!("[4Runr] Connected to WebSocket server");
                Some(client)
            }
            Err(e) => {
                eprintln!("[4Runr] WebSocket connection failed: {}", e);
                eprintln!("[4Runr] Falling back to stdio mode");
                None
            }
        }
    });

    // Track real time for uptime (not from tick counter)
    let start_time = Instant::now();
    let mut last_tick = Instant::now();
    let mut portal_connect_ui_tick = Instant::now();

    // Track previous screen to detect portal navigation
    let mut previous_screen: Option<crate::screens::Screen> = None;
    // Last terminal size while Connection or Setup portal is visible (windowed hosts: sync buffer + clear on dimension drift).
    let mut standalone_portal_last_terminal_dims: Option<(u16, u16)> = None;

    // Force initial render
    app.request_render("initial");

    // Main loop - CRITICAL FIX: Animations run EVERY iteration, not just when no input!
    // Per instructions Bug 1 fix: Move tick() and uptime OUTSIDE poll block
    loop {
        // ┌─────────────────────────────────────────────────────────┐
        // │ STEP 1: UPDATE ANIMATIONS (runs EVERY loop iteration)  │
        // └─────────────────────────────────────────────────────────┘

        // Check if portal is active - if so, skip base screen state updates
        // This prevents base screen from affecting portal performance
        use crate::screens::Screen;
        let current_screen = app.state.navigation.current_screen().clone();
        let is_portal_active = matches!(
            current_screen,
            Screen::ConnectionPortal | Screen::SetupPortal | Screen::PortalMonitoring
        );

        // DEBUG: Log portal detection
        #[cfg(debug_assertions)]
        {
            static mut LOG_COUNT: u64 = 0;
            unsafe {
                LOG_COUNT += 1;
                if LOG_COUNT % 100 == 0 {
                    eprintln!(
                        "[MAIN] Current screen: {:?}, Is portal active: {}",
                        current_screen, is_portal_active
                    );
                    eprintln!(
                        "[MAIN] Base screen: {:?}, Overlay stack: {:?}",
                        app.state.navigation.base_screen, app.state.navigation.overlay_stack
                    );
                }
            }
        }

        // Only update base screen state when NOT in a portal
        // Portals are standalone screens and don't need base screen updates
        if !is_portal_active {
            // Uptime - always update from real clock (OUTSIDE poll block)
            app.state.uptime_secs = start_time.elapsed().as_secs();

            // Spinner - update on timer (every 150ms) - OUTSIDE poll block
            if last_tick.elapsed() >= Duration::from_millis(150) {
                app.tick(); // Advances spinner_frame
                last_tick = Instant::now();
                app.request_render("animation_tick"); // Request render for animation update
            }
        }

        // Connection Portal: redraw while waiting on gateway.connect so the elapsed timer advances
        if matches!(current_screen, Screen::ConnectionPortal)
            && app.state.connection_portal.connecting
        {
            if portal_connect_ui_tick.elapsed() >= Duration::from_millis(500) {
                portal_connect_ui_tick = Instant::now();
                app.request_immediate_render("portal_connecting_tick");
            }
        }

        // Portal Monitoring: auto-refresh every 5 seconds when enabled
        if matches!(current_screen, Screen::PortalMonitoring) {
            let should_auto_refresh = {
                let pm = &app.state.portal_monitoring;
                pm.auto_refresh_enabled
                    && !pm.loading
                    && pm
                        .last_refresh
                        .map(|lr| lr.elapsed() >= pm.auto_refresh_interval)
                        .unwrap_or(true)
            };

            if should_auto_refresh {
                if let Some(ws) = &ws_client {
                    app.state.portal_monitoring.last_refresh = Some(Instant::now());
                    app.begin_portal_observability_request(ws);
                }
            }
        }

        // Shield dashboard: live poll shield.load every ~5s when enabled
        if matches!(current_screen, Screen::ShieldDashboard)
            && app.state.operation_mode == OperationMode::Connected
        {
            let shield_poll = {
                let sd = &app.state.shield_dashboard;
                if !sd.auto_refresh_enabled || sd.loading {
                    false
                } else {
                    sd.last_refresh
                        .map(|lr| lr.elapsed() >= sd.auto_refresh_interval)
                        .unwrap_or(true)
                }
            };
            if shield_poll {
                if let Some(ws) = &ws_client {
                    app.begin_shield_load_request(ws, false);
                }
            }

            // Loading timeout — never spin forever if CLI deduped or dropped a response
            let loading_stuck = {
                let sd = &app.state.shield_dashboard;
                sd.loading
                    && sd
                        .loading_started
                        .map(|t| t.elapsed() >= std::time::Duration::from_secs(12))
                        .unwrap_or(false)
            };
            if loading_stuck {
                app.state.shield_dashboard.fail_loading(
                    "Shield load timed out — press R to refresh.".to_string(),
                );
                app.state.pending_shield_load_id = None;
                app.request_render("shield_load_timeout");
            }
        }

        // Run Manager: live poll run.list every ~3s when enabled (no manual R)
        if matches!(current_screen, Screen::RunManager)
            && app.state.operation_mode == OperationMode::Connected
        {
            let run_poll = {
                let rm = &app.state.run_manager;
                if !rm.auto_refresh_enabled {
                    None
                } else {
                    let due = rm
                        .last_refresh
                        .map(|lr| lr.elapsed() >= rm.auto_refresh_interval)
                        .unwrap_or(true);
                    if !due {
                        None
                    } else {
                        let detail_run_id = rm.detail_run_id.clone();
                        Some(detail_run_id)
                    }
                }
            };
            if let Some(detail_run_id) = run_poll {
                if let Some(ws) = &ws_client {
                    app.state.run_manager.last_refresh = Some(Instant::now());
                    app.begin_run_list_request(ws, true);
                    if let Some(run_id) = detail_run_id {
                        app.begin_run_get_request(ws, &run_id);
                    }
                }
            }
        }

        // ┌─────────────────────────────────────────────────────────┐
        // │ STEP 2: CHECK WEBSOCKET MESSAGES                       │
        // └─────────────────────────────────────────────────────────┘
        if let Some(ref client) = ws_client {
            while let Some(msg) = client.try_recv() {
                match msg {
                    WsClientMessage::Connected => {
                        app.add_log("WebSocket connected".to_string());

                        // Step 4.9: Send startup commands to load real data
                        if let Ok(id) = client.send_command("system.status", None) {
                            app.add_log(format!(
                                "Sent system.status (id: {})",
                                &id[id.len().saturating_sub(8)..]
                            ));
                        }
                        if let Ok(id) = client.send_command("agent.list", None) {
                            app.add_log(format!(
                                "Sent agent.list (id: {})",
                                &id[id.len().saturating_sub(8)..]
                            ));
                        }
                    }
                    WsClientMessage::Disconnected => {
                        app.add_log(
                            "[WEBSOCKET] Disconnected from CLI backend — Gateway link cleared"
                                .to_string(),
                        );
                        app.on_cli_backend_disconnect();
                    }
                    WsClientMessage::ConnectionLost => {
                        app.add_log("[WEBSOCKET] Connection lost — Gateway link cleared (reconnect may restore)".to_string());
                        app.on_cli_backend_disconnect();
                    }
                    WsClientMessage::Reconnecting => {
                        app.add_log("[WEBSOCKET] Reconnecting...".to_string());
                    }
                    WsClientMessage::ReconnectFailed(reason) => {
                        app.add_log(format!("[WEBSOCKET] Reconnection failed: {}", reason));
                    }
                    WsClientMessage::Response(resp) => {
                        if resp.payload.success {
                            // Format response nicely and update state based on command
                            let short_id = &resp.id[resp.id.len().saturating_sub(8)..];

                            // Try to extract and apply data to state
                            if let Some(data) = &resp.payload.data {
                                if let Some(obj) = data.as_object() {
                                    // Handle system.status response
                                    if let Some(mode) = obj.get("mode") {
                                        if let Some(mode_str) = mode.as_str() {
                                            app.state.network_status = mode_str.to_string();
                                        }
                                        if let Some(posture) = obj.get("posture") {
                                            if let Some(posture_str) = posture.as_str() {
                                                app.state.posture_status = posture_str.to_string();
                                            }
                                        }
                                        if let Some(sh) =
                                            obj.get("shield").and_then(|v| v.as_object())
                                        {
                                            if let Some(m) =
                                                sh.get("mode").and_then(|v| v.as_str())
                                            {
                                                app.state.shield_mode = m.to_string();
                                            }
                                            if let Some(dets) =
                                                sh.get("detectors").and_then(|v| v.as_array())
                                            {
                                                app.state.shield_detectors = dets
                                                    .iter()
                                                    .filter_map(|v| {
                                                        v.as_str().map(|s| s.to_string())
                                                    })
                                                    .collect();
                                            }
                                            app.state.shield_blocks_total = sh
                                                .get("blocksTotal")
                                                .and_then(|v| v.as_u64())
                                                .unwrap_or(0);
                                            app.state.shield_masks_total = sh
                                                .get("masksTotal")
                                                .and_then(|v| v.as_u64())
                                                .unwrap_or(0);
                                        }
                                        if let Some(sent) =
                                            obj.get("sentinel").and_then(|v| v.as_object())
                                        {
                                            app.state.sentinel_active_runs = sent
                                                .get("watchedRuns")
                                                .and_then(|v| v.as_u64())
                                                .unwrap_or(0) as usize;
                                            let enabled = sent
                                                .get("enabled")
                                                .and_then(|v| v.as_bool())
                                                .unwrap_or(false);
                                            let healthy = sent
                                                .get("healthy")
                                                .and_then(|v| v.as_bool())
                                                .unwrap_or(false);
                                            app.state.sentinel_state = if !enabled {
                                                "off".to_string()
                                            } else if app.state.sentinel_active_runs > 0 {
                                                "watching".to_string()
                                            } else if healthy {
                                                "idle".to_string()
                                            } else {
                                                "degraded".to_string()
                                            };
                                        }
                                        app.add_log(format!(
                                            "✓ [{}] Mode: {}, Posture: {}",
                                            short_id,
                                            mode.as_str().unwrap_or("?"),
                                            obj.get("posture")
                                                .and_then(|p| p.as_str())
                                                .unwrap_or("?")
                                        ));
                                    }
                                    // Shield dashboard — match pending id before legacy shieldStatus branch
                                    else if Some(&resp.id)
                                        == app.state.pending_shield_load_id.as_ref()
                                    {
                                        app.state.pending_shield_load_id = None;
                                        if obj.get("shieldLoad").and_then(|v| v.as_bool()) == Some(true)
                                        {
                                            use crate::ui::shield_dashboard::{
                                                parse_shield_activity, parse_shield_breakdown,
                                                parse_shield_load,
                                            };
                                            let health_val = obj
                                                .get("health")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let config_val = obj
                                                .get("config")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let metrics_val = obj
                                                .get("metrics")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let gateway_health_val = obj
                                                .get("gatewayHealth")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let runs_val = obj
                                                .get("recentRuns")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let warnings_vec: Vec<String> = obj
                                                .get("warnings")
                                                .and_then(|v| v.as_array())
                                                .map(|a| {
                                                    a.iter()
                                                        .filter_map(|w| {
                                                            w.as_str().map(|s| s.to_string())
                                                        })
                                                        .collect()
                                                })
                                                .unwrap_or_default();
                                            let activity_val = obj
                                                .get("activity")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let breakdown_val = obj
                                                .get("shieldBreakdown")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let snapshot_at = obj
                                                .get("snapshotAt")
                                                .and_then(|v| v.as_str())
                                                .unwrap_or("")
                                                .to_string();
                                            let (h, c, m, mut w, recent) = parse_shield_load(
                                                &health_val,
                                                &config_val,
                                                &metrics_val,
                                                &gateway_health_val,
                                                &runs_val,
                                            );
                                            w.extend(warnings_vec);
                                            let activity = parse_shield_activity(&activity_val);
                                            let breakdown_lines =
                                                parse_shield_breakdown(&breakdown_val);
                                            app.state.shield_dashboard.apply_load(
                                                &h,
                                                &c,
                                                &m,
                                                w,
                                                recent,
                                                activity,
                                                breakdown_lines,
                                                snapshot_at,
                                            );
                                            app.state.shield_mode = h.mode.clone();
                                            app.state.shield_blocks_total = m.blocks;
                                            app.state.shield_masks_total = m.masks;
                                            app.add_log(format!(
                                                "✓ [{}] Shield loaded — {} block(s), {} mask(s)",
                                                short_id, m.blocks, m.masks
                                            ));
                                        } else {
                                            app.state.shield_dashboard.fail_loading(
                                                "Shield load: unexpected response shape".to_string(),
                                            );
                                        }
                                        app.request_render("shield_loaded");
                                    }
                                    else if obj.get("shieldDemo").and_then(|v| v.as_bool())
                                        == Some(true)
                                    {
                                        let msg = obj
                                            .get("message")
                                            .and_then(|v| v.as_str())
                                            .unwrap_or("Shield demo run started");
                                        app.add_log(format!("✓ [{}] {}", short_id, msg));
                                        if let Some(rid) =
                                            obj.get("runId").and_then(|v| v.as_str())
                                        {
                                            app.add_log(format!(
                                                "[SHIELD] Demo run id: {} — open Run Manager",
                                                rid
                                            ));
                                        }
                                    }
                                    else if obj.get("shieldProbe").and_then(|v| v.as_bool())
                                        == Some(true)
                                    {
                                        let result = obj.get("result");
                                        let action = result
                                            .and_then(|r| r.get("decision"))
                                            .and_then(|d| d.get("action"))
                                            .and_then(|a| a.as_str())
                                            .unwrap_or("?");
                                        app.add_log(format!(
                                            "✓ [{}] Shield probe: action={}",
                                            short_id, action
                                        ));
                                    }
                                    else if obj.get("shieldStatus").and_then(|v| v.as_bool())
                                        == Some(true)
                                    {
                                        let metrics = obj.get("metrics").and_then(|v| v.as_object());
                                        let blocks = metrics
                                            .and_then(|m| m.get("blocks"))
                                            .and_then(|v| v.as_u64())
                                            .unwrap_or(0);
                                        let masks = metrics
                                            .and_then(|m| m.get("masks"))
                                            .and_then(|v| v.as_u64())
                                            .unwrap_or(0);
                                        app.state.shield_blocks_total = blocks;
                                        app.state.shield_masks_total = masks;
                                        let mode = obj
                                            .get("health")
                                            .and_then(|h| h.get("mode"))
                                            .and_then(|v| v.as_str())
                                            .unwrap_or("?");
                                        app.add_log(format!(
                                            "✓ [{}] Shield {} — {} block(s), {} mask(s)",
                                            short_id, mode, blocks, masks
                                        ));
                                    }
                                    // Handle agent.list response
                                    else if let Some(agents_data) = obj.get("agents") {
                                        if let Some(agents_array) = agents_data.as_array() {
                                            // Parse agents into AgentInfo structs
                                            use crate::app::AgentInfo;
                                            let agents: Vec<AgentInfo> = agents_array
                                                .iter()
                                                .filter_map(|agent| {
                                                    let obj = agent.as_object()?;
                                                    Some(AgentInfo {
                                                        name: obj
                                                            .get("name")?
                                                            .as_str()?
                                                            .to_string(),
                                                        description: obj
                                                            .get("description")
                                                            .and_then(|d| d.as_str())
                                                            .map(|s| s.to_string()),
                                                        model: obj
                                                            .get("model")
                                                            .and_then(|m| m.as_str())
                                                            .unwrap_or("unknown")
                                                            .to_string(),
                                                        provider: obj
                                                            .get("provider")
                                                            .and_then(|p| p.as_str())
                                                            .unwrap_or("unknown")
                                                            .to_string(),
                                                        system_prompt: obj
                                                            .get("systemPrompt")
                                                            .and_then(|sp| sp.as_str())
                                                            .map(|s| s.to_string()),
                                                        temperature: obj
                                                            .get("temperature")
                                                            .and_then(|t| t.as_f64())
                                                            .map(|f| f as f32),
                                                        max_tokens: obj
                                                            .get("maxTokens")
                                                            .and_then(|mt| mt.as_u64())
                                                            .map(|u| u as u32),
                                                        tools: obj
                                                            .get("tools")
                                                            .and_then(|t| t.as_array())
                                                            .map(|arr| {
                                                                arr.iter()
                                                                    .filter_map(|v| {
                                                                        v.as_str()
                                                                            .map(|s| s.to_string())
                                                                    })
                                                                    .collect()
                                                            })
                                                            .unwrap_or_default(),
                                                    })
                                                })
                                                .collect();

                                            // Update capabilities with agent names
                                            app.state.capabilities =
                                                agents.iter().map(|a| a.name.clone()).collect();

                                            // Update cache with agent data
                                            if let Some(cache) = &mut app.state.cache {
                                                use crate::storage::cache::AgentData;
                                                use std::time::{SystemTime, UNIX_EPOCH};

                                                let cache_agents: Vec<AgentData> = agents
                                                    .iter()
                                                    .map(|agent| AgentData {
                                                        name: agent.name.clone(),
                                                        description: agent.description.clone(),
                                                        model: agent.model.clone(),
                                                        provider: agent.provider.clone(),
                                                        created_at: SystemTime::now()
                                                            .duration_since(UNIX_EPOCH)
                                                            .unwrap()
                                                            .as_secs(),
                                                    })
                                                    .collect();

                                                let _ = cache.update_agents(cache_agents);
                                            }

                                            // Update agent list state
                                            app.state.agent_list.agents = agents;
                                            app.state.agent_list.selected_index = 0;
                                            app.state.agent_list.detail_view = None;

                                            // Clear pending agent.list ID if this response matches (Step 5.2)
                                            if Some(&resp.id)
                                                == app.state.pending_agent_list_id.as_ref()
                                            {
                                                app.state.pending_agent_list_id = None;
                                            }

                                            // Only open AgentList overlay if we're on Main screen (not during boot)
                                            use crate::screens::Screen;
                                            if app.state.navigation.current_screen()
                                                == &Screen::Main
                                            {
                                                app.push_overlay(Screen::AgentList);
                                                app.request_render("agent_list_opened");
                                            }
                                        }
                                    }
                                    // Handle agent.create response (Step 5.1)
                                    else if let Some(agent) = obj.get("agent") {
                                        // Check if this is a response to our agent.create command
                                        if Some(&resp.id)
                                            == app.state.pending_agent_create_id.as_ref()
                                        {
                                            app.state.pending_agent_create_id = None;

                                            // Extract agent name for success message
                                            let agent_name = agent
                                                .as_object()
                                                .and_then(|a| a.get("name"))
                                                .and_then(|n| n.as_str())
                                                .unwrap_or("unknown");

                                            app.add_log(format!(
                                                "✓ [{}] Agent '{}' created successfully",
                                                short_id, agent_name
                                            ));

                                            // Refresh agent list (Step 5.3)
                                            if let Some(ref ws) = ws_client {
                                                if let Ok(list_id) =
                                                    ws.send_command("agent.list", None)
                                                {
                                                    app.state.pending_agent_list_id = Some(list_id);
                                                }
                                            }
                                        } else {
                                            app.add_log(format!(
                                                "✓ [{}] Agent response received",
                                                short_id
                                            ));
                                        }
                                    }
                                    // Handle agent.delete response (Step 5.5)
                                    else if let Some(deleted) = obj.get("deleted") {
                                        if deleted.as_bool().unwrap_or(false) {
                                            if Some(&resp.id)
                                                == app.state.pending_agent_delete_id.as_ref()
                                            {
                                                app.state.pending_agent_delete_id = None;
                                                app.add_log(format!(
                                                    "✓ [{}] Agent deleted successfully",
                                                    short_id
                                                ));

                                                // Refresh agent list (Step 5.5)
                                                if let Some(ref ws) = ws_client {
                                                    if let Ok(list_id) =
                                                        ws.send_command("agent.list", None)
                                                    {
                                                        app.state.pending_agent_list_id =
                                                            Some(list_id);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    // Step 6: run.list (match pending id first — avoids stealing the chain on unrelated `runs` keys)
                                    else if Some(&resp.id)
                                        == app.state.pending_run_list_id.as_ref()
                                    {
                                        let silent = app.state.pending_run_list_silent;
                                        app.state.pending_run_list_id = None;
                                        app.state.pending_run_list_silent = false;
                                        app.state.run_manager.loading = false;
                                        if let Some(runs_arr) =
                                            obj.get("runs").and_then(|r| r.as_array())
                                        {
                                            use crate::ui::run_manager::run_info_from_gateway_value;
                                            let mut parsed = Vec::new();
                                            for item in runs_arr {
                                                if let Some(info) =
                                                    run_info_from_gateway_value(item)
                                                {
                                                    parsed.push(info);
                                                }
                                            }
                                            let preserve =
                                                !app.state.run_manager.runs.is_empty();
                                            if preserve {
                                                app.state
                                                    .run_manager
                                                    .replace_runs_preserve(parsed);
                                            } else {
                                                app.state.run_manager.replace_runs(parsed);
                                            }
                                            if !silent {
                                                app.add_log(format!(
                                                    "✓ [{}] Loaded {} run(s) from Gateway",
                                                    short_id,
                                                    app.state.run_manager.runs.len()
                                                ));
                                            }
                                            app.request_render("run_list_loaded");
                                        } else {
                                            app.add_log(format!(
                                                "⚠ [{}] run.list: missing or invalid `runs` array",
                                                short_id
                                            ));
                                        }
                                    }
                                    // Step 6: run.get (detail) — pending id first so `run` never blocks other handlers
                                    else if Some(&resp.id)
                                        == app.state.pending_run_get_id.as_ref()
                                    {
                                        app.state.pending_run_get_id = None;
                                        if let Some(run_val) = obj.get("run") {
                                            use crate::ui::run_manager::run_info_from_gateway_value;
                                            if let Some(info) = run_info_from_gateway_value(run_val)
                                            {
                                                app.state.run_manager.merge_run_detail(info);
                                            }
                                        }
                                        app.request_render("run_detail_loaded");
                                    }
                                    // Step 6: run.cancel
                                    else if Some(&resp.id)
                                        == app.state.pending_run_cancel_id.as_ref()
                                    {
                                        app.state.pending_run_cancel_id = None;
                                        if let (Some(rid), Some(st)) = (
                                            obj.get("runId").and_then(|v| v.as_str()),
                                            obj.get("status").and_then(|v| v.as_str()),
                                        ) {
                                            app.add_log(format!(
                                                "✓ [{}] Run {} cancelled ({})",
                                                short_id, rid, st
                                            ));
                                        } else {
                                            app.add_log(format!(
                                                "✓ [{}] Run cancel acknowledged",
                                                short_id
                                            ));
                                        }
                                        if let Some(ref ws) = ws_client {
                                            let data = serde_json::json!({ "limit": 50 });
                                            if let Ok(lid) = ws.send_command("run.list", Some(data))
                                            {
                                                app.state.pending_run_list_id = Some(lid);
                                            }
                                        }
                                        app.request_render("run_cancelled");
                                    }
                                    // Step 6: run.quick
                                    else if Some(&resp.id)
                                        == app.state.pending_run_quick_id.as_ref()
                                    {
                                        app.state.pending_run_quick_id = None;
                                        if let (Some(rid), Some(st)) = (
                                            obj.get("runId").and_then(|v| v.as_str()),
                                            obj.get("status").and_then(|v| v.as_str()),
                                        ) {
                                            app.add_log(format!(
                                                "✓ [{}] Run {} queued ({})",
                                                short_id, rid, st
                                            ));
                                        } else {
                                            app.add_log(format!("✓ [{}] Run queued", short_id));
                                        }
                                        if let Some(ref ws) = ws_client {
                                            let data = serde_json::json!({ "limit": 50 });
                                            if let Ok(lid) = ws.send_command("run.list", Some(data))
                                            {
                                                app.state.pending_run_list_id = Some(lid);
                                            }
                                        }
                                        app.request_render("run_quick_ok");
                                    }
                                    // Sentinel config: load templates + current + health
                                    else if Some(&resp.id)
                                        == app.state.pending_sentinel_load_id.as_ref()
                                    {
                                        app.state.pending_sentinel_load_id = None;
                                        if obj.get("sentinelLoad").and_then(|v| v.as_bool()) == Some(true)
                                        {
                                            use crate::ui::sentinel_config::{
                                                parse_current_config, parse_health,
                                                parse_sentinel_load,
                                            };
                                            let templates_vec: Vec<serde_json::Value> = obj
                                                .get("templates")
                                                .and_then(|v| v.as_array())
                                                .map(|a| a.clone())
                                                .unwrap_or_default();
                                            let current_val = obj
                                                .get("current")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let health_val = obj
                                                .get("health")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let (rows, cur, h) = parse_sentinel_load(
                                                &templates_vec,
                                                &current_val,
                                                &health_val,
                                            );
                                            app.state.sentinel_config.apply_load(rows, &cur, &h);
                                            app.state.sentinel_active_runs = h.watched_runs as usize;
                                            app.state.sentinel_state = if h.watched_runs > 0 {
                                                "watching".to_string()
                                            } else if h.status == "healthy" {
                                                "ready".to_string()
                                            } else {
                                                h.status.clone()
                                            };
                                            app.add_log(format!(
                                                "✓ [{}] Sentinel config loaded (idle {}ms)",
                                                short_id, cur.idle_ms
                                            ));
                                        }
                                        app.request_render("sentinel_loaded");
                                    }
                                    // Sentinel config: apply template
                                    else if Some(&resp.id)
                                        == app.state.pending_sentinel_apply_id.as_ref()
                                    {
                                        app.state.pending_sentinel_apply_id = None;
                                        if obj.get("sentinelApply").and_then(|v| v.as_bool())
                                            == Some(true)
                                        {
                                            use crate::ui::sentinel_config::parse_current_config;
                                            let current_val = obj
                                                .get("current")
                                                .cloned()
                                                .unwrap_or(serde_json::Value::Null);
                                            let cur = parse_current_config(&current_val);
                                            let msg = obj
                                                .get("message")
                                                .and_then(|v| v.as_str())
                                                .map(|s| s.to_string());
                                            app.state
                                                .sentinel_config
                                                .apply_current(&cur, msg.clone());
                                            if let Some(m) = msg {
                                                app.add_log(format!("✓ [{}] {}", short_id, m));
                                            }
                                        }
                                        app.request_render("sentinel_applied");
                                    }
                                    // Portal Monitoring Phase 2: monitoring.refresh (section slice)
                                    else if Some(&resp.id)
                                        == app.state.pending_monitoring_refresh_id.as_ref()
                                    {
                                        app.state.pending_monitoring_refresh_id = None;
                                        app.state.portal_monitoring.section_refresh_loading = None;
                                        if obj.get("monitoringRefresh").and_then(|v| v.as_bool())
                                            == Some(true)
                                        {
                                            let section_str = obj
                                                .get("section")
                                                .and_then(|v| v.as_str())
                                                .unwrap_or("");
                                            use crate::monitoring::MonitoringSection;
                                            if let Some(sec) =
                                                MonitoringSection::from_cli_slug(section_str)
                                            {
                                                let lines: Vec<String> = obj
                                                    .get("lines")
                                                    .and_then(|a| a.as_array())
                                                    .map(|arr| {
                                                        arr.iter()
                                                            .filter_map(|v| {
                                                                v.as_str()
                                                                    .map(sanitize_monitoring_line)
                                                            })
                                                            .collect()
                                                    })
                                                    .unwrap_or_default();
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(sec, lines);
                                            }
                                        }
                                        app.request_render("monitoring_refresh_ok");
                                    }
                                    // Portal Monitoring Phase 2: monitoring.logs
                                    else if Some(&resp.id)
                                        == app.state.pending_monitoring_logs_id.as_ref()
                                    {
                                        app.state.pending_monitoring_logs_id = None;
                                        app.state.portal_monitoring.logs_fetch_loading = false;
                                        if obj.get("monitoringLogs").and_then(|v| v.as_bool())
                                            == Some(true)
                                        {
                                            let lines: Vec<String> = obj
                                                .get("logs")
                                                .and_then(|a| a.as_array())
                                                .map(|arr| {
                                                    arr.iter()
                                                        .filter_map(|entry| {
                                                            let e = entry.as_object()?;
                                                            let ts = e
                                                                .get("timestamp")
                                                                .and_then(|v| v.as_str())
                                                                .unwrap_or("");
                                                            let lvl = e
                                                                .get("level")
                                                                .and_then(|v| v.as_str())
                                                                .unwrap_or("");
                                                            let msg = e
                                                                .get("message")
                                                                .and_then(|v| v.as_str())
                                                                .unwrap_or("");
                                                            if ts.is_empty()
                                                                && lvl.is_empty()
                                                                && msg.is_empty()
                                                            {
                                                                return None;
                                                            }
                                                            Some(sanitize_monitoring_line(
                                                                &format!(
                                                                    "{} [{}] {}",
                                                                    ts, lvl, msg
                                                                ),
                                                            ))
                                                        })
                                                        .collect()
                                                })
                                                .unwrap_or_default();
                                            use crate::monitoring::MonitoringSection;
                                            app.state
                                                .portal_monitoring
                                                .section_overrides
                                                .insert(MonitoringSection::Logs, lines);
                                        }
                                        app.request_render("monitoring_logs_ok");
                                    }
                                    // Portal Monitoring Phase 3: monitoring.drill (metrics sub-panels / dependency detail)
                                    else if Some(&resp.id)
                                        == app.state.pending_monitoring_drill_id.as_ref()
                                    {
                                        app.state.pending_monitoring_drill_id = None;
                                        app.state.portal_monitoring.metrics_drill_loading = false;
                                        app.state.portal_monitoring.section_refresh_loading = None;
                                        if obj.get("monitoringDrill").and_then(|v| v.as_bool())
                                            == Some(true)
                                        {
                                            let target = obj
                                                .get("target")
                                                .and_then(|v| v.as_str())
                                                .unwrap_or("");
                                            let lines: Vec<String> = obj
                                                .get("lines")
                                                .and_then(|a| a.as_array())
                                                .map(|arr| {
                                                    arr.iter()
                                                        .filter_map(|v| {
                                                            v.as_str().map(sanitize_monitoring_line)
                                                        })
                                                        .collect()
                                                })
                                                .unwrap_or_default();
                                            use crate::monitoring::{
                                                MetricsDrillPanel, MonitoringSection,
                                            };
                                            if target == "dependencies_detail" {
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(MonitoringSection::Dependencies, lines);
                                            } else if target == "system_diagnostics" {
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(MonitoringSection::System, lines);
                                            } else if let Some(panel) =
                                                MetricsDrillPanel::from_cli_target(target)
                                            {
                                                app.state.portal_monitoring.metrics_drill =
                                                    Some(panel);
                                                app.state.portal_monitoring.metrics_drill_lines =
                                                    lines;
                                            }
                                        }
                                        app.request_render("monitoring_drill_ok");
                                    }
                                    // Portal Monitoring: gateway.observability (/metrics snapshot)
                                    else if Some(&resp.id)
                                        == app.state.pending_gateway_observability_id.as_ref()
                                    {
                                        app.state.pending_gateway_observability_id = None;
                                        app.state.portal_monitoring.loading = false;
                                        app.state.portal_observability_last_poll =
                                            Some(Instant::now());
                                        if obj.get("observability").and_then(|v| v.as_bool())
                                            == Some(true)
                                        {
                                            let snapshot = obj
                                                .get("snapshotAt")
                                                .and_then(|v| v.as_str())
                                                .unwrap_or("")
                                                .to_string();
                                            // Snapshot markers — keep substring order compatible with
                                            // `portal_monitoring::SNAPSHOT_MARKER_*` (CLI healthLines + layout below).
                                            let mut lines: Vec<String> = vec![
                                                "Portal snapshot (health + metrics on each refresh / auto poll)."
                                                    .to_string(),
                                                format!("Fetched: {}", if snapshot.is_empty() { "—" } else { snapshot.as_str() }),
                                                String::new(),
                                            ];
                                            if let Some(tot) = obj
                                                .get("totals")
                                                .and_then(|v| v.get("httpRequests"))
                                            {
                                                let current = tot
                                                    .as_u64()
                                                    .or_else(|| tot.as_f64().map(|f| f as u64));
                                                if let Some(c) = current {
                                                    if let (Some(prev), Some(last_t)) = (
                                                        app.state
                                                            .portal_monitoring
                                                            .last_http_for_delta,
                                                        app.state
                                                            .portal_monitoring
                                                            .last_instant_for_delta,
                                                    ) {
                                                        let d = c.saturating_sub(prev);
                                                        let el =
                                                            last_t.elapsed().as_secs_f32().max(0.1);
                                                        let per_min = (d as f32 / el) * 60.0;
                                                        let pulse =
                                                            if d > 0 { "  *" } else { "  --" };
                                                        lines.push(format!(
                                                            "LIVE: +{} HTTP since last screen refresh (~{:.0}/min){}",
                                                            d, per_min, pulse
                                                        ));
                                                    } else {
                                                        lines.push("LIVE: first sample — next refresh shows HTTP request rate (poll interval).".to_string());
                                                    }
                                                    app.state
                                                        .portal_monitoring
                                                        .last_http_for_delta = Some(c);
                                                    app.state
                                                        .portal_monitoring
                                                        .last_instant_for_delta =
                                                        Some(std::time::Instant::now());
                                                    app.state
                                                        .portal_monitoring
                                                        .http_total_sparkline
                                                        .push_back(c);
                                                    while app
                                                        .state
                                                        .portal_monitoring
                                                        .http_total_sparkline
                                                        .len()
                                                        > 48
                                                    {
                                                        app.state
                                                            .portal_monitoring
                                                            .http_total_sparkline
                                                            .pop_front();
                                                    }
                                                }
                                            }
                                            if let Some(totals) =
                                                obj.get("totals").and_then(|v| v.as_object())
                                            {
                                                app.state
                                                    .portal_monitoring
                                                    .metric_history
                                                    .push_back(
                                                    crate::app::metric_history_entry_from_totals(
                                                        if snapshot.is_empty() {
                                                            "—".to_string()
                                                        } else {
                                                            snapshot.clone()
                                                        },
                                                        totals,
                                                    ),
                                                );
                                                while app
                                                    .state
                                                    .portal_monitoring
                                                    .metric_history
                                                    .len()
                                                    > 720
                                                {
                                                    app.state
                                                        .portal_monitoring
                                                        .metric_history
                                                        .pop_front();
                                                }
                                            }
                                            // Phase 5 v1: dependency alerts are text-derived from
                                            // observability health lines, not a structured readiness enum.
                                            let dependency_status = {
                                                let mut status = if obj
                                                    .get("healthOk")
                                                    .and_then(|v| v.as_bool())
                                                    == Some(false)
                                                {
                                                    "unhealthy"
                                                } else {
                                                    "healthy"
                                                };
                                                if let Some(arr) = obj
                                                    .get("healthLines")
                                                    .and_then(|a| a.as_array())
                                                {
                                                    for v in arr {
                                                        if let Some(s) = v.as_str() {
                                                            let l = s.to_lowercase();
                                                            if l.contains("✗")
                                                                || l.contains(": down")
                                                                || l.contains("not ready")
                                                            {
                                                                status = "unhealthy";
                                                                break;
                                                            }
                                                            if l.contains("⚠")
                                                                || l.contains("degraded")
                                                                || l.contains("warning")
                                                            {
                                                                status = "degraded";
                                                            }
                                                        }
                                                    }
                                                }
                                                status.to_string()
                                            };
                                            if let Some(prev) = app
                                                .state
                                                .portal_monitoring
                                                .last_dependency_status
                                                .clone()
                                            {
                                                if prev != dependency_status {
                                                    app.state
                                                        .portal_monitoring
                                                        .dependency_alerts
                                                        .push_back(
                                                            crate::app::DependencyAlertEntry {
                                                                timestamp: if snapshot.is_empty() {
                                                                    "—".to_string()
                                                                } else {
                                                                    snapshot.clone()
                                                                },
                                                                previous: prev,
                                                                current: dependency_status.clone(),
                                                            },
                                                        );
                                                    while app
                                                        .state
                                                        .portal_monitoring
                                                        .dependency_alerts
                                                        .len()
                                                        > 10
                                                    {
                                                        app.state
                                                            .portal_monitoring
                                                            .dependency_alerts
                                                            .pop_front();
                                                    }
                                                }
                                            }
                                            app.state.portal_monitoring.last_dependency_status =
                                                Some(dependency_status);
                                            lines.push(String::new());
                                            if let Some(arr) =
                                                obj.get("healthLines").and_then(|a| a.as_array())
                                            {
                                                for v in arr {
                                                    if let Some(s) = v.as_str() {
                                                        lines.push(s.to_string());
                                                    }
                                                }
                                                lines.push(String::new());
                                            }
                                            lines.push(
                                                "Prometheus /metrics (HTTP total includes this screen’s /health, /ready, and /metrics polls; add API/agent traffic for runs/queue/SSE)."
                                                    .to_string(),
                                            );
                                            lines.push(String::new());
                                            if let Some(arr) =
                                                obj.get("statsLines").and_then(|a| a.as_array())
                                            {
                                                for v in arr {
                                                    if let Some(s) = v.as_str() {
                                                        lines.push(s.to_string());
                                                    }
                                                }
                                            }
                                            lines.push(String::new());
                                            lines.push(
                                                "Top HTTP routes (from http_requests_total labels)"
                                                    .to_string(),
                                            );
                                            if let Some(arr) =
                                                obj.get("routeLines").and_then(|a| a.as_array())
                                            {
                                                if !arr.is_empty() {
                                                    lines.push(
                                                        "  (method route)                         count".to_string(),
                                                    );
                                                    for v in arr {
                                                        if let Some(s) = v.as_str() {
                                                            lines.push(format!("  {}", s));
                                                        }
                                                    }
                                                } else {
                                                    lines.push(
                                                        "  (no labeled requests yet — exercise the Gateway to populate.)"
                                                            .to_string(),
                                                    );
                                                }
                                            } else {
                                                lines.push(
                                                    "  (no route breakdown in response.)"
                                                        .to_string(),
                                                );
                                            }
                                            let preserve_scroll =
                                                app.state.portal_monitoring.scroll_offset;
                                            let preserve_metrics_drill =
                                                app.state.portal_monitoring.metrics_drill;
                                            let preserve_metrics_drill_lines = app
                                                .state
                                                .portal_monitoring
                                                .metrics_drill_lines
                                                .clone();
                                            let preserve_logs = app
                                                .state
                                                .portal_monitoring
                                                .section_overrides
                                                .get(&crate::monitoring::MonitoringSection::Logs)
                                                .cloned();
                                            // System diagnostics (`S`) and dependency drill (`T`) live in
                                            // `section_overrides`. Full observability runs as soon as no pending
                                            // drill id exists, so we must restore these or the UI "blinks closed".
                                            let preserve_system = app
                                                .state
                                                .portal_monitoring
                                                .section_overrides
                                                .get(
                                                    &crate::monitoring::MonitoringSection::System,
                                                )
                                                .cloned();
                                            let preserve_dependencies = app
                                                .state
                                                .portal_monitoring
                                                .section_overrides
                                                .get(
                                                    &crate::monitoring::MonitoringSection::Dependencies,
                                                )
                                                .cloned();
                                            app.state.portal_monitoring.last_updated =
                                                Some(snapshot);
                                            app.state.portal_monitoring.content_lines = lines;
                                            app.state.portal_monitoring.section_overrides.clear();
                                            if let Some(logs) = preserve_logs {
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(
                                                        crate::monitoring::MonitoringSection::Logs,
                                                        logs,
                                                    );
                                            }
                                            if let Some(system) = preserve_system {
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(
                                                        crate::monitoring::MonitoringSection::System,
                                                        system,
                                                    );
                                            }
                                            if let Some(deps) = preserve_dependencies {
                                                app.state
                                                    .portal_monitoring
                                                    .section_overrides
                                                    .insert(
                                                        crate::monitoring::MonitoringSection::Dependencies,
                                                        deps,
                                                    );
                                            }
                                            app.state.portal_monitoring.section_refresh_loading =
                                                None;
                                            app.state.portal_monitoring.logs_fetch_loading = false;
                                            app.state.portal_monitoring.metrics_drill =
                                                preserve_metrics_drill;
                                            app.state.portal_monitoring.metrics_drill_lines =
                                                preserve_metrics_drill_lines;
                                            app.state.portal_monitoring.metrics_drill_loading =
                                                false;
                                            app.state.portal_monitoring.error = None;
                                            app.state.portal_monitoring.scroll_offset =
                                                preserve_scroll;
                                        } else {
                                            app.state.portal_monitoring.error = Some(
                                                "Unexpected observability response.".to_string(),
                                            );
                                        }
                                        app.request_render("portal_observability_ok");
                                    }
                                    // Handle gateway.connect response (Step 7.5)
                                    else if let Some(connected) = obj.get("connected") {
                                        if connected.as_bool().unwrap_or(false) {
                                            if Some(&resp.id)
                                                == app.state.pending_gateway_connect_id.as_ref()
                                            {
                                                app.state.pending_gateway_connect_id = None;

                                                use crate::app::OperationMode;
                                                app.state.operation_mode = OperationMode::Connected;

                                                let url = obj
                                                    .get("url")
                                                    .and_then(|v| v.as_str())
                                                    .unwrap_or("unknown");

                                                app.state.gateway_url = Some(url.to_string());
                                                app.state.connection_portal.last_successful_url =
                                                    Some(url.to_string());
                                                app.state.connection_portal.finish_connecting();
                                                app.state.connection_portal.connection_success =
                                                    true; // Phase 1.3: Set success flag
                                                          // Dashboard "Demo Mode / NET" used `connected`, which was never set — align with Gateway link
                                                app.state.connected = true;

                                                // Parse activity log if present
                                                if let Some(activity_log) = obj.get("activityLog") {
                                                    if let Some(log_array) = activity_log.as_array()
                                                    {
                                                        app.state
                                                            .connection_portal
                                                            .activity_log
                                                            .clear();
                                                        for entry in log_array {
                                                            if let Some(entry_obj) =
                                                                entry.as_object()
                                                            {
                                                                let timestamp = entry_obj
                                                                    .get("timestamp")
                                                                    .and_then(|v| v.as_str())
                                                                    .unwrap_or("")
                                                                    .to_string();
                                                                let level_str = entry_obj
                                                                    .get("level")
                                                                    .and_then(|v| v.as_str())
                                                                    .unwrap_or("info");
                                                                let message = entry_obj
                                                                    .get("message")
                                                                    .and_then(|v| v.as_str())
                                                                    .unwrap_or("")
                                                                    .to_string();

                                                                use crate::app::LogLevel;
                                                                let level = match level_str {
                                                                    "success" => LogLevel::Success,
                                                                    "warning" => LogLevel::Warning,
                                                                    "error" => LogLevel::Error,
                                                                    _ => LogLevel::Info,
                                                                };

                                                                app.state
                                                                    .connection_portal
                                                                    .add_log_entry(
                                                                        timestamp, level, message,
                                                                    );
                                                            }
                                                        }
                                                    }
                                                }

                                                // Phase 1.2: Prominent connection success notification
                                                app.add_log(
                                                    "═══════════════════════════════════════════"
                                                        .to_string(),
                                                );
                                                app.add_log(format!("✅ CONNECTED TO GATEWAY"));
                                                app.add_log(format!("   URL: {}", url));
                                                app.add_log(format!("   Status: Connected"));
                                                app.add_log("   Note: connect checks /health + /ready; Shield/Sentinel run on the Gateway during jobs.".to_string());
                                                app.add_log(
                                                    "═══════════════════════════════════════════"
                                                        .to_string(),
                                                );
                                                app.add_log(format!(
                                                    "[MODE] Switched to CONNECTED mode"
                                                ));

                                                // Phase 2.1: Immediately verify connection with health check
                                                app.add_log(format!(
                                                    "[GATEWAY] Verifying connection health..."
                                                ));
                                                if let Some(ref ws) = ws_client {
                                                    if let Ok(health_id) =
                                                        ws.send_command("gateway.health", None)
                                                    {
                                                        app.state.pending_gateway_health_id =
                                                            Some(health_id);
                                                    }
                                                    // Refresh posture / NET labels from CLI (matches gateway-backed system.status)
                                                    let _ = ws.send_command("system.status", None);
                                                }

                                                // CRITICAL: Request render after state change to update UI
                                                // CRITICAL: Use immediate render for instant success display
                                                app.request_immediate_render(
                                                    "gateway_connect_success",
                                                );

                                                // Phase 1.3: Portal will show success state (user closes with ESC)
                                                // Don't auto-close - let user see success message
                                            }
                                        } else {
                                            // Handle disconnect response (Step 7.8)
                                            use crate::app::OperationMode;
                                            app.state.operation_mode = OperationMode::Local;
                                            app.state.gateway_url = None;
                                            app.state.connected = false;
                                            app.state.gateway_healthy = false;
                                            app.state.gateway_db_summary = None;
                                            app.add_log(format!(
                                                "✓ [{}] Disconnected from Gateway",
                                                short_id
                                            ));
                                            if let Some(msg) =
                                                obj.get("message").and_then(|v| v.as_str())
                                            {
                                                if !msg.is_empty() {
                                                    app.add_log(msg.to_string());
                                                }
                                            }
                                            app.add_log(format!("[MODE] Switched to LOCAL mode"));
                                            app.state.pending_gateway_observability_id = None;
                                            app.state.pending_monitoring_refresh_id = None;
                                            app.state.pending_monitoring_logs_id = None;
                                            app.state.pending_monitoring_drill_id = None;
                                            app.state.portal_observability_last_poll = None;
                                            app.state.portal_monitoring =
                                                crate::app::PortalMonitoringState::default();
                                            app.state.advanced_monitoring =
                                                crate::app::AdvancedMonitoringState::default();
                                            app.state.connection_portal.finish_connecting();
                                            app.state.connection_portal.connection_success = false;
                                            app.state.connection_portal.error = None;
                                            app.state.run_manager.clear_runs();
                                            if let Some(ref ws) = ws_client {
                                                let _ = ws.send_command("system.status", None);
                                            }
                                        }
                                    }
                                    // Handle setup.detect response
                                    else if let Some(local_bundle) = obj.get("localBundle") {
                                        if Some(&resp.id)
                                            == app.state.pending_setup_detect_id.as_ref()
                                        {
                                            app.state.pending_setup_detect_id = None;
                                            app.state.setup_portal.detecting = false;
                                            app.state.setup_portal.detecting_since = None;

                                            use crate::app::{
                                                BundleStatus, CloudStatus, DetectionResult,
                                            };

                                            let bundle_status = if let Some(bundle_obj) =
                                                local_bundle.as_object()
                                            {
                                                BundleStatus {
                                                    available: bundle_obj
                                                        .get("available")
                                                        .and_then(|v| v.as_bool())
                                                        .unwrap_or(false),
                                                    running: bundle_obj
                                                        .get("running")
                                                        .and_then(|v| v.as_bool())
                                                        .unwrap_or(false),
                                                    path: bundle_obj
                                                        .get("path")
                                                        .and_then(|v| v.as_str())
                                                        .map(|s| s.to_string()),
                                                }
                                            } else {
                                                app.state.setup_portal.error = Some(
                                                    "Invalid setup.detect response (localBundle). Try setup portal again.".into(),
                                                );
                                                BundleStatus {
                                                    available: false,
                                                    running: false,
                                                    path: None,
                                                }
                                            };

                                            let cloud_obj =
                                                obj.get("cloudServer").and_then(|v| v.as_object());
                                            let cloud_status = CloudStatus {
                                                available: cloud_obj
                                                    .and_then(|o| o.get("available"))
                                                    .and_then(|v| v.as_bool())
                                                    .unwrap_or(false),
                                                status: cloud_obj
                                                    .and_then(|o| o.get("status"))
                                                    .and_then(|v| v.as_str())
                                                    .unwrap_or("Unknown")
                                                    .to_string(),
                                            };

                                            app.state.setup_portal.detection_result =
                                                Some(DetectionResult {
                                                    local_bundle: bundle_status,
                                                    cloud_server: cloud_status,
                                                });

                                            app.add_log(
                                                "[SETUP] Gateway detection complete".to_string(),
                                            );
                                        }
                                    }
                                    // Phase 2.1: Handle gateway.health response
                                    else if let Some(healthy) = obj.get("healthy") {
                                        if Some(&resp.id)
                                            == app.state.pending_gateway_health_id.as_ref()
                                        {
                                            app.state.pending_gateway_health_id = None;

                                            let is_healthy = healthy.as_bool().unwrap_or(false);
                                            let latency = obj
                                                .get("latency")
                                                .and_then(|v| v.as_u64())
                                                .unwrap_or(0);
                                            let deps_ready =
                                                obj.get("ready").and_then(|v| v.as_bool());

                                            let mut db_summary: Option<String> = None;
                                            if let Some(h) =
                                                obj.get("health").and_then(|v| v.as_object())
                                            {
                                                if let Some(fr) =
                                                    h.get("fourRunrDb").and_then(|v| v.as_object())
                                                {
                                                    let mode = fr
                                                        .get("mode")
                                                        .and_then(|v| v.as_str())
                                                        .unwrap_or("?");
                                                    let pc = fr
                                                        .get("prismaConnected")
                                                        .and_then(|v| v.as_bool())
                                                        .unwrap_or(false);
                                                    db_summary = Some(format!(
                                                        "{} · Prisma {}",
                                                        mode,
                                                        if pc {
                                                            "connected"
                                                        } else {
                                                            "not connected"
                                                        }
                                                    ));
                                                }
                                            }
                                            app.state.gateway_db_summary = db_summary;

                                            if is_healthy {
                                                app.state.gateway_healthy = true;
                                                app.state.connection_portal.finish_connecting();
                                                let suffix = match deps_ready {
                                                    Some(true) => " — dependencies ready",
                                                    Some(false) => " — Gateway up; /ready not OK (see Portal Monitoring)",
                                                    None => "",
                                                };
                                                app.add_log(format!(
                                                    "✓ [{}] Gateway verified{} ({}ms)",
                                                    short_id, suffix, latency
                                                ));
                                            } else {
                                                app.state.gateway_healthy = false;
                                                app.state.gateway_db_summary = None;
                                                app.state.connection_portal.finish_connecting();
                                                let error = obj
                                                    .get("error")
                                                    .and_then(|v| v.as_str())
                                                    .unwrap_or("Unknown error");
                                                app.add_log(format!(
                                                    "⚠ [{}] Gateway health check failed: {} ({}ms)",
                                                    short_id, error, latency
                                                ));
                                            }

                                            // CRITICAL: Request render after health state change
                                            app.request_render("gateway_health_update");
                                        }
                                    } else {
                                        app.add_log(format!("✓ [{}] Success", short_id));
                                    }
                                } else {
                                    app.add_log(format!("✓ [{}] Success", short_id));
                                }
                            } else {
                                app.add_log(format!("✓ [{}] Success", short_id));
                            }
                        } else {
                            let short_id = &resp.id[resp.id.len().saturating_sub(8)..];
                            let error_msg = resp
                                .payload
                                .error
                                .unwrap_or_else(|| "Unknown error".to_string());

                            // Check if this is a response to agent.create command (Step 5.1)
                            if Some(&resp.id) == app.state.pending_agent_create_id.as_ref() {
                                app.state.pending_agent_create_id = None;
                                app.add_log(format!(
                                    "✗ [{}] Agent creation failed: {}",
                                    short_id, error_msg
                                ));
                            }
                            // Check if this is a response to agent.delete command (Step 5.5)
                            else if Some(&resp.id) == app.state.pending_agent_delete_id.as_ref() {
                                app.state.pending_agent_delete_id = None;
                                app.add_log(format!(
                                    "✗ [{}] Agent deletion failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_run_list_id.as_ref() {
                                app.state.pending_run_list_id = None;
                                app.state.pending_run_list_silent = false;
                                app.state.run_manager.loading = false;
                                app.add_log(format!(
                                    "✗ [{}] run.list failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_run_get_id.as_ref() {
                                app.state.pending_run_get_id = None;
                                app.add_log(format!(
                                    "✗ [{}] run.get failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_run_cancel_id.as_ref() {
                                app.state.pending_run_cancel_id = None;
                                app.add_log(format!(
                                    "✗ [{}] run.cancel failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_run_quick_id.as_ref() {
                                app.state.pending_run_quick_id = None;
                                app.add_log(format!(
                                    "✗ [{}] run.quick failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_shield_load_id.as_ref() {
                                app.state.pending_shield_load_id = None;
                                app.state
                                    .shield_dashboard
                                    .fail_loading(error_msg.clone());
                                app.add_log(format!(
                                    "✗ [{}] shield.load failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_sentinel_load_id.as_ref()
                            {
                                app.state.pending_sentinel_load_id = None;
                                app.state.sentinel_config.loading = false;
                                app.state.sentinel_config.error = Some(error_msg.clone());
                                app.add_log(format!(
                                    "✗ [{}] sentinel.load failed: {}",
                                    short_id, error_msg
                                ));
                            } else if Some(&resp.id) == app.state.pending_sentinel_apply_id.as_ref()
                            {
                                app.state.pending_sentinel_apply_id = None;
                                app.state.sentinel_config.applying = false;
                                app.state.sentinel_config.error = Some(error_msg.clone());
                                app.add_log(format!(
                                    "✗ [{}] sentinel.apply failed: {}",
                                    short_id, error_msg
                                ));
                            }
                            // Check if this is a response to gateway.connect command (Step 7.5)
                            else if Some(&resp.id)
                                == app.state.pending_gateway_connect_id.as_ref()
                            {
                                app.state.pending_gateway_connect_id = None;
                                app.state.connection_portal.finish_connecting();
                                app.state.connection_portal.error = Some(error_msg.clone());

                                // Parse activity log from error response if present
                                if let Some(data_value) = &resp.payload.data {
                                    if let Some(obj) = data_value.as_object() {
                                        if let Some(activity_log) = obj.get("activityLog") {
                                            if let Some(log_array) = activity_log.as_array() {
                                                app.state.connection_portal.activity_log.clear();
                                                for entry in log_array {
                                                    if let Some(entry_obj) = entry.as_object() {
                                                        let timestamp = entry_obj
                                                            .get("timestamp")
                                                            .and_then(|v| v.as_str())
                                                            .unwrap_or("")
                                                            .to_string();
                                                        let level_str = entry_obj
                                                            .get("level")
                                                            .and_then(|v| v.as_str())
                                                            .unwrap_or("info");
                                                        let message = entry_obj
                                                            .get("message")
                                                            .and_then(|v| v.as_str())
                                                            .unwrap_or("")
                                                            .to_string();

                                                        use crate::app::LogLevel;
                                                        let level = match level_str {
                                                            "success" => LogLevel::Success,
                                                            "warning" => LogLevel::Warning,
                                                            "error" => LogLevel::Error,
                                                            _ => LogLevel::Info,
                                                        };

                                                        app.state.connection_portal.add_log_entry(
                                                            timestamp, level, message,
                                                        );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                app.add_log(format!(
                                    "✗ [{}] Gateway connection failed: {}",
                                    short_id, error_msg
                                ));

                                // CRITICAL: Use immediate render for instant error display
                                app.request_immediate_render("gateway_connect_error");
                            }
                            // Check if this is a response to setup.detect command
                            else if Some(&resp.id) == app.state.pending_setup_detect_id.as_ref() {
                                app.state.pending_setup_detect_id = None;
                                app.state.setup_portal.detecting = false;
                                app.state.setup_portal.detecting_since = None;
                                app.state.setup_portal.error = Some(error_msg.clone());
                                app.add_log(format!(
                                    "✗ [{}] Setup detection failed: {}",
                                    short_id, error_msg
                                ));
                            }
                            // Phase 2.1: Check if this is a response to gateway.health command
                            else if Some(&resp.id) == app.state.pending_gateway_health_id.as_ref()
                            {
                                app.state.pending_gateway_health_id = None;
                                app.state.gateway_healthy = false;
                                app.state.gateway_db_summary = None;
                                app.add_log(format!(
                                    "⚠ [{}] Gateway health check failed: {}",
                                    short_id, error_msg
                                ));

                                // CRITICAL: Request render after health state change
                                app.request_render("gateway_health_error");
                            } else if Some(&resp.id)
                                == app.state.pending_gateway_observability_id.as_ref()
                            {
                                app.state.pending_gateway_observability_id = None;
                                app.state.portal_monitoring.loading = false;
                                app.state.portal_observability_last_poll = Some(Instant::now());
                                app.state.portal_monitoring.error = Some(error_msg.clone());
                                app.request_render("portal_observability_err");
                            } else if Some(&resp.id)
                                == app.state.pending_monitoring_refresh_id.as_ref()
                            {
                                app.state.pending_monitoring_refresh_id = None;
                                app.state.portal_monitoring.section_refresh_loading = None;
                                app.state.portal_monitoring.error =
                                    Some(format!("monitoring.refresh failed: {}", error_msg));
                                app.add_log(format!(
                                    "✗ [{}] monitoring.refresh failed: {}",
                                    short_id, error_msg
                                ));
                                app.request_render("monitoring_refresh_err");
                            } else if Some(&resp.id)
                                == app.state.pending_monitoring_logs_id.as_ref()
                            {
                                app.state.pending_monitoring_logs_id = None;
                                app.state.portal_monitoring.logs_fetch_loading = false;
                                app.state.portal_monitoring.error =
                                    Some(format!("monitoring.logs failed: {}", error_msg));
                                app.add_log(format!(
                                    "✗ [{}] monitoring.logs failed: {}",
                                    short_id, error_msg
                                ));
                                app.request_render("monitoring_logs_err");
                            } else if Some(&resp.id)
                                == app.state.pending_monitoring_drill_id.as_ref()
                            {
                                app.state.pending_monitoring_drill_id = None;
                                app.state.portal_monitoring.metrics_drill_loading = false;
                                app.state.portal_monitoring.section_refresh_loading = None;
                                app.state.portal_monitoring.error =
                                    Some(format!("monitoring.drill failed: {}", error_msg));
                                app.add_log(format!(
                                    "✗ [{}] monitoring.drill failed: {}",
                                    short_id, error_msg
                                ));
                                app.request_render("monitoring_drill_err");
                            }
                            // Generic error handling
                            else {
                                // Try to extract command name from response for better error messages
                                let command_hint = if resp.id.contains("agent") {
                                    " (agent command)"
                                } else if resp.id.contains("system") {
                                    " (system command)"
                                } else if resp.id.contains("run") {
                                    " (run command)"
                                } else if resp.id.contains("tool") {
                                    " (tool command)"
                                } else {
                                    ""
                                };

                                app.add_log(format!(
                                    "✗ [{}]{} Error: {}",
                                    short_id, command_hint, error_msg
                                ));
                            }
                        }
                    }
                    WsClientMessage::Event(event) => {
                        app.add_log(format!(
                            "Event {}: {}",
                            event.payload.event,
                            serde_json::to_string(&event.payload.data).unwrap_or_default()
                        ));
                    }
                    WsClientMessage::Error(err) => {
                        app.add_log(format!("[WEBSOCKET] ✗ Error: {}", err));
                        app.state.connected = false;
                    }
                }
            }
        }

        // Portal Monitoring: auto-refresh Gateway /metrics while the screen is open
        {
            use crate::app::OperationMode;
            use crate::screens::Screen;
            if let Some(ref ws) = ws_client {
                if app.state.navigation.current_screen() == &Screen::PortalMonitoring
                    && app.state.operation_mode == OperationMode::Connected
                    && app.state.pending_gateway_observability_id.is_none()
                    && app.state.pending_monitoring_refresh_id.is_none()
                    && app.state.pending_monitoring_logs_id.is_none()
                    && app.state.pending_monitoring_drill_id.is_none()
                {
                    let interval = Duration::from_secs(5);
                    let due = app
                        .state
                        .portal_observability_last_poll
                        .map(|t| t.elapsed() >= interval)
                        .unwrap_or(true);
                    if due {
                        app.begin_portal_observability_request(ws);
                    }
                }
            }
        }

        // ┌─────────────────────────────────────────────────────────┐
        // │ STEP 3: RENDER (conditional - only when needed)         │
        // └─────────────────────────────────────────────────────────┘
        // Check if render is scheduled and throttle limit has passed
        // OPTIMIZATION: Removed input debounce check - using immediate render for typing
        if app.should_render() {
            // Clone current screen before mutable borrow
            let current_screen = app.state.navigation.current_screen().clone();
            if !matches!(
                current_screen,
                crate::screens::Screen::ConnectionPortal
                    | crate::screens::Screen::SetupPortal
                    | crate::screens::Screen::PortalMonitoring
            ) {
                standalone_portal_last_terminal_dims = None;
            }
            let _is_portal_active = matches!(
                current_screen,
                crate::screens::Screen::ConnectionPortal
                    | crate::screens::Screen::SetupPortal
                    | crate::screens::Screen::PortalMonitoring
            );

            // Clear only on screen switch (but not when switching TO Setup Portal; we'll clear after autoresize)
            let is_switching = match (&previous_screen, &current_screen) {
                (Some(prev), current) => prev != current,
                (None, _) => true,
            };
            let switching_to_setup_portal =
                is_switching && matches!(current_screen, crate::screens::Screen::SetupPortal);

            if is_switching && !switching_to_setup_portal {
                #[cfg(debug_assertions)]
                eprintln!(
                    "[MAIN] SCREEN SWITCH: {:?} -> {:?} (clearing)",
                    previous_screen, current_screen
                );
                debug_log::log_screen(
                    &format!("{:?}", previous_screen.as_ref().unwrap_or(&current_screen)),
                    &format!("{:?}", current_screen),
                );
                terminal.clear()?;
            }

            // Connection + Setup portals: autoresize every frame (windowed CMD/conhost buffer vs viewport drift).
            // Clear on Setup entry (generic switch clear is skipped for Setup); clear when terminal dimensions
            // change while either portal is open. See docs/SETUP-PORTAL-WINDOWED-FIX-PLAN.md.
            let on_standalone_portal = matches!(
                current_screen,
                crate::screens::Screen::ConnectionPortal
                    | crate::screens::Screen::SetupPortal
                    | crate::screens::Screen::PortalMonitoring
            );
            if on_standalone_portal {
                terminal.autoresize()?;
                let dims = match terminal.size() {
                    Ok(s) => (s.width, s.height),
                    Err(_) => (0, 0),
                };
                if switching_to_setup_portal {
                    debug_log::log_flicker_debug("CLEAR_BEFORE", "enter_setup");
                    terminal.clear()?;
                    #[cfg(debug_assertions)]
                    eprintln!("[MAIN] ENTERED Setup Portal (autoresize + clear)");
                    debug_log::log_screen(
                        &format!("{:?}", previous_screen.as_ref().unwrap_or(&current_screen)),
                        &format!("{:?}", current_screen),
                    );
                    standalone_portal_last_terminal_dims = Some(dims);
                } else if let Some(last) = standalone_portal_last_terminal_dims {
                    if last != dims {
                        debug_log::log_flicker_debug("CLEAR_BEFORE", "size_change");
                        terminal.clear()?;
                    }
                    standalone_portal_last_terminal_dims = Some(dims);
                } else {
                    standalone_portal_last_terminal_dims = Some(dims);
                }
            }

            let render_start = Instant::now();
            terminal.draw(|f| app.render(f))?;
            let render_duration = render_start.elapsed().as_millis() as u64;
            app.record_render(render_duration);
            if on_standalone_portal {
                debug_log::log_flicker_debug("DRAW_DONE", &format!("ms={}", render_duration));
            }

            // Update previous screen tracking
            previous_screen = Some(current_screen);
        }

        // Setup Portal: if detection has been running too long, unstick so user can ESC or retry
        if app.state.navigation.current_screen() == &screens::Screen::SetupPortal
            && app.state.setup_portal.detecting
        {
            if let Some(since) = app.state.setup_portal.detecting_since {
                if since.elapsed() >= Duration::from_secs(8) {
                    app.state.setup_portal.detecting = false;
                    app.state.setup_portal.detecting_since = None;
                    app.state.setup_portal.error =
                        Some("Detection timed out. Press ESC to close or try again.".to_string());
                    app.add_log("[SETUP] Detection timed out".to_string());
                    app.request_immediate_render("setup_detect_timeout");
                }
            }
        }

        // ┌─────────────────────────────────────────────────────────┐
        // │ STEP 4: CHECK FOR INPUT (non-blocking with timeout)    │
        // └─────────────────────────────────────────────────────────┘
        // OPTIMIZATION: Reduced poll timeout to 1ms for faster input detection
        // This allows loop to run ~1000 times/second when idle, but input is detected immediately
        // Handle ESC on both Press and Release so we never miss it (e.g. when stuck on "Detecting...")
        if crossterm::event::poll(Duration::from_millis(1))? {
            let on_setup = app.state.navigation.current_screen() == &screens::Screen::SetupPortal;
            let on_portal_monitoring =
                app.state.navigation.current_screen() == &screens::Screen::PortalMonitoring;
            let on_standalone_input = matches!(
                app.state.navigation.current_screen(),
                &screens::Screen::ConnectionPortal
                    | &screens::Screen::SetupPortal
                    | &screens::Screen::PortalMonitoring
            );
            match event::read()? {
                Event::Key(key) => {
                    if on_standalone_input {
                        debug_log::log_input("Key", &format!("{:?}", key));
                    }
                    let should_handle = key.kind == KeyEventKind::Press
                        || (key.code == KeyCode::Esc && key.kind == KeyEventKind::Release);
                    if should_handle {
                        if app.handle_input(key, &mut io_handler, ws_client.as_ref())? {
                            break; // Exit requested
                        }
                    }
                }
                Event::Mouse(mouse) => {
                    if on_standalone_input {
                        debug_log::log_input("Mouse", &format!("{:?}", mouse));
                    }
                    // Handle mouse scroll events for standalone portals
                    if on_portal_monitoring {
                        match mouse.kind {
                            MouseEventKind::ScrollUp => app.scroll_portal_monitoring_by(-3),
                            MouseEventKind::ScrollDown => app.scroll_portal_monitoring_by(3),
                            _ => {}
                        }
                    } else if on_setup {
                        if !app.state.setup_portal.detecting {
                            match mouse.kind {
                                MouseEventKind::ScrollUp => {
                                    app.state.setup_portal.cycle_option(-1);
                                    app.request_immediate_render("setup_portal_scroll_up");
                                }
                                MouseEventKind::ScrollDown => {
                                    app.state.setup_portal.cycle_option(1);
                                    app.request_immediate_render("setup_portal_scroll_down");
                                }
                                _ => {}
                            }
                        }
                    } else if app.state.navigation.current_screen()
                        == &screens::Screen::SentinelConfig
                    {
                        match mouse.kind {
                            MouseEventKind::ScrollUp => app.sentinel_scroll_custom_value(true),
                            MouseEventKind::ScrollDown => app.sentinel_scroll_custom_value(false),
                            _ => {}
                        }
                    }
                }
                Event::Resize(w, h) => {
                    if on_standalone_input {
                        debug_log::log_input("Resize", &format!("{}x{}", w, h));
                    }
                    // Terminal was resized - force immediate re-render with correct dimensions
                    terminal.clear()?;
                    app.request_immediate_render("terminal_resize");
                }
                _ => {}
            }
        }
        // If no input, loop continues immediately - animations keep running!

        // Check for pending agent deletion (Step 5.5)
        if app.state.agent_delete_requested {
            app.state.agent_delete_requested = false;
            app.delete_selected_agent(&ws_client);
        }

        // Check for IO updates (non-blocking)
        io_handler.update(&mut app).ok();
    }

    // Restore terminal
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), cursor::Show, LeaveAlternateScreen)?;

    Ok(())
}
