//! Collapsible widget - a single expandable/collapsible section
//!
//! Similar to HTML's `<details>/<summary>` elements.
//!
//! # Example
//!
//! ```rust,ignore
//! use revue::widget::{Collapsible, collapsible};
//!
//! // Basic usage
//! let details = Collapsible::new("More Info")
//!     .content("Hidden content here\nMultiple lines supported")
//!     .expanded(false);
//!
//! // With custom icons
//! let custom = collapsible("Settings")
//!     .icons('+', '-')
//!     .content("Configuration options...")
//!     .expanded(true);
//! ```

use crate::event::Key;
use crate::layout::Rect;
use crate::render::{Cell, Modifier};
use crate::style::Color;
use crate::widget::theme::{DARK_GRAY, DISABLED_FG};
use crate::widget::traits::{RenderContext, View, WidgetProps, WidgetState};
use crate::{impl_styled_view, impl_widget_builders};

/// A single collapsible/expandable section widget
///
/// Like HTML's `<details>/<summary>`, this provides a toggleable
/// section with a header and hidden content.
pub struct Collapsible {
    /// Header/summary text
    title: String,
    /// Content lines (shown when expanded)
    content: Vec<String>,
    /// Whether the content is visible
    expanded: bool,
    /// Icon when collapsed
    collapsed_icon: char,
    /// Icon when expanded
    expanded_icon: char,
    /// Header foreground color
    header_fg: Color,
    /// Header background color (optional)
    header_bg: Option<Color>,
    /// Content foreground color
    content_fg: Color,
    /// Content background color (optional)
    content_bg: Option<Color>,
    /// Show border around content
    show_border: bool,
    /// Border color
    border_color: Color,
    /// Widget state (focused, disabled, etc.)
    state: WidgetState,
    /// Minimum width constraint (0 = no constraint)
    min_width: u16,
    /// Minimum height constraint (0 = no constraint)
    min_height: u16,
    /// Maximum width constraint (0 = no constraint)
    max_width: u16,
    /// Maximum height constraint (0 = no constraint)
    max_height: u16,
    /// Widget properties (id, classes)
    props: WidgetProps,
}

impl Collapsible {
    /// Create a new collapsible section with the given title
    pub fn new(title: impl Into<String>) -> Self {
        Self {
            title: title.into(),
            content: Vec::new(),
            expanded: false,
            collapsed_icon: '▶',
            expanded_icon: '▼',
            header_fg: Color::WHITE,
            header_bg: None,
            content_fg: Color::rgb(200, 200, 200),
            content_bg: None,
            show_border: true,
            border_color: DARK_GRAY,
            state: WidgetState::new(),
            min_width: 0,
            min_height: 0,
            max_width: 0,
            max_height: 0,
            props: WidgetProps::new(),
        }
    }

    /// Set content text (splits by newlines)
    pub fn content(mut self, text: impl Into<String>) -> Self {
        self.content = text.into().lines().map(|s| s.to_string()).collect();
        self
    }

    /// Add a single content line
    pub fn line(mut self, line: impl Into<String>) -> Self {
        self.content.push(line.into());
        self
    }

    /// Add multiple content lines
    pub fn lines(mut self, lines: &[&str]) -> Self {
        self.content.extend(lines.iter().map(|s| s.to_string()));
        self
    }

    /// Set expanded state
    pub fn expanded(mut self, expanded: bool) -> Self {
        self.expanded = expanded;
        self
    }

    /// Set custom icons for collapsed/expanded states
    pub fn icons(mut self, collapsed: char, expanded: char) -> Self {
        self.collapsed_icon = collapsed;
        self.expanded_icon = expanded;
        self
    }

    /// Set header colors
    pub fn header_colors(mut self, fg: Color, bg: Option<Color>) -> Self {
        self.header_fg = fg;
        self.header_bg = bg;
        self
    }

    /// Set content colors
    pub fn content_colors(mut self, fg: Color, bg: Option<Color>) -> Self {
        self.content_fg = fg;
        self.content_bg = bg;
        self
    }

    /// Show/hide border around content
    pub fn border(mut self, show: bool) -> Self {
        self.show_border = show;
        self
    }

    /// Set border color
    pub fn border_color(mut self, color: Color) -> Self {
        self.border_color = color;
        self
    }

    /// Toggle expanded state
    pub fn toggle(&mut self) {
        self.expanded = !self.expanded;
    }

    /// Expand the content
    pub fn expand(&mut self) {
        self.expanded = true;
    }

    /// Collapse the content
    pub fn collapse(&mut self) {
        self.expanded = false;
    }

    /// Check if expanded
    pub fn is_expanded(&self) -> bool {
        self.expanded
    }

    /// Set expanded state mutably
    pub fn set_expanded(&mut self, expanded: bool) {
        self.expanded = expanded;
    }

    /// Set minimum width constraint
    pub fn min_width(mut self, width: u16) -> Self {
        self.min_width = width;
        self
    }

    /// Set minimum height constraint
    pub fn min_height(mut self, height: u16) -> Self {
        self.min_height = height;
        self
    }

    /// Set maximum width constraint (0 = no limit)
    pub fn max_width(mut self, width: u16) -> Self {
        self.max_width = width;
        self
    }

    /// Set maximum height constraint (0 = no limit)
    pub fn max_height(mut self, height: u16) -> Self {
        self.max_height = height;
        self
    }

    /// Set both min width and height
    pub fn min_size(self, width: u16, height: u16) -> Self {
        self.min_width(width).min_height(height)
    }

    /// Set both max width and height (0 = no limit)
    pub fn max_size(self, width: u16, height: u16) -> Self {
        self.max_width(width).max_height(height)
    }

    /// Set all size constraints at once
    pub fn constrain(self, min_w: u16, min_h: u16, max_w: u16, max_h: u16) -> Self {
        self.min_width(min_w)
            .min_height(min_h)
            .max_width(max_w)
            .max_height(max_h)
    }

    /// Apply size constraints to the available area
    fn apply_constraints(&self, area: Rect) -> Rect {
        let eff_max_w = if self.max_width > 0 {
            self.max_width.max(self.min_width)
        } else {
            u16::MAX
        };
        let eff_max_h = if self.max_height > 0 {
            self.max_height.max(self.min_height)
        } else {
            u16::MAX
        };
        let width = area.width.clamp(self.min_width, eff_max_w);
        let height = area.height.clamp(self.min_height, eff_max_h);

        Rect::new(area.x, area.y, width, height)
    }

    /// Get the current icon based on state
    fn icon(&self) -> char {
        if self.expanded {
            self.expanded_icon
        } else {
            self.collapsed_icon
        }
    }

    /// Calculate total height needed
    pub fn height(&self) -> u16 {
        if self.expanded {
            let content_height = self.content.len() as u16;
            if self.show_border {
                // header + content + bottom border
                1 + content_height + 1
            } else {
                1 + content_height
            }
        } else {
            1 // Just header
        }
    }

    /// Handle keyboard input
    ///
    /// Returns `true` if the key was handled.
    ///
    /// Supported keys:
    /// - Enter/Space: Toggle expanded state
    /// - Right/l: Expand
    /// - Left/h: Collapse
    pub fn handle_key(&mut self, key: &Key) -> bool {
        if self.state.disabled {
            return false;
        }

        match key {
            Key::Enter | Key::Char(' ') => {
                self.toggle();
                true
            }
            Key::Right | Key::Char('l') => {
                self.expand();
                true
            }
            Key::Left | Key::Char('h') => {
                self.collapse();
                true
            }
            _ => false,
        }
    }
}

impl Default for Collapsible {
    fn default() -> Self {
        Self::new("Details")
    }
}

impl View for Collapsible {
    crate::impl_view_meta!("Collapsible");

    fn render(&self, ctx: &mut RenderContext) {
        let area = self.apply_constraints(ctx.area);
        if area.width < 4 || area.height < 1 {
            return;
        }

        let is_focused = self.state.focused || ctx.is_focused();
        let header_fg = if self.state.disabled {
            DISABLED_FG
        } else {
            self.header_fg
        };

        // Render header line
        let mut x: u16 = 0;

        // Background for header (if set)
        if let Some(bg) = self.header_bg {
            for dx in 0..area.width {
                let mut cell = Cell::new(' ');
                cell.bg = Some(bg);
                ctx.set(dx, 0, cell);
            }
        }

        // Icon
        let mut icon_cell = Cell::new(self.icon());
        icon_cell.fg = Some(header_fg);
        if let Some(bg) = self.header_bg {
            icon_cell.bg = Some(bg);
        }
        if is_focused {
            icon_cell.modifier |= Modifier::BOLD;
        }
        ctx.set(x, 0, icon_cell);
        x += 2; // icon + space

        // Title
        let max_title_width = (area.width.saturating_sub(3)) as usize;
        let title_display = crate::utils::truncate_to_width(&self.title, max_title_width);
        for ch in title_display.chars() {
            let cw = crate::utils::char_width(ch) as u16;
            if x + cw > area.width {
                break;
            }
            let mut cell = Cell::new(ch);
            cell.fg = Some(header_fg);
            if let Some(bg) = self.header_bg {
                cell.bg = Some(bg);
            }
            if is_focused {
                cell.modifier |= Modifier::BOLD;
            }
            ctx.set(x, 0, cell);
            x += cw;
        }

        // Render content if expanded
        if self.expanded && area.height > 1 {
            let content_start_y: u16 = 1;
            let available_height = area.height.saturating_sub(1);
            let content_width = area.width.saturating_sub(2);

            // Calculate how many lines we can show
            let lines_to_show = if self.show_border {
                available_height.saturating_sub(1) as usize
            } else {
                available_height as usize
            };

            // Draw content lines with left border
            for (i, line) in self.content.iter().take(lines_to_show).enumerate() {
                let y = content_start_y + i as u16;
                if y >= area.height {
                    break;
                }

                // Background for content
                if let Some(bg) = self.content_bg {
                    for dx in 0..area.width {
                        let mut cell = Cell::new(' ');
                        cell.bg = Some(bg);
                        ctx.set(dx, y, cell);
                    }
                }

                // Left border
                if self.show_border {
                    let mut border_cell = Cell::new('│');
                    border_cell.fg = Some(self.border_color);
                    ctx.set(0, y, border_cell);
                }

                // Content text
                let text_x: u16 = if self.show_border { 2 } else { 1 };
                let max_content_width = content_width.saturating_sub(1) as usize;

                for (ci, ch) in line.chars().enumerate() {
                    if ci >= max_content_width {
                        break;
                    }
                    let mut cell = Cell::new(ch);
                    cell.fg = Some(self.content_fg);
                    if let Some(bg) = self.content_bg {
                        cell.bg = Some(bg);
                    }
                    ctx.set(text_x + ci as u16, y, cell);
                }
            }

            // Draw bottom border
            if self.show_border {
                let bottom_y = content_start_y + lines_to_show.min(self.content.len()) as u16;
                if bottom_y < area.height {
                    // Corner
                    let mut corner = Cell::new('└');
                    corner.fg = Some(self.border_color);
                    ctx.set(0, bottom_y, corner);

                    // Horizontal line
                    let line_width = area.width.saturating_sub(1);
                    for dx in 1..line_width {
                        let mut line_cell = Cell::new('─');
                        line_cell.fg = Some(self.border_color);
                        ctx.set(dx, bottom_y, line_cell);
                    }
                }
            }
        }
    }
}

impl_styled_view!(Collapsible);
impl_widget_builders!(Collapsible);

/// Helper function to create a Collapsible widget
pub fn collapsible(title: impl Into<String>) -> Collapsible {
    Collapsible::new(title)
}

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

    #[test]
    fn test_collapsible_new() {
        let c = Collapsible::new("Info");
        assert_eq!(c.title, "Info");
        assert!(!c.is_expanded());
        assert_eq!(c.height(), 1);
    }

    #[test]
    fn test_collapsible_expand_collapse() {
        let mut c = Collapsible::new("Info").content("Line 1\nLine 2");
        assert!(!c.is_expanded());
        assert_eq!(c.height(), 1);

        c.expand();
        assert!(c.is_expanded());
        assert!(c.height() > 1);

        c.collapse();
        assert!(!c.is_expanded());
        assert_eq!(c.height(), 1);
    }

    #[test]
    fn test_collapsible_toggle() {
        let mut c = Collapsible::new("Info");
        assert!(!c.is_expanded());
        c.toggle();
        assert!(c.is_expanded());
        c.toggle();
        assert!(!c.is_expanded());
    }

    #[test]
    fn test_collapsible_content() {
        let c = Collapsible::new("Info")
            .content("Line 1\nLine 2\nLine 3")
            .expanded(true);
        assert!(c.is_expanded());
        assert_eq!(c.content.len(), 3);
        // header(1) + 3 content lines + border(1) = 5
        assert_eq!(c.height(), 5);
    }

    #[test]
    fn test_collapsible_content_no_border() {
        let c = Collapsible::new("Info")
            .content("Line 1\nLine 2")
            .border(false)
            .expanded(true);
        // header(1) + 2 content lines = 3 (no border)
        assert_eq!(c.height(), 3);
    }

    #[test]
    fn test_collapsible_handle_key() {
        let mut c = Collapsible::new("Info").content("Text");

        assert!(c.handle_key(&Key::Enter));
        assert!(c.is_expanded());

        assert!(c.handle_key(&Key::Char(' ')));
        assert!(!c.is_expanded());

        assert!(c.handle_key(&Key::Right));
        assert!(c.is_expanded());

        assert!(c.handle_key(&Key::Left));
        assert!(!c.is_expanded());

        assert!(!c.handle_key(&Key::Char('x')));
    }

    #[test]
    fn test_collapsible_handle_key_disabled() {
        let mut c = Collapsible::new("Info").content("Text");
        c.state.disabled = true;
        assert!(!c.handle_key(&Key::Enter));
        assert!(!c.is_expanded());
    }

    #[test]
    fn test_collapsible_icons() {
        let c = Collapsible::new("Info").icons('+', '-');
        assert_eq!(c.icon(), '+');

        let c = Collapsible::new("Info").icons('+', '-').expanded(true);
        assert_eq!(c.icon(), '-');
    }

    #[test]
    fn test_collapsible_render_collapsed() {
        let mut buf = Buffer::new(30, 10);
        let area = Rect::new(0, 0, 30, 10);
        let mut ctx = RenderContext::new(&mut buf, area);

        let c = Collapsible::new("Details");
        c.render(&mut ctx);
        // Collapsed icon should be rendered
        assert_eq!(buf.get(0, 0).unwrap().symbol, '▶');
    }

    #[test]
    fn test_collapsible_render_expanded() {
        let mut buf = Buffer::new(30, 10);
        let area = Rect::new(0, 0, 30, 10);
        let mut ctx = RenderContext::new(&mut buf, area);

        let c = Collapsible::new("Details").content("Hello").expanded(true);
        c.render(&mut ctx);
        assert_eq!(buf.get(0, 0).unwrap().symbol, '▼');
    }

    #[test]
    fn test_collapsible_render_small_area_no_panic() {
        let mut buf = Buffer::new(10, 5);
        let area = Rect::new(0, 0, 3, 1);
        let mut ctx = RenderContext::new(&mut buf, area);
        let c = Collapsible::new("Long Title").expanded(true);
        c.render(&mut ctx); // Width < 4, should return early
    }

    #[test]
    fn test_collapsible_default() {
        let c = Collapsible::default();
        assert_eq!(c.title, "Details");
    }

    #[test]
    fn test_collapsible_helper_fn() {
        let c = collapsible("Test");
        assert_eq!(c.title, "Test");
    }

    #[test]
    fn test_collapsible_line_and_lines() {
        let c = Collapsible::new("Info")
            .line("First")
            .lines(&["Second", "Third"]);
        assert_eq!(c.content.len(), 3);
    }
}
