//! Ratatui TUI for OpenFang interactive mode.
//!
//! Two-level navigation: Phase::Boot (Welcome/Wizard) → Phase::Main with 16 tabs.

pub mod chat_runner;
pub mod event;
pub mod screens;
pub mod theme;

use event::{AppEvent, BackendRef};
use openfang_kernel::OpenFangKernel;
use openfang_runtime::llm_driver::StreamEvent;
use openfang_types::agent::AgentId;
use screens::{
    agents, audit, channels, chat, dashboard, extensions, hands, logs, memory, peers, security,
    sessions, settings, skills, templates, triggers, usage, welcome, wizard, workflows,
};
use std::path::PathBuf;
use std::sync::{mpsc, Arc};
use std::time::Duration;

use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;

// ─── Core types ──────────────────────────────────────────────────────────────

#[derive(Clone, Copy, PartialEq, Eq)]
enum Phase {
    Boot(BootScreen),
    Main,
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum BootScreen {
    Welcome,
    Wizard,
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum Tab {
    Dashboard,
    Agents,
    Chat,
    Sessions,
    Workflows,
    Triggers,
    Memory,
    Channels,
    Skills,
    Hands,
    Extensions,
    Templates,
    Peers,
    Security,
    Audit,
    Usage,
    Settings,
    Logs,
}

const TABS: &[Tab] = &[
    Tab::Dashboard,
    Tab::Agents,
    Tab::Chat,
    Tab::Sessions,
    Tab::Workflows,
    Tab::Triggers,
    Tab::Memory,
    Tab::Channels,
    Tab::Skills,
    Tab::Hands,
    Tab::Extensions,
    Tab::Templates,
    Tab::Peers,
    Tab::Security,
    Tab::Audit,
    Tab::Usage,
    Tab::Settings,
    Tab::Logs,
];

impl Tab {
    fn label(self) -> &'static str {
        match self {
            Tab::Dashboard => "Dashboard",
            Tab::Agents => "Agents",
            Tab::Chat => "Chat",
            Tab::Sessions => "Sessions",
            Tab::Workflows => "Workflows",
            Tab::Triggers => "Triggers",
            Tab::Memory => "Memory",
            Tab::Channels => "Channels",
            Tab::Skills => "Skills",
            Tab::Hands => "Hands",
            Tab::Extensions => "Extensions",
            Tab::Templates => "Templates",
            Tab::Peers => "Peers",
            Tab::Security => "Security",
            Tab::Audit => "Audit",
            Tab::Usage => "Usage",
            Tab::Settings => "Settings",
            Tab::Logs => "Logs",
        }
    }

    fn index(self) -> usize {
        TABS.iter().position(|&t| t == self).unwrap_or(0)
    }
}

enum Backend {
    Daemon { base_url: String },
    InProcess { kernel: Arc<OpenFangKernel> },
    None,
}

impl Backend {
    fn to_ref(&self) -> Option<BackendRef> {
        match self {
            Backend::Daemon { base_url } => Some(BackendRef::Daemon(base_url.clone())),
            Backend::InProcess { kernel } => Some(BackendRef::InProcess(kernel.clone())),
            Backend::None => None,
        }
    }
}

struct ChatTarget {
    agent_id_daemon: Option<String>,
    agent_id_inprocess: Option<AgentId>,
    agent_name: String,
}

struct App {
    phase: Phase,
    active_tab: Tab,
    tab_scroll_offset: usize,
    config_path: Option<PathBuf>,
    should_quit: bool,
    event_tx: mpsc::Sender<AppEvent>,
    /// Double Ctrl+C quit: true after first Ctrl+C press.
    ctrl_c_pending: bool,
    /// Tick counter when first Ctrl+C was pressed (auto-resets after ~2s).
    ctrl_c_tick: usize,
    /// Global tick counter for Ctrl+C timeout tracking.
    tick_count: usize,

    backend: Backend,
    chat_target: Option<ChatTarget>,

    // Screen states
    welcome: welcome::WelcomeState,
    wizard: wizard::WizardState,
    agents: agents::AgentSelectState,
    chat: chat::ChatState,
    dashboard: dashboard::DashboardState,
    channels: channels::ChannelState,
    workflows: workflows::WorkflowState,
    triggers: triggers::TriggerState,
    sessions: sessions::SessionsState,
    memory: memory::MemoryState,
    skills: skills::SkillsState,
    hands: hands::HandsState,
    extensions: extensions::ExtensionsState,
    templates: templates::TemplatesState,
    security: security::SecurityState,
    audit: audit::AuditState,
    usage: usage::UsageState,
    settings: settings::SettingsState,
    peers: peers::PeersState,
    logs: logs::LogsState,

    kernel_booting: bool,
    kernel_boot_error: Option<String>,
}

// ─── App construction ────────────────────────────────────────────────────────

impl App {
    fn new(config_path: Option<PathBuf>, event_tx: mpsc::Sender<AppEvent>) -> Self {
        Self {
            phase: Phase::Boot(BootScreen::Welcome),
            active_tab: Tab::Dashboard,
            tab_scroll_offset: 0,
            config_path,
            should_quit: false,
            event_tx,
            backend: Backend::None,
            chat_target: None,
            welcome: welcome::WelcomeState::new(),
            wizard: wizard::WizardState::new(),
            agents: agents::AgentSelectState::new(),
            chat: chat::ChatState::new(),
            dashboard: dashboard::DashboardState::new(),
            channels: channels::ChannelState::new(),
            workflows: workflows::WorkflowState::new(),
            triggers: triggers::TriggerState::new(),
            sessions: sessions::SessionsState::new(),
            memory: memory::MemoryState::new(),
            skills: skills::SkillsState::new(),
            hands: hands::HandsState::new(),
            extensions: extensions::ExtensionsState::new(),
            templates: templates::TemplatesState::new(),
            security: security::SecurityState::new(),
            audit: audit::AuditState::new(),
            usage: usage::UsageState::new(),
            settings: settings::SettingsState::new(),
            peers: peers::PeersState::new(),
            logs: logs::LogsState::new(),
            kernel_booting: false,
            kernel_boot_error: None,
            ctrl_c_pending: false,
            ctrl_c_tick: 0,
            tick_count: 0,
        }
    }

    // ─── Event dispatch ──────────────────────────────────────────────────────

    fn handle_event(&mut self, ev: AppEvent) {
        match ev {
            AppEvent::Key(key) => self.handle_key(key),
            AppEvent::Tick => self.handle_tick(),
            AppEvent::Stream(stream_ev) => self.handle_stream(stream_ev),
            AppEvent::StreamDone(result) => self.handle_stream_done(result),
            AppEvent::KernelReady(kernel) => self.handle_kernel_ready(kernel),
            AppEvent::KernelError(err) => self.handle_kernel_error(err),
            AppEvent::AgentSpawned { id, name } => self.handle_agent_spawned(id, name),
            AppEvent::AgentSpawnError(err) => self.handle_agent_spawn_error(err),
            AppEvent::DaemonDetected { url, agent_count } => {
                self.welcome.on_daemon_detected(url, agent_count);
            }
            // ── New tab events ──
            AppEvent::DashboardData {
                agent_count,
                uptime_secs,
                version,
                provider,
                model,
            } => {
                self.dashboard.agent_count = agent_count;
                self.dashboard.uptime_secs = uptime_secs;
                self.dashboard.version = version;
                self.dashboard.provider = provider;
                self.dashboard.model = model;
                self.dashboard.loading = false;
            }
            AppEvent::AuditLoaded(rows) => {
                self.dashboard.recent_audit = rows;
                self.dashboard.loading = false;
            }
            AppEvent::ChannelListLoaded(list) => {
                if !list.is_empty() {
                    self.channels.channels = list;
                    self.channels.list_state.select(Some(0));
                }
                self.channels.loading = false;
            }
            AppEvent::ChannelTestResult { success, message } => {
                self.channels.test_result = Some((success, message));
            }
            AppEvent::WorkflowListLoaded(list) => {
                self.workflows.workflows = list;
                if !self.workflows.workflows.is_empty() {
                    self.workflows.list_state.select(Some(0));
                }
                self.workflows.loading = false;
            }
            AppEvent::WorkflowRunsLoaded(runs) => {
                self.workflows.runs = runs;
                if !self.workflows.runs.is_empty() {
                    self.workflows.runs_list_state.select(Some(0));
                }
                self.workflows.loading = false;
            }
            AppEvent::WorkflowRunResult(result) => {
                self.workflows.run_result = Some(result);
                self.workflows.loading = false;
            }
            AppEvent::WorkflowCreated(_id) => {
                self.workflows.status_msg = "Workflow created!".to_string();
                self.refresh_workflows();
            }
            AppEvent::TriggerListLoaded(list) => {
                self.triggers.triggers = list;
                if !self.triggers.triggers.is_empty() {
                    self.triggers.list_state.select(Some(0));
                }
                self.triggers.loading = false;
            }
            AppEvent::TriggerCreated(_id) => {
                self.triggers.status_msg = "Trigger created!".to_string();
                self.refresh_triggers();
            }
            AppEvent::TriggerDeleted(id) => {
                self.triggers.triggers.retain(|t| t.id != id);
                self.triggers.status_msg = format!("Trigger {id} deleted.");
            }
            AppEvent::AgentKilled { id } => {
                self.agents.status_msg = format!("Agent {id} killed.");
                self.agents.sub = agents::AgentSubScreen::AgentList;
                self.refresh_agents();
            }
            AppEvent::AgentKillError(err) => {
                self.agents.status_msg = format!("Kill failed: {err}");
            }
            AppEvent::AgentSkillsLoaded {
                assigned,
                available,
            } => {
                // Populate skill editor: mark assigned skills as checked
                self.agents.available_skills = available
                    .into_iter()
                    .map(|name| {
                        let checked = assigned.contains(&name);
                        (name, checked)
                    })
                    .collect();
                self.agents.skill_cursor = 0;
            }
            AppEvent::AgentMcpServersLoaded {
                assigned,
                available,
            } => {
                // Populate MCP editor: mark assigned servers as checked
                self.agents.available_mcp = available
                    .into_iter()
                    .map(|name| {
                        let checked = assigned.contains(&name);
                        (name, checked)
                    })
                    .collect();
                self.agents.mcp_cursor = 0;
            }
            AppEvent::AgentSkillsUpdated(id) => {
                self.agents.status_msg = format!("Skills updated for agent {id}.");
                self.agents.sub = agents::AgentSubScreen::AgentDetail;
            }
            AppEvent::AgentMcpServersUpdated(id) => {
                self.agents.status_msg = format!("MCP servers updated for agent {id}.");
                self.agents.sub = agents::AgentSubScreen::AgentDetail;
            }
            AppEvent::FetchError(err) => {
                // Route to the active tab's status message
                match self.active_tab {
                    Tab::Workflows => self.workflows.status_msg = err,
                    Tab::Triggers => self.triggers.status_msg = err,
                    Tab::Channels => self.channels.status_msg = err,
                    Tab::Sessions => self.sessions.status_msg = err,
                    Tab::Memory => self.memory.status_msg = err,
                    Tab::Skills => self.skills.status_msg = err,
                    Tab::Hands => self.hands.status_msg = err,
                    Tab::Extensions => self.extensions.status_msg = err,
                    Tab::Templates => self.templates.status_msg = err,
                    Tab::Settings => self.settings.status_msg = err,
                    _ => {}
                }
            }

            // ── New screen events ──
            AppEvent::SessionsLoaded(list) => {
                self.sessions.sessions = list;
                self.sessions.refilter();
                self.sessions.loading = false;
            }
            AppEvent::SessionDeleted(id) => {
                self.sessions.sessions.retain(|s| s.id != id);
                self.sessions.refilter();
                self.sessions.status_msg = format!("Session {id} deleted.");
            }
            AppEvent::MemoryAgentsLoaded(agents) => {
                self.memory.agents = agents;
                if !self.memory.agents.is_empty() {
                    self.memory.agent_list_state.select(Some(0));
                }
                self.memory.loading = false;
            }
            AppEvent::MemoryKvLoaded(pairs) => {
                self.memory.kv_pairs = pairs;
                if !self.memory.kv_pairs.is_empty() {
                    self.memory.kv_list_state.select(Some(0));
                }
                self.memory.loading = false;
            }
            AppEvent::MemoryKvSaved { key } => {
                self.memory.status_msg = format!("Saved key: {key}");
                // Refresh KV pairs
                if let Some(agent) = &self.memory.selected_agent {
                    if let Some(backend) = self.backend.to_ref() {
                        event::spawn_fetch_memory_kv(
                            backend,
                            agent.id.clone(),
                            self.event_tx.clone(),
                        );
                    }
                }
            }
            AppEvent::MemoryKvDeleted(key) => {
                self.memory.kv_pairs.retain(|kv| kv.key != key);
                self.memory.status_msg = format!("Deleted key: {key}");
            }
            AppEvent::SkillsLoaded(list) => {
                self.skills.installed = list;
                if !self.skills.installed.is_empty() {
                    self.skills.installed_list.select(Some(0));
                }
                self.skills.loading = false;
            }
            AppEvent::ClawHubLoaded(results) => {
                self.skills.clawhub_results = results;
                if !self.skills.clawhub_results.is_empty() {
                    self.skills.clawhub_list.select(Some(0));
                }
                self.skills.loading = false;
            }
            AppEvent::SkillInstalled(name) => {
                self.skills.status_msg = format!("Installed: {name}");
                self.refresh_skills();
            }
            AppEvent::SkillUninstalled(name) => {
                self.skills.installed.retain(|s| s.name != name);
                self.skills.status_msg = format!("Uninstalled: {name}");
            }
            AppEvent::McpServersLoaded(servers) => {
                self.skills.mcp_servers = servers;
                if !self.skills.mcp_servers.is_empty() {
                    self.skills.mcp_list.select(Some(0));
                }
                self.skills.loading = false;
            }
            AppEvent::TemplateProvidersLoaded(providers) => {
                self.templates.providers = providers;
            }
            AppEvent::SecurityLoaded(features) => {
                self.security.features = features;
                self.security.loading = false;
            }
            AppEvent::SecurityChainVerified { valid, message } => {
                self.security.chain_verified = Some(valid);
                self.security.verify_result = message;
                self.security.loading = false;
            }
            AppEvent::AuditEntriesLoaded(entries) => {
                self.audit.entries = entries;
                self.audit.refilter();
                self.audit.loading = false;
            }
            AppEvent::AuditChainVerified(valid) => {
                self.audit.chain_verified = Some(valid);
            }
            AppEvent::UsageSummaryLoaded(summary) => {
                self.usage.summary = summary;
                self.usage.loading = false;
            }
            AppEvent::UsageByModelLoaded(models) => {
                self.usage.by_model = models;
                if !self.usage.by_model.is_empty() {
                    self.usage.model_list.select(Some(0));
                }
            }
            AppEvent::UsageByAgentLoaded(agents) => {
                self.usage.by_agent = agents;
                if !self.usage.by_agent.is_empty() {
                    self.usage.agent_list.select(Some(0));
                }
            }
            AppEvent::SettingsProvidersLoaded(providers) => {
                self.settings.providers = providers;
                if !self.settings.providers.is_empty() {
                    self.settings.provider_list.select(Some(0));
                }
                self.settings.loading = false;
            }
            AppEvent::SettingsModelsLoaded(models) => {
                self.settings.models = models;
                if !self.settings.models.is_empty() {
                    self.settings.model_list.select(Some(0));
                }
                self.settings.loading = false;
            }
            AppEvent::SettingsToolsLoaded(tools) => {
                self.settings.tools = tools;
                if !self.settings.tools.is_empty() {
                    self.settings.tool_list.select(Some(0));
                }
                self.settings.loading = false;
            }
            AppEvent::ProviderKeySaved(name) => {
                self.settings.status_msg = format!("Key saved for {name}");
                self.refresh_settings_providers();
            }
            AppEvent::ProviderKeyDeleted(name) => {
                self.settings.status_msg = format!("Key deleted for {name}");
                self.refresh_settings_providers();
            }
            AppEvent::ProviderTestResult(result) => {
                self.settings.test_result = Some(result);
            }
            AppEvent::PeersLoaded(list) => {
                self.peers.peers = list;
                if !self.peers.peers.is_empty() && self.peers.list_state.selected().is_none() {
                    self.peers.list_state.select(Some(0));
                }
                self.peers.loading = false;
            }
            AppEvent::LogsLoaded(entries) => {
                self.logs.entries = entries;
                self.logs.refilter();
                self.logs.loading = false;
            }
            AppEvent::HandsLoaded(list) => {
                self.hands.definitions = list;
                if !self.hands.definitions.is_empty() {
                    self.hands.marketplace_list.select(Some(0));
                }
                self.hands.loading = false;
            }
            AppEvent::ActiveHandsLoaded(list) => {
                self.hands.instances = list;
                if !self.hands.instances.is_empty() && self.hands.active_list.selected().is_none() {
                    self.hands.active_list.select(Some(0));
                }
                self.hands.loading = false;
            }
            AppEvent::HandActivated(name) => {
                self.hands.status_msg = format!("Activated: {name}");
                self.refresh_hands();
            }
            AppEvent::HandDeactivated(id) => {
                self.hands.instances.retain(|i| i.instance_id != id);
                self.hands.status_msg = format!("Deactivated: {id}");
            }
            AppEvent::HandPaused(id) => {
                if let Some(inst) = self
                    .hands
                    .instances
                    .iter_mut()
                    .find(|i| i.instance_id == id)
                {
                    inst.status = "Paused".to_string();
                }
                self.hands.status_msg = "Hand paused".to_string();
            }
            AppEvent::HandResumed(id) => {
                if let Some(inst) = self
                    .hands
                    .instances
                    .iter_mut()
                    .find(|i| i.instance_id == id)
                {
                    inst.status = "Active".to_string();
                }
                self.hands.status_msg = "Hand resumed".to_string();
            }
            AppEvent::ExtensionsLoaded(list) => {
                self.extensions.all_extensions = list;
                if !self.extensions.all_extensions.is_empty()
                    && self.extensions.browse_list.selected().is_none()
                {
                    self.extensions.browse_list.select(Some(0));
                }
                self.extensions.loading = false;
            }
            AppEvent::ExtensionHealthLoaded(entries) => {
                self.extensions.health_entries = entries;
                if !self.extensions.health_entries.is_empty()
                    && self.extensions.health_list.selected().is_none()
                {
                    self.extensions.health_list.select(Some(0));
                }
            }
            AppEvent::ExtensionInstalled(id) => {
                self.extensions.status_msg = format!("Installed: {id}");
                self.refresh_extensions();
            }
            AppEvent::ExtensionRemoved(id) => {
                self.extensions.status_msg = format!("Removed: {id}");
                self.refresh_extensions();
            }
            AppEvent::ExtensionReconnected(id, tools) => {
                self.extensions.status_msg = format!("Reconnected {id}: {tools} tools");
                self.refresh_extension_health();
            }
        }
    }

    fn handle_key(&mut self, key: ratatui::crossterm::event::KeyEvent) {
        use ratatui::crossterm::event::{KeyCode, KeyModifiers};

        // ── Global: Double Ctrl+C to quit (all phases) ──────────────────────
        let is_ctrl_c =
            key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL);
        if is_ctrl_c {
            if self.ctrl_c_pending {
                self.should_quit = true;
                return;
            }
            self.ctrl_c_pending = true;
            self.ctrl_c_tick = self.tick_count;
            // In Main phase, don't pass the first Ctrl+C to screen handlers —
            // just show the "press again to quit" hint (rendered in status bar).
            if matches!(self.phase, Phase::Main) {
                return;
            }
            // In Boot phase, let it fall through to the welcome/wizard handler
            // which has its own double-Ctrl+C logic.
        } else {
            // Any other key clears the pending Ctrl+C state
            self.ctrl_c_pending = false;
        }

        // ── Global: Ctrl+Q quit from Main phase ─────────────────────────────
        if matches!(self.phase, Phase::Main) {
            if key.code == KeyCode::Char('q') && key.modifiers.contains(KeyModifiers::CONTROL) {
                self.should_quit = true;
                return;
            }
            // Tab switching: F1-F12 for direct jump (reliable on all terminals)
            match key.code {
                KeyCode::F(1) => {
                    self.switch_tab(Tab::Dashboard);
                    return;
                }
                KeyCode::F(2) => {
                    self.switch_tab(Tab::Agents);
                    return;
                }
                KeyCode::F(3) => {
                    self.switch_tab(Tab::Chat);
                    return;
                }
                KeyCode::F(4) => {
                    self.switch_tab(Tab::Sessions);
                    return;
                }
                KeyCode::F(5) => {
                    self.switch_tab(Tab::Workflows);
                    return;
                }
                KeyCode::F(6) => {
                    self.switch_tab(Tab::Triggers);
                    return;
                }
                KeyCode::F(7) => {
                    self.switch_tab(Tab::Memory);
                    return;
                }
                KeyCode::F(8) => {
                    self.switch_tab(Tab::Channels);
                    return;
                }
                KeyCode::F(9) => {
                    self.switch_tab(Tab::Skills);
                    return;
                }
                KeyCode::F(10) => {
                    self.switch_tab(Tab::Templates);
                    return;
                }
                KeyCode::F(11) => {
                    self.switch_tab(Tab::Peers);
                    return;
                }
                KeyCode::F(12) => {
                    self.switch_tab(Tab::Security);
                    return;
                }
                _ => {}
            }
            // Tab cycling: Tab / Shift+Tab
            if key.code == KeyCode::Tab && key.modifiers.is_empty() {
                self.next_tab();
                return;
            }
            if key.code == KeyCode::BackTab {
                self.prev_tab();
                return;
            }
            // Tab cycling: Ctrl+Left/Right
            if key.modifiers.contains(KeyModifiers::CONTROL) {
                match key.code {
                    KeyCode::Left => {
                        self.prev_tab();
                        return;
                    }
                    KeyCode::Right => {
                        self.next_tab();
                        return;
                    }
                    _ => {}
                }
            }
            // Tab cycling: Ctrl+[ / Ctrl+] (reliable on MINGW/Windows terminals)
            if key.modifiers.contains(KeyModifiers::CONTROL) {
                match key.code {
                    KeyCode::Char('[') => {
                        self.prev_tab();
                        return;
                    }
                    KeyCode::Char(']') => {
                        self.next_tab();
                        return;
                    }
                    _ => {}
                }
            }
            // Fallback: Alt+1-9,0
            if key.modifiers.contains(KeyModifiers::ALT) {
                match key.code {
                    KeyCode::Char('1') => {
                        self.switch_tab(Tab::Dashboard);
                        return;
                    }
                    KeyCode::Char('2') => {
                        self.switch_tab(Tab::Agents);
                        return;
                    }
                    KeyCode::Char('3') => {
                        self.switch_tab(Tab::Chat);
                        return;
                    }
                    KeyCode::Char('4') => {
                        self.switch_tab(Tab::Sessions);
                        return;
                    }
                    KeyCode::Char('5') => {
                        self.switch_tab(Tab::Workflows);
                        return;
                    }
                    KeyCode::Char('6') => {
                        self.switch_tab(Tab::Triggers);
                        return;
                    }
                    KeyCode::Char('7') => {
                        self.switch_tab(Tab::Memory);
                        return;
                    }
                    KeyCode::Char('8') => {
                        self.switch_tab(Tab::Channels);
                        return;
                    }
                    KeyCode::Char('9') => {
                        self.switch_tab(Tab::Skills);
                        return;
                    }
                    KeyCode::Char('0') => {
                        self.switch_tab(Tab::Templates);
                        return;
                    }
                    _ => {}
                }
            }
        }

        // ── Route to screen handler ─────────────────────────────────────────
        match self.phase {
            Phase::Boot(BootScreen::Welcome) => {
                if let Some(action) = self.welcome.handle_key(key) {
                    self.handle_welcome_action(action);
                }
            }
            Phase::Boot(BootScreen::Wizard) => match self.wizard.handle_key(key) {
                wizard::WizardResult::Cancelled => {
                    self.phase = Phase::Boot(BootScreen::Welcome);
                    self.start_daemon_detect();
                }
                wizard::WizardResult::Continue => {
                    if self.wizard.step == wizard::WizardStep::Done
                        && self.wizard.created_config.is_some()
                    {
                        self.config_path = self.wizard.created_config.clone();
                        self.welcome.setup_just_completed = true;
                        self.phase = Phase::Boot(BootScreen::Welcome);
                        self.start_daemon_detect();
                    }
                }
            },
            Phase::Main => match self.active_tab {
                Tab::Dashboard => {
                    let action = self.dashboard.handle_key(key);
                    self.handle_dashboard_action(action);
                }
                Tab::Agents => {
                    let action = self.agents.handle_key(key);
                    self.handle_agent_action(action);
                }
                Tab::Chat => {
                    let action = self.chat.handle_key(key);
                    self.handle_chat_action(action);
                }
                Tab::Channels => {
                    let action = self.channels.handle_key(key);
                    self.handle_channel_action(action);
                }
                Tab::Workflows => {
                    let action = self.workflows.handle_key(key);
                    self.handle_workflow_action(action);
                }
                Tab::Triggers => {
                    let action = self.triggers.handle_key(key);
                    self.handle_trigger_action(action);
                }
                Tab::Sessions => {
                    let action = self.sessions.handle_key(key);
                    self.handle_sessions_action(action);
                }
                Tab::Memory => {
                    let action = self.memory.handle_key(key);
                    self.handle_memory_action(action);
                }
                Tab::Skills => {
                    let action = self.skills.handle_key(key);
                    self.handle_skills_action(action);
                }
                Tab::Extensions => {
                    let action = self.extensions.handle_key(key);
                    self.handle_extensions_action(action);
                }
                Tab::Hands => {
                    let action = self.hands.handle_key(key);
                    self.handle_hands_action(action);
                }
                Tab::Templates => {
                    let action = self.templates.handle_key(key);
                    self.handle_templates_action(action);
                }
                Tab::Security => {
                    let action = self.security.handle_key(key);
                    self.handle_security_action(action);
                }
                Tab::Audit => {
                    let action = self.audit.handle_key(key);
                    self.handle_audit_action(action);
                }
                Tab::Usage => {
                    let action = self.usage.handle_key(key);
                    self.handle_usage_action(action);
                }
                Tab::Settings => {
                    let action = self.settings.handle_key(key);
                    self.handle_settings_action(action);
                }
                Tab::Peers => {
                    let action = self.peers.handle_key(key);
                    self.handle_peers_action(action);
                }
                Tab::Logs => {
                    let action = self.logs.handle_key(key);
                    self.handle_logs_action(action);
                }
            },
        }
    }

    fn handle_tick(&mut self) {
        self.tick_count = self.tick_count.wrapping_add(1);
        // Auto-reset Ctrl+C pending after ~2s (40 ticks at 50ms)
        if self.ctrl_c_pending && self.tick_count.wrapping_sub(self.ctrl_c_tick) > 40 {
            self.ctrl_c_pending = false;
        }
        self.welcome.tick();
        self.chat.tick();
        self.dashboard.tick();
        self.channels.tick();
        self.workflows.tick();
        self.triggers.tick();
        self.sessions.tick();
        self.memory.tick();
        self.skills.tick();
        self.hands.tick();
        self.extensions.tick();
        self.templates.tick();
        self.security.tick();
        self.audit.tick();
        self.usage.tick();
        self.settings.tick();
        self.peers.tick();
        self.logs.tick();

        // Auto-poll for active tabs
        if self.phase == Phase::Main {
            match self.active_tab {
                Tab::Logs if self.logs.should_poll() => self.refresh_logs(),
                Tab::Peers if self.peers.should_poll() => self.refresh_peers(),
                _ => {}
            }
        }
    }

    // ─── Tab navigation ──────────────────────────────────────────────────────

    fn next_tab(&mut self) {
        let idx = self.active_tab.index();
        let next = (idx + 1) % TABS.len();
        self.switch_tab(TABS[next]);
    }

    fn prev_tab(&mut self) {
        let idx = self.active_tab.index();
        let prev = if idx == 0 { TABS.len() - 1 } else { idx - 1 };
        self.switch_tab(TABS[prev]);
    }

    fn switch_tab(&mut self, tab: Tab) {
        self.active_tab = tab;
        // Keep active tab visible in the scrollable tab bar
        let idx = tab.index();
        if idx < self.tab_scroll_offset {
            self.tab_scroll_offset = idx;
        }
        // Will be further adjusted during draw based on actual width
        self.on_tab_enter(tab);
    }

    /// Called when a tab becomes active — load data if needed.
    fn on_tab_enter(&mut self, tab: Tab) {
        match tab {
            Tab::Dashboard => self.refresh_dashboard(),
            Tab::Agents => self.refresh_agents(),
            Tab::Channels => self.refresh_channels(),
            Tab::Workflows => self.refresh_workflows(),
            Tab::Triggers => self.refresh_triggers(),
            Tab::Sessions => self.refresh_sessions(),
            Tab::Memory => self.refresh_memory(),
            Tab::Skills => self.refresh_skills(),
            Tab::Hands => self.refresh_hands(),
            Tab::Extensions => self.refresh_extensions(),
            Tab::Templates => self.refresh_templates(),
            Tab::Security => self.refresh_security(),
            Tab::Audit => self.refresh_audit(),
            Tab::Usage => self.refresh_usage(),
            Tab::Settings => self.refresh_settings_providers(),
            Tab::Peers => self.refresh_peers(),
            Tab::Logs => self.refresh_logs(),
            Tab::Chat => {} // Chat doesn't need refresh on enter
        }
    }

    /// Transition from Boot to Main phase.
    fn enter_main_phase(&mut self) {
        self.phase = Phase::Main;
        self.active_tab = Tab::Agents;
        // Load initial data for visible tabs
        self.refresh_agents();
        self.refresh_dashboard();
        self.refresh_channels();
    }

    // ─── Data refresh helpers ────────────────────────────────────────────────

    fn refresh_dashboard(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.dashboard.loading = true;
            event::spawn_fetch_dashboard(backend, self.event_tx.clone());
        }
    }

    fn refresh_agents(&mut self) {
        match &self.backend {
            Backend::Daemon { base_url } => {
                self.agents.load_daemon_agents(base_url);
            }
            Backend::InProcess { kernel } => {
                self.agents.load_inprocess_agents(kernel);
            }
            Backend::None => {}
        }
    }

    fn refresh_channels(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.channels.loading = true;
            event::spawn_fetch_channels(backend, self.event_tx.clone());
        }
        // Also build defaults from env detection
        if self.channels.channels.is_empty() {
            self.channels.build_default_channels();
        }
    }

    fn refresh_workflows(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.workflows.loading = true;
            event::spawn_fetch_workflows(backend, self.event_tx.clone());
        }
    }

    fn refresh_triggers(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.triggers.loading = true;
            event::spawn_fetch_triggers(backend, self.event_tx.clone());
        }
    }

    fn refresh_sessions(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.sessions.loading = true;
            event::spawn_fetch_sessions(backend, self.event_tx.clone());
        }
    }

    fn refresh_memory(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.memory.loading = true;
            event::spawn_fetch_memory_agents(backend, self.event_tx.clone());
        }
    }

    fn refresh_skills(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.skills.loading = true;
            event::spawn_fetch_skills(backend, self.event_tx.clone());
        }
    }

    fn refresh_hands(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.hands.loading = true;
            event::spawn_fetch_hands(backend.clone(), self.event_tx.clone());
            event::spawn_fetch_active_hands(backend, self.event_tx.clone());
        }
    }

    fn refresh_extensions(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.extensions.loading = true;
            event::spawn_fetch_extensions(backend, self.event_tx.clone());
        }
    }

    fn refresh_extension_health(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            event::spawn_fetch_extension_health(backend, self.event_tx.clone());
        }
    }

    fn refresh_templates(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            event::spawn_fetch_template_providers(backend, self.event_tx.clone());
        }
    }

    fn refresh_security(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.security.loading = true;
            event::spawn_fetch_security(backend, self.event_tx.clone());
        }
    }

    fn refresh_audit(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.audit.loading = true;
            event::spawn_fetch_audit(backend, self.event_tx.clone());
        }
    }

    fn refresh_usage(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.usage.loading = true;
            event::spawn_fetch_usage(backend, self.event_tx.clone());
        }
    }

    fn refresh_settings_providers(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.settings.loading = true;
            event::spawn_fetch_providers(backend, self.event_tx.clone());
        }
    }

    fn refresh_settings_models(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.settings.loading = true;
            event::spawn_fetch_models(backend, self.event_tx.clone());
        }
    }

    fn refresh_settings_tools(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            event::spawn_fetch_tools(backend, self.event_tx.clone());
        }
    }

    fn refresh_peers(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.peers.loading = true;
            event::spawn_fetch_peers(backend, self.event_tx.clone());
        }
    }

    fn refresh_logs(&mut self) {
        if let Some(backend) = self.backend.to_ref() {
            self.logs.loading = true;
            event::spawn_fetch_logs(backend, self.event_tx.clone());
        }
    }

    // ─── Streaming ───────────────────────────────────────────────────────────

    fn handle_stream(&mut self, ev: StreamEvent) {
        match ev {
            StreamEvent::TextDelta { text } => {
                self.chat.thinking = false;
                if self.chat.active_tool.is_some() {
                    self.chat.active_tool = None;
                }
                self.chat.append_stream(&text);
            }
            StreamEvent::ToolUseStart { name, .. } => {
                if !self.chat.streaming_text.is_empty() {
                    let text = std::mem::take(&mut self.chat.streaming_text);
                    self.chat.push_message(chat::Role::Agent, text);
                }
                self.chat.tool_start(&name);
            }
            StreamEvent::ToolInputDelta { text } => {
                self.chat.tool_input_buf.push_str(&text);
            }
            StreamEvent::ToolUseEnd { name, input, .. } => {
                let input_str = if !self.chat.tool_input_buf.is_empty() {
                    std::mem::take(&mut self.chat.tool_input_buf)
                } else {
                    serde_json::to_string(&input).unwrap_or_default()
                };
                self.chat.tool_use_end(&name, &input_str);
            }
            StreamEvent::ContentComplete { usage, .. } => {
                self.chat.last_tokens = Some((usage.input_tokens, usage.output_tokens));
            }
            StreamEvent::PhaseChange { phase, detail } => {
                if phase == "tool_use" {
                    if let Some(tool_name) = detail {
                        self.chat.tool_start(&tool_name);
                    }
                } else if phase == "thinking" {
                    self.chat.thinking = true;
                }
            }
            StreamEvent::ThinkingDelta { text } => {
                self.chat.thinking = true;
                self.chat.append_stream(&text);
            }
            StreamEvent::ToolExecutionResult {
                name,
                result_preview,
                is_error,
            } => {
                self.chat.tool_result(&name, &result_preview, is_error);
            }
        }
    }

    fn handle_stream_done(
        &mut self,
        result: Result<openfang_runtime::agent_loop::AgentLoopResult, String>,
    ) {
        self.chat.finalize_stream();
        match result {
            Ok(r) => {
                // Only add if the response wasn't already streamed
                if !r.response.is_empty()
                    && self.chat.messages.last().map(|m| m.text.as_str()) != Some(&r.response)
                {
                    self.chat.push_message(chat::Role::Agent, r.response);
                }
                if r.total_usage.input_tokens > 0 || r.total_usage.output_tokens > 0 {
                    self.chat.last_tokens =
                        Some((r.total_usage.input_tokens, r.total_usage.output_tokens));
                }
            }
            Err(e) => {
                self.chat.status_msg = Some(format!("Error: {e}"));
            }
        }
        // Auto-send the next staged message if any
        if let Some(msg) = self.chat.take_staged() {
            self.send_message(msg);
        }
    }

    // ─── Kernel lifecycle ────────────────────────────────────────────────────

    fn handle_kernel_ready(&mut self, kernel: Arc<OpenFangKernel>) {
        self.kernel_booting = false;
        self.backend = Backend::InProcess { kernel };
        self.agents.reset();
        self.enter_main_phase();
    }

    fn handle_kernel_error(&mut self, err: String) {
        self.kernel_booting = false;
        self.kernel_boot_error = Some(err.clone());
        if err.contains("Missing API key") || err.contains("api_key") {
            self.wizard.reset();
            self.phase = Phase::Boot(BootScreen::Wizard);
        } else {
            self.phase = Phase::Boot(BootScreen::Welcome);
            self.start_daemon_detect();
        }
    }

    fn handle_agent_spawned(&mut self, id: String, name: String) {
        self.agents.sub = agents::AgentSubScreen::AgentList;
        self.enter_chat_daemon(id, name);
    }

    fn handle_agent_spawn_error(&mut self, err: String) {
        self.agents.status_msg = err;
        self.agents.sub = agents::AgentSubScreen::AgentList;
    }

    // ─── Screen transitions ──────────────────────────────────────────────────

    fn start_daemon_detect(&mut self) {
        self.welcome.detecting = true;
        event::spawn_daemon_detect(self.event_tx.clone());
    }

    fn handle_welcome_action(&mut self, action: welcome::WelcomeAction) {
        match action {
            welcome::WelcomeAction::Exit => self.should_quit = true,
            welcome::WelcomeAction::ConnectDaemon => {
                if let Some(ref url) = self.welcome.daemon_url {
                    self.backend = Backend::Daemon {
                        base_url: url.clone(),
                    };
                    self.agents.reset();
                    self.enter_main_phase();
                }
            }
            welcome::WelcomeAction::InProcess => {
                if self.kernel_booting {
                    return;
                }
                self.kernel_booting = true;
                self.kernel_boot_error = None;
                event::spawn_kernel_boot(self.config_path.clone(), self.event_tx.clone());
            }
            welcome::WelcomeAction::Wizard => {
                self.wizard.reset();
                self.phase = Phase::Boot(BootScreen::Wizard);
            }
        }
    }

    // ─── Tab action handlers ─────────────────────────────────────────────────

    fn handle_dashboard_action(&mut self, action: dashboard::DashboardAction) {
        match action {
            dashboard::DashboardAction::Continue => {}
            dashboard::DashboardAction::Refresh => self.refresh_dashboard(),
            dashboard::DashboardAction::GoToAgents => {
                self.switch_tab(Tab::Agents);
            }
        }
    }

    fn handle_agent_action(&mut self, action: agents::AgentAction) {
        match action {
            agents::AgentAction::Continue => {}
            agents::AgentAction::Back => {
                // In Main phase, Esc from agents just stays on the tab
            }
            agents::AgentAction::CreatedManifest(toml_content) => {
                self.spawn_agent(toml_content);
            }
            agents::AgentAction::ChatWithAgent { id, name } => {
                // From detail view — enter chat with this agent
                if let Some(agent) = self.agents.daemon_agents.iter().find(|a| a.id == id) {
                    self.enter_chat_daemon(agent.id.clone(), agent.name.clone());
                } else if let Some(agent) = self
                    .agents
                    .inprocess_agents
                    .iter()
                    .find(|a| format!("{}", a.id) == id)
                {
                    self.enter_chat_inprocess(agent.id, agent.name.clone());
                } else {
                    // Fallback: treat as daemon
                    self.enter_chat_daemon(id, name);
                }
            }
            agents::AgentAction::KillAgent(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_kill_agent(backend, id, self.event_tx.clone());
                }
            }
            agents::AgentAction::UpdateSkills { id, skills } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_update_agent_skills(backend, id, skills, self.event_tx.clone());
                }
            }
            agents::AgentAction::UpdateMcpServers { id, servers } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_update_agent_mcp_servers(
                        backend,
                        id,
                        servers,
                        self.event_tx.clone(),
                    );
                }
            }
            agents::AgentAction::FetchAgentSkills(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_fetch_agent_skills(backend, id, self.event_tx.clone());
                }
            }
            agents::AgentAction::FetchAgentMcpServers(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_fetch_agent_mcp_servers(backend, id, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_chat_action(&mut self, action: chat::ChatAction) {
        match action {
            chat::ChatAction::Continue => {}
            chat::ChatAction::Back => {
                // In Main phase, go back to Agents tab
                self.chat.reset();
                self.chat_target = None;
                self.switch_tab(Tab::Agents);
            }
            chat::ChatAction::SendMessage(msg) => self.send_message(msg),
            chat::ChatAction::SlashCommand(cmd) => self.handle_slash_command(&cmd),
        }
    }

    fn handle_channel_action(&mut self, action: channels::ChannelAction) {
        match action {
            channels::ChannelAction::Continue => {}
            channels::ChannelAction::Refresh => self.refresh_channels(),
            channels::ChannelAction::TestChannel(name) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_test_channel(backend, name, self.event_tx.clone());
                }
            }
            channels::ChannelAction::ToggleChannel(_name, _enabled) => {
                // Toggle is handled locally in the state; daemon toggle
                // could be spawned here if the API supports it.
            }
            channels::ChannelAction::SaveChannel(name, values) => {
                // Save channel credentials via daemon API
                if let Some(backend) = self.backend.to_ref() {
                    let tx = self.event_tx.clone();
                    std::thread::spawn(move || {
                        if let event::BackendRef::Daemon(base_url) = backend {
                            let client = reqwest::blocking::Client::builder()
                                .timeout(std::time::Duration::from_secs(10))
                                .build()
                                .ok();
                            if let Some(client) = client {
                                let mut fields = serde_json::Map::new();
                                for (k, v) in &values {
                                    fields.insert(k.clone(), serde_json::Value::String(v.clone()));
                                }
                                let body = serde_json::json!({ "fields": fields });
                                let _ = client
                                    .post(format!("{base_url}/api/channels/{name}/configure"))
                                    .json(&body)
                                    .send();
                            }
                        }
                        // Signal tick so the UI refreshes next cycle
                        let _ = tx.send(event::AppEvent::Tick);
                    });
                }
                // Immediately trigger a refresh of the channel list
                self.refresh_channels();
            }
        }
    }

    fn handle_workflow_action(&mut self, action: workflows::WorkflowAction) {
        match action {
            workflows::WorkflowAction::Continue => {}
            workflows::WorkflowAction::Refresh => self.refresh_workflows(),
            workflows::WorkflowAction::LoadRuns(wf_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    self.workflows.loading = true;
                    event::spawn_fetch_workflow_runs(backend, wf_id, self.event_tx.clone());
                }
            }
            workflows::WorkflowAction::CreateWorkflow {
                name,
                description,
                steps_json,
            } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_create_workflow(
                        backend,
                        name,
                        description,
                        steps_json,
                        self.event_tx.clone(),
                    );
                }
            }
            workflows::WorkflowAction::RunWorkflow { id, input } => {
                if let Some(backend) = self.backend.to_ref() {
                    self.workflows.loading = true;
                    event::spawn_run_workflow(backend, id, input, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_trigger_action(&mut self, action: triggers::TriggerAction) {
        match action {
            triggers::TriggerAction::Continue => {}
            triggers::TriggerAction::Refresh => self.refresh_triggers(),
            triggers::TriggerAction::CreateTrigger {
                agent_id,
                pattern_type,
                pattern_param,
                prompt,
                max_fires,
            } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_create_trigger(
                        backend,
                        agent_id,
                        pattern_type,
                        pattern_param,
                        prompt,
                        max_fires,
                        self.event_tx.clone(),
                    );
                }
            }
            triggers::TriggerAction::DeleteTrigger(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_delete_trigger(backend, id, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_sessions_action(&mut self, action: sessions::SessionsAction) {
        match action {
            sessions::SessionsAction::Continue => {}
            sessions::SessionsAction::Refresh => self.refresh_sessions(),
            sessions::SessionsAction::OpenInChat {
                agent_id,
                agent_name,
            } => {
                self.enter_chat_daemon(agent_id, agent_name);
            }
            sessions::SessionsAction::DeleteSession(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_delete_session(backend, id, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_memory_action(&mut self, action: memory::MemoryAction) {
        match action {
            memory::MemoryAction::Continue => {}
            memory::MemoryAction::LoadAgents => self.refresh_memory(),
            memory::MemoryAction::LoadKv(agent_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    self.memory.loading = true;
                    event::spawn_fetch_memory_kv(backend, agent_id, self.event_tx.clone());
                }
            }
            memory::MemoryAction::SaveKv {
                agent_id,
                key,
                value,
            } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_save_memory_kv(
                        backend,
                        agent_id,
                        key,
                        value,
                        self.event_tx.clone(),
                    );
                }
            }
            memory::MemoryAction::DeleteKv { agent_id, key } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_delete_memory_kv(backend, agent_id, key, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_skills_action(&mut self, action: skills::SkillsAction) {
        match action {
            skills::SkillsAction::Continue => {}
            skills::SkillsAction::RefreshInstalled => self.refresh_skills(),
            skills::SkillsAction::SearchClawHub(query) => {
                if let Some(backend) = self.backend.to_ref() {
                    self.skills.loading = true;
                    event::spawn_search_clawhub(backend, query, self.event_tx.clone());
                }
            }
            skills::SkillsAction::BrowseClawHub(sort) => {
                if let Some(backend) = self.backend.to_ref() {
                    self.skills.loading = true;
                    event::spawn_browse_clawhub(backend, sort, self.event_tx.clone());
                }
            }
            skills::SkillsAction::InstallSkill(slug) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_install_skill(backend, slug, self.event_tx.clone());
                }
            }
            skills::SkillsAction::UninstallSkill(name) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_uninstall_skill(backend, name, self.event_tx.clone());
                }
            }
            skills::SkillsAction::RefreshMcp => {
                if let Some(backend) = self.backend.to_ref() {
                    self.skills.loading = true;
                    event::spawn_fetch_mcp_servers(backend, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_extensions_action(&mut self, action: extensions::ExtensionsAction) {
        match action {
            extensions::ExtensionsAction::Continue => {}
            extensions::ExtensionsAction::RefreshAll => self.refresh_extensions(),
            extensions::ExtensionsAction::RefreshHealth => self.refresh_extension_health(),
            extensions::ExtensionsAction::Install(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_install_extension(backend, id, self.event_tx.clone());
                }
            }
            extensions::ExtensionsAction::Remove(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_remove_extension(backend, id, self.event_tx.clone());
                }
            }
            extensions::ExtensionsAction::Reconnect(id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_reconnect_extension(backend, id, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_hands_action(&mut self, action: hands::HandsAction) {
        match action {
            hands::HandsAction::Continue => {}
            hands::HandsAction::RefreshDefinitions => self.refresh_hands(),
            hands::HandsAction::RefreshActive => {
                if let Some(backend) = self.backend.to_ref() {
                    self.hands.loading = true;
                    event::spawn_fetch_active_hands(backend, self.event_tx.clone());
                }
            }
            hands::HandsAction::ActivateHand(hand_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_activate_hand(backend, hand_id, self.event_tx.clone());
                }
            }
            hands::HandsAction::DeactivateHand(instance_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_deactivate_hand(backend, instance_id, self.event_tx.clone());
                }
            }
            hands::HandsAction::PauseHand(instance_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_pause_hand(backend, instance_id, self.event_tx.clone());
                }
            }
            hands::HandsAction::ResumeHand(instance_id) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_resume_hand(backend, instance_id, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_templates_action(&mut self, action: templates::TemplatesAction) {
        match action {
            templates::TemplatesAction::Continue => {}
            templates::TemplatesAction::Refresh => self.refresh_templates(),
            templates::TemplatesAction::SpawnTemplate(name) => {
                // Find template and generate TOML manifest
                if let Some(t) = self.templates.templates.iter().find(|t| t.name == name) {
                    let toml_content = format!(
                        "name = \"{}\"\ndescription = \"{}\"\n\n[model]\nprovider = \"{}\"\nmodel = \"{}\"\n\n[capabilities]\ntools = [\"shell\", \"file_read\", \"file_write\", \"web_fetch\", \"web_search\"]\n",
                        t.name, t.description, t.provider, t.model,
                    );
                    self.spawn_agent(toml_content);
                }
            }
        }
    }

    fn handle_security_action(&mut self, action: security::SecurityAction) {
        match action {
            security::SecurityAction::Continue => {}
            security::SecurityAction::Refresh => self.refresh_security(),
            security::SecurityAction::VerifyChain => {
                if let Some(backend) = self.backend.to_ref() {
                    self.security.loading = true;
                    event::spawn_verify_chain(backend, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_audit_action(&mut self, action: audit::AuditAction) {
        match action {
            audit::AuditAction::Continue => {}
            audit::AuditAction::Refresh => self.refresh_audit(),
            audit::AuditAction::VerifyChain => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_verify_chain(backend, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_usage_action(&mut self, action: usage::UsageAction) {
        match action {
            usage::UsageAction::Continue => {}
            usage::UsageAction::Refresh => self.refresh_usage(),
        }
    }

    fn handle_settings_action(&mut self, action: settings::SettingsAction) {
        match action {
            settings::SettingsAction::Continue => {}
            settings::SettingsAction::RefreshProviders => self.refresh_settings_providers(),
            settings::SettingsAction::RefreshModels => self.refresh_settings_models(),
            settings::SettingsAction::RefreshTools => self.refresh_settings_tools(),
            settings::SettingsAction::SaveProviderKey { name, key } => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_save_provider_key(backend, name, key, self.event_tx.clone());
                }
            }
            settings::SettingsAction::DeleteProviderKey(name) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_delete_provider_key(backend, name, self.event_tx.clone());
                }
            }
            settings::SettingsAction::TestProvider(name) => {
                if let Some(backend) = self.backend.to_ref() {
                    event::spawn_test_provider(backend, name, self.event_tx.clone());
                }
            }
        }
    }

    fn handle_peers_action(&mut self, action: peers::PeersAction) {
        match action {
            peers::PeersAction::Continue => {}
            peers::PeersAction::Refresh => self.refresh_peers(),
        }
    }

    fn handle_logs_action(&mut self, action: logs::LogsAction) {
        match action {
            logs::LogsAction::Continue => {}
            logs::LogsAction::Refresh => self.refresh_logs(),
        }
    }

    // ─── Chat helpers ────────────────────────────────────────────────────────

    fn enter_chat_daemon(&mut self, id: String, name: String) {
        self.chat.reset();
        self.chat.agent_name = name.clone();
        self.chat.mode_label = "daemon".to_string();

        if let Backend::Daemon { ref base_url } = self.backend {
            let client = crate::daemon_client();
            if let Ok(resp) = client.get(format!("{base_url}/api/agents/{id}")).send() {
                if let Ok(body) = resp.json::<serde_json::Value>() {
                    let provider = body["model_provider"].as_str().unwrap_or("?");
                    let model = body["model_name"].as_str().unwrap_or("?");
                    self.chat.model_label = format!("{provider}/{model}");
                }
            }
        }

        self.chat_target = Some(ChatTarget {
            agent_id_daemon: Some(id),
            agent_id_inprocess: None,
            agent_name: name,
        });
        self.chat.push_message(
            chat::Role::System,
            "/help for commands \u{2022} /exit to quit".to_string(),
        );
        self.active_tab = Tab::Chat;
    }

    fn enter_chat_inprocess(&mut self, id: AgentId, name: String) {
        self.chat.reset();
        self.chat.agent_name = name.clone();
        self.chat.mode_label = "in-process".to_string();

        if let Backend::InProcess { ref kernel } = self.backend {
            if let Some(entry) = kernel.registry.get(id) {
                self.chat.model_label = format!(
                    "{}/{}",
                    entry.manifest.model.provider, entry.manifest.model.model
                );
            }
        }

        self.chat_target = Some(ChatTarget {
            agent_id_daemon: None,
            agent_id_inprocess: Some(id),
            agent_name: name,
        });
        self.chat.push_message(
            chat::Role::System,
            "/help for commands \u{2022} /exit to quit".to_string(),
        );
        self.active_tab = Tab::Chat;
    }

    fn send_message(&mut self, message: String) {
        self.chat.is_streaming = true;
        self.chat.thinking = true;
        self.chat.streaming_chars = 0;
        self.chat.last_tokens = None;
        self.chat.status_msg = None;

        match (&self.backend, &self.chat_target) {
            (Backend::Daemon { base_url }, Some(target)) if target.agent_id_daemon.is_some() => {
                event::spawn_daemon_stream(
                    base_url.clone(),
                    target.agent_id_daemon.as_ref().unwrap().clone(),
                    message,
                    self.event_tx.clone(),
                );
            }
            (Backend::InProcess { kernel }, Some(target))
                if target.agent_id_inprocess.is_some() =>
            {
                event::spawn_inprocess_stream(
                    kernel.clone(),
                    target.agent_id_inprocess.unwrap(),
                    message,
                    self.event_tx.clone(),
                );
            }
            _ => {
                self.chat.is_streaming = false;
                self.chat.status_msg = Some("No active connection".to_string());
            }
        }
    }

    fn spawn_agent(&mut self, toml_content: String) {
        match &self.backend {
            Backend::Daemon { base_url } => {
                self.agents.sub = agents::AgentSubScreen::Spawning;
                event::spawn_daemon_agent(base_url.clone(), toml_content, self.event_tx.clone());
            }
            Backend::InProcess { kernel } => {
                let manifest: openfang_types::agent::AgentManifest =
                    match toml::from_str(&toml_content) {
                        Ok(m) => m,
                        Err(e) => {
                            self.agents.status_msg = format!("Invalid manifest: {e}");
                            self.agents.sub = agents::AgentSubScreen::AgentList;
                            return;
                        }
                    };
                let name = manifest.name.clone();
                match kernel.spawn_agent(manifest) {
                    Ok(id) => self.enter_chat_inprocess(id, name),
                    Err(e) => {
                        self.agents.status_msg = format!("Spawn failed: {e}");
                        self.agents.sub = agents::AgentSubScreen::AgentList;
                    }
                }
            }
            Backend::None => {
                self.agents.status_msg = "No backend connected".to_string();
                self.agents.sub = agents::AgentSubScreen::AgentList;
            }
        }
    }

    // ─── Slash commands ──────────────────────────────────────────────────────

    fn handle_slash_command(&mut self, cmd: &str) {
        let parts: Vec<&str> = cmd.splitn(2, ' ').collect();
        match parts[0] {
            "/exit" | "/quit" => self.handle_chat_action(chat::ChatAction::Back),
            "/help" => {
                self.chat.push_message(
                    chat::Role::System,
                    [
                        "/help    \u{2014} show this help",
                        "/status  \u{2014} connection & agent info",
                        "/agents  \u{2014} list running agents",
                        "/model   \u{2014} show current model",
                        "/clear   \u{2014} clear chat history",
                        "/kill    \u{2014} kill the current agent",
                        "/exit    \u{2014} end chat session",
                    ]
                    .join("\n"),
                );
            }
            "/status" => {
                let mut s = Vec::new();
                match &self.backend {
                    Backend::Daemon { base_url } => {
                        s.push(format!("Mode: daemon ({base_url})"));
                        if let Some(ref t) = self.chat_target {
                            s.push(format!("Agent: {}", t.agent_name));
                        }
                    }
                    Backend::InProcess { kernel } => {
                        s.push("Mode: in-process".to_string());
                        s.push(format!("Agents: {}", kernel.registry.count()));
                        if let Some(ref t) = self.chat_target {
                            s.push(format!("Agent: {}", t.agent_name));
                        }
                    }
                    Backend::None => s.push("Mode: disconnected".to_string()),
                }
                self.chat.push_message(chat::Role::System, s.join("\n"));
            }
            "/agents" => {
                let mut lines = Vec::new();
                match &self.backend {
                    Backend::Daemon { base_url } => {
                        let client = crate::daemon_client();
                        if let Ok(resp) = client.get(format!("{base_url}/api/agents")).send() {
                            if let Ok(body) = resp.json::<serde_json::Value>() {
                                if let Some(arr) = body.as_array() {
                                    for a in arr {
                                        lines.push(format!(
                                            "{} [{}] {}",
                                            a["name"].as_str().unwrap_or("?"),
                                            a["state"].as_str().unwrap_or("?"),
                                            a["model_name"].as_str().unwrap_or("?"),
                                        ));
                                    }
                                }
                            }
                        }
                    }
                    Backend::InProcess { kernel } => {
                        for e in kernel.registry.list() {
                            lines.push(format!(
                                "{} [{:?}] {}/{}",
                                e.name, e.state, e.manifest.model.provider, e.manifest.model.model,
                            ));
                        }
                    }
                    Backend::None => {}
                }
                let msg = if lines.is_empty() {
                    "No agents running.".to_string()
                } else {
                    lines.join("\n")
                };
                self.chat.push_message(chat::Role::System, msg);
            }
            "/clear" => {
                let name = self.chat.agent_name.clone();
                let model = self.chat.model_label.clone();
                let mode = self.chat.mode_label.clone();
                self.chat.reset();
                self.chat.agent_name = name;
                self.chat.model_label = model;
                self.chat.mode_label = mode;
                self.chat
                    .push_message(chat::Role::System, "Chat history cleared.".to_string());
            }
            "/kill" => {
                if let Some(ref target) = self.chat_target {
                    let name = target.agent_name.clone();
                    match &self.backend {
                        Backend::Daemon { base_url } => {
                            if let Some(ref id) = target.agent_id_daemon {
                                let client = crate::daemon_client();
                                let url = format!("{base_url}/api/agents/{id}");
                                match client.delete(&url).send() {
                                    Ok(r) if r.status().is_success() => {
                                        self.chat.push_message(
                                            chat::Role::System,
                                            format!("Agent \"{name}\" killed."),
                                        );
                                    }
                                    _ => {
                                        self.chat.push_message(
                                            chat::Role::System,
                                            format!("Failed to kill agent \"{name}\"."),
                                        );
                                    }
                                }
                            }
                        }
                        Backend::InProcess { kernel } => {
                            if let Some(id) = target.agent_id_inprocess {
                                match kernel.kill_agent(id) {
                                    Ok(()) => {
                                        self.chat.push_message(
                                            chat::Role::System,
                                            format!("Agent \"{name}\" killed."),
                                        );
                                    }
                                    Err(e) => {
                                        self.chat.push_message(
                                            chat::Role::System,
                                            format!("Kill failed: {e}"),
                                        );
                                    }
                                }
                            }
                        }
                        Backend::None => {
                            self.chat.push_message(
                                chat::Role::System,
                                "No backend connected.".to_string(),
                            );
                        }
                    }
                }
            }
            "/model" => {
                self.chat.push_message(
                    chat::Role::System,
                    format!("Model: {}", self.chat.model_label),
                );
            }
            "/hands" => match &self.backend {
                Backend::InProcess { kernel } => {
                    let defs = kernel.hand_registry.list_definitions();
                    let instances = kernel.hand_registry.list_instances();
                    let mut msg = format!("Available hands ({}):\n", defs.len());
                    for d in &defs {
                        let reqs_met = kernel
                            .hand_registry
                            .check_requirements(&d.id)
                            .map(|r| r.iter().all(|(_, ok)| *ok))
                            .unwrap_or(false);
                        let badge = if reqs_met { "Ready" } else { "Setup" };
                        msg.push_str(&format!(
                            "  {} {} — {} [{}]\n",
                            d.icon, d.name, d.description, badge
                        ));
                    }
                    if !instances.is_empty() {
                        msg.push_str(&format!("\nActive hands ({}):\n", instances.len()));
                        for i in &instances {
                            msg.push_str(&format!(
                                "  {} — {} ({})\n",
                                i.agent_name, i.hand_id, i.status
                            ));
                        }
                    }
                    self.chat.push_message(chat::Role::System, msg);
                }
                _ => {
                    self.chat.push_message(
                        chat::Role::System,
                        "Hands info requires in-process mode. Use the Hands tab instead."
                            .to_string(),
                    );
                }
            },
            _ => {
                self.chat.push_message(
                    chat::Role::System,
                    format!("Unknown command: {}. Type /help", parts[0]),
                );
            }
        }
    }

    // ─── Drawing ─────────────────────────────────────────────────────────────

    fn draw(&mut self, frame: &mut ratatui::Frame) {
        let area = frame.area();

        match self.phase {
            Phase::Boot(BootScreen::Welcome) => {
                welcome::draw(frame, area, &mut self.welcome);

                // Overlay boot status on top of the welcome card
                if self.kernel_booting {
                    let spinner =
                        theme::SPINNER_FRAMES[self.welcome.tick % theme::SPINNER_FRAMES.len()];
                    let msg = format!(" {spinner} Booting kernel\u{2026}");
                    render_toast(frame, area, &msg, theme::YELLOW);
                }
                if let Some(ref err) = self.kernel_boot_error {
                    let msg = format!(" \u{2718} {err}");
                    render_toast(frame, area, &msg, theme::RED);
                }
            }
            Phase::Boot(BootScreen::Wizard) => wizard::draw(frame, area, &mut self.wizard),
            Phase::Main => {
                // Split: tab bar (1 line) + content
                let chunks = Layout::vertical([
                    Constraint::Length(1), // tab bar
                    Constraint::Min(1),    // content
                ])
                .split(area);

                self.draw_tab_bar(frame, chunks[0]);

                match self.active_tab {
                    Tab::Dashboard => dashboard::draw(frame, chunks[1], &mut self.dashboard),
                    Tab::Agents => agents::draw(frame, chunks[1], &mut self.agents),
                    Tab::Chat => chat::draw(frame, chunks[1], &mut self.chat),
                    Tab::Channels => channels::draw(frame, chunks[1], &mut self.channels),
                    Tab::Workflows => workflows::draw(frame, chunks[1], &mut self.workflows),
                    Tab::Triggers => triggers::draw(frame, chunks[1], &mut self.triggers),
                    Tab::Sessions => sessions::draw(frame, chunks[1], &mut self.sessions),
                    Tab::Memory => memory::draw(frame, chunks[1], &mut self.memory),
                    Tab::Skills => skills::draw(frame, chunks[1], &mut self.skills),
                    Tab::Hands => hands::draw(frame, chunks[1], &mut self.hands),
                    Tab::Extensions => extensions::draw(frame, chunks[1], &mut self.extensions),
                    Tab::Templates => templates::draw(frame, chunks[1], &mut self.templates),
                    Tab::Security => security::draw(frame, chunks[1], &mut self.security),
                    Tab::Audit => audit::draw(frame, chunks[1], &mut self.audit),
                    Tab::Usage => usage::draw(frame, chunks[1], &mut self.usage),
                    Tab::Settings => settings::draw(frame, chunks[1], &mut self.settings),
                    Tab::Peers => peers::draw(frame, chunks[1], &mut self.peers),
                    Tab::Logs => logs::draw(frame, chunks[1], &mut self.logs),
                }
            }
        }
    }

    fn draw_tab_bar(&mut self, frame: &mut ratatui::Frame, area: Rect) {
        let width = area.width as usize;

        // Compute all tab labels with their widths
        let tab_labels: Vec<(usize, String)> = TABS
            .iter()
            .map(|tab| {
                let label = format!(" {} ", tab.label());
                let w = label.len() + 1; // +1 for spacing
                (w, label)
            })
            .collect();

        // Reserve space for overflow indicators (2 chars each) and hint
        let indicator_width = 2; // "< " or " >"
        let hint = if self.ctrl_c_pending {
            "Press Ctrl+C again to quit"
        } else {
            "Ctrl+C×2 quit  Tab/Ctrl+\u{2190}\u{2192} switch"
        };
        let hint_width = hint.len() + 2;
        let available = width.saturating_sub(hint_width + 2);

        // Ensure active tab is visible by adjusting scroll offset
        let active_idx = self.active_tab.index();

        // Scroll so active tab fits in the visible window
        if active_idx < self.tab_scroll_offset {
            self.tab_scroll_offset = active_idx;
        }

        // Find how many tabs fit starting from scroll offset
        loop {
            let mut used = if self.tab_scroll_offset > 0 {
                indicator_width
            } else {
                1
            }; // leading space or left indicator
            let mut last_visible = self.tab_scroll_offset;
            for (i, (tab_w, _)) in tab_labels.iter().enumerate().skip(self.tab_scroll_offset) {
                if used + tab_w > available {
                    break;
                }
                used += tab_w;
                last_visible = i;
            }
            if active_idx <= last_visible || self.tab_scroll_offset >= TABS.len() - 1 {
                break;
            }
            self.tab_scroll_offset += 1;
        }

        let mut spans: Vec<Span> = Vec::new();

        // Left overflow indicator
        if self.tab_scroll_offset > 0 {
            spans.push(Span::styled(
                "\u{25c0} ",
                Style::default().fg(theme::TEXT_TERTIARY),
            ));
        } else {
            spans.push(Span::raw(" "));
        }

        // Render visible tabs
        let mut used = if self.tab_scroll_offset > 0 {
            indicator_width
        } else {
            1
        };
        let mut last_rendered = self.tab_scroll_offset;
        for (i, ((tab_w, label), &tab)) in tab_labels
            .iter()
            .zip(TABS.iter())
            .enumerate()
            .skip(self.tab_scroll_offset)
        {
            if used + tab_w > available {
                break;
            }
            if tab == self.active_tab {
                spans.push(Span::styled(label.clone(), theme::tab_active()));
            } else {
                spans.push(Span::styled(label.clone(), theme::tab_inactive()));
            }
            spans.push(Span::raw(" "));
            used += tab_w;
            last_rendered = i;
        }

        // Right overflow indicator
        if last_rendered < TABS.len() - 1 {
            spans.push(Span::styled(
                " \u{25b6}",
                Style::default().fg(theme::TEXT_TERTIARY),
            ));
        }

        // Right-aligned hint (yellow warning when Ctrl+C pending)
        let hint_style = if self.ctrl_c_pending {
            Style::default()
                .fg(theme::YELLOW)
                .add_modifier(ratatui::style::Modifier::BOLD)
        } else {
            theme::hint_style()
        };
        let spans_width: usize = spans.iter().map(|s| s.content.len()).sum();
        let padding = width.saturating_sub(spans_width + hint.len());
        if padding > 0 {
            spans.push(Span::raw(" ".repeat(padding)));
            spans.push(Span::styled(hint, hint_style));
        }

        let bar = Paragraph::new(Line::from(spans)).style(Style::default().bg(theme::BG_CARD));
        frame.render_widget(bar, area);
    }
}

/// Draw a one-line toast at the bottom of the screen.
fn render_toast(frame: &mut ratatui::Frame, area: Rect, msg: &str, color: ratatui::style::Color) {
    let w = (msg.len() as u16 + 4).min(area.width);
    let x = area.width.saturating_sub(w) / 2;
    let y = area.height.saturating_sub(2);
    let toast_area = Rect::new(x, y, w, 1);
    let para = Paragraph::new(Line::from(vec![Span::styled(
        msg,
        Style::default().fg(color),
    )]));
    frame.render_widget(para, toast_area);
}

// ─── Entry point ─────────────────────────────────────────────────────────────

/// Entry point for the TUI interactive mode.
pub fn run(config: Option<PathBuf>) {
    // Panic hook: always restore terminal
    let original_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        ratatui::restore();
        original_hook(info);
    }));

    let mut terminal = ratatui::init();

    // 50ms tick → 20fps spinner animation, snappy key response
    let (tx, rx) = event::spawn_event_thread(Duration::from_millis(50));
    let mut app = App::new(config, tx);

    // Initial screen
    if wizard::needs_setup() {
        app.wizard.reset();
        app.phase = Phase::Boot(BootScreen::Wizard);
    } else {
        app.phase = Phase::Boot(BootScreen::Welcome);
        // Non-blocking daemon detection
        app.start_daemon_detect();
    }

    // ── Main loop ────────────────────────────────────────────────────────────
    // Draw first, then block on events. This ensures the first frame appears
    // immediately, before any event processing.
    while !app.should_quit {
        terminal
            .draw(|frame| app.draw(frame))
            .expect("Failed to draw");

        // Block until at least one event arrives (or 33ms timeout for ~30fps)
        match rx.recv_timeout(Duration::from_millis(33)) {
            Ok(ev) => app.handle_event(ev),
            Err(mpsc::RecvTimeoutError::Timeout) => {}
            Err(mpsc::RecvTimeoutError::Disconnected) => break,
        }
        // Drain all queued events immediately (batch processing)
        while let Ok(ev) = rx.try_recv() {
            app.handle_event(ev);
        }
    }

    ratatui::restore();
}
