use std::collections::{HashMap, HashSet};

use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::ui::types::Rect;

use super::data_types::{
    CanvasCommand, CharStyle, ColorPickerMode, CommandEntry, ContextMenuItem, ContextMenuItemDef,
    DataGridColumn, DataGridPoolRow, InputMask, RangeSliderThumb, ResizeEdge, RichTextSnapshot,
    SplitDirection, TextSnapshot, TextSpan, UiPanelKind, UndoStack, VirtualListPoolItem,
};

#[derive(Clone, Debug, Default)]
pub struct UiButtonData {
    pub clicked: bool,
    /// Text slot of the button's own label, or `None` for buttons whose label
    /// is composed of independently styled spans (`add_button_rich`). `None`
    /// keeps despawn and `set_text` from touching a slot the button does not
    /// own, which would otherwise corrupt slot 0.
    pub text_slot: Option<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiSliderData {
    pub min: f32,
    pub max: f32,
    pub value: f32,
    pub changed: bool,
    pub fill_entity: freecs::Entity,
    pub text_slot: usize,
    pub logarithmic: bool,
    pub precision: usize,
    pub prefix: String,
    pub suffix: String,
}

#[derive(Clone, Debug, Default)]
pub struct UiRangeSliderData {
    pub min: f32,
    pub max: f32,
    pub low_value: f32,
    pub high_value: f32,
    pub changed: bool,
    pub fill_entity: freecs::Entity,
    pub low_thumb_entity: freecs::Entity,
    pub high_thumb_entity: freecs::Entity,
    pub text_slot: usize,
    pub active_thumb: Option<RangeSliderThumb>,
    pub precision: usize,
    pub thumb_half_size: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiToggleData {
    pub value: bool,
    pub changed: bool,
    pub animated_position: f32,
    pub knob_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiCheckboxData {
    pub value: bool,
    pub changed: bool,
    pub inner_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiRadioData {
    pub group_id: u32,
    pub option_index: usize,
    pub selected: bool,
    pub changed: bool,
    pub inner_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiProgressBarData {
    pub value: f32,
    pub fill_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiCollapsingHeaderData {
    pub open: bool,
    pub changed: bool,
    pub content_entity: freecs::Entity,
    pub arrow_text_slot: usize,
}

#[derive(Clone, Debug, Default)]
pub struct UiScrollAreaData {
    pub scroll_offset: f32,
    pub content_entity: freecs::Entity,
    pub content_height: f32,
    pub visible_height: f32,
    pub thumb_entity: freecs::Entity,
    pub track_entity: freecs::Entity,
    pub thumb_dragging: bool,
    pub thumb_drag_start_offset: f32,
    pub snap_interval: Option<f32>,
}

#[derive(Clone, Debug, Default)]
pub struct UiTabBarData {
    pub selected_tab: usize,
    pub changed: bool,
    pub tab_entities: Vec<freecs::Entity>,
    pub tab_text_slots: Vec<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiBreadcrumbData {
    pub segments: Vec<String>,
    pub clicked_segment: Option<usize>,
    pub changed: bool,
    pub segment_entities: Vec<freecs::Entity>,
    pub segment_text_slots: Vec<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiSplitterData {
    pub direction: SplitDirection,
    pub ratio: f32,
    pub changed: bool,
    pub first_pane: freecs::Entity,
    pub second_pane: freecs::Entity,
    pub divider_entity: freecs::Entity,
    pub min_ratio: f32,
    pub max_ratio: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiSpinnerData {
    pub dots: Vec<freecs::Entity>,
    pub rotation_speed: f32,
    pub phase: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiTextInputData {
    pub text: String,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
    pub changed: bool,
    pub text_slot: usize,
    pub cursor_entity: freecs::Entity,
    pub selection_entity: freecs::Entity,
    pub scroll_offset: f32,
    pub cursor_blink_timer: f64,
    pub placeholder_entity: Option<freecs::Entity>,
    /// Undo history wrapped in `Arc` so the per-frame `data.clone()` in the
    /// widget interaction loop is O(1) instead of cloning the whole snapshot
    /// vector. Mutation paths use [`std::sync::Arc::make_mut`] to clone-on-write
    /// only when the stack actually changes.
    pub undo_stack: std::sync::Arc<UndoStack<TextSnapshot>>,
    pub input_mask: InputMask,
    pub max_length: Option<usize>,
    pub password: bool,
}

#[derive(Clone, Debug, Default)]
pub struct UiDropdownData {
    pub options: Vec<String>,
    pub selected_index: usize,
    pub changed: bool,
    pub open: bool,
    pub header_text_slot: usize,
    pub popup_entities: Vec<freecs::Entity>,
    pub popup_container_entity: freecs::Entity,
    pub hovered_index: Option<usize>,
    pub is_theme_dropdown: bool,
    pub searchable: bool,
    pub filter_text: String,
    pub filter_input_entity: Option<freecs::Entity>,
    pub filtered_indices: Vec<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiMultiSelectData {
    pub options: Vec<String>,
    pub selected_indices: HashSet<usize>,
    pub changed: bool,
    pub open: bool,
    pub header_text_slot: usize,
    pub popup_entities: Vec<freecs::Entity>,
    pub popup_container_entity: freecs::Entity,
    pub hovered_index: Option<usize>,
    pub check_entities: Vec<freecs::Entity>,
}

#[derive(Clone, Debug, Default)]
pub struct UiDatePickerData {
    pub year: i32,
    pub month: u32,
    pub day: u32,
    pub changed: bool,
    pub open: bool,
    pub header_text_slot: usize,
    pub popup_entity: freecs::Entity,
    pub day_entities: Vec<freecs::Entity>,
    pub day_text_slots: Vec<usize>,
    pub month_label_slot: usize,
    pub prev_month_entity: freecs::Entity,
    pub next_month_entity: freecs::Entity,
    pub selected_day_entity: Option<freecs::Entity>,
}

#[derive(Clone, Debug, Default)]
pub struct UiMenuData {
    pub items: Vec<String>,
    pub clicked_item: Option<usize>,
    pub open: bool,
    pub label_text_slot: usize,
    pub popup_entities: Vec<freecs::Entity>,
    pub popup_container_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiPanelData {
    pub id: String,
    pub title: String,
    pub title_text_slot: usize,
    pub content_entity: freecs::Entity,
    pub header_entity: freecs::Entity,
    pub collapsed: bool,
    pub panel_kind: UiPanelKind,
    pub focus_order: i32,
    pub pinned: bool,
    pub min_size: Vec2,
    pub drag_offset: Option<Vec2>,
    pub resize_edge: Option<ResizeEdge>,
    pub resize_start_rect: Option<Rect>,
    pub resize_start_mouse: Option<Vec2>,
    pub undocked_rect: Option<Rect>,
    pub default_dock_size: f32,
    pub collapse_button_entity: Option<freecs::Entity>,
    pub collapse_button_text_slot: Option<usize>,
    pub header_visible: bool,
    pub resizable: bool,
}

#[derive(Clone, Debug, Default)]
pub struct UiColorPickerData {
    pub color: Vec4,
    pub changed: bool,
    pub swatch_entity: freecs::Entity,
    pub slider_entities: [freecs::Entity; 4],
    pub mode: ColorPickerMode,
    pub popup_entity: freecs::Entity,
    pub popup_wheel_entity: freecs::Entity,
    pub popup_open: bool,
}

#[derive(Clone, Debug, Default)]
pub struct UiColorWheelData {
    pub color: Vec4,
    pub hue: f32,
    pub saturation: f32,
    pub value: f32,
    pub alpha: f32,
    pub changed: bool,
    pub wheel_entity: freecs::Entity,
    pub dot_entity: freecs::Entity,
    pub swatch_entity: freecs::Entity,
    pub value_slider_entity: freecs::Entity,
    pub alpha_slider_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiSelectableLabelData {
    pub selected: bool,
    pub changed: bool,
    pub text_slot: usize,
    pub group_id: Option<u32>,
}

#[derive(Clone, Debug, Default)]
pub struct UiDragValueData {
    pub value: f32,
    pub min: f32,
    pub max: f32,
    pub speed: f32,
    pub precision: usize,
    pub prefix: String,
    pub suffix: String,
    pub changed: bool,
    pub text_slot: usize,
    pub editing: bool,
    pub cursor_entity: freecs::Entity,
    pub selection_entity: freecs::Entity,
    pub edit_text: String,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
    pub cursor_blink_timer: f64,
    pub scroll_offset: f32,
    pub drag_start_value: f32,
    pub undo_stack: UndoStack<TextSnapshot>,
    pub up_entity: Option<freecs::Entity>,
    pub down_entity: Option<freecs::Entity>,
    pub step: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiContextMenuData {
    pub items: Vec<ContextMenuItem>,
    pub open: bool,
    pub clicked_item: Option<usize>,
    pub popup_entity: freecs::Entity,
    pub item_entities: Vec<Option<freecs::Entity>>,
    pub item_defs: Vec<ContextMenuItemDef>,
}

#[derive(Clone, Debug, Default)]
pub struct UiTreeNodeData {
    pub label: String,
    pub text_slot: usize,
    pub depth: usize,
    pub expanded: bool,
    pub selected: bool,
    pub row_entity: freecs::Entity,
    pub arrow_entity: freecs::Entity,
    pub arrow_text_slot: usize,
    pub children_container: freecs::Entity,
    pub user_data: u64,
    pub parent_node: Option<freecs::Entity>,
    pub wrapper_entity: freecs::Entity,
    pub lazy: bool,
    pub lazy_loaded: bool,
}

#[derive(Clone, Debug, Default)]
pub struct UiTreeViewData {
    pub selected_nodes: Vec<freecs::Entity>,
    pub multi_select: bool,
    pub node_entities: Vec<freecs::Entity>,
    pub changed: bool,
    pub content_entity: freecs::Entity,
    pub context_menu_node: Option<freecs::Entity>,
    pub filter_text: String,
    pub filter_active: bool,
    pub pre_filter_expanded: HashMap<freecs::Entity, bool>,
    pub scroll_offset: f32,
    pub thumb_entity: freecs::Entity,
    pub track_entity: freecs::Entity,
    pub thumb_dragging: bool,
    pub thumb_drag_start_offset: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiModalDialogData {
    pub title_text_slot: usize,
    pub content_entity: freecs::Entity,
    pub backdrop_entity: freecs::Entity,
    pub ok_button: Option<freecs::Entity>,
    pub cancel_button: Option<freecs::Entity>,
    pub result: Option<bool>,
}

#[derive(Clone, Debug, Default)]
pub struct UiRichTextData {
    pub span_entities: Vec<freecs::Entity>,
    pub span_text_slots: Vec<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiDataGridData {
    pub columns: Vec<DataGridColumn>,
    pub row_height: f32,
    pub pool_size: usize,
    pub total_rows: usize,
    pub scroll_entity: freecs::Entity,
    pub body_entity: freecs::Entity,
    pub header_entities: Vec<freecs::Entity>,
    pub header_text_slots: Vec<usize>,
    pub top_spacer: freecs::Entity,
    pub bottom_spacer: freecs::Entity,
    pub pool_rows: Vec<DataGridPoolRow>,
    pub visible_start: usize,
    pub sort_column: Option<usize>,
    pub sort_ascending: bool,
    pub sort_changed: bool,
    pub selected_rows: HashSet<usize>,
    pub selection_anchor: Option<usize>,
    pub selection_changed: bool,
    pub focused: bool,
    pub resize_column: Option<usize>,
    pub resize_start_x: f32,
    pub resize_start_width: f32,
    pub filtered_indices: Option<Vec<usize>>,
    pub header_divider_entities: Vec<freecs::Entity>,
    pub filter_row_entity: Option<freecs::Entity>,
    pub filter_input_entities: Vec<freecs::Entity>,
    pub filter_texts: Vec<String>,
    pub editing_cell: Option<(usize, usize)>,
    pub editing_input_entity: Option<freecs::Entity>,
}

#[derive(Clone, Debug, Default)]
pub struct UiPropertyGridData {
    pub label_width: f32,
    pub row_entities: Vec<freecs::Entity>,
    pub label_entities: Vec<freecs::Entity>,
    pub resize_active: bool,
    pub resize_start_x: f32,
    pub resize_start_width: f32,
}

#[derive(Clone, Debug, Default)]
pub struct UiCommandPaletteData {
    pub commands: Vec<CommandEntry>,
    pub filter_text: String,
    pub filtered_indices: Vec<usize>,
    pub selected_index: usize,
    pub open: bool,
    pub executed_command: Option<usize>,
    pub text_input_entity: freecs::Entity,
    pub result_entities: Vec<freecs::Entity>,
    pub result_text_slots: Vec<usize>,
    pub backdrop_entity: freecs::Entity,
    pub dialog_entity: freecs::Entity,
    pub pool_size: usize,
    pub scroll_entity: freecs::Entity,
}

#[derive(Clone, Debug, Default)]
pub struct UiVirtualListData {
    pub item_height: f32,
    pub pool_size: usize,
    pub total_items: usize,
    pub visible_start: usize,
    pub scroll_entity: freecs::Entity,
    pub body_entity: freecs::Entity,
    pub top_spacer: freecs::Entity,
    pub bottom_spacer: freecs::Entity,
    pub pool_items: Vec<VirtualListPoolItem>,
    pub selection: Option<usize>,
    pub selection_changed: bool,
}

#[derive(Clone, Debug, Default)]
pub struct UiCanvasData {
    pub commands: Vec<CanvasCommand>,
    pub hit_test_enabled: bool,
    pub command_bounds: Vec<(u32, crate::ecs::ui::types::Rect)>,
}

#[derive(Clone, Debug, Default)]
pub struct UiTextAreaData {
    pub text: String,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
    pub changed: bool,
    pub text_slot: usize,
    pub cursor_entity: freecs::Entity,
    pub selection_pool: Vec<freecs::Entity>,
    pub scroll_offset_y: f32,
    pub cursor_blink_timer: f64,
    pub placeholder_entity: Option<freecs::Entity>,
    pub line_height: f32,
    pub visible_rows: usize,
    pub undo_stack: UndoStack<TextSnapshot>,
    pub max_length: Option<usize>,
}

#[derive(Clone, Debug, Default)]
pub struct UiRichTextEditorData {
    pub text: String,
    pub char_styles: Vec<CharStyle>,
    pub current_style: CharStyle,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
    pub changed: bool,
    pub text_slot: usize,
    pub cursor_entity: freecs::Entity,
    pub selection_pool: Vec<freecs::Entity>,
    pub scroll_offset_y: f32,
    pub cursor_blink_timer: f64,
    pub line_height: f32,
    pub visible_rows: usize,
    pub placeholder_entity: Option<freecs::Entity>,
    pub undo_stack: UndoStack<RichTextSnapshot>,
}

impl UiRichTextEditorData {
    pub fn spans(&self) -> Vec<TextSpan> {
        if self.text.is_empty() {
            return Vec::new();
        }
        let chars: Vec<char> = self.text.chars().collect();
        let mut spans = Vec::new();
        let mut run_start = 0;
        let default_style = CharStyle::default();
        for index in 1..=chars.len() {
            let current_style = self.char_styles.get(index).unwrap_or(&default_style);
            let prev_style = self.char_styles.get(run_start).unwrap_or(&default_style);
            if index == chars.len() || current_style != prev_style {
                let run_text: String = chars[run_start..index].iter().collect();
                let style = self.char_styles.get(run_start).cloned().unwrap_or_default();
                let mut span = TextSpan::new(&run_text);
                span.bold = style.bold;
                span.italic = style.italic;
                span.underline = style.underline;
                span.color = style.color;
                spans.push(span);
                run_start = index;
            }
        }
        spans
    }
}

pub trait WidgetData: Sized {
    fn get(world: &crate::ecs::world::World, entity: freecs::Entity) -> Option<&Self>;
    fn get_mut(world: &mut crate::ecs::world::World, entity: freecs::Entity) -> Option<&mut Self>;
    fn set(world: &mut crate::ecs::world::World, entity: freecs::Entity, data: Self);
}

macro_rules! impl_widget_data {
    ($data:ty, $get:ident, $get_mut:ident, $set:ident) => {
        impl WidgetData for $data {
            fn get(world: &crate::ecs::world::World, entity: freecs::Entity) -> Option<&Self> {
                world.ui.$get(entity)
            }
            fn get_mut(
                world: &mut crate::ecs::world::World,
                entity: freecs::Entity,
            ) -> Option<&mut Self> {
                world.ui.$get_mut(entity)
            }
            fn set(world: &mut crate::ecs::world::World, entity: freecs::Entity, data: Self) {
                world.ui.$set(entity, data);
            }
        }
    };
}

impl_widget_data!(
    UiButtonData,
    get_ui_button,
    get_ui_button_mut,
    set_ui_button
);
impl_widget_data!(
    UiSliderData,
    get_ui_slider,
    get_ui_slider_mut,
    set_ui_slider
);
impl_widget_data!(
    UiToggleData,
    get_ui_toggle,
    get_ui_toggle_mut,
    set_ui_toggle
);
impl_widget_data!(
    UiCheckboxData,
    get_ui_checkbox,
    get_ui_checkbox_mut,
    set_ui_checkbox
);
impl_widget_data!(UiRadioData, get_ui_radio, get_ui_radio_mut, set_ui_radio);
impl_widget_data!(
    UiProgressBarData,
    get_ui_progress_bar,
    get_ui_progress_bar_mut,
    set_ui_progress_bar
);
impl_widget_data!(
    UiCollapsingHeaderData,
    get_ui_collapsing_header,
    get_ui_collapsing_header_mut,
    set_ui_collapsing_header
);
impl_widget_data!(
    UiScrollAreaData,
    get_ui_scroll_area,
    get_ui_scroll_area_mut,
    set_ui_scroll_area
);
impl_widget_data!(
    UiTabBarData,
    get_ui_tab_bar,
    get_ui_tab_bar_mut,
    set_ui_tab_bar
);
impl_widget_data!(
    UiTextInputData,
    get_ui_text_input,
    get_ui_text_input_mut,
    set_ui_text_input
);
impl_widget_data!(
    UiDropdownData,
    get_ui_dropdown,
    get_ui_dropdown_mut,
    set_ui_dropdown
);
impl_widget_data!(UiMenuData, get_ui_menu, get_ui_menu_mut, set_ui_menu);
impl_widget_data!(UiPanelData, get_ui_panel, get_ui_panel_mut, set_ui_panel);
impl_widget_data!(
    UiColorPickerData,
    get_ui_color_picker,
    get_ui_color_picker_mut,
    set_ui_color_picker
);
impl_widget_data!(
    UiColorWheelData,
    get_ui_color_wheel,
    get_ui_color_wheel_mut,
    set_ui_color_wheel
);
impl_widget_data!(
    UiSelectableLabelData,
    get_ui_selectable_label,
    get_ui_selectable_label_mut,
    set_ui_selectable_label
);
impl_widget_data!(
    UiDragValueData,
    get_ui_drag_value,
    get_ui_drag_value_mut,
    set_ui_drag_value
);
impl_widget_data!(
    UiContextMenuData,
    get_ui_context_menu,
    get_ui_context_menu_mut,
    set_ui_context_menu
);
impl_widget_data!(
    UiTreeViewData,
    get_ui_tree_view,
    get_ui_tree_view_mut,
    set_ui_tree_view
);
impl_widget_data!(
    UiTreeNodeData,
    get_ui_tree_node,
    get_ui_tree_node_mut,
    set_ui_tree_node
);
impl_widget_data!(
    UiModalDialogData,
    get_ui_modal_dialog,
    get_ui_modal_dialog_mut,
    set_ui_modal_dialog
);
impl_widget_data!(
    UiRichTextData,
    get_ui_rich_text,
    get_ui_rich_text_mut,
    set_ui_rich_text
);
impl_widget_data!(
    UiDataGridData,
    get_ui_data_grid,
    get_ui_data_grid_mut,
    set_ui_data_grid
);
impl_widget_data!(
    UiPropertyGridData,
    get_ui_property_grid,
    get_ui_property_grid_mut,
    set_ui_property_grid
);
impl_widget_data!(
    UiCommandPaletteData,
    get_ui_command_palette,
    get_ui_command_palette_mut,
    set_ui_command_palette
);
impl_widget_data!(
    UiCanvasData,
    get_ui_canvas,
    get_ui_canvas_mut,
    set_ui_canvas
);
impl_widget_data!(
    UiTextAreaData,
    get_ui_text_area,
    get_ui_text_area_mut,
    set_ui_text_area
);
impl_widget_data!(
    super::tiles::UiTileContainerData,
    get_ui_tile_container,
    get_ui_tile_container_mut,
    set_ui_tile_container
);
impl_widget_data!(
    UiVirtualListData,
    get_ui_virtual_list,
    get_ui_virtual_list_mut,
    set_ui_virtual_list
);
impl_widget_data!(
    UiRichTextEditorData,
    get_ui_rich_text_editor,
    get_ui_rich_text_editor_mut,
    set_ui_rich_text_editor
);
impl_widget_data!(
    UiRangeSliderData,
    get_ui_range_slider,
    get_ui_range_slider_mut,
    set_ui_range_slider
);
impl_widget_data!(
    UiBreadcrumbData,
    get_ui_breadcrumb,
    get_ui_breadcrumb_mut,
    set_ui_breadcrumb
);
impl_widget_data!(
    UiSplitterData,
    get_ui_splitter,
    get_ui_splitter_mut,
    set_ui_splitter
);
impl_widget_data!(
    UiMultiSelectData,
    get_ui_multi_select,
    get_ui_multi_select_mut,
    set_ui_multi_select
);
impl_widget_data!(
    UiDatePickerData,
    get_ui_date_picker,
    get_ui_date_picker_mut,
    set_ui_date_picker
);
impl_widget_data!(
    UiSpinnerData,
    get_ui_spinner,
    get_ui_spinner_mut,
    set_ui_spinner
);
