//! Options menu for runtime configuration.
//!
//! This module provides the options menu that allows users to adjust
//! physics and display settings during simulation.

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

/// Menu item value types for configuration options.
#[derive(Debug, Clone)]
pub enum MenuValue {
    Integer(i32),
    Float(f32),
    Boolean(bool),
}

impl MenuValue {
    pub fn display(&self) -> String {
        match self {
            MenuValue::Integer(v) => v.to_string(),
            MenuValue::Float(v) => format!("{:.2}", v),
            MenuValue::Boolean(v) => {
                if *v {
                    "ON".to_string()
                } else {
                    "OFF".to_string()
                }
            }
        }
    }

    /// Adjusts the value by the given direction and step.
    ///
    /// # Arguments
    ///
    /// * `direction` - 1.0 for increase, -1.0 for decrease
    /// * `step` - Step size for numeric values
    /// * `min` - Minimum allowed value
    /// * `max` - Maximum allowed value
    pub fn adjust(&mut self, direction: f32, step: f32, min: Option<f32>, max: Option<f32>) {
        match self {
            MenuValue::Integer(v) => {
                let new_val = *v + (step * direction) as i32;
                if let (Some(min), Some(max)) = (min, max) {
                    *v = new_val.clamp(min as i32, max as i32);
                } else {
                    *v = new_val;
                }
            }
            MenuValue::Float(f) => {
                let new_val = *f + step * direction;
                if let (Some(min), Some(max)) = (min, max) {
                    *f = new_val.clamp(min, max);
                } else {
                    *f = new_val;
                }
            }
            MenuValue::Boolean(b) => {
                *b = !*b;
            }
        }
    }
}

#[derive(Debug, Clone)]
pub struct MenuItem {
    pub label: &'static str,
    pub value: MenuValue,
    pub min: Option<f32>,
    pub max: Option<f32>,
    pub step: f32,
}

impl MenuItem {
    pub fn integer(label: &'static str, value: i32, min: i32, max: i32, step: i32) -> Self {
        Self {
            label,
            value: MenuValue::Integer(value),
            min: Some(min as f32),
            max: Some(max as f32),
            step: step as f32,
        }
    }

    pub fn float(label: &'static str, value: f32, min: f32, max: f32, step: f32) -> Self {
        Self {
            label,
            value: MenuValue::Float(value),
            min: Some(min),
            max: Some(max),
            step,
        }
    }

    pub fn boolean(label: &'static str, value: bool) -> Self {
        Self {
            label,
            value: MenuValue::Boolean(value),
            min: None,
            max: None,
            step: 1.0,
        }
    }
}

/// Options menu state managing configurable options and current selection.
#[derive(Debug, Clone)]
pub struct OptionsMenu {
    pub visible: bool,
    pub selected_index: usize,
    pub items: Vec<MenuItem>,
    /// Text being edited; starts empty when user begins editing (replacing existing value).
    editing_text: Option<String>,
    original_value: Option<MenuValue>,
    /// Toggled via 'c' key or Colors button.
    color_mode: bool,
}

impl Default for OptionsMenu {
    fn default() -> Self {
        Self {
            visible: false,
            selected_index: 0,
            items: vec![
                MenuItem::boolean("Frame Cap", false),
                MenuItem::boolean("Show FPS", true),
                MenuItem::integer("Ball Count", 5000, 100, 15000, 100),
                MenuItem::integer("Gravity %", 100, 0, 500, 10),
                MenuItem::integer("Force %", 100, 10, 500, 10),
                MenuItem::integer("Friction %", 100, 0, 500, 10),
                MenuItem::integer("Spawn Color %", 80, 0, 100, 5),
            ],
            editing_text: None,
            original_value: None,
            color_mode: false,
        }
    }
}

impl OptionsMenu {
    pub fn toggle(&mut self) {
        self.visible = !self.visible;
    }

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

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

    pub fn select_previous(&mut self) {
        if self.selected_index > 0 {
            self.selected_index -= 1;
        }
    }

    pub fn select_next(&mut self) {
        if self.selected_index < self.items.len().saturating_sub(1) {
            self.selected_index += 1;
        }
    }

    pub fn increase_value(&mut self) {
        if let Some(item) = self.items.get_mut(self.selected_index) {
            item.value.adjust(1.0, item.step, item.min, item.max);
        }
    }

    pub fn decrease_value(&mut self) {
        if let Some(item) = self.items.get_mut(self.selected_index) {
            item.value.adjust(-1.0, item.step, item.min, item.max);
        }
    }

    pub fn is_editing(&self) -> bool {
        self.editing_text.is_some()
    }

    /// Stores the original value for reversion if cancelled.
    /// Only works for Integer and Float values.
    pub fn start_editing(&mut self) {
        if let Some(item) = self.items.get(self.selected_index) {
            match &item.value {
                MenuValue::Integer(_) | MenuValue::Float(_) => {
                    // Store original value for revert on cancel
                    self.original_value = Some(item.value.clone());
                    // Start with empty text (user replaces the value)
                    self.editing_text = Some(String::new());
                }
                MenuValue::Boolean(_) => {
                    // Boolean values can't be edited, just toggled
                }
            }
        }
    }

    pub fn handle_edit_char(&mut self, c: char) {
        if let Some(ref mut text) = self.editing_text {
            if c.is_ascii_digit() || (c == '.' && !text.contains('.')) {
                text.push(c);
            }
        }
    }

    pub fn handle_edit_backspace(&mut self) {
        if let Some(ref mut text) = self.editing_text {
            text.pop();
        }
    }

    /// Returns true if value was successfully applied; reverts if empty or invalid.
    pub fn confirm_edit(&mut self) -> bool {
        let Some(text) = self.editing_text.take() else {
            return false;
        };

        let Some(item) = self.items.get_mut(self.selected_index) else {
            self.original_value = None;
            return false;
        };

        // If text is empty, revert to original
        if text.is_empty() {
            if let Some(original) = self.original_value.take() {
                item.value = original;
            }
            return false;
        }

        let success = match &mut item.value {
            MenuValue::Integer(v) => {
                if let Ok(parsed) = text.parse::<i32>() {
                    let clamped = if let (Some(min), Some(max)) = (item.min, item.max) {
                        parsed.clamp(min as i32, max as i32)
                    } else {
                        parsed
                    };
                    *v = clamped;
                    true
                } else {
                    // Invalid input, revert to original
                    if let Some(original) = self.original_value.take() {
                        item.value = original;
                    }
                    false
                }
            }
            MenuValue::Float(f) => {
                if let Ok(parsed) = text.parse::<f32>() {
                    let clamped = if let (Some(min), Some(max)) = (item.min, item.max) {
                        parsed.clamp(min, max)
                    } else {
                        parsed
                    };
                    *f = clamped;
                    true
                } else {
                    // Invalid input, revert to original
                    if let Some(original) = self.original_value.take() {
                        item.value = original;
                    }
                    false
                }
            }
            MenuValue::Boolean(_) => false,
        };

        self.original_value = None;
        success
    }

    pub fn cancel_edit(&mut self) {
        self.editing_text = None;

        if let Some(original) = self.original_value.take() {
            if let Some(item) = self.items.get_mut(self.selected_index) {
                item.value = original;
            }
        }
    }

    pub fn editing_text(&self) -> Option<&str> {
        self.editing_text.as_deref()
    }

    pub fn get_value(&self, label: &str) -> Option<&MenuValue> {
        self.items
            .iter()
            .find(|item| item.label == label)
            .map(|item| &item.value)
    }

    pub fn set_value(&mut self, label: &str, value: MenuValue) {
        if let Some(item) = self.items.iter_mut().find(|i| i.label == label) {
            item.value = value;
        }
    }

    /// When enabled, frame rate is capped at 60 FPS; otherwise runs as fast as possible.
    pub fn fps_cap_enabled(&self) -> bool {
        match self.get_value("Frame Cap") {
            Some(MenuValue::Boolean(v)) => *v,
            _ => false,
        }
    }

    pub fn show_fps(&self) -> bool {
        match self.get_value("Show FPS") {
            Some(MenuValue::Boolean(v)) => *v,
            _ => false,
        }
    }

    pub fn ball_count(&self) -> usize {
        match self.get_value("Ball Count") {
            Some(MenuValue::Integer(v)) => (*v).max(1) as usize,
            _ => 2000,
        }
    }

    pub fn set_ball_count(&mut self, count: usize) {
        if let Some(item) = self.items.iter_mut().find(|i| i.label == "Ball Count") {
            let clamped = (count as i32).clamp(
                item.min.unwrap_or(100.0) as i32,
                item.max.unwrap_or(15000.0) as i32,
            );
            item.value = MenuValue::Integer(clamped);
        }
    }

    /// Base gravity is 9.81 m/s^2.
    pub fn gravity(&self) -> f32 {
        let percent = match self.get_value("Gravity %") {
            Some(MenuValue::Integer(v)) => *v as f32,
            _ => 100.0,
        };
        9.81 * (percent / 100.0)
    }

    pub fn gravity_percent(&self) -> i32 {
        match self.get_value("Gravity %") {
            Some(MenuValue::Integer(v)) => *v,
            _ => 100,
        }
    }

    /// Base friction is 0.30.
    pub fn friction(&self) -> f32 {
        let percent = match self.get_value("Friction %") {
            Some(MenuValue::Integer(v)) => *v as f32,
            _ => 100.0,
        };
        0.30 * (percent / 100.0)
    }

    pub fn friction_percent(&self) -> i32 {
        match self.get_value("Friction %") {
            Some(MenuValue::Integer(v)) => *v,
            _ => 100,
        }
    }

    /// Returns force multiplier for scaling burst effects.
    pub fn force_percent(&self) -> f32 {
        let percent = match self.get_value("Force %") {
            Some(MenuValue::Integer(v)) => *v as f32,
            _ => 100.0,
        };
        percent / 100.0
    }

    /// When enabled, ball colors are displayed; otherwise all balls appear white.
    pub fn color_mode(&self) -> bool {
        self.color_mode
    }

    pub fn set_color_mode(&mut self, enabled: bool) {
        self.color_mode = enabled;
    }

    pub fn toggle_color_mode(&mut self) {
        self.color_mode = !self.color_mode;
    }

    /// Probability (0-100%) that newly spawned balls have a random color instead of white.
    pub fn spawn_color_percent(&self) -> i32 {
        match self.get_value("Spawn Color %") {
            Some(MenuValue::Integer(v)) => *v,
            _ => 50,
        }
    }

    /// Renders the options menu popup.
    ///
    /// # Arguments
    ///
    /// * `frame` - The ratatui frame to render to
    /// * `area` - The full terminal area (popup will be centered)
    pub fn render(&self, frame: &mut Frame, area: Rect) {
        if !self.visible {
            return;
        }

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

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

        // Build list items
        let items: Vec<ListItem> = self
            .items
            .iter()
            .enumerate()
            .map(|(i, item)| {
                let is_selected = i == self.selected_index;
                let is_editing = is_selected && self.editing_text.is_some();

                let style = if is_selected {
                    Style::default()
                        .fg(Color::Yellow)
                        .add_modifier(Modifier::BOLD)
                } else {
                    Style::default().fg(Color::White)
                };

                // Show editing text with cursor, or normal value
                let value_str = if is_editing {
                    format!("{}_", self.editing_text.as_deref().unwrap_or(""))
                } else {
                    item.value.display()
                };

                let value_style = if is_editing {
                    // Editing mode: highlight the input
                    Style::default().fg(Color::Black).bg(Color::Cyan)
                } else {
                    style.fg(Color::Cyan)
                };

                let content = Line::from(vec![
                    Span::styled(format!("{}: ", item.label), style),
                    Span::styled(value_str, value_style),
                ]);

                ListItem::new(content)
            })
            .collect();

        // Show different title based on editing state
        let title = if self.is_editing() {
            " Enter value, Enter to confirm, Esc to cancel "
        } else {
            " Options (Enter to edit, Arrows to adjust) "
        };

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

        let menu_list = List::new(items).block(menu_block);

        frame.render_widget(menu_list, popup_area);
    }
}

/// 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(30);
    let popup_height = popup_height.max(10);

    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_default_menu_values() {
        let menu = OptionsMenu::default();
        assert!(!menu.fps_cap_enabled());
        assert!(menu.show_fps());
        assert_eq!(menu.ball_count(), 5000);
        assert!((menu.gravity() - 9.81).abs() < 0.01);
        assert!((menu.friction() - 0.30).abs() < 0.01);
        assert_eq!(menu.gravity_percent(), 100);
        assert_eq!(menu.friction_percent(), 100);
        assert!((menu.force_percent() - 1.0).abs() < 0.01);
        assert!(!menu.color_mode());
    }

    #[test]
    fn test_menu_navigation() {
        let mut menu = OptionsMenu::default();
        assert_eq!(menu.selected_index, 0);

        menu.select_next();
        assert_eq!(menu.selected_index, 1);

        menu.select_previous();
        assert_eq!(menu.selected_index, 0);

        menu.select_previous(); // Should not go below 0
        assert_eq!(menu.selected_index, 0);
    }

    #[test]
    fn test_value_adjustment() {
        let mut menu = OptionsMenu::default();
        assert!(!menu.fps_cap_enabled());
        menu.increase_value();
        assert!(menu.fps_cap_enabled());
        menu.decrease_value();
        assert!(!menu.fps_cap_enabled());
    }

    #[test]
    fn test_toggle_visibility() {
        let mut menu = OptionsMenu::default();
        assert!(!menu.visible);

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

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