use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use std::time::Duration;

use crate::tui_app::{App, InputMode};

/// Poll for and handle events. Returns true if an event was processed.
pub fn handle_events(app: &mut App) -> std::io::Result<bool> {
    // Always tick the pull progress and worker messages (non-blocking)
    app.tick_pull();
    app.tick_bench();
    app.tick_bench_offer();
    app.tick_bench_fetch();

    if event::poll(Duration::from_millis(50))?
        && let Event::Key(key) = event::read()?
    {
        // Only handle Press events (ignore Release on some platforms)
        if key.kind != KeyEventKind::Press {
            return Ok(false);
        }
        match app.input_mode {
            InputMode::Normal => handle_normal_mode(app, key),
            InputMode::Visual => handle_visual_mode(app, key),
            InputMode::Select => handle_select_mode(app, key),
            InputMode::Search => handle_search_mode(app, key),
            InputMode::Plan => handle_plan_mode(app, key),
            InputMode::ProviderPopup => handle_provider_popup_mode(app, key),
            InputMode::UseCasePopup => handle_use_case_popup_mode(app, key),
            InputMode::CapabilityPopup => handle_capability_popup_mode(app, key),
            InputMode::DownloadProviderPopup => handle_download_provider_popup_mode(app, key),
            InputMode::QuantPopup => handle_quant_popup_mode(app, key),
            InputMode::RunModePopup => handle_run_mode_popup_mode(app, key),
            InputMode::ParamsBucketPopup => handle_params_bucket_popup_mode(app, key),
            InputMode::LicensePopup => handle_license_popup_mode(app, key),
            InputMode::RuntimePopup => handle_runtime_popup_mode(app, key),
            InputMode::HelpPopup => handle_help_popup_mode(app, key),
            InputMode::Simulation => handle_simulation_mode(app, key),
            InputMode::AdvancedConfig => handle_advanced_config_mode(app, key),
            InputMode::DownloadManager => handle_download_manager_mode(app, key),
            InputMode::FilterPopup => handle_filter_popup_mode(app, key),
            InputMode::Benchmarks => handle_benchmarks_mode(app, key),
            InputMode::BenchOffer => handle_bench_offer_mode(app, key),
        }
        return Ok(true);
    }
    Ok(false)
}

fn handle_normal_mode(app: &mut App, key: KeyEvent) {
    // Handle bench quit-confirmation first (overrides all other handlers)
    if app.bench_confirm_quit {
        match key.code {
            KeyCode::Char('q') | KeyCode::Char('y') | KeyCode::Char('Y') => {
                app.bench_confirm_quit = false;
                app.close_bench();
            }
            _ => {
                app.bench_confirm_quit = false;
                app.bench_progress = format!(
                    "{}/{} tests — Benchmarking...",
                    app.bench_tests_done, app.bench_tests_total
                );
            }
        }
        return;
    }

    match key.code {
        // Quit
        KeyCode::Char('q') | KeyCode::Esc => {
            if app.show_bench {
                if app.bench_show_detail {
                    app.bench_show_detail = false;
                } else if app.bench_running {
                    app.bench_confirm_quit = true;
                    app.bench_progress =
                        "Inference bench running! Press q again to exit, any key to cancel"
                            .to_string();
                } else {
                    app.close_bench();
                }
            } else if app.show_downloads {
                app.close_downloads();
            } else if app.show_multi_compare {
                app.close_multi_compare();
            } else if app.show_detail {
                app.show_detail = false;
            } else if app.show_compare {
                app.show_compare = false;
            } else {
                app.save_filters();
                app.should_quit = true;
            }
        }

        // Live bench view navigation (only active when bench view is open)
        KeyCode::Char('j') | KeyCode::Down if app.show_bench => {
            if app.bench_show_detail {
                app.live_bench_scroll += 1;
            } else {
                let max = app.bench_model_status.len().saturating_sub(1);
                if app.bench_selected_row < max {
                    app.bench_selected_row += 1;
                }
            }
        }
        KeyCode::Char('k') | KeyCode::Up if app.show_bench => {
            if app.bench_show_detail {
                app.live_bench_scroll = app.live_bench_scroll.saturating_sub(1);
            } else if app.bench_selected_row > 0 {
                app.bench_selected_row -= 1;
            }
        }
        KeyCode::Char('r') if app.show_bench => {
            app.toggle_bench_view();
        }
        KeyCode::Enter if app.show_bench => {
            if app.bench_show_detail {
                app.bench_show_detail = false;
            } else {
                app.bench_show_detail = true;
                app.live_bench_scroll = 0;
            }
        }

        // Navigation — in multi-compare, h/l scroll columns
        KeyCode::Char('h') if app.show_multi_compare => app.multi_compare_scroll_left(),
        KeyCode::Char('l') if app.show_multi_compare => app.multi_compare_scroll_right(),
        KeyCode::Left if app.show_multi_compare => app.multi_compare_scroll_left(),
        KeyCode::Right if app.show_multi_compare => app.multi_compare_scroll_right(),

        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => app.half_page_up(),
        KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => app.half_page_down(),
        KeyCode::Up | KeyCode::Char('k') => app.move_up(),
        KeyCode::Down | KeyCode::Char('j') => app.move_down(),
        KeyCode::PageUp => app.page_up(),
        KeyCode::PageDown => app.page_down(),
        KeyCode::Home | KeyCode::Char('g') => app.cycle_top_bottom(),
        // Visual mode
        KeyCode::Char('v') => app.enter_visual_mode(),

        // Select mode
        KeyCode::Char('V') => app.enter_select_mode(),

        // Search
        KeyCode::Char('/') => app.enter_search(),

        // Fit filter
        KeyCode::Char('f') => app.cycle_fit_filter(),

        // Filter popup (range filters, sort direction, fit)
        KeyCode::Char('F') => app.open_filter_popup(),

        // Availability filter
        KeyCode::Char('a') => app.cycle_availability_filter(),

        // TP compatibility filter
        KeyCode::Char('T') => app.cycle_tp_filter(),

        // Sort column
        KeyCode::Char('s') => app.cycle_sort_column(),

        // Theme
        KeyCode::Char('t') => app.cycle_theme(),

        // Plan view
        KeyCode::Char('p') => app.open_plan_mode(),

        // Provider popup
        KeyCode::Char('P') => app.open_provider_popup(),
        KeyCode::Char('U') => app.open_use_case_popup(),
        KeyCode::Char('C') => app.open_capability_popup(),
        KeyCode::Char('L') => app.open_license_popup(),
        KeyCode::Char('R') => app.open_runtime_popup(),
        KeyCode::Char('S') => app.open_simulation_popup(),
        KeyCode::Char('h') => app.open_help_popup(),

        // Installed-first sort toggle (any provider)
        KeyCode::Char('i')
            if app.ollama_available
                || app.mlx_available
                || app.llamacpp_available
                || app.lmstudio_available
                || app.vllm_available
                || app.ramalama_available =>
        {
            app.toggle_installed_first()
        }

        // Download model via best provider (requires confirmation)
        KeyCode::Char('d')
            if app.ollama_available
                || app.mlx_available
                || app.llamacpp_available
                || app.lmstudio_available
                || app.vllm_available =>
        {
            if app.pull_active.is_none() {
                app.start_download();
            }
        }

        // Refresh installed models
        KeyCode::Char('r')
            if app.ollama_available
                || app.mlx_available
                || app.llamacpp_available
                || app.lmstudio_available
                || app.vllm_available
                || app.ramalama_available =>
        {
            app.refresh_installed()
        }

        // Download manager view
        KeyCode::Char('D') => app.toggle_downloads(),

        // Benchmarks view (localmaxxing.com community leaderboard)
        KeyCode::Char('b') => app.open_benchmarks(),

        // Live inference-bench view (llmfit bench — I=open, I again=rerun)
        KeyCode::Char('I') if app.show_bench => app.rerun_bench(),
        KeyCode::Char('I') => app.open_bench(),

        // Advanced Config popup
        KeyCode::Char('A') => app.open_advanced_config_popup(),

        // Detail view
        KeyCode::Enter => app.toggle_detail(),

        // Compare view
        KeyCode::Char('m') => app.mark_selected_for_compare(),
        KeyCode::Char('c') => app.toggle_compare_view(),
        KeyCode::Char('x') => app.clear_compare_mark(),
        KeyCode::Char('y') => app.copy_selected_model_name(),

        _ => {}
    }
}

fn handle_visual_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        // Exit visual mode
        KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('v') => app.exit_visual_mode(),

        // Navigation (extends selection)
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => app.half_page_up(),
        KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => app.half_page_down(),
        KeyCode::Up | KeyCode::Char('k') => app.move_up(),
        KeyCode::Down | KeyCode::Char('j') => app.move_down(),
        KeyCode::PageUp => app.page_up(),
        KeyCode::PageDown => app.page_down(),
        KeyCode::Home | KeyCode::Char('g') => app.cycle_top_bottom(),

        // Mark all selected for compare
        KeyCode::Char('m') => app.mark_selected_for_compare(),

        // Compare first and last in visual selection
        KeyCode::Char('c') => app.visual_compare(),

        _ => {}
    }
}

fn handle_select_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        // Exit select mode
        KeyCode::Esc | KeyCode::Char('q') => app.exit_select_mode(),

        // Column navigation
        KeyCode::Left | KeyCode::Char('h') => app.select_column_left(),
        KeyCode::Right | KeyCode::Char('l') => app.select_column_right(),

        // Activate filter for current column
        KeyCode::Enter | KeyCode::Char(' ') => app.activate_select_column_filter(),

        // Row navigation (still works in select mode)
        KeyCode::Up | KeyCode::Char('k') => app.move_up(),
        KeyCode::Down | KeyCode::Char('j') => app.move_down(),

        _ => {}
    }
}

fn handle_search_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Enter => app.exit_search(),

        KeyCode::Backspace => app.search_backspace(),
        KeyCode::Delete => app.search_delete(),

        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.clear_search();
        }

        KeyCode::Left => app.search_cursor_left(),
        KeyCode::Right => app.search_cursor_right(),

        KeyCode::Char(c) if allows_search_text_input(key.modifiers) => app.search_input(c),

        // Allow navigation while searching
        KeyCode::Up => app.move_up(),
        KeyCode::Down => app.move_down(),

        _ => {}
    }
}

fn handle_provider_popup_mode(app: &mut App, key: KeyEvent) {
    let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
    let shift = key.modifiers.contains(KeyModifiers::SHIFT);
    match key.code {
        KeyCode::Esc => app.close_provider_popup(),

        KeyCode::Up if shift => app.provider_popup_up(25),
        KeyCode::Down if shift => app.provider_popup_down(25),
        KeyCode::Up => app.provider_popup_up(1),
        KeyCode::Down => app.provider_popup_down(1),

        KeyCode::Left => app.provider_search_cursor_left(),
        KeyCode::Right => app.provider_search_cursor_right(),
        KeyCode::Home => app.provider_search_cursor_home(),
        KeyCode::End => app.provider_search_cursor_end(),

        // Space toggles too (provider names never contain spaces).
        KeyCode::Enter | KeyCode::Char(' ') => app.provider_popup_toggle(),

        KeyCode::Backspace => app.provider_search_backspace(),
        KeyCode::Delete => app.provider_search_delete(),

        // Ctrl shortcuts (typing plain letters filters, so these are modified).
        KeyCode::Char('u') if ctrl => app.provider_search_clear(),
        KeyCode::Char('a') if ctrl => app.provider_popup_select_all(),
        KeyCode::Char('n') if ctrl => app.provider_popup_clear_all(),

        // Plain printable ASCII filters the provider list. Reject modified
        // character events such as macOS Option/Command-arrow artifacts.
        KeyCode::Char(c) if is_plain_provider_filter_char(c, key.modifiers) => {
            app.provider_search_input(c)
        }

        _ => {}
    }
}

fn allows_search_text_input(modifiers: KeyModifiers) -> bool {
    !modifiers.intersects(
        KeyModifiers::CONTROL
            | KeyModifiers::ALT
            | KeyModifiers::SUPER
            | KeyModifiers::HYPER
            | KeyModifiers::META,
    )
}

fn is_plain_provider_filter_char(c: char, modifiers: KeyModifiers) -> bool {
    c.is_ascii_graphic()
        && !modifiers.intersects(
            KeyModifiers::CONTROL
                | KeyModifiers::ALT
                | KeyModifiers::SUPER
                | KeyModifiers::HYPER
                | KeyModifiers::META,
        )
}

fn handle_plan_mode(app: &mut App, key: KeyEvent) {
    // Every plan field is a text input and quant names contain 'q'/'j'/'k'
    // (q4_k_m), so no plain letter may double as a binding here (#781).
    match key.code {
        KeyCode::Esc => app.close_plan_mode(),
        KeyCode::Tab | KeyCode::Down => app.plan_next_field(),
        KeyCode::BackTab | KeyCode::Up => app.plan_prev_field(),
        KeyCode::Left => app.plan_cursor_left(),
        KeyCode::Right => app.plan_cursor_right(),
        KeyCode::Backspace => app.plan_backspace(),
        KeyCode::Delete => app.plan_delete(),
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.plan_clear_field()
        }
        KeyCode::Char(c) if allows_search_text_input(key.modifiers) => app.plan_input(c),
        _ => {}
    }
}

fn handle_use_case_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('U') | KeyCode::Char('q') => app.close_use_case_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.use_case_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.use_case_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.use_case_popup_toggle(),

        KeyCode::Char('a') => app.use_case_popup_select_all(),

        _ => {}
    }
}

fn handle_capability_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('C') | KeyCode::Char('q') => app.close_capability_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.capability_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.capability_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.capability_popup_toggle(),

        KeyCode::Char('a') => app.capability_popup_select_all(),

        _ => {}
    }
}

fn handle_download_provider_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_download_provider_popup(),
        KeyCode::Up | KeyCode::Char('k') => app.download_provider_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.download_provider_popup_down(),
        KeyCode::Enter | KeyCode::Char(' ') => app.confirm_download_provider_selection(),
        _ => {}
    }
}

fn handle_quant_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_quant_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.quant_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.quant_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.quant_popup_toggle(),

        KeyCode::Char('a') => app.quant_popup_select_all(),

        _ => {}
    }
}

fn handle_run_mode_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_run_mode_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.run_mode_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.run_mode_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.run_mode_popup_toggle(),

        KeyCode::Char('a') => app.run_mode_popup_select_all(),

        _ => {}
    }
}

fn handle_params_bucket_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_params_bucket_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.params_bucket_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.params_bucket_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.params_bucket_popup_toggle(),

        KeyCode::Char('a') => app.params_bucket_popup_select_all(),

        _ => {}
    }
}

fn handle_license_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('L') | KeyCode::Char('q') => app.close_license_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.license_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.license_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.license_popup_toggle(),

        KeyCode::Char('a') => app.license_popup_select_all(),

        _ => {}
    }
}

fn handle_runtime_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('R') | KeyCode::Char('q') => app.close_runtime_popup(),

        KeyCode::Up | KeyCode::Char('k') => app.runtime_popup_up(),
        KeyCode::Down | KeyCode::Char('j') => app.runtime_popup_down(),

        KeyCode::Char(' ') | KeyCode::Enter => app.runtime_popup_toggle(),

        KeyCode::Char('a') => app.runtime_popup_select_all(),

        _ => {}
    }
}

fn handle_help_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('h') | KeyCode::Char('q') => app.close_help_popup(),
        KeyCode::Up | KeyCode::Char('k') => {
            if app.help_scroll > 0 {
                app.help_scroll -= 1;
            }
        }
        KeyCode::Down | KeyCode::Char('j') => {
            app.help_scroll += 1;
        }
        _ => {}
    }
}

fn handle_simulation_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_simulation_popup(),

        // Apply simulation
        KeyCode::Enter => app.apply_simulation(),

        // Reset to real hardware
        KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.reset_simulation();
            app.close_simulation_popup();
        }

        // Field navigation
        KeyCode::Tab | KeyCode::Down | KeyCode::Char('j') => app.sim_next_field(),
        KeyCode::BackTab | KeyCode::Up | KeyCode::Char('k') => app.sim_prev_field(),

        // Cursor movement within field
        KeyCode::Left => app.sim_cursor_left(),
        KeyCode::Right => app.sim_cursor_right(),

        // Editing
        KeyCode::Backspace => app.sim_backspace(),
        KeyCode::Delete => app.sim_delete(),
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.sim_clear_field()
        }

        // Character input (digits and decimal point)
        KeyCode::Char(c) if c.is_ascii_digit() || c == '.' => app.sim_input(c),

        _ => {}
    }
}

fn handle_advanced_config_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_advanced_config_popup(),

        // Apply config changes
        KeyCode::Enter => app.apply_advanced_config(),

        // Field navigation
        KeyCode::Tab | KeyCode::Down | KeyCode::Char('j') => app.adv_config_next_field(),
        KeyCode::BackTab | KeyCode::Up | KeyCode::Char('k') => app.adv_config_prev_field(),

        // Cursor movement within field
        KeyCode::Left => app.adv_config_cursor_left(),
        KeyCode::Right => app.adv_config_cursor_right(),

        // Editing
        KeyCode::Backspace => app.adv_config_backspace(),
        KeyCode::Delete => app.adv_config_delete(),
        KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.reset_advanced_config()
        }
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.adv_config_clear_field()
        }

        // Character input (digits and decimal point)
        KeyCode::Char(c) if c.is_ascii_digit() || c == '.' => app.adv_config_input(c),

        _ => {}
    }
}

fn handle_download_manager_mode(app: &mut App, key: KeyEvent) {
    use crate::tui_app::DownloadManagerFocus;

    // Handle delete confirmation first
    if app.dm_confirm_delete {
        match key.code {
            KeyCode::Char('y') => {
                app.delete_selected_download();
                app.dm_confirm_delete = false;
            }
            _ => app.dm_confirm_delete = false,
        }
        return;
    }

    // Handle directory editing mode
    if app.dm_editing_dir {
        match key.code {
            KeyCode::Esc => {
                app.dm_editing_dir = false;
            }
            KeyCode::Enter => {
                app.apply_download_dir();
                app.dm_editing_dir = false;
            }
            KeyCode::Backspace => {
                app.dm_dir_backspace();
            }
            KeyCode::Delete => {
                app.dm_dir_delete();
            }
            KeyCode::Left => {
                app.dm_dir_cursor_left();
            }
            KeyCode::Right => {
                app.dm_dir_cursor_right();
            }
            KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                app.dm_dir_clear();
            }
            KeyCode::Char(c) => {
                app.insert_dm_dir_char(c);
            }
            _ => {}
        }
        return;
    }

    match key.code {
        // Close
        KeyCode::Esc | KeyCode::Char('D') | KeyCode::Char('q') => app.close_downloads(),

        // Focus cycling
        KeyCode::Tab => app.dm_focus = app.dm_focus.next(),
        KeyCode::BackTab => app.dm_focus = app.dm_focus.prev(),

        // Navigation within history
        KeyCode::Up | KeyCode::Char('k') if app.dm_focus == DownloadManagerFocus::History => {
            if app.dm_history_cursor > 0 {
                app.dm_history_cursor -= 1;
            }
        }
        KeyCode::Down | KeyCode::Char('j') if app.dm_focus == DownloadManagerFocus::History => {
            let len = app.download_history.records.len();
            if len > 0 && app.dm_history_cursor < len - 1 {
                app.dm_history_cursor += 1;
            }
        }

        // Delete model
        KeyCode::Char('x') if app.dm_focus == DownloadManagerFocus::History => {
            if !app.download_history.records.is_empty() {
                app.dm_confirm_delete = true;
            }
        }

        // Edit download directory
        KeyCode::Char('e') if app.dm_focus == DownloadManagerFocus::Config => {
            app.start_editing_download_dir();
        }

        _ => {}
    }
}

fn handle_filter_popup_mode(app: &mut App, key: KeyEvent) {
    match key.code {
        KeyCode::Esc | KeyCode::Char('q') => app.close_filter_popup(),

        KeyCode::Enter => app.apply_filter_popup(),

        // Field navigation
        KeyCode::Tab | KeyCode::Down => app.filter_next_field(),
        KeyCode::BackTab | KeyCode::Up => app.filter_prev_field(),

        // Cursor movement within field
        KeyCode::Left => app.filter_cursor_left(),
        KeyCode::Right => app.filter_cursor_right(),

        // Editing
        KeyCode::Backspace => app.filter_backspace(),
        KeyCode::Delete => app.filter_delete(),
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            if matches!(
                app.filter_field,
                crate::tui_app::FilterPopupField::SortDirection
                    | crate::tui_app::FilterPopupField::FitFilter
                    | crate::tui_app::FilterPopupField::Availability
            ) {
                return;
            }
            app.filter_clear_active_input();
        }

        // Sort direction toggle
        KeyCode::Char(' ')
            if app.filter_field == crate::tui_app::FilterPopupField::SortDirection =>
        {
            app.filter_toggle_sort_direction()
        }

        // Fit filter cycling
        KeyCode::Char(' ') if app.filter_field == crate::tui_app::FilterPopupField::FitFilter => {
            app.cycle_filter_fit()
        }

        // Availability filter cycling (All / GGUF Avail / Installed)
        KeyCode::Char(' ')
            if app.filter_field == crate::tui_app::FilterPopupField::Availability =>
        {
            app.cycle_filter_availability()
        }

        // Numeric input
        KeyCode::Char(c) if c.is_ascii_digit() || c == '.' => app.filter_input(c),

        _ => {}
    }
}

fn handle_bench_offer_mode(app: &mut App, key: KeyEvent) {
    use crate::tui_app::BenchOfferState;
    match app.bench_offer_state {
        BenchOfferState::Offer => match key.code {
            KeyCode::Enter => app.bench_offer_confirm(),
            KeyCode::Char(' ') | KeyCode::Char('s') => app.bench_offer_toggle_share(),
            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('n') => app.bench_offer_dismiss(),
            _ => {}
        },
        // While running, Esc detaches the worker and drops to the leaderboard.
        BenchOfferState::Running => {
            if key.code == KeyCode::Esc {
                app.bench_offer_dismiss();
            }
        }
        BenchOfferState::Done | BenchOfferState::Error => match key.code {
            KeyCode::Enter | KeyCode::Esc | KeyCode::Char('q') => app.bench_offer_dismiss(),
            _ => {}
        },
    }
}

fn handle_benchmarks_mode(app: &mut App, key: KeyEvent) {
    // Hardware picker sub-modal takes priority when open
    if app.bench_hw_picker_open {
        match key.code {
            KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('H') => app.close_bench_hw_picker(),
            KeyCode::Up | KeyCode::Char('k') => app.bench_hw_picker_up(),
            KeyCode::Down | KeyCode::Char('j') => app.bench_hw_picker_down(),
            KeyCode::Enter | KeyCode::Char(' ') => app.bench_hw_picker_select(),
            _ => {}
        }
        return;
    }

    // `/` search box captures keystrokes while active
    if app.bench_search_active {
        match key.code {
            KeyCode::Esc => app.bench_search_clear(),
            KeyCode::Enter => app.bench_search_accept(),
            KeyCode::Backspace => app.bench_search_backspace(),
            KeyCode::Up => app.bench_move_up(),
            KeyCode::Down => app.bench_move_down(),
            KeyCode::Char(c) if allows_search_text_input(key.modifiers) => {
                app.bench_search_input(c)
            }
            _ => {}
        }
        return;
    }

    match key.code {
        // With a filter applied, Esc clears it first; q/b still close directly.
        KeyCode::Esc if !app.bench_search_query.is_empty() => app.bench_search_clear(),
        KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('b') => app.close_benchmarks(),
        KeyCode::Char('/') => app.bench_search_start(),
        KeyCode::Up | KeyCode::Char('k') => app.bench_move_up(),
        KeyCode::Down | KeyCode::Char('j') => app.bench_move_down(),
        KeyCode::Char('r') => app.bench_refresh(),
        KeyCode::Char('H') => app.open_bench_hw_picker(),
        _ => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tui_app::PlanField;
    use llmfit_core::hardware::{GpuBackend, SystemSpecs};

    fn plan_mode_app() -> App {
        let mut app = App::with_specs_and_context(
            SystemSpecs {
                total_ram_gb: 16.0,
                available_ram_gb: 12.0,
                total_cpu_cores: 8,
                cpu_name: "Test CPU".to_string(),
                has_gpu: false,
                gpu_vram_gb: None,
                total_gpu_vram_gb: None,
                gpu_available_gb: None,
                gpu_name: None,
                gpu_count: 0,
                unified_memory: false,
                backend: GpuBackend::CpuX86,
                gpus: Vec::new(),
                cluster_mode: false,
                cluster_node_count: 0,
            },
            None,
        );
        app.input_mode = InputMode::Plan;
        app.show_plan = true;
        app
    }

    fn plain(c: char) -> KeyEvent {
        KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)
    }

    #[test]
    fn plan_mode_text_fields_accept_q_j_k() {
        // Regression for #781: 'q' closed the plan screen and 'j'/'k' jumped
        // fields, making quant names like q4_k_m impossible to type.
        let mut app = plan_mode_app();
        app.plan_field = PlanField::KvQuant;
        for c in ['q', '4', '_', 'k', 'j'] {
            handle_plan_mode(&mut app, plain(c));
        }
        assert_eq!(app.input_mode, InputMode::Plan, "plan must stay open");
        assert_eq!(app.plan_field, PlanField::KvQuant, "field must not change");
        assert_eq!(app.plan_kv_quant_input, "q4_kj");
    }

    #[test]
    fn plan_mode_esc_still_closes_and_tab_navigates() {
        let mut app = plan_mode_app();
        app.plan_field = PlanField::Context;
        handle_plan_mode(&mut app, KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
        assert_eq!(app.plan_field, PlanField::Quant);
        handle_plan_mode(&mut app, KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
        assert_eq!(app.input_mode, InputMode::Normal);
    }

    #[test]
    fn search_text_accepts_unmodified_and_shift_modified_input() {
        assert!(allows_search_text_input(KeyModifiers::NONE));
        assert!(allows_search_text_input(KeyModifiers::SHIFT));
    }

    #[test]
    fn search_text_rejects_modified_navigation_artifacts() {
        assert!(!allows_search_text_input(KeyModifiers::ALT));
        assert!(!allows_search_text_input(KeyModifiers::SUPER));
        assert!(!allows_search_text_input(KeyModifiers::CONTROL));
        assert!(!allows_search_text_input(KeyModifiers::META));
        assert!(!allows_search_text_input(KeyModifiers::HYPER));
        assert!(!allows_search_text_input(
            KeyModifiers::ALT | KeyModifiers::SHIFT
        ));
        assert!(!allows_search_text_input(
            KeyModifiers::SUPER | KeyModifiers::SHIFT
        ));
    }

    #[test]
    fn provider_filter_text_accepts_plain_ascii_graphic_chars() {
        assert!(is_plain_provider_filter_char('o', KeyModifiers::NONE));
        assert!(is_plain_provider_filter_char('O', KeyModifiers::SHIFT));
        assert!(is_plain_provider_filter_char('-', KeyModifiers::NONE));
    }

    #[test]
    fn provider_filter_text_rejects_non_ascii_space_and_modified_chars() {
        assert!(!is_plain_provider_filter_char('你', KeyModifiers::NONE));
        assert!(!is_plain_provider_filter_char(' ', KeyModifiers::NONE));
        assert!(!is_plain_provider_filter_char('b', KeyModifiers::ALT));
        assert!(!is_plain_provider_filter_char('f', KeyModifiers::ALT));
        assert!(!is_plain_provider_filter_char('a', KeyModifiers::SUPER));
        assert!(!is_plain_provider_filter_char('e', KeyModifiers::SUPER));
        assert!(!is_plain_provider_filter_char('x', KeyModifiers::CONTROL));
    }
}
