//! Menu widget for navigable option selection
//!
//! Implements R-WIDGET-COMPOSITION and R-STATE-SEPARATION:
//! - Menu state is held separately from rendering
//! - Simple, composable widget that works with other widgets
//! - Non-blocking keyboard navigation
//! - Theme-aware styling

use crate::config::Theme;
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, List};

/// A single menu item with label and optional key binding
///
/// # Example
///
/// ```ignore
/// let item = MenuItem::new("Save".to_string())
///     .with_key_binding("Ctrl+S")
///     .with_enabled(true);
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MenuItem {
    /// Display label for the menu item
    label: String,
    /// Optional keyboard shortcut display (e.g., "Ctrl+S")
    key_binding: Option<String>,
    /// Whether item is selectable
    enabled: bool,
    /// Action identifier for this menu item
    action: String,
}

impl MenuItem {
    /// Create a new menu item with given label
    #[must_use]
    pub fn new(label: String) -> Self {
        Self {
            action: label.to_lowercase().replace(' ', "_"),
            label,
            key_binding: None,
            enabled: true,
        }
    }

    /// Set the key binding display
    #[must_use]
    pub fn with_key_binding(mut self, binding: &str) -> Self {
        self.key_binding = Some(binding.to_string());
        self
    }

    /// Set whether item is enabled
    #[must_use]
    pub const fn with_enabled(mut self, enabled: bool) -> Self {
        self.enabled = enabled;
        self
    }

    /// Set the action identifier
    #[must_use]
    pub fn with_action(mut self, action: &str) -> Self {
        self.action = action.to_string();
        self
    }

    /// Get the label
    #[must_use]
    pub fn label(&self) -> &str {
        &self.label
    }

    /// Get the key binding if set
    #[must_use]
    pub fn key_binding(&self) -> Option<&str> {
        self.key_binding.as_deref()
    }

    /// Check if item is enabled
    #[must_use]
    pub const fn is_enabled(&self) -> bool {
        self.enabled
    }

    /// Get the action identifier
    #[must_use]
    pub fn action(&self) -> &str {
        &self.action
    }
}

impl Default for MenuItem {
    fn default() -> Self {
        Self::new("Item".to_string())
    }
}

/// Menu widget state
///
/// Implements R-STATE-SEPARATION: All state is held in this struct,
/// rendering is handled separately via the render method.
///
/// # Example
///
/// ```ignore
/// let mut menu = Menu::new(vec![
///     MenuItem::new("New".to_string()),
///     MenuItem::new("Open".to_string()),
///     MenuItem::new("Exit".to_string()),
/// ]);
///
/// menu.next(); // Move to next enabled item
/// menu.render(&theme, frame, area);
/// ```
#[derive(Clone, Debug)]
pub struct Menu {
    /// Menu items
    items: Vec<MenuItem>,
    /// Currently selected item index
    selected: usize,
    /// Dirty flag for efficient rendering
    dirty: bool,
}

impl Menu {
    /// Create a new menu with given items
    #[must_use]
    pub const fn new(items: Vec<MenuItem>) -> Self {
        Self {
            items,
            selected: 0,
            dirty: true,
        }
    }

    /// Get the number of items
    #[must_use]
    pub const fn len(&self) -> usize {
        self.items.len()
    }

    /// Check if menu is empty
    #[must_use]
    pub const fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    /// Get currently selected item
    #[must_use]
    pub fn selected(&self) -> Option<&MenuItem> {
        if self.selected < self.items.len() {
            Some(&self.items[self.selected])
        } else {
            None
        }
    }

    /// Get the index of currently selected item
    #[must_use]
    pub const fn selected_index(&self) -> usize {
        self.selected
    }

    /// Get all items
    #[must_use]
    pub fn items(&self) -> &[MenuItem] {
        &self.items
    }

    /// Move selection to next enabled item
    pub fn next(&mut self) {
        if self.items.is_empty() {
            return;
        }

        let mut next = (self.selected + 1) % self.items.len();
        let mut attempts = 0;
        let max_attempts = self.items.len();

        while !self.items[next].is_enabled() && attempts < max_attempts {
            next = (next + 1) % self.items.len();
            attempts += 1;
        }

        if self.items[next].is_enabled() {
            self.selected = next;
            self.dirty = true;
        }
    }

    /// Move selection to previous enabled item
    pub fn previous(&mut self) {
        if self.items.is_empty() {
            return;
        }

        let mut prev = if self.selected == 0 {
            self.items.len() - 1
        } else {
            self.selected - 1
        };

        let mut attempts = 0;
        let max_attempts = self.items.len();

        while !self.items[prev].is_enabled() && attempts < max_attempts {
            prev = if prev == 0 {
                self.items.len() - 1
            } else {
                prev - 1
            };
            attempts += 1;
        }

        if self.items[prev].is_enabled() {
            self.selected = prev;
            self.dirty = true;
        }
    }

    /// Jump to specific item by index
    pub fn select(&mut self, index: usize) {
        if index < self.items.len() && self.items[index].is_enabled() {
            self.selected = index;
            self.dirty = true;
        }
    }

    /// Get the action of the currently selected item
    #[must_use]
    pub fn selected_action(&self) -> Option<String> {
        self.selected().map(|item| item.action().to_string())
    }

    /// Check if state has changed since last render
    #[must_use]
    pub const fn is_dirty(&self) -> bool {
        self.dirty
    }

    /// Reset dirty flag after rendering
    pub const fn reset_dirty(&mut self) {
        self.dirty = false;
    }

    /// Render the menu using the given theme
    ///
    /// # Arguments
    ///
    /// * `theme` - Theme for styling
    /// * `frame` - `RataTui` frame for drawing
    /// * `area` - Available drawing area
    pub fn render(&mut self, theme: &Theme, frame: &mut Frame, area: Rect) {
        if area.height < 1 {
            return;
        }

        let mut lines = Vec::new();

        for (idx, item) in self.items.iter().enumerate() {
            let is_selected = idx == self.selected;

            let style = if is_selected {
                theme.selected_style()
            } else if item.is_enabled() {
                theme.normal_style()
            } else {
                theme.dimmed_style()
            };

            let mut text = item.label().to_string();

            if let Some(binding) = item.key_binding() {
                text.push(' ');
                text.push_str(
                    &" ".repeat(
                        (area.width as usize).saturating_sub(text.len() + binding.len() + 2),
                    ),
                );
                text.push_str(binding);
            }

            let span = Span::styled(text, style);
            lines.push(Line::from(span));
        }

        let list = List::new(lines)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Menu")
                    .border_style(theme.focused_style()),
            )
            .highlight_style(theme.selected_style())
            .highlight_symbol("> ");

        frame.render_widget(list, area);
        self.reset_dirty();
    }
}

impl Default for Menu {
    fn default() -> Self {
        Self::new(vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
        ])
    }
}

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

    #[test]
    fn test_menu_item_new() {
        let item = MenuItem::new("Save".to_string());
        assert_eq!(item.label(), "Save");
        assert_eq!(item.action(), "save");
        assert!(item.is_enabled());
        assert_eq!(item.key_binding(), None);
    }

    #[test]
    fn test_menu_item_key_binding() {
        let item = MenuItem::new("Save".to_string()).with_key_binding("Ctrl+S");
        assert_eq!(item.key_binding(), Some("Ctrl+S"));
    }

    #[test]
    fn test_menu_item_disabled() {
        let item = MenuItem::new("Paste".to_string()).with_enabled(false);
        assert!(!item.is_enabled());
    }

    #[test]
    fn test_menu_item_custom_action() {
        let item = MenuItem::new("Save File".to_string()).with_action("save_document");
        assert_eq!(item.action(), "save_document");
    }

    #[test]
    fn test_menu_item_clone() {
        let item1 = MenuItem::new("Test".to_string());
        let item2 = item1.clone();
        assert_eq!(item1, item2);
    }

    #[test]
    fn test_menu_new() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
            MenuItem::new("Item 3".to_string()),
        ];
        let menu = Menu::new(items);
        assert_eq!(menu.len(), 3);
        assert!(!menu.is_empty());
    }

    #[test]
    fn test_menu_selected() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
        ];
        let menu = Menu::new(items);
        assert_eq!(menu.selected_index(), 0);
        assert_eq!(menu.selected().unwrap().label(), "Item 1");
    }

    #[test]
    fn test_menu_next() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
            MenuItem::new("Item 3".to_string()),
        ];
        let mut menu = Menu::new(items);
        assert_eq!(menu.selected_index(), 0);

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

        menu.next();
        assert_eq!(menu.selected_index(), 2);

        menu.next();
        assert_eq!(menu.selected_index(), 0); // Wraps around
    }

    #[test]
    fn test_menu_previous() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
            MenuItem::new("Item 3".to_string()),
        ];
        let mut menu = Menu::new(items);
        menu.select(1);
        assert_eq!(menu.selected_index(), 1);

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

        menu.previous();
        assert_eq!(menu.selected_index(), 2); // Wraps around
    }

    #[test]
    fn test_menu_next_skips_disabled() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()).with_enabled(false),
            MenuItem::new("Item 3".to_string()),
        ];
        let mut menu = Menu::new(items);
        menu.next();
        assert_eq!(menu.selected_index(), 2); // Skipped disabled Item 2
    }

    #[test]
    fn test_menu_select() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
            MenuItem::new("Item 3".to_string()),
        ];
        let mut menu = Menu::new(items);
        menu.select(2);
        assert_eq!(menu.selected_index(), 2);
    }

    #[test]
    fn test_menu_select_disabled_fails() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()).with_enabled(false),
        ];
        let mut menu = Menu::new(items);
        menu.select(1);
        assert_eq!(menu.selected_index(), 0); // Should not select disabled
    }

    #[test]
    fn test_menu_selected_action() {
        let items = vec![
            MenuItem::new("Save".to_string()).with_action("save_file"),
            MenuItem::new("Exit".to_string()).with_action("quit"),
        ];
        let menu = Menu::new(items);
        assert_eq!(menu.selected_action(), Some("save_file".to_string()));
    }

    #[test]
    fn test_menu_dirty_flag() {
        let items = vec![MenuItem::new("Item 1".to_string())];
        let mut menu = Menu::new(items);
        assert!(menu.is_dirty());

        menu.reset_dirty();
        assert!(!menu.is_dirty());

        menu.next();
        assert!(menu.is_dirty());
    }

    #[test]
    fn test_menu_empty() {
        let menu: Menu = Menu::new(vec![]);
        assert!(menu.is_empty());
        assert_eq!(menu.len(), 0);
        assert_eq!(menu.selected(), None);
    }

    #[test]
    fn test_menu_default() {
        let menu = Menu::default();
        assert_eq!(menu.len(), 2);
        assert_eq!(menu.selected_index(), 0);
    }

    #[test]
    fn test_menu_item_default() {
        let item = MenuItem::default();
        assert_eq!(item.label(), "Item");
        assert!(item.is_enabled());
    }

    #[test]
    fn test_menu_clone() {
        let items = vec![
            MenuItem::new("Item 1".to_string()),
            MenuItem::new("Item 2".to_string()),
        ];
        let menu1 = Menu::new(items);
        let menu2 = menu1.clone();
        assert_eq!(menu1.selected_index(), menu2.selected_index());
        assert_eq!(menu1.len(), menu2.len());
    }
}
