//! Event handling logic.
//!
//! This module processes keyboard, mouse, and resize events,
//! translating them into application actions.

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};

use crate::ui::{StatusBar, StatusBarButton};

/// High-level application events abstracted from raw terminal events.
#[derive(Debug, Clone)]
pub enum AppEvent {
    Quit,
    ToggleOptions,
    ToggleShapes,
    ToggleColorMode,
    ClearShapes,
    PlaceSelectedShape,
    ShapeMenuClick {
        x: u16,
        y: u16,
    },
    ShapeMenuUp,
    ShapeMenuDown,
    ShapeMenuLeft,
    ShapeMenuRight,
    CloseShapeMenu,
    RotateShapeClockwise,
    RotateShapeCounterClockwise,
    MoveSelectedShape {
        dx: f32,
        dy: f32,
    },
    DeleteSelectedShape,
    DeselectShape,
    CycleColorForward,
    CycleColorBackward,
    Reset,
    MenuUp,
    MenuDown,
    MenuIncrease,
    MenuDecrease,
    CloseMenu,
    MenuStartEdit,
    MenuEditChar(char),
    MenuEditBackspace,
    MenuConfirmEdit,
    MenuCancelEdit,
    SpawnBalls {
        x: f32,
        y: f32,
    },
    ApplyBurst {
        x: f32,
        y: f32,
    },
    MouseDown {
        x: f32,
        y: f32,
        in_spawn_zone: bool,
    },
    MouseDrag {
        x: f32,
        y: f32,
    },
    MouseUp,
    StartShapeDrag {
        x: f32,
        y: f32,
    },
    DragShape {
        x: f32,
        y: f32,
    },
    EndShapeDrag,
    DoubleClick {
        x: f32,
        y: f32,
    },
    NumberBurst {
        digit: u8,
    },
    Nudge {
        dx: f32,
        dy: f32,
    },
    SpawnAtSection {
        digit: u8,
    },
    SpaceDown,
    SpaceUp,
    SaveLevel,
    LoadLevel,
    OpenSaveExplorer,
    OpenLoadExplorer,
    CloseFileExplorer,
    FileExplorerUp,
    FileExplorerDown,
    FileExplorerConfirm,
    FileExplorerChar(char),
    FileExplorerBackspace,
    ToggleHelp,
    CloseHelpMenu,
    HelpMenuScrollUp,
    HelpMenuScrollDown,
    HelpMenuPageUp,
    HelpMenuPageDown,
    HelpMenuScrollToTop,
    HelpMenuScrollToBottom,
    Resize {
        new_width: u16,
        new_height: u16,
        delta_width: i32,
        delta_height: i32,
    },
    None,
}

/// Context needed to interpret events correctly.
#[derive(Debug, Clone)]
pub struct EventContext {
    pub terminal_width: u16,
    pub terminal_height: u16,
    pub menu_open: bool,
    pub menu_editing: bool,
    pub shape_menu_open: bool,
    pub file_explorer_open: bool,
    pub file_explorer_editing: bool,
    pub help_menu_open: bool,
    pub world_width: f32,
    pub world_height: f32,
    pub shape_selected: bool,
    pub shape_dragging: bool,
}

/// Processes a keyboard event and returns the corresponding app event.
///
/// # Arguments
///
/// * `key` - The keyboard event to process
/// * `ctx` - Event handling context
///
/// # Returns
///
/// The application event to perform.
pub fn handle_key_event(key: KeyEvent, ctx: &EventContext) -> AppEvent {
    // Handle help menu keys when help menu is open
    if ctx.help_menu_open {
        return handle_help_menu_key(key);
    }

    // Handle file explorer keys when file explorer is open
    if ctx.file_explorer_open {
        return handle_file_explorer_key(key, ctx.file_explorer_editing);
    }

    // Handle options menu-specific keys when menu is open
    if ctx.menu_open {
        return handle_menu_key(key, ctx.menu_editing);
    }

    // Handle shape menu-specific keys when shape menu is open
    if ctx.shape_menu_open {
        return handle_shape_menu_key(key);
    }

    // Shape movement speed (physics units per keypress)
    const SHAPE_MOVE_SPEED: f32 = 1.0;
    // Nudge strength for arrow keys (pinball-style)
    const NUDGE_STRENGTH: f32 = 3.0;

    // Global keyboard shortcuts
    match key.code {
        KeyCode::Char('q') | KeyCode::Char('Q') => AppEvent::Quit,
        KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => AppEvent::Quit,
        // Ctrl+S: Save level (direct to level.json)
        KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => AppEvent::SaveLevel,
        // Ctrl+L: Load level (direct from level.json)
        KeyCode::Char('l') if key.modifiers.contains(KeyModifiers::CONTROL) => AppEvent::LoadLevel,
        KeyCode::Char('o') | KeyCode::Char('O') => AppEvent::ToggleOptions,
        KeyCode::Char('s') | KeyCode::Char('S') => AppEvent::ToggleShapes,
        KeyCode::Char('c') | KeyCode::Char('C') => AppEvent::ToggleColorMode,
        // ? key toggles help menu
        KeyCode::Char('?') => AppEvent::ToggleHelp,
        // n: cycle color forward (only when shape selected)
        KeyCode::Char('n') | KeyCode::Char('N') => {
            if ctx.shape_selected {
                AppEvent::CycleColorForward
            } else {
                AppEvent::None
            }
        }
        // m: cycle color backward (only when shape selected)
        KeyCode::Char('m') | KeyCode::Char('M') => {
            if ctx.shape_selected {
                AppEvent::CycleColorBackward
            } else {
                AppEvent::None
            }
        }
        KeyCode::Char('r') | KeyCode::Char('R') => AppEvent::Reset,
        // Escape: deselect shape if selected, otherwise close menu
        KeyCode::Esc => {
            if ctx.shape_selected {
                AppEvent::DeselectShape
            } else {
                AppEvent::CloseMenu
            }
        }
        // z/x rotate selected shape
        KeyCode::Char('z') => AppEvent::RotateShapeClockwise,
        KeyCode::Char('x') => AppEvent::RotateShapeCounterClockwise,
        // Delete key deletes selected shape
        KeyCode::Delete | KeyCode::Backspace => {
            if ctx.shape_selected {
                AppEvent::DeleteSelectedShape
            } else {
                AppEvent::None
            }
        }
        // Arrow keys: move shape if selected, otherwise nudge balls
        KeyCode::Up => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: 0.0,
                    dy: SHAPE_MOVE_SPEED,
                }
            } else {
                AppEvent::Nudge {
                    dx: 0.0,
                    dy: NUDGE_STRENGTH,
                }
            }
        }
        KeyCode::Down => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: 0.0,
                    dy: -SHAPE_MOVE_SPEED,
                }
            } else {
                AppEvent::Nudge {
                    dx: 0.0,
                    dy: -NUDGE_STRENGTH,
                }
            }
        }
        KeyCode::Left => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: -SHAPE_MOVE_SPEED,
                    dy: 0.0,
                }
            } else {
                AppEvent::Nudge {
                    dx: -NUDGE_STRENGTH,
                    dy: 0.0,
                }
            }
        }
        KeyCode::Right => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: SHAPE_MOVE_SPEED,
                    dy: 0.0,
                }
            } else {
                AppEvent::Nudge {
                    dx: NUDGE_STRENGTH,
                    dy: 0.0,
                }
            }
        }
        // WASD keys: move shape if selected
        KeyCode::Char('w') | KeyCode::Char('W') => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: 0.0,
                    dy: SHAPE_MOVE_SPEED,
                }
            } else {
                AppEvent::None
            }
        }
        KeyCode::Char('a') | KeyCode::Char('A') => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: -SHAPE_MOVE_SPEED,
                    dy: 0.0,
                }
            } else {
                AppEvent::None
            }
        }
        KeyCode::Char('d') | KeyCode::Char('D') => {
            if ctx.shape_selected {
                AppEvent::MoveSelectedShape {
                    dx: SHAPE_MOVE_SPEED,
                    dy: 0.0,
                }
            } else {
                AppEvent::None
            }
        }
        // Space bar spawns across full width
        KeyCode::Char(' ') => AppEvent::SpaceDown,
        // Symbol keys spawn at section top (Shift+1-6 on US keyboard)
        KeyCode::Char('!') => AppEvent::SpawnAtSection { digit: 1 },
        KeyCode::Char('@') => AppEvent::SpawnAtSection { digit: 2 },
        KeyCode::Char('#') => AppEvent::SpawnAtSection { digit: 3 },
        KeyCode::Char('$') => AppEvent::SpawnAtSection { digit: 4 },
        KeyCode::Char('%') => AppEvent::SpawnAtSection { digit: 5 },
        KeyCode::Char('^') => AppEvent::SpawnAtSection { digit: 6 },
        // Number keys 1-6 trigger upward bursts (geyser)
        KeyCode::Char('1') => AppEvent::NumberBurst { digit: 1 },
        KeyCode::Char('2') => AppEvent::NumberBurst { digit: 2 },
        KeyCode::Char('3') => AppEvent::NumberBurst { digit: 3 },
        KeyCode::Char('4') => AppEvent::NumberBurst { digit: 4 },
        KeyCode::Char('5') => AppEvent::NumberBurst { digit: 5 },
        KeyCode::Char('6') => AppEvent::NumberBurst { digit: 6 },
        _ => AppEvent::None,
    }
}

/// Handles keyboard events when the shape menu is open.
fn handle_shape_menu_key(key: KeyEvent) -> AppEvent {
    match key.code {
        KeyCode::Esc => AppEvent::CloseShapeMenu,
        KeyCode::Enter => AppEvent::PlaceSelectedShape,
        KeyCode::Up | KeyCode::Char('k') => AppEvent::ShapeMenuUp,
        KeyCode::Down | KeyCode::Char('j') => AppEvent::ShapeMenuDown,
        KeyCode::Left | KeyCode::Char('h') => AppEvent::ShapeMenuLeft,
        KeyCode::Right | KeyCode::Char('l') => AppEvent::ShapeMenuRight,
        KeyCode::Char('q') | KeyCode::Char('Q') => AppEvent::Quit,
        _ => AppEvent::None,
    }
}

/// Handles keyboard events when the menu is open.
fn handle_menu_key(key: KeyEvent, editing: bool) -> AppEvent {
    if editing {
        // Editing mode: handle text input
        match key.code {
            KeyCode::Esc => AppEvent::MenuCancelEdit,
            KeyCode::Enter => AppEvent::MenuConfirmEdit,
            KeyCode::Backspace => AppEvent::MenuEditBackspace,
            KeyCode::Char(c) if c.is_ascii_digit() || c == '.' => AppEvent::MenuEditChar(c),
            _ => AppEvent::None,
        }
    } else {
        // Normal menu mode
        match key.code {
            KeyCode::Esc => AppEvent::CloseMenu,
            KeyCode::Enter => AppEvent::MenuStartEdit,
            KeyCode::Up | KeyCode::Char('k') => AppEvent::MenuUp,
            KeyCode::Down | KeyCode::Char('j') => AppEvent::MenuDown,
            KeyCode::Right | KeyCode::Char('l') => AppEvent::MenuIncrease,
            KeyCode::Left | KeyCode::Char('h') => AppEvent::MenuDecrease,
            KeyCode::Char('q') | KeyCode::Char('Q') => AppEvent::Quit,
            _ => AppEvent::None,
        }
    }
}

/// Handles keyboard events when the file explorer is open.
fn handle_file_explorer_key(key: KeyEvent, editing_filename: bool) -> AppEvent {
    match key.code {
        KeyCode::Esc => AppEvent::CloseFileExplorer,
        KeyCode::Enter => AppEvent::FileExplorerConfirm,
        KeyCode::Up | KeyCode::Char('k') => AppEvent::FileExplorerUp,
        KeyCode::Down | KeyCode::Char('j') => AppEvent::FileExplorerDown,
        KeyCode::Backspace if editing_filename => AppEvent::FileExplorerBackspace,
        KeyCode::Char(c) if editing_filename => AppEvent::FileExplorerChar(c),
        KeyCode::Char('q') | KeyCode::Char('Q') => AppEvent::Quit,
        _ => AppEvent::None,
    }
}

/// Handles keyboard events when the help menu is open.
fn handle_help_menu_key(key: KeyEvent) -> AppEvent {
    match key.code {
        KeyCode::Esc | KeyCode::Char('?') => AppEvent::CloseHelpMenu,
        KeyCode::Up | KeyCode::Char('k') => AppEvent::HelpMenuScrollUp,
        KeyCode::Down | KeyCode::Char('j') => AppEvent::HelpMenuScrollDown,
        KeyCode::PageUp => AppEvent::HelpMenuPageUp,
        KeyCode::PageDown => AppEvent::HelpMenuPageDown,
        KeyCode::Home => AppEvent::HelpMenuScrollToTop,
        KeyCode::End => AppEvent::HelpMenuScrollToBottom,
        KeyCode::Char('q') | KeyCode::Char('Q') => AppEvent::Quit,
        _ => AppEvent::None,
    }
}

/// Processes a mouse event and returns the corresponding app event.
///
/// # Arguments
///
/// * `mouse` - The mouse event to process
/// * `ctx` - Event handling context
///
/// # Returns
///
/// The application event to perform.
pub fn handle_mouse_event(mouse: MouseEvent, ctx: &EventContext) -> AppEvent {
    let MouseEvent {
        kind,
        column,
        row,
        modifiers: _,
    } = mouse;

    // Calculate canvas area (terminal minus status bar)
    let canvas_height = ctx.terminal_height.saturating_sub(StatusBar::HEIGHT);

    // Handle mouse release
    if matches!(kind, MouseEventKind::Up(MouseButton::Left)) {
        return AppEvent::MouseUp;
    }

    // Handle mouse scroll events for help menu
    if ctx.help_menu_open {
        match kind {
            MouseEventKind::ScrollUp => return AppEvent::HelpMenuScrollUp,
            MouseEventKind::ScrollDown => return AppEvent::HelpMenuScrollDown,
            _ => {}
        }
    }

    // Handle drag events for continuous burst/spawn or shape dragging
    if matches!(kind, MouseEventKind::Drag(MouseButton::Left)) {
        // Ignore drags in status bar
        if row >= canvas_height {
            return AppEvent::None;
        }
        // Ignore drags when options menu is open
        if ctx.menu_open {
            return AppEvent::None;
        }
        // Ignore drags when shape menu is open
        if ctx.shape_menu_open {
            return AppEvent::None;
        }
        let (phys_x, phys_y) = terminal_to_physics(
            column,
            row,
            ctx.terminal_width,
            canvas_height,
            ctx.world_width,
            ctx.world_height,
        );
        return AppEvent::MouseDrag {
            x: phys_x,
            y: phys_y,
        };
    }

    // Handle right-click to cycle shape colors
    if matches!(kind, MouseEventKind::Down(MouseButton::Right)) {
        // Ignore right-clicks in status bar
        if row >= canvas_height {
            return AppEvent::None;
        }
        // Only cycle color if a shape is selected
        if ctx.shape_selected {
            return AppEvent::CycleColorForward;
        }
        return AppEvent::None;
    }

    // Only handle left mouse button down events for the rest
    if !matches!(kind, MouseEventKind::Down(MouseButton::Left)) {
        return AppEvent::None;
    }

    // Check if click is in status bar area
    if row >= canvas_height {
        let row_in_bar = row - canvas_height;
        if let Some(button) = StatusBar::button_at(column, row_in_bar, ctx.terminal_width) {
            return match button {
                StatusBarButton::Options => AppEvent::ToggleOptions,
                StatusBarButton::Shapes => AppEvent::ToggleShapes,
                StatusBarButton::Colors => AppEvent::ToggleColorMode,
                StatusBarButton::Clear => AppEvent::ClearShapes,
                StatusBarButton::Save => AppEvent::OpenSaveExplorer,
                StatusBarButton::Load => AppEvent::OpenLoadExplorer,
                StatusBarButton::Reset => AppEvent::Reset,
                StatusBarButton::Help => AppEvent::ToggleHelp,
                StatusBarButton::Quit => AppEvent::Quit,
                StatusBarButton::Number(digit) => AppEvent::NumberBurst { digit },
            };
        }
        return AppEvent::None;
    }

    // If help menu is open, clicking outside closes it
    if ctx.help_menu_open {
        return AppEvent::CloseHelpMenu;
    }

    // If file explorer is open, clicking outside closes it
    if ctx.file_explorer_open {
        return AppEvent::CloseFileExplorer;
    }

    // If options menu is open, clicking outside closes it
    if ctx.menu_open {
        return AppEvent::CloseMenu;
    }

    // If shape menu is open, handle click within menu
    if ctx.shape_menu_open {
        return AppEvent::ShapeMenuClick { x: column, y: row };
    }

    // Convert click position to physics coordinates
    let (phys_x, phys_y) = terminal_to_physics(
        column,
        row,
        ctx.terminal_width,
        canvas_height,
        ctx.world_width,
        ctx.world_height,
    );

    // Check if click is in top 1/4 of canvas for spawning
    let spawn_threshold = canvas_height / 4;
    let in_spawn_zone = row < spawn_threshold;

    // Return MouseDown event to track hold state
    AppEvent::MouseDown {
        x: phys_x,
        y: phys_y,
        in_spawn_zone,
    }
}

/// Processes a resize event and returns the corresponding app event.
///
/// # Arguments
///
/// * `new_width` - New terminal width
/// * `new_height` - New terminal height
/// * `old_width` - Previous terminal width
/// * `old_height` - Previous terminal height
///
/// # Returns
///
/// A resize app event with delta information.
pub fn handle_resize_event(
    new_width: u16,
    new_height: u16,
    old_width: u16,
    old_height: u16,
) -> AppEvent {
    AppEvent::Resize {
        new_width,
        new_height,
        delta_width: i32::from(new_width) - i32::from(old_width),
        delta_height: i32::from(new_height) - i32::from(old_height),
    }
}

/// Converts terminal coordinates to physics coordinates.
///
/// Handles the coordinate system transformation:
/// - Terminal uses Y-down (0 at top)
/// - Physics uses Y-up (0 at bottom)
///
/// # Arguments
///
/// * `column` - Terminal column (0-indexed)
/// * `row` - Terminal row (0-indexed)
/// * `term_width` - Terminal width in columns
/// * `term_height` - Terminal height in rows (canvas only, excluding status bar)
/// * `world_width` - Physics world width
/// * `world_height` - Physics world height
///
/// # Returns
///
/// `(physics_x, physics_y)` coordinates.
fn terminal_to_physics(
    column: u16,
    row: u16,
    term_width: u16,
    term_height: u16,
    world_width: f32,
    world_height: f32,
) -> (f32, f32) {
    // Normalize to 0.0-1.0 range
    let norm_x = f32::from(column) / f32::from(term_width.max(1));
    // Flip Y: terminal has Y-down, physics has Y-up
    let norm_y = 1.0 - (f32::from(row) / f32::from(term_height.max(1)));

    // Scale to physics coordinates
    let phys_x = norm_x * world_width;
    let phys_y = norm_y * world_height;

    (phys_x, phys_y)
}

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

    fn make_context() -> EventContext {
        EventContext {
            terminal_width: 80,
            terminal_height: 24,
            menu_open: false,
            menu_editing: false,
            shape_menu_open: false,
            file_explorer_open: false,
            file_explorer_editing: false,
            help_menu_open: false,
            world_width: 40.0,
            world_height: 22.0,
            shape_selected: false,
            shape_dragging: false,
        }
    }

    #[test]
    fn test_quit_key() {
        let ctx = make_context();
        let event = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE);
        assert!(matches!(handle_key_event(event, &ctx), AppEvent::Quit));
    }

    #[test]
    fn test_options_key() {
        let ctx = make_context();
        let event = KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE);
        assert!(matches!(
            handle_key_event(event, &ctx),
            AppEvent::ToggleOptions
        ));
    }

    #[test]
    fn test_menu_navigation() {
        let mut ctx = make_context();
        ctx.menu_open = true;

        let up = KeyEvent::new(KeyCode::Up, KeyModifiers::NONE);
        assert!(matches!(handle_key_event(up, &ctx), AppEvent::MenuUp));

        let down = KeyEvent::new(KeyCode::Down, KeyModifiers::NONE);
        assert!(matches!(handle_key_event(down, &ctx), AppEvent::MenuDown));
    }

    #[test]
    fn test_terminal_to_physics_center() {
        let (x, y) = terminal_to_physics(40, 11, 80, 22, 40.0, 22.0);
        // Center of terminal should map to center of physics world
        assert!((x - 20.0).abs() < 0.5);
        assert!((y - 11.0).abs() < 0.5);
    }
}
