//! Help menu displaying all keyboard shortcuts.
//!
//! This module provides a scrollable help menu that displays all available
//! keyboard shortcuts for the terminal application. Supports both keyboard
//! and mouse scrolling.

use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
    Frame,
};

struct HelpEntry {
    key: &'static str,
    description: &'static str,
}

/// All keyboard shortcuts organized by category.
/// Each category is a tuple of (category_name, entries).
const HELP_SECTIONS: &[(&str, &[HelpEntry])] = &[
    (
        "General",
        &[
            HelpEntry {
                key: "Q",
                description: "Quit the application",
            },
            HelpEntry {
                key: "Ctrl+C",
                description: "Quit the application",
            },
            HelpEntry {
                key: "R",
                description: "Reset simulation (respawn all balls)",
            },
            HelpEntry {
                key: "Esc",
                description: "Close menu / Deselect shape",
            },
            HelpEntry {
                key: "?",
                description: "Toggle this help menu",
            },
        ],
    ),
    (
        "Menus",
        &[
            HelpEntry {
                key: "O",
                description: "Toggle Options menu",
            },
            HelpEntry {
                key: "S",
                description: "Toggle Shapes menu",
            },
            HelpEntry {
                key: "C",
                description: "Toggle Color Mode",
            },
        ],
    ),
    (
        "Ball Spawning",
        &[
            HelpEntry {
                key: "Space",
                description: "Spawn balls across full width (hold for more)",
            },
            HelpEntry {
                key: "Click top 1/4",
                description: "Spawn balls at cursor (hold for more)",
            },
            HelpEntry {
                key: "Shift+1-6",
                description: "Spawn balls at section top (!/@ /#/$/%/^)",
            },
        ],
    ),
    (
        "Ball Physics",
        &[
            HelpEntry {
                key: "1-6",
                description: "Trigger upward geyser burst at zone",
            },
            HelpEntry {
                key: "Click bottom 3/4",
                description: "Apply burst force at cursor",
            },
            HelpEntry {
                key: "Arrow Keys",
                description: "Nudge all balls (when no shape selected)",
            },
        ],
    ),
    (
        "Shape Controls",
        &[
            HelpEntry {
                key: "Click shape",
                description: "Select shape",
            },
            HelpEntry {
                key: "Drag shape",
                description: "Move selected shape",
            },
            HelpEntry {
                key: "Double-click",
                description: "Delete shape at cursor",
            },
            HelpEntry {
                key: "Z",
                description: "Rotate shape clockwise",
            },
            HelpEntry {
                key: "X",
                description: "Rotate shape counter-clockwise",
            },
            HelpEntry {
                key: "Arrow/WASD",
                description: "Move selected shape",
            },
            HelpEntry {
                key: "N / M",
                description: "Cycle shape color forward / backward",
            },
            HelpEntry {
                key: "Right-click",
                description: "Cycle shape color forward",
            },
            HelpEntry {
                key: "Delete/Backspace",
                description: "Delete selected shape",
            },
        ],
    ),
    (
        "Save/Load",
        &[
            HelpEntry {
                key: "Ctrl+S",
                description: "Quick save to level.json",
            },
            HelpEntry {
                key: "Ctrl+L",
                description: "Quick load from level.json",
            },
            HelpEntry {
                key: "Save button",
                description: "Open save file explorer",
            },
            HelpEntry {
                key: "Load button",
                description: "Open load file explorer",
            },
        ],
    ),
    (
        "Menu Navigation",
        &[
            HelpEntry {
                key: "Up/Down or J/K",
                description: "Navigate menu items",
            },
            HelpEntry {
                key: "Left/Right or H/L",
                description: "Adjust values",
            },
            HelpEntry {
                key: "Enter",
                description: "Edit value directly",
            },
        ],
    ),
    (
        "Mouse Controls",
        &[
            HelpEntry {
                key: "Click number bar",
                description: "Trigger geyser at zone 1-6",
            },
            HelpEntry {
                key: "Click buttons",
                description: "Activate menu buttons",
            },
            HelpEntry {
                key: "Scroll in help",
                description: "Scroll help content",
            },
        ],
    ),
];

/// Scrollable list of keyboard shortcuts.
#[derive(Debug, Clone)]
pub struct HelpMenu {
    pub visible: bool,
    scroll_offset: usize,
    total_lines: usize,
    viewport_height: usize,
}

impl Default for HelpMenu {
    fn default() -> Self {
        Self::new()
    }
}

impl HelpMenu {
    pub fn new() -> Self {
        let mut total = 0;
        for (_, entries) in HELP_SECTIONS {
            total += 2;
            total += entries.len();
        }

        Self {
            visible: false,
            scroll_offset: 0,
            total_lines: total,
            viewport_height: 10,
        }
    }

    pub fn toggle(&mut self) {
        self.visible = !self.visible;
        if self.visible {
            self.scroll_offset = 0;
        }
    }

    pub fn show(&mut self) {
        self.visible = true;
        self.scroll_offset = 0;
    }

    pub fn hide(&mut self) {
        self.visible = false;
    }

    pub fn scroll_up(&mut self, lines: usize) {
        self.scroll_offset = self.scroll_offset.saturating_sub(lines);
    }

    pub fn scroll_down(&mut self, lines: usize) {
        let max_offset = self.total_lines.saturating_sub(self.viewport_height);
        self.scroll_offset = (self.scroll_offset + lines).min(max_offset);
    }

    pub fn page_up(&mut self) {
        self.scroll_up(self.viewport_height.saturating_sub(2));
    }

    pub fn page_down(&mut self) {
        self.scroll_down(self.viewport_height.saturating_sub(2));
    }

    pub fn scroll_to_top(&mut self) {
        self.scroll_offset = 0;
    }

    pub fn scroll_to_bottom(&mut self) {
        let max_offset = self.total_lines.saturating_sub(self.viewport_height);
        self.scroll_offset = max_offset;
    }

    /// Positive delta scrolls up, negative scrolls down.
    pub fn handle_scroll(&mut self, delta: i32) {
        if delta > 0 {
            self.scroll_up(delta.unsigned_abs() as usize);
        } else {
            self.scroll_down(delta.unsigned_abs() as usize);
        }
    }

    fn build_content(&self) -> Vec<Line<'static>> {
        let mut lines = Vec::new();

        let header_style = Style::default()
            .fg(Color::Yellow)
            .add_modifier(Modifier::BOLD);
        let key_style = Style::default()
            .fg(Color::Cyan)
            .add_modifier(Modifier::BOLD);
        let desc_style = Style::default().fg(Color::White);

        for (category, entries) in HELP_SECTIONS {
            // Category header
            lines.push(Line::from(vec![Span::styled(
                format!("  {} ", category),
                header_style,
            )]));

            // Entries
            for entry in *entries {
                // Pad key to fixed width for alignment
                let key_padded = format!("    {:16}", entry.key);
                lines.push(Line::from(vec![
                    Span::styled(key_padded, key_style),
                    Span::styled(entry.description, desc_style),
                ]));
            }

            // Blank line after section
            lines.push(Line::from(""));
        }

        lines
    }

    pub fn render(&mut self, frame: &mut Frame, area: Rect) {
        if !self.visible {
            return;
        }

        // Calculate centered popup area (70% width, 80% height)
        let popup_area = centered_rect(70, 80, area);

        // Update viewport height for scroll calculations
        // Account for border (2 lines) and title (1 line)
        self.viewport_height = popup_area.height.saturating_sub(2) as usize;

        // Recalculate max scroll based on content
        let max_offset = self.total_lines.saturating_sub(self.viewport_height);
        self.scroll_offset = self.scroll_offset.min(max_offset);

        // Clear the area behind the popup
        frame.render_widget(Clear, popup_area);

        // Build content
        let content = self.build_content();

        // Create scrollable paragraph
        let title = format!(
            " Help - Scroll: Up/Down/PgUp/PgDn/Home/End ({}/{}) ",
            self.scroll_offset + 1,
            self.total_lines.saturating_sub(self.viewport_height).max(1)
        );

        let help_block = Block::default()
            .title(title)
            .borders(Borders::ALL)
            .border_style(Style::default().fg(Color::Magenta))
            .style(Style::default().bg(Color::Black));

        let paragraph = Paragraph::new(content)
            .block(help_block)
            .scroll((self.scroll_offset as u16, 0));

        frame.render_widget(paragraph, popup_area);

        // Render scrollbar if content exceeds viewport
        if self.total_lines > self.viewport_height {
            let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
                .begin_symbol(Some("^"))
                .end_symbol(Some("v"));

            let mut scrollbar_state =
                ScrollbarState::new(self.total_lines.saturating_sub(self.viewport_height))
                    .position(self.scroll_offset);

            // Scrollbar area is inside the border
            let scrollbar_area = Rect {
                x: popup_area.x + popup_area.width - 1,
                y: popup_area.y + 1,
                width: 1,
                height: popup_area.height.saturating_sub(2),
            };

            frame.render_stateful_widget(scrollbar, scrollbar_area, &mut scrollbar_state);
        }
    }

    pub fn is_click_inside(&self, x: u16, y: u16, area: Rect) -> bool {
        let popup_area = centered_rect(70, 80, area);
        x >= popup_area.x
            && x < popup_area.x + popup_area.width
            && y >= popup_area.y
            && y < popup_area.y + popup_area.height
    }
}

/// Creates a centered rectangle within the given area.
///
/// # Arguments
///
/// * `percent_x` - Width as percentage of area width
/// * `percent_y` - Height as percentage of area height
/// * `area` - The containing area
///
/// # Returns
///
/// A centered `Rect` with the specified proportions.
fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
    let popup_width = area.width * percent_x / 100;
    let popup_height = area.height * percent_y / 100;

    // Ensure minimum size
    let popup_width = popup_width.max(40);
    let popup_height = popup_height.max(15);

    let vertical = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length((area.height.saturating_sub(popup_height)) / 2),
            Constraint::Length(popup_height),
            Constraint::Min(0),
        ])
        .split(area);

    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Length((area.width.saturating_sub(popup_width)) / 2),
            Constraint::Length(popup_width),
            Constraint::Min(0),
        ])
        .split(vertical[1])[1]
}

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

    #[test]
    fn test_help_menu_toggle() {
        let mut menu = HelpMenu::new();
        assert!(!menu.visible);

        menu.toggle();
        assert!(menu.visible);
        assert_eq!(menu.scroll_offset, 0);

        menu.toggle();
        assert!(!menu.visible);
    }

    #[test]
    fn test_scroll_bounds() {
        let mut menu = HelpMenu::new();
        menu.visible = true;
        menu.viewport_height = 10;

        // Scroll up at top should stay at 0
        menu.scroll_up(5);
        assert_eq!(menu.scroll_offset, 0);

        // Scroll down
        menu.scroll_down(5);
        assert_eq!(menu.scroll_offset, 5);

        // Scroll up
        menu.scroll_up(3);
        assert_eq!(menu.scroll_offset, 2);
    }

    #[test]
    fn test_show_hide() {
        let mut menu = HelpMenu::new();
        menu.scroll_offset = 10;

        menu.show();
        assert!(menu.visible);
        assert_eq!(menu.scroll_offset, 0); // Reset on show

        menu.hide();
        assert!(!menu.visible);
    }
}
