//! Collapsible categorized list widget with section headers.

use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::{Block, Borders, List, ListItem};
use ratatui::Frame;

use crate::theme;

/// Entry in a category list: either a section header or a leaf item.
pub struct CategoryItem<'a> {
    pub label: &'a str,
    pub is_section: bool,
    pub collapsed: bool,
}

/// What is currently selected: a leaf item or a section header.
pub enum Selection {
    Item(usize),
    Section(usize),
}

/// Scrollable list with collapsible section headers.
pub struct CategoryList<'a> {
    items: &'a [CategoryItem<'a>],
    selection: Selection,
    focused: bool,
    title: &'a str,
    section_color: Color,
    focused_color: Color,
    selected_color: Color,
    normal_color: Color,
    dimmed_color: Option<Color>,
}

impl<'a> CategoryList<'a> {
    pub fn new(items: &'a [CategoryItem<'a>], selection: Selection) -> Self {
        let theme = theme::get();
        Self {
            items,
            selection,
            focused: false,
            title: "",
            section_color: theme.ui.text_dim,
            focused_color: theme.dict.category_focused,
            selected_color: theme.dict.category_selected,
            normal_color: theme.dict.category_normal,
            dimmed_color: None,
        }
    }

    pub fn focused(mut self, focused: bool) -> Self {
        self.focused = focused;
        self
    }

    pub fn title(mut self, title: &'a str) -> Self {
        self.title = title;
        self
    }

    pub fn selected_color(mut self, color: Color) -> Self {
        self.selected_color = color;
        self
    }

    pub fn normal_color(mut self, color: Color) -> Self {
        self.normal_color = color;
        self
    }

    pub fn dimmed(mut self, color: Color) -> Self {
        self.dimmed_color = Some(color);
        self
    }

    /// Build the visible items list, filtering out children of collapsed sections.
    /// Returns (item, section_index_if_section, item_index_if_item).
    fn visible_items(&self) -> Vec<(&CategoryItem<'a>, Option<usize>, Option<usize>)> {
        let mut result = Vec::new();
        let mut skipping = false;
        let mut section_idx = 0usize;
        let mut item_idx = 0usize;
        for item in self.items.iter() {
            if item.is_section {
                skipping = item.collapsed;
                result.push((item, Some(section_idx), None));
                section_idx += 1;
            } else if !skipping {
                result.push((item, None, Some(item_idx)));
                item_idx += 1;
            } else {
                item_idx += 1;
            }
        }
        result
    }

    pub fn render(self, frame: &mut Frame, area: Rect) {
        let theme = theme::get();
        let visible = self.visible_items();

        let visible_height = area.height.saturating_sub(2) as usize;
        let total_items = visible.len();

        let selected_visual_idx = match &self.selection {
            Selection::Item(sel) => visible
                .iter()
                .position(|(_, _, item_idx)| *item_idx == Some(*sel))
                .unwrap_or(0),
            Selection::Section(sel) => visible
                .iter()
                .position(|(_, sec_idx, _)| *sec_idx == Some(*sel))
                .unwrap_or(0),
        };

        let scroll = if selected_visual_idx < visible_height / 2 {
            0
        } else if selected_visual_idx > total_items.saturating_sub(visible_height / 2) {
            total_items.saturating_sub(visible_height)
        } else {
            selected_visual_idx.saturating_sub(visible_height / 2)
        };

        let is_dimmed = self.dimmed_color.is_some();

        let items: Vec<ListItem> = visible
            .iter()
            .skip(scroll)
            .take(visible_height)
            .enumerate()
            .map(|(vis_offset, (item, sec_idx, _itm_idx))| {
                let visual_pos = scroll + vis_offset;
                if item.is_section {
                    let is_selected =
                        matches!(&self.selection, Selection::Section(s) if Some(*s) == *sec_idx);
                    let arrow = if item.collapsed { "▸" } else { "▾" };
                    let style = if is_selected && self.focused {
                        Style::new()
                            .fg(self.focused_color)
                            .add_modifier(Modifier::BOLD)
                    } else if is_selected {
                        Style::new()
                            .fg(self.selected_color)
                            .add_modifier(Modifier::BOLD)
                    } else {
                        Style::new().fg(self.section_color)
                    };
                    let prefix = if is_selected && !is_dimmed { "> " } else { "" };
                    ListItem::new(format!("{prefix}{arrow} {}", item.label)).style(style)
                } else {
                    let is_selected = visual_pos == selected_visual_idx
                        && matches!(&self.selection, Selection::Item(_));
                    let style = if let Some(dim_color) = self.dimmed_color {
                        Style::new().fg(dim_color)
                    } else if is_selected && self.focused {
                        Style::new()
                            .fg(self.focused_color)
                            .add_modifier(Modifier::BOLD)
                    } else if is_selected {
                        Style::new()
                            .fg(self.selected_color)
                            .add_modifier(Modifier::BOLD)
                    } else {
                        Style::new().fg(self.normal_color)
                    };
                    let prefix = if is_selected && !is_dimmed {
                        "> "
                    } else {
                        "  "
                    };
                    ListItem::new(format!("{prefix}{}", item.label)).style(style)
                }
            })
            .collect();

        let border_color = if self.focused {
            theme.dict.border_focused
        } else {
            theme.dict.border_normal
        };
        let block = Block::default()
            .borders(Borders::ALL)
            .border_style(Style::new().fg(border_color))
            .title(self.title);
        let list = List::new(items).block(block);
        frame.render_widget(list, area);
    }
}
