//! Settings layout for hit testing
//!
//! Tracks the layout of rendered settings UI elements for mouse interaction.

use super::render::ControlLayoutInfo;
use crate::view::ui::point_in_rect;
use ratatui::layout::Rect;

/// Layout information for the entire settings UI
#[derive(Debug, Clone, Default)]
pub struct SettingsLayout {
    /// The modal area
    pub modal_area: Rect,
    /// Category list items (index, area)
    pub categories: Vec<(usize, Rect)>,
    /// Setting items (index, path, area, control_layout)
    pub items: Vec<ItemLayout>,
    /// Search result items (page_index, item_index, area)
    pub search_results: Vec<SearchResultLayout>,
    /// Layer button area
    pub layer_button: Option<Rect>,
    /// Edit config file button area
    pub edit_button: Option<Rect>,
    /// Save button area
    pub save_button: Option<Rect>,
    /// Cancel button area
    pub cancel_button: Option<Rect>,
    /// Reset button area
    pub reset_button: Option<Rect>,
    /// Clear category button area (shown in page header for nullable categories)
    pub clear_category_button: Option<Rect>,
    /// Sections rendered under expanded categories: (cat_idx, section_idx, area).
    pub sections: Vec<(usize, usize, Rect)>,
    /// Disclosure-chevron areas for expandable categories: (cat_idx, area).
    pub category_disclosures: Vec<(usize, Rect)>,
    /// Categories panel area (for mouse-wheel detection).
    pub categories_panel_area: Option<Rect>,
    /// Categories panel scrollbar area (for drag).
    pub categories_scrollbar_area: Option<Rect>,
    /// Settings panel area (for scroll hit testing)
    pub settings_panel_area: Option<Rect>,
    /// Scrollbar area (for drag detection)
    pub scrollbar_area: Option<Rect>,
    /// Search results scrollbar area (for search results scrolling)
    pub search_scrollbar_area: Option<Rect>,
    /// Search results content area (for scroll wheel detection)
    pub search_results_area: Option<Rect>,
}

/// Layout info for a search result
#[derive(Debug, Clone)]
pub struct SearchResultLayout {
    /// Page index (category)
    pub page_index: usize,
    /// Item index within the page
    pub item_index: usize,
    /// Full area for this result
    pub area: Rect,
}

/// Layout info for a setting item
#[derive(Debug, Clone)]
pub struct ItemLayout {
    /// Item index within current page
    pub index: usize,
    /// JSON path for this setting
    pub path: String,
    /// Full item area (for selection)
    pub area: Rect,
    /// Control-specific layout info
    pub control: ControlLayoutInfo,
    /// Area of the [Inherit] button (only for nullable, explicitly-set items)
    pub inherit_button: Option<Rect>,
}

impl SettingsLayout {
    /// Create a new layout for the given modal area
    pub fn new(modal_area: Rect) -> Self {
        Self {
            modal_area,
            categories: Vec::new(),
            items: Vec::new(),
            search_results: Vec::new(),
            layer_button: None,
            edit_button: None,
            save_button: None,
            cancel_button: None,
            reset_button: None,
            clear_category_button: None,
            sections: Vec::new(),
            category_disclosures: Vec::new(),
            categories_panel_area: None,
            categories_scrollbar_area: None,
            settings_panel_area: None,
            scrollbar_area: None,
            search_scrollbar_area: None,
            search_results_area: None,
        }
    }

    /// Add a category to the layout
    pub fn add_category(&mut self, index: usize, area: Rect) {
        self.categories.push((index, area));
    }

    /// Add a section row (under an expanded category) for hit-testing.
    pub fn add_section(&mut self, cat_idx: usize, section_idx: usize, area: Rect) {
        self.sections.push((cat_idx, section_idx, area));
    }

    /// Register the disclosure-chevron area for an expandable category.
    pub fn add_category_disclosure(&mut self, cat_idx: usize, area: Rect) {
        self.category_disclosures.push((cat_idx, area));
    }

    /// Add a setting item to the layout
    pub fn add_item(
        &mut self,
        index: usize,
        path: String,
        area: Rect,
        control: ControlLayoutInfo,
        inherit_button: Option<Rect>,
    ) {
        self.items.push(ItemLayout {
            index,
            path,
            area,
            control,
            inherit_button,
        });
    }

    /// Add a search result to the layout
    pub fn add_search_result(&mut self, page_index: usize, item_index: usize, area: Rect) {
        self.search_results.push(SearchResultLayout {
            page_index,
            item_index,
            area,
        });
    }

    /// Hit test a position and return what was clicked
    pub fn hit_test(&self, x: u16, y: u16) -> Option<SettingsHit> {
        // Check if outside modal
        if !point_in_rect(self.modal_area, x, y) {
            return Some(SettingsHit::Outside);
        }

        // Check footer buttons
        if let Some(ref layer) = self.layer_button {
            if point_in_rect(*layer, x, y) {
                return Some(SettingsHit::LayerButton);
            }
        }
        if let Some(ref edit) = self.edit_button {
            if point_in_rect(*edit, x, y) {
                return Some(SettingsHit::EditButton);
            }
        }
        if let Some(ref save) = self.save_button {
            if point_in_rect(*save, x, y) {
                return Some(SettingsHit::SaveButton);
            }
        }
        if let Some(ref cancel) = self.cancel_button {
            if point_in_rect(*cancel, x, y) {
                return Some(SettingsHit::CancelButton);
            }
        }
        if let Some(ref reset) = self.reset_button {
            if point_in_rect(*reset, x, y) {
                return Some(SettingsHit::ResetButton);
            }
        }
        if let Some(ref clear_cat) = self.clear_category_button {
            if point_in_rect(*clear_cat, x, y) {
                return Some(SettingsHit::ClearCategoryButton);
            }
        }

        // Check categories tree (chevrons → sections → category rows; chevron
        // first so clicking just the disclosure toggles expansion without
        // changing the body-panel selection).
        for (cat_idx, area) in &self.category_disclosures {
            if point_in_rect(*area, x, y) {
                return Some(SettingsHit::CategoryDisclosure(*cat_idx));
            }
        }
        for (cat_idx, section_idx, area) in &self.sections {
            if point_in_rect(*area, x, y) {
                return Some(SettingsHit::CategorySection(*cat_idx, *section_idx));
            }
        }
        for (index, area) in &self.categories {
            if point_in_rect(*area, x, y) {
                return Some(SettingsHit::Category(*index));
            }
        }
        if let Some(ref scrollbar) = self.categories_scrollbar_area {
            if point_in_rect(*scrollbar, x, y) {
                return Some(SettingsHit::CategoriesScrollbar);
            }
        }
        if let Some(ref panel) = self.categories_panel_area {
            if point_in_rect(*panel, x, y) {
                return Some(SettingsHit::CategoriesPanel);
            }
        }

        // Check search scrollbar (before search results, for click/drag priority)
        if let Some(ref scrollbar) = self.search_scrollbar_area {
            if point_in_rect(*scrollbar, x, y) {
                return Some(SettingsHit::SearchScrollbar);
            }
        }

        // Check search results (before regular items, since they replace the item list during search)
        for (idx, result) in self.search_results.iter().enumerate() {
            if point_in_rect(result.area, x, y) {
                return Some(SettingsHit::SearchResult(idx));
            }
        }

        // Check search results area (for scroll wheel when over the results area but not on a result)
        if let Some(ref area) = self.search_results_area {
            if point_in_rect(*area, x, y) {
                return Some(SettingsHit::SearchResultsPanel);
            }
        }

        // Check setting items
        for item in &self.items {
            if point_in_rect(item.area, x, y) {
                // Check inherit button first (highest priority within item)
                if let Some(ref inherit_area) = item.inherit_button {
                    if point_in_rect(*inherit_area, x, y) {
                        return Some(SettingsHit::ControlInherit(item.index));
                    }
                }

                // Check specific control areas
                match &item.control {
                    ControlLayoutInfo::Toggle(toggle_area) => {
                        if point_in_rect(*toggle_area, x, y) {
                            return Some(SettingsHit::ControlToggle(item.index));
                        }
                    }
                    ControlLayoutInfo::Number {
                        decrement,
                        increment,
                        value,
                    } => {
                        if point_in_rect(*decrement, x, y) {
                            return Some(SettingsHit::ControlDecrement(item.index));
                        }
                        if point_in_rect(*increment, x, y) {
                            return Some(SettingsHit::ControlIncrement(item.index));
                        }
                        if point_in_rect(*value, x, y) {
                            return Some(SettingsHit::ControlNumberValue(item.index));
                        }
                    }
                    ControlLayoutInfo::Dropdown {
                        button_area,
                        option_areas,
                        scroll_offset,
                    } => {
                        // Check option areas first (when dropdown is open)
                        for (i, area) in option_areas.iter().enumerate() {
                            if point_in_rect(*area, x, y) {
                                return Some(SettingsHit::ControlDropdownOption(
                                    item.index,
                                    scroll_offset + i,
                                ));
                            }
                        }
                        if point_in_rect(*button_area, x, y) {
                            return Some(SettingsHit::ControlDropdown(item.index));
                        }
                    }
                    ControlLayoutInfo::Text { area, .. } => {
                        if point_in_rect(*area, x, y) {
                            return Some(SettingsHit::ControlText(item.index));
                        }
                    }
                    ControlLayoutInfo::TextList { rows } => {
                        for &(data_idx, row_area) in rows.iter() {
                            if point_in_rect(row_area, x, y) {
                                return Some(SettingsHit::ControlTextListRow(
                                    item.index,
                                    data_idx.unwrap_or(usize::MAX),
                                ));
                            }
                        }
                    }
                    ControlLayoutInfo::Map {
                        entry_rows,
                        add_row_area,
                    } => {
                        // Check click on add-new row first (so it has priority)
                        if let Some(add_area) = add_row_area {
                            if point_in_rect(*add_area, x, y) {
                                return Some(SettingsHit::ControlMapAddNew(item.index));
                            }
                        }
                        for &(data_idx, row_area) in entry_rows.iter() {
                            if point_in_rect(row_area, x, y) {
                                return Some(SettingsHit::ControlMapRow(item.index, data_idx));
                            }
                        }
                    }
                    ControlLayoutInfo::ObjectArray { entry_rows } => {
                        for &(data_idx, row_area) in entry_rows.iter() {
                            if point_in_rect(row_area, x, y) {
                                return Some(SettingsHit::ControlMapRow(item.index, data_idx));
                            }
                        }
                    }
                    ControlLayoutInfo::DualList(dual_layout) => {
                        use crate::view::controls::DualListHit;
                        if let Some(hit) = dual_layout.hit_test(x, y) {
                            return Some(match hit {
                                DualListHit::AvailableRow(row) => {
                                    SettingsHit::ControlDualListAvailable(item.index, row)
                                }
                                DualListHit::IncludedRow(row) => {
                                    SettingsHit::ControlDualListIncluded(item.index, row)
                                }
                                DualListHit::AddButton => {
                                    SettingsHit::ControlDualListAdd(item.index)
                                }
                                DualListHit::RemoveButton => {
                                    SettingsHit::ControlDualListRemove(item.index)
                                }
                                DualListHit::MoveUpButton => {
                                    SettingsHit::ControlDualListMoveUp(item.index)
                                }
                                DualListHit::MoveDownButton => {
                                    SettingsHit::ControlDualListMoveDown(item.index)
                                }
                            });
                        }
                    }
                    ControlLayoutInfo::Json { edit_area } => {
                        if point_in_rect(*edit_area, x, y) {
                            return Some(SettingsHit::ControlText(item.index));
                        }
                    }
                    ControlLayoutInfo::Complex => {}
                }

                return Some(SettingsHit::Item(item.index));
            }
        }

        // Check scrollbar area (for drag detection)
        if let Some(ref scrollbar) = self.scrollbar_area {
            if point_in_rect(*scrollbar, x, y) {
                return Some(SettingsHit::Scrollbar);
            }
        }

        // Check settings panel area (for scroll wheel)
        if let Some(ref panel) = self.settings_panel_area {
            if point_in_rect(*panel, x, y) {
                return Some(SettingsHit::SettingsPanel);
            }
        }

        Some(SettingsHit::Background)
    }
}

/// Result of a hit test on the settings UI
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingsHit {
    /// Click outside the modal
    Outside,
    /// Click on modal background
    Background,
    /// Click on a category (index)
    Category(usize),
    /// Click on the chevron of an expandable category — toggles expansion
    /// without changing the body-panel selection.
    CategoryDisclosure(usize),
    /// Click on a section row inside an expanded category in the tree view.
    CategorySection(usize, usize),
    /// Click on the categories panel itself (for mouse-wheel scroll).
    CategoriesPanel,
    /// Click on the categories panel scrollbar (for drag).
    CategoriesScrollbar,
    /// Click on a setting item (index)
    Item(usize),
    /// Click on a search result (index in search_results)
    SearchResult(usize),
    /// Click on toggle control
    ControlToggle(usize),
    /// Click on number decrement button
    ControlDecrement(usize),
    /// Click on number increment button
    ControlIncrement(usize),
    /// Click on the value area between the brackets of a number control —
    /// should focus the item and enter inline editing mode.
    ControlNumberValue(usize),
    /// Click on dropdown button
    ControlDropdown(usize),
    /// Click on dropdown option (item_idx, option_idx)
    ControlDropdownOption(usize, usize),
    /// Click on text input
    ControlText(usize),
    /// Click on text list row (item_idx, row_idx)
    ControlTextListRow(usize, usize),
    /// Click on map row (item_idx, row_idx)
    ControlMapRow(usize, usize),
    /// Click on map add-new row (item_idx)
    ControlMapAddNew(usize),
    /// Click on inherit button (item_idx) - unset a nullable value
    ControlInherit(usize),
    /// Click on dual-list available row (item_idx, row_idx)
    ControlDualListAvailable(usize, usize),
    /// Click on dual-list included row (item_idx, row_idx)
    ControlDualListIncluded(usize, usize),
    /// Click on dual-list add button (item_idx)
    ControlDualListAdd(usize),
    /// Click on dual-list remove button (item_idx)
    ControlDualListRemove(usize),
    /// Click on dual-list move-up button (item_idx)
    ControlDualListMoveUp(usize),
    /// Click on dual-list move-down button (item_idx)
    ControlDualListMoveDown(usize),
    /// Click on layer button
    LayerButton,
    /// Click on edit config file button
    EditButton,
    /// Click on save button
    SaveButton,
    /// Click on cancel button
    CancelButton,
    /// Click on reset button
    ResetButton,
    /// Click on clear category button (for nullable categories)
    ClearCategoryButton,
    /// Click on settings panel scrollbar
    Scrollbar,
    /// Click on settings panel (scrollable area)
    SettingsPanel,
    /// Click on search results scrollbar
    SearchScrollbar,
    /// Click on search results area (for scroll wheel)
    SearchResultsPanel,
}

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

    #[test]
    fn test_layout_creation() {
        let modal = Rect::new(10, 5, 80, 30);
        let mut layout = SettingsLayout::new(modal);

        layout.add_category(0, Rect::new(11, 6, 20, 1));
        layout.add_category(1, Rect::new(11, 7, 20, 1));

        assert_eq!(layout.categories.len(), 2);
    }

    #[test]
    fn test_hit_test_outside() {
        let modal = Rect::new(10, 5, 80, 30);
        let layout = SettingsLayout::new(modal);

        assert_eq!(layout.hit_test(0, 0), Some(SettingsHit::Outside));
        assert_eq!(layout.hit_test(5, 5), Some(SettingsHit::Outside));
    }

    #[test]
    fn test_hit_test_category() {
        let modal = Rect::new(10, 5, 80, 30);
        let mut layout = SettingsLayout::new(modal);

        layout.add_category(0, Rect::new(11, 6, 20, 1));
        layout.add_category(1, Rect::new(11, 7, 20, 1));

        assert_eq!(layout.hit_test(15, 6), Some(SettingsHit::Category(0)));
        assert_eq!(layout.hit_test(15, 7), Some(SettingsHit::Category(1)));
    }

    #[test]
    fn test_hit_test_buttons() {
        let modal = Rect::new(10, 5, 80, 30);
        let mut layout = SettingsLayout::new(modal);

        layout.save_button = Some(Rect::new(60, 32, 8, 1));
        layout.cancel_button = Some(Rect::new(70, 32, 10, 1));

        assert_eq!(layout.hit_test(62, 32), Some(SettingsHit::SaveButton));
        assert_eq!(layout.hit_test(75, 32), Some(SettingsHit::CancelButton));
    }

    #[test]
    fn test_hit_test_item_with_toggle() {
        let modal = Rect::new(10, 5, 80, 30);
        let mut layout = SettingsLayout::new(modal);

        layout.add_item(
            0,
            "/test".to_string(),
            Rect::new(35, 10, 50, 2),
            ControlLayoutInfo::Toggle(Rect::new(49, 11, 3, 1)),
            None,
        );

        // Click on toggle chip
        assert_eq!(layout.hit_test(50, 11), Some(SettingsHit::ControlToggle(0)));

        // Click on item/label but not on toggle
        assert_eq!(layout.hit_test(35, 10), Some(SettingsHit::Item(0)));
        assert_eq!(layout.hit_test(40, 11), Some(SettingsHit::Item(0)));
    }

    /// Reproducer for issue #1825: clicking on the value area between the
    /// brackets of a Number control used to return `Item`, which only
    /// changes selection. It must now return a dedicated hit that the mouse
    /// handler can route to "start editing".
    #[test]
    fn test_hit_test_number_value_area() {
        let modal = Rect::new(10, 5, 80, 30);
        let mut layout = SettingsLayout::new(modal);

        let item_area = Rect::new(35, 10, 50, 1);
        let decrement = Rect::new(50, 10, 3, 1);
        let increment = Rect::new(54, 10, 3, 1);
        let value = Rect::new(42, 10, 7, 1);

        layout.add_item(
            0,
            "/tab_size".to_string(),
            item_area,
            ControlLayoutInfo::Number {
                decrement,
                increment,
                value,
            },
            None,
        );

        assert_eq!(
            layout.hit_test(45, 10),
            Some(SettingsHit::ControlNumberValue(0))
        );
        assert_eq!(
            layout.hit_test(51, 10),
            Some(SettingsHit::ControlDecrement(0))
        );
        assert_eq!(
            layout.hit_test(55, 10),
            Some(SettingsHit::ControlIncrement(0))
        );
    }
}
