use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
use ratatui::layout::{Constraint, Layout, Rect};

use crate::commands::AppCommand;
use crate::page::Page;
use crate::state::{
    DeviceKind, DictFocus, EngineSection, HelpFocus, MainLayout, MinimapMode, Modal, OptionsFocus,
    PatternsColumn, SettingKind,
};
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view};

use super::InputContext;

const STEPS_PER_PAGE: usize = 32;

pub fn handle_mouse(ctx: &mut InputContext, mouse: MouseEvent, term: Rect) {
    let kind = mouse.kind;
    let col = mouse.column;
    let row = mouse.row;

    // Dismiss title screen on any click
    if ctx.app.ui.show_title {
        if matches!(kind, MouseEventKind::Down(MouseButton::Left)) {
            ctx.dispatch(AppCommand::HideTitle);
        }
        return;
    }

    match kind {
        MouseEventKind::Down(MouseButton::Left) => handle_click(ctx, col, row, term),
        MouseEventKind::ScrollUp => handle_scroll(ctx, col, row, term, true),
        MouseEventKind::ScrollDown => handle_scroll(ctx, col, row, term, false),
        _ => {}
    }
}

fn padded(term: Rect) -> Rect {
    Rect {
        x: term.x + 4,
        y: term.y + 1,
        width: term.width.saturating_sub(8),
        height: term.height.saturating_sub(2),
    }
}

fn top_level_layout(padded: Rect) -> (Rect, Rect, Rect) {
    let header_height = 3u16;
    let [header, _pad, body, _bpad, footer] = Layout::vertical([
        Constraint::Length(header_height),
        Constraint::Length(1),
        Constraint::Fill(1),
        Constraint::Length(1),
        Constraint::Length(3),
    ])
    .areas(padded);
    (header, body, footer)
}

fn contains(area: Rect, col: u16, row: u16) -> bool {
    col >= area.x && col < area.x + area.width && row >= area.y && row < area.y + area.height
}

fn handle_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
    // Sticky minimap intercepts all clicks
    if matches!(ctx.app.ui.minimap, MinimapMode::Sticky) {
        if let Some((gc, gr)) = cagire_ratatui::hit_test_tile(col, row, term) {
            if let Some(page) = Page::at_pos(gc, gr) {
                ctx.dispatch(AppCommand::GoToPage(page));
            }
        }
        ctx.app.ui.dismiss_minimap();
        return;
    }

    ctx.dispatch(AppCommand::ClearStatus);

    // If a modal is active, clicks outside dismiss it (except Editor/Preview)
    if !matches!(ctx.app.ui.modal, Modal::None) {
        handle_modal_click(ctx, col, row, term);
        return;
    }

    let padded = padded(term);
    let (header, body, footer) = top_level_layout(padded);

    if contains(header, col, row) {
        handle_header_click(ctx, col, row, header);
    } else if contains(footer, col, row) {
        handle_footer_click(ctx, col, row, footer);
    } else if contains(body, col, row) {
        handle_body_click(ctx, col, row, body);
    }
}

fn handle_scroll(ctx: &mut InputContext, col: u16, row: u16, term: Rect, up: bool) {
    // Modal scroll
    if matches!(ctx.app.ui.modal, Modal::KeybindingsHelp { .. }) {
        if up {
            ctx.dispatch(AppCommand::HelpScrollUp(3));
        } else {
            ctx.dispatch(AppCommand::HelpScrollDown(3));
        }
        return;
    }

    if !matches!(ctx.app.ui.modal, Modal::None) {
        return;
    }

    let padded = padded(term);
    let (_header, body, _footer) = top_level_layout(padded);

    if !contains(body, col, row) {
        return;
    }

    match ctx.app.page {
        Page::Main => {
            if up {
                ctx.dispatch(AppCommand::StepUp);
            } else {
                ctx.dispatch(AppCommand::StepDown);
            }
        }
        Page::Help => {
            let [topics_area, content_area] = help_view::layout(body);
            if contains(topics_area, col, row) {
                if up {
                    ctx.dispatch(AppCommand::HelpPrevTopic(1));
                } else {
                    ctx.dispatch(AppCommand::HelpNextTopic(1));
                }
            } else if contains(content_area, col, row) {
                if up {
                    ctx.dispatch(AppCommand::HelpScrollUp(3));
                } else {
                    ctx.dispatch(AppCommand::HelpScrollDown(3));
                }
            }
        }
        Page::Dict => {
            let (header_area, [cat_area, words_area]) = dict_view::layout(body);
            if contains(header_area, col, row) {
                return;
            }
            if contains(cat_area, col, row) {
                if up {
                    ctx.dispatch(AppCommand::DictPrevCategory);
                } else {
                    ctx.dispatch(AppCommand::DictNextCategory);
                }
            } else if contains(words_area, col, row) {
                if up {
                    ctx.dispatch(AppCommand::DictScrollUp(3));
                } else {
                    ctx.dispatch(AppCommand::DictScrollDown(3));
                }
            }
        }
        Page::Patterns => {
            let [banks_area, _gap, patterns_area] = patterns_view::layout(body);

            if contains(banks_area, col, row) {
                if up {
                    ctx.app.patterns_nav.column = PatternsColumn::Banks;
                    ctx.app.patterns_nav.move_up_clamped();
                } else {
                    ctx.app.patterns_nav.column = PatternsColumn::Banks;
                    ctx.app.patterns_nav.move_down_clamped();
                }
            } else if contains(patterns_area, col, row) {
                if up {
                    ctx.app.patterns_nav.column = PatternsColumn::Patterns;
                    ctx.app.patterns_nav.move_up_clamped();
                } else {
                    ctx.app.patterns_nav.column = PatternsColumn::Patterns;
                    ctx.app.patterns_nav.move_down_clamped();
                }
            }
        }
        Page::Options => {
            if up {
                ctx.dispatch(AppCommand::OptionsPrevFocus);
            } else {
                ctx.dispatch(AppCommand::OptionsNextFocus);
            }
        }
        Page::Engine => {
            let [left_col, _, _] = engine_view::layout(body);
            if contains(left_col, col, row) {
                match ctx.app.audio.section {
                    EngineSection::Devices => {
                        if ctx.app.audio.device_kind == DeviceKind::Input {
                            if up {
                                ctx.dispatch(AppCommand::AudioInputListUp);
                            } else {
                                ctx.dispatch(AppCommand::AudioInputListDown(1));
                            }
                        } else if up {
                            ctx.dispatch(AppCommand::AudioOutputListUp);
                        } else {
                            ctx.dispatch(AppCommand::AudioOutputListDown(1));
                        }
                    }
                    EngineSection::Settings => {
                        if up {
                            ctx.dispatch(AppCommand::AudioSettingPrev);
                        } else {
                            ctx.dispatch(AppCommand::AudioSettingNext);
                        }
                    }
                    EngineSection::Samples => {}
                }
            }
        }
    }
}

// --- Header ---

fn handle_header_click(ctx: &mut InputContext, col: u16, _row: u16, header: Rect) {
    let [transport_area, _live, _tempo, _bank, _pattern, _stats] = Layout::horizontal([
        Constraint::Min(12),
        Constraint::Length(9),
        Constraint::Min(14),
        Constraint::Fill(1),
        Constraint::Fill(2),
        Constraint::Min(20),
    ])
    .areas(header);

    if contains(transport_area, col, _row) {
        ctx.dispatch(AppCommand::TogglePlaying);
    }
}

// --- Footer ---

fn handle_footer_click(ctx: &mut InputContext, col: u16, row: u16, footer: Rect) {
    let block_inner = Rect {
        x: footer.x + 1,
        y: footer.y + 1,
        width: footer.width.saturating_sub(2),
        height: footer.height.saturating_sub(2),
    };
    if !contains(block_inner, col, row) {
        return;
    }

    let badge_text = match ctx.app.page {
        Page::Main => " MAIN ",
        Page::Patterns => " PATTERNS ",
        Page::Engine => " ENGINE ",
        Page::Options => " OPTIONS ",
        Page::Help => " HELP ",
        Page::Dict => " DICT ",
    };
    let badge_end = block_inner.x + badge_text.len() as u16;
    if col < badge_end {
        ctx.app.ui.minimap = MinimapMode::Sticky;
    }
}

// --- Body ---

fn handle_body_click(ctx: &mut InputContext, col: u16, row: u16, body: Rect) {
    // Account for side panel splitting
    let page_area = if ctx.app.panel.visible && ctx.app.panel.side.is_some() {
        if body.width >= 120 {
            let panel_width = body.width * 35 / 100;
            let [main, _side] =
                Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
                    .areas(body);
            main
        } else {
            let panel_height = body.height * 40 / 100;
            let [main, _side] =
                Layout::vertical([Constraint::Fill(1), Constraint::Length(panel_height)])
                    .areas(body);
            main
        }
    } else {
        body
    };

    if !contains(page_area, col, row) {
        return;
    }

    match ctx.app.page {
        Page::Main => handle_main_click(ctx, col, row, page_area),
        Page::Patterns => handle_patterns_click(ctx, col, row, page_area),
        Page::Help => handle_help_click(ctx, col, row, page_area),
        Page::Dict => handle_dict_click(ctx, col, row, page_area),
        Page::Options => handle_options_click(ctx, col, row, page_area),
        Page::Engine => handle_engine_click(ctx, col, row, page_area),
    }
}

// --- Main page (grid) ---

fn handle_main_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    let [_patterns_area, _, main_area, _, _vu_area] = main_view::layout(area);

    if !contains(main_area, col, row) {
        return;
    }

    // Replay viz/sequencer split
    let show_scope = ctx.app.audio.config.show_scope;
    let show_spectrum = ctx.app.audio.config.show_spectrum;
    let show_preview = ctx.app.audio.config.show_preview;
    let has_viz = show_scope || show_spectrum || show_preview;
    let layout = ctx.app.audio.config.layout;

    let sequencer_area = match layout {
        MainLayout::Top => {
            let viz_height = if has_viz { 16 } else { 0 };
            let [_viz, seq] =
                Layout::vertical([Constraint::Length(viz_height), Constraint::Fill(1)])
                    .areas(main_area);
            seq
        }
        MainLayout::Bottom => {
            let viz_height = if has_viz { 16 } else { 0 };
            let [seq, _viz] =
                Layout::vertical([Constraint::Fill(1), Constraint::Length(viz_height)])
                    .areas(main_area);
            seq
        }
        MainLayout::Left => {
            let viz_width = if has_viz { 33 } else { 0 };
            let [_viz, _spacer, seq] = Layout::horizontal([
                Constraint::Percentage(viz_width),
                Constraint::Length(2),
                Constraint::Fill(1),
            ])
            .areas(main_area);
            seq
        }
        MainLayout::Right => {
            let viz_width = if has_viz { 33 } else { 0 };
            let [seq, _spacer, _viz] = Layout::horizontal([
                Constraint::Fill(1),
                Constraint::Length(2),
                Constraint::Percentage(viz_width),
            ])
            .areas(main_area);
            seq
        }
    };

    if !contains(sequencer_area, col, row) {
        return;
    }

    // Replay grid layout to find which step was clicked
    if let Some(step) = hit_test_grid(ctx, col, row, sequencer_area) {
        ctx.dispatch(AppCommand::GoToStep(step));
    }
}

fn hit_test_grid(ctx: &InputContext, col: u16, row: u16, area: Rect) -> Option<usize> {
    if area.width < 50 {
        return None;
    }

    let pattern = ctx.app.current_edit_pattern();
    let length = pattern.length;
    let page = ctx.app.editor_ctx.step / STEPS_PER_PAGE;
    let page_start = page * STEPS_PER_PAGE;
    let steps_on_page = (page_start + STEPS_PER_PAGE).min(length) - page_start;

    let num_rows = steps_on_page.div_ceil(8);
    let steps_per_row = steps_on_page.div_ceil(num_rows);

    let row_height = area.height / num_rows as u16;

    let row_constraints: Vec<Constraint> = (0..num_rows)
        .map(|_| Constraint::Length(row_height))
        .collect();
    let rows = Layout::vertical(row_constraints).split(area);

    for row_idx in 0..num_rows {
        let row_area = rows[row_idx];
        if !contains(row_area, col, row) {
            continue;
        }

        let start_step = row_idx * steps_per_row;
        let end_step = (start_step + steps_per_row).min(steps_on_page);
        let cols_in_row = end_step - start_step;

        let col_constraints: Vec<Constraint> = (0..cols_in_row * 2 - 1)
            .map(|i| {
                if i % 2 == 0 {
                    Constraint::Fill(1)
                } else if i == cols_in_row - 1 {
                    Constraint::Length(2)
                } else {
                    Constraint::Length(1)
                }
            })
            .collect();
        let cols = Layout::horizontal(col_constraints).split(row_area);

        for col_idx in 0..cols_in_row {
            let tile_area = cols[col_idx * 2];
            if contains(tile_area, col, row) {
                let step_idx = page_start + start_step + col_idx;
                if step_idx < length {
                    return Some(step_idx);
                }
            }
        }
    }

    None
}

// --- Patterns page ---

fn handle_patterns_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    let [banks_area, _gap, patterns_area] = patterns_view::layout(area);

    if contains(banks_area, col, row) {
        if let Some(bank) = hit_test_patterns_list(ctx, col, row, banks_area, true) {
            ctx.app.patterns_nav.column = PatternsColumn::Banks;
            ctx.dispatch(AppCommand::PatternsSelectBank(bank));
        }
    } else if contains(patterns_area, col, row) {
        if let Some(pattern) = hit_test_patterns_list(ctx, col, row, patterns_area, false) {
            ctx.app.patterns_nav.column = PatternsColumn::Patterns;
            ctx.dispatch(AppCommand::PatternsSelectPattern(pattern));
        }
    }
}

fn hit_test_patterns_list(
    ctx: &InputContext,
    _col: u16,
    row: u16,
    area: Rect,
    is_banks: bool,
) -> Option<usize> {
    use crate::model::{MAX_BANKS, MAX_PATTERNS};

    let [_title, inner] =
        Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);

    let max_items = if is_banks { MAX_BANKS } else { MAX_PATTERNS };
    let cursor = if is_banks {
        ctx.app.patterns_nav.bank_cursor
    } else {
        ctx.app.patterns_nav.pattern_cursor
    };

    let max_visible = (inner.height as usize).max(1);
    let scroll_offset = if max_items <= max_visible {
        0
    } else {
        cursor
            .saturating_sub(max_visible / 2)
            .min(max_items - max_visible)
    };

    let visible_count = max_items.min(max_visible);
    let row_height = (inner.height / visible_count as u16).max(1);

    if row < inner.y {
        return None;
    }

    let relative_y = row - inner.y;
    let visible_idx = (relative_y / row_height) as usize;

    if visible_idx < visible_count {
        let idx = scroll_offset + visible_idx;
        if idx < max_items {
            return Some(idx);
        }
    }

    None
}

// --- Help page ---

fn handle_help_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    let [topics_area, content_area] = help_view::layout(area);

    if contains(topics_area, col, row) {
        use crate::model::docs::{self, DocEntry, DOCS};
        let items = build_visible_list(
            &DOCS
                .iter()
                .map(|e| matches!(e, DocEntry::Section(_)))
                .collect::<Vec<_>>(),
            &ctx.app.ui.help_collapsed,
        );
        let sel = match ctx.app.ui.help_on_section {
            Some(s) => VisibleEntry::Section(s),
            None => VisibleEntry::Item(ctx.app.ui.help_topic),
        };
        if let Some(entry) = hit_test_visible_list(&items, &sel, topics_area, row) {
            match entry {
                VisibleEntry::Item(i) => ctx.dispatch(AppCommand::HelpSelectTopic(i)),
                VisibleEntry::Section(s) => {
                    let collapsed = ctx.app.ui.help_collapsed.get(s).copied().unwrap_or(false);
                    if collapsed {
                        if let Some(v) = ctx.app.ui.help_collapsed.get_mut(s) {
                            *v = false;
                        }
                        ctx.app.ui.help_on_section = None;
                        if let Some(first) = docs::first_topic_in_section(s) {
                            ctx.app.ui.help_topic = first;
                        }
                    } else {
                        if let Some(v) = ctx.app.ui.help_collapsed.get_mut(s) {
                            *v = true;
                        }
                        ctx.app.ui.help_on_section = Some(s);
                    }
                }
            }
        }
        ctx.app.ui.help_focus = HelpFocus::Topics;
    } else if contains(content_area, col, row) {
        ctx.app.ui.help_focus = HelpFocus::Content;
    }
}

// --- Dict page ---

fn handle_dict_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    let (_header_area, [cat_area, words_area]) = dict_view::layout(area);

    if contains(cat_area, col, row) {
        use crate::model::categories::{self, CatEntry, CATEGORIES};
        let items = build_visible_list(
            &CATEGORIES
                .iter()
                .map(|e| matches!(e, CatEntry::Section(_)))
                .collect::<Vec<_>>(),
            &ctx.app.ui.dict_collapsed,
        );
        let sel = match ctx.app.ui.dict_on_section {
            Some(s) => VisibleEntry::Section(s),
            None => VisibleEntry::Item(ctx.app.ui.dict_category),
        };
        if let Some(entry) = hit_test_visible_list(&items, &sel, cat_area, row) {
            match entry {
                VisibleEntry::Item(i) => ctx.dispatch(AppCommand::DictSelectCategory(i)),
                VisibleEntry::Section(s) => {
                    let collapsed = ctx.app.ui.dict_collapsed.get(s).copied().unwrap_or(false);
                    if collapsed {
                        if let Some(v) = ctx.app.ui.dict_collapsed.get_mut(s) {
                            *v = false;
                        }
                        ctx.app.ui.dict_on_section = None;
                        if let Some(first) = categories::first_category_in_section(s) {
                            ctx.app.ui.dict_category = first;
                        }
                    } else {
                        if let Some(v) = ctx.app.ui.dict_collapsed.get_mut(s) {
                            *v = true;
                        }
                        ctx.app.ui.dict_on_section = Some(s);
                    }
                }
            }
        }
        ctx.app.ui.dict_focus = DictFocus::Categories;
    } else if contains(words_area, col, row) {
        ctx.app.ui.dict_focus = DictFocus::Words;
    }
}

// --- CategoryList hit test ---

#[derive(Clone, Copy)]
enum VisibleEntry {
    Item(usize),
    Section(usize),
}

fn build_visible_list(is_section: &[bool], collapsed: &[bool]) -> Vec<VisibleEntry> {
    let mut result = Vec::new();
    let mut section_idx = 0usize;
    let mut item_idx = 0usize;
    let mut skipping = false;
    for &is_sec in is_section {
        if is_sec {
            let is_collapsed = collapsed.get(section_idx).copied().unwrap_or(false);
            result.push(VisibleEntry::Section(section_idx));
            skipping = is_collapsed;
            section_idx += 1;
        } else if !skipping {
            result.push(VisibleEntry::Item(item_idx));
            item_idx += 1;
        } else {
            item_idx += 1;
        }
    }
    result
}

fn hit_test_visible_list(
    items: &[VisibleEntry],
    selected: &VisibleEntry,
    area: Rect,
    click_row: u16,
) -> Option<VisibleEntry> {
    let visible_height = area.height.saturating_sub(2) as usize;
    if visible_height == 0 {
        return None;
    }
    let total_items = items.len();

    let selected_visual_idx = match selected {
        VisibleEntry::Item(sel) => items
            .iter()
            .position(|v| matches!(v, VisibleEntry::Item(i) if *i == *sel))
            .unwrap_or(0),
        VisibleEntry::Section(sel) => items
            .iter()
            .position(|v| matches!(v, VisibleEntry::Section(s) if *s == *sel))
            .unwrap_or(0),
    };

    let scroll = if selected_visual_idx < visible_height / 2 {
        0
    } else if selected_visual_idx > total_items.saturating_sub(visible_height / 2) {
        total_items.saturating_sub(visible_height)
    } else {
        selected_visual_idx.saturating_sub(visible_height / 2)
    };

    let inner_y = area.y + 1;
    if click_row < inner_y {
        return None;
    }
    let relative = (click_row - inner_y) as usize;
    if relative >= visible_height {
        return None;
    }

    let visual_idx = scroll + relative;
    if visual_idx >= total_items {
        return None;
    }

    Some(items[visual_idx])
}

// --- Options page ---

fn handle_options_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    // Replicate options_view layout: Block with borders → inner → padded (+2, +1)
    let inner = Rect {
        x: area.x + 1,
        y: area.y + 1,
        width: area.width.saturating_sub(2),
        height: area.height.saturating_sub(2),
    };
    let padded = Rect {
        x: inner.x + 2,
        y: inner.y + 1,
        width: inner.width.saturating_sub(4),
        height: inner.height.saturating_sub(2),
    };

    if row < padded.y || row >= padded.y + padded.height {
        return;
    }

    let focus = ctx.app.options.focus;
    let plugin_mode = ctx.app.plugin_mode;
    let focus_line = focus.line_index(plugin_mode);
    let total_lines = crate::state::options::total_lines(plugin_mode);
    let max_visible = padded.height as usize;

    let scroll_offset = if total_lines <= max_visible {
        0
    } else {
        focus_line
            .saturating_sub(max_visible / 2)
            .min(total_lines.saturating_sub(max_visible))
    };

    let relative_y = (row - padded.y) as usize;
    let abs_line = scroll_offset + relative_y;

    if let Some(new_focus) = OptionsFocus::at_line(abs_line, plugin_mode) {
        ctx.dispatch(AppCommand::OptionsSetFocus(new_focus));

        // Value area starts at prefix(2) + label(20) = offset 22 from padded.x
        let value_x = padded.x + 22;
        if col >= value_x {
            let right = col >= value_x + 4; // past the "< " prefix → right half
            super::options_page::cycle_option_value(ctx, right);
        }
    }
}

// --- Engine page ---

fn handle_engine_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
    let [left_col, _, _] = engine_view::layout(area);

    if !contains(left_col, col, row) {
        return;
    }

    // Replicate engine_view render_settings_section layout
    let inner = Rect {
        x: left_col.x + 1,
        y: left_col.y + 1,
        width: left_col.width.saturating_sub(2),
        height: left_col.height.saturating_sub(2),
    };
    let padded = Rect {
        x: inner.x + 1,
        y: inner.y + 1,
        width: inner.width.saturating_sub(2),
        height: inner.height.saturating_sub(1),
    };

    if row < padded.y || row >= padded.y + padded.height {
        return;
    }

    let devices_lines = engine_view::devices_section_height(ctx.app) as usize;
    let settings_lines: usize = 8;
    let samples_lines: usize = 6;
    let total_lines = devices_lines + 1 + settings_lines + 1 + samples_lines;
    let max_visible = padded.height as usize;

    let (focus_start, focus_height) = match ctx.app.audio.section {
        EngineSection::Devices => (0, devices_lines),
        EngineSection::Settings => (devices_lines + 1, settings_lines),
        EngineSection::Samples => (devices_lines + 1 + settings_lines + 1, samples_lines),
    };

    let scroll_offset = if total_lines <= max_visible {
        0
    } else {
        let focus_end = focus_start + focus_height;
        if focus_end <= max_visible {
            0
        } else {
            focus_start.min(total_lines.saturating_sub(max_visible))
        }
    };

    let relative_y = (row - padded.y) as usize;
    let abs_line = scroll_offset + relative_y;

    let devices_end = devices_lines;
    let settings_start = devices_lines + 1;
    let settings_end = settings_start + settings_lines;
    let samples_start = settings_end + 1;

    if abs_line < devices_end {
        ctx.dispatch(AppCommand::AudioSetSection(EngineSection::Devices));
        // Determine output vs input sub-column
        let [output_col, _sep, input_col] = Layout::horizontal([
            Constraint::Percentage(48),
            Constraint::Length(3),
            Constraint::Percentage(48),
        ])
        .areas(padded);
        if contains(input_col, col, row) {
            ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Input));
        } else if contains(output_col, col, row) {
            ctx.dispatch(AppCommand::SetDeviceKind(DeviceKind::Output));
        }
    } else if abs_line >= settings_start && abs_line < settings_end {
        ctx.dispatch(AppCommand::AudioSetSection(EngineSection::Settings));
        // Settings section: 2 header lines + 6 table rows
        // Rows 0-3 are adjustable (Channels, Buffer, Voices, Nudge)
        let row_in_section = abs_line - settings_start;
        if row_in_section >= 2 {
            let table_row = row_in_section - 2;
            let setting = match table_row {
                0 => Some(SettingKind::Channels),
                1 => Some(SettingKind::BufferSize),
                2 => Some(SettingKind::Polyphony),
                3 => Some(SettingKind::Nudge),
                _ => None,
            };
            if let Some(kind) = setting {
                ctx.app.audio.setting_kind = kind;
                // Table columns: [Length(14), Fill(1)] — value starts at padded.x + 14
                let value_x = padded.x + 14;
                if col >= value_x {
                    let right = col >= value_x + 4;
                    super::engine_page::cycle_engine_setting(ctx, right);
                }
            }
        }
    } else if abs_line >= samples_start {
        ctx.dispatch(AppCommand::AudioSetSection(EngineSection::Samples));
    }
}

// --- Modal ---

fn handle_modal_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
    match &ctx.app.ui.modal {
        Modal::Editor | Modal::Preview => {
            // Don't dismiss editor/preview on click
        }
        Modal::Confirm { .. } => {
            handle_confirm_click(ctx, col, row, term);
        }
        Modal::KeybindingsHelp { .. } => {
            // Click outside keybindings help to dismiss
            let padded = padded(term);
            let width = (padded.width * 80 / 100).clamp(60, 100);
            let height = (padded.height * 80 / 100).max(15);
            let modal_area = centered_rect(term, width, height);
            if !contains(modal_area, col, row) {
                ctx.dispatch(AppCommand::CloseModal);
            }
        }
        _ => {
            // For other modals, don't dismiss on click (they have their own input)
        }
    }
}

fn handle_confirm_click(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
    // The confirm modal is rendered centered. Approximate its area.
    let modal_area = centered_rect(term, 40, 7);
    if !contains(modal_area, col, row) {
        ctx.dispatch(AppCommand::CloseModal);
        return;
    }

    // The confirm modal has two buttons at the bottom row of the inner area
    // Button row is approximately at modal_area.y + modal_area.height - 2
    let button_row = modal_area.y + modal_area.height.saturating_sub(3);
    if row == button_row || row == button_row + 1 {
        if let Modal::Confirm { selected, .. } = &mut ctx.app.ui.modal {
            let mid = modal_area.x + modal_area.width / 2;
            *selected = col < mid;
        }
    }
}

fn centered_rect(term: Rect, width: u16, height: u16) -> Rect {
    let x = term.x + term.width.saturating_sub(width) / 2;
    let y = term.y + term.height.saturating_sub(height) / 2;
    Rect {
        x,
        y,
        width: width.min(term.width),
        height: height.min(term.height),
    }
}
