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

use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::layout_types::{FlowLayout, GridLayout, UiLayoutType};
use crate::ecs::ui::state::{STATE_COUNT, UiBase, UiStateTrait};
use crate::ecs::ui::theme::UiTheme;
use crate::ecs::ui::types::Rect;
use crate::ecs::ui::units::UiValue;
use crate::render::wgpu::passes::geometry::UiLayer;

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum AutoSizeMode {
    #[default]
    None,
    Width,
    Height,
    Both,
}

#[derive(Clone, Debug)]
pub struct UiLayoutRoot {
    pub absolute_scale: f32,
    pub default_font_size: f32,
    pub target_window: Option<usize>,
}

impl Default for UiLayoutRoot {
    fn default() -> Self {
        Self {
            absolute_scale: 1.0,
            default_font_size: 16.0,
            target_window: None,
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum TextOverflow {
    #[default]
    Visible,
    Ellipsis,
    Clip,
    Wrap,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UiDepthMode {
    Add(f32),
    Set(f32),
}

impl Default for UiDepthMode {
    fn default() -> Self {
        Self::Add(1.0)
    }
}

#[derive(Clone, Debug)]
pub struct UiLayoutNode {
    pub layouts: Vec<Option<UiLayoutType>>,
    pub flow_layout: Option<FlowLayout>,
    pub responsive_flow: Option<crate::ecs::ui::layout_types::ResponsiveFlowOverride>,
    pub grid_layout: Option<GridLayout>,
    pub depth: UiDepthMode,
    pub font_size: Option<f32>,
    pub clip_content: bool,
    pub pointer_events: bool,
    pub visible: bool,
    pub flow_child_size: Option<UiValue<Vec2>>,
    pub flex_grow: Option<f32>,
    pub flex_shrink: Option<f32>,
    pub min_size: Option<Vec2>,
    pub max_size: Option<Vec2>,
    pub z_index: Option<i32>,
    pub auto_size: AutoSizeMode,
    pub auto_size_padding: Vec2,
    pub computed_rect: Rect,
    pub computed_depth: f32,
    pub computed_clip_rect: Option<Rect>,
    pub layer: Option<UiLayer>,
    pub computed_layer: Option<UiLayer>,
    pub animation: Option<UiNodeAnimation>,
}

impl Default for UiLayoutNode {
    fn default() -> Self {
        Self {
            layouts: vec![None; STATE_COUNT],
            flow_layout: None,
            responsive_flow: None,
            grid_layout: None,
            depth: UiDepthMode::default(),
            font_size: None,
            clip_content: false,
            pointer_events: true,
            visible: true,
            flow_child_size: None,
            flex_grow: None,
            flex_shrink: None,
            min_size: None,
            max_size: None,
            z_index: None,
            auto_size: AutoSizeMode::None,
            auto_size_padding: Vec2::new(0.0, 0.0),
            computed_rect: Rect::default(),
            computed_depth: 0.0,
            computed_clip_rect: None,
            layer: None,
            computed_layer: None,
            animation: None,
        }
    }
}

impl UiLayoutNode {
    pub fn base_layout(&self) -> Option<&UiLayoutType> {
        self.layouts[UiBase::INDEX].as_ref()
    }

    pub fn ensure_state_capacity(&mut self, count: usize) {
        if self.layouts.len() < count {
            self.layouts.resize(count, None);
        }
    }
}

#[derive(Clone, Debug)]
pub struct UiNodeColor {
    pub colors: Vec<Option<Vec4>>,
    pub computed_color: Vec4,
}

impl Default for UiNodeColor {
    fn default() -> Self {
        Self {
            colors: vec![None; STATE_COUNT],
            computed_color: Vec4::new(1.0, 1.0, 1.0, 1.0),
        }
    }
}

impl UiNodeColor {
    pub fn ensure_state_capacity(&mut self, count: usize) {
        if self.colors.len() < count {
            self.colors.resize(count, None);
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum UiNodeContent {
    None,
    Rect {
        corner_radius: f32,
        border_width: f32,
        border_color: Vec4,
    },
    Text {
        text_slot: usize,
        font_index: usize,
        font_size_override: Option<f32>,
        outline_color: Vec4,
        outline_width: f32,
        alignment: TextAlignment,
        vertical_alignment: VerticalAlignment,
        overflow: TextOverflow,
    },
    Image {
        texture_index: u32,
        uv_min: Vec2,
        uv_max: Vec2,
    },
}

impl Default for UiNodeContent {
    fn default() -> Self {
        Self::None
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum AccessibleRole {
    Button,
    Slider,
    Checkbox,
    Toggle,
    TextInput,
    TextArea,
    Dropdown,
    Tab,
    TabPanel,
    Tree,
    TreeItem,
    Grid,
    GridCell,
    Dialog,
    Alert,
    ProgressBar,
    Menu,
    MenuItem,
}

#[derive(Clone, Debug, Default)]
pub struct UiNodeInteraction {
    pub hovered: bool,
    pub pressed: bool,
    pub clicked: bool,
    pub focused: bool,
    pub drag_start: Option<Vec2>,
    pub dragging: bool,
    pub cursor_icon: Option<winit::window::CursorIcon>,
    pub double_clicked: bool,
    pub right_clicked: bool,
    pub tooltip_text: Option<String>,
    pub tooltip_entity: Option<freecs::Entity>,
    pub tab_index: Option<i32>,
    pub disabled: bool,
    pub error_text: Option<String>,
    pub validation_rules: Vec<ValidationRule>,
    pub accessible_role: Option<AccessibleRole>,
    pub accessible_label: Option<String>,
}

#[derive(Clone, Copy, Debug)]
pub struct StateTransition {
    pub enter_speed: f32,
    pub exit_speed: f32,
    pub easing: crate::ecs::tween::easing::EasingFunction,
}

impl Default for StateTransition {
    fn default() -> Self {
        Self {
            enter_speed: 10.0,
            exit_speed: 4.0,
            easing: crate::ecs::tween::easing::EasingFunction::Linear,
        }
    }
}

#[derive(Clone, Debug)]
pub struct UiStateWeights {
    pub weights: Vec<f32>,
    pub transitions: Vec<Option<StateTransition>>,
    pub progress: Vec<f32>,
    pub targets: Vec<f32>,
    pub start_weights: Vec<f32>,
}

impl Default for UiStateWeights {
    fn default() -> Self {
        let mut weights = vec![0.0; STATE_COUNT];
        weights[UiBase::INDEX] = 1.0;
        let mut targets = vec![0.0; STATE_COUNT];
        targets[UiBase::INDEX] = 1.0;
        Self {
            weights: weights.clone(),
            transitions: vec![None; STATE_COUNT],
            progress: vec![1.0; STATE_COUNT],
            targets,
            start_weights: weights,
        }
    }
}

impl UiStateWeights {
    pub fn ensure_state_capacity(&mut self, count: usize) {
        if self.weights.len() < count {
            self.weights.resize(count, 0.0);
        }
        if self.transitions.len() < count {
            self.transitions.resize(count, None);
        }
        if self.progress.len() < count {
            self.progress.resize(count, 1.0);
        }
        if self.targets.len() < count {
            self.targets.resize(count, 0.0);
        }
        if self.start_weights.len() < count {
            self.start_weights.resize(count, 0.0);
        }
    }
}

#[derive(Clone, Debug)]
pub struct UiButtonData {
    pub clicked: bool,
    pub text_slot: usize,
}

#[derive(Clone, Debug)]
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, Copy, Debug, PartialEq)]
pub enum RangeSliderThumb {
    Low,
    High,
}

#[derive(Clone, Debug)]
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)]
pub struct UiToggleData {
    pub value: bool,
    pub changed: bool,
    pub animated_position: f32,
    pub knob_entity: freecs::Entity,
}

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

#[derive(Clone, Debug)]
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)]
pub struct UiProgressBarData {
    pub value: f32,
    pub fill_entity: freecs::Entity,
}

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

#[derive(Clone, Debug)]
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)]
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)]
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)]
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)]
pub struct TextSnapshot {
    pub text: String,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
}

#[derive(Clone, Debug)]
pub struct RichTextSnapshot {
    pub text: String,
    pub char_styles: Vec<CharStyle>,
    pub cursor_position: usize,
    pub selection_start: Option<usize>,
}

#[derive(Clone, Debug)]
pub struct UndoStack<T> {
    history: Vec<T>,
    position: usize,
    max_entries: usize,
    last_push_time: f64,
}

impl<T> UndoStack<T> {
    pub fn new(max_entries: usize) -> Self {
        Self {
            history: Vec::new(),
            position: 0,
            max_entries,
            last_push_time: 0.0,
        }
    }

    pub fn push_initial(&mut self, snapshot: T) {
        if self.history.is_empty() {
            self.history.push(snapshot);
            self.position = 0;
        }
    }

    pub fn push(&mut self, snapshot: T, time: f64) {
        self.history.truncate(self.position + 1);
        if time - self.last_push_time < 0.3 && self.history.len() > 1 {
            *self.history.last_mut().unwrap() = snapshot;
        } else {
            self.history.push(snapshot);
            if self.history.len() > self.max_entries {
                self.history.remove(0);
            }
        }
        self.position = self.history.len() - 1;
        self.last_push_time = time;
    }

    pub fn undo(&mut self) -> Option<&T> {
        if self.position > 0 {
            self.position -= 1;
            Some(&self.history[self.position])
        } else {
            None
        }
    }

    pub fn redo(&mut self) -> Option<&T> {
        if self.position + 1 < self.history.len() {
            self.position += 1;
            Some(&self.history[self.position])
        } else {
            None
        }
    }

    pub fn can_undo(&self) -> bool {
        self.position > 0
    }

    pub fn can_redo(&self) -> bool {
        self.position + 1 < self.history.len()
    }

    pub fn is_empty(&self) -> bool {
        self.history.is_empty()
    }
}

#[derive(Clone, Debug, Default)]
pub enum InputMask {
    #[default]
    None,
    Numeric,
    Decimal,
    Alpha,
    AlphaNumeric,
    Custom(fn(char) -> bool),
}

impl InputMask {
    pub fn accepts(&self, character: char) -> bool {
        match self {
            Self::None => true,
            Self::Numeric => character.is_ascii_digit(),
            Self::Decimal => character.is_ascii_digit() || character == '.' || character == '-',
            Self::Alpha => character.is_alphabetic(),
            Self::AlphaNumeric => character.is_alphanumeric(),
            Self::Custom(predicate) => predicate(character),
        }
    }
}

#[derive(Clone)]
pub enum ValidationRule {
    Required,
    MinLength(usize),
    MaxLength(usize),
    Custom(fn(&str) -> Result<(), String>),
}

impl std::fmt::Debug for ValidationRule {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Required => write!(formatter, "Required"),
            Self::MinLength(n) => write!(formatter, "MinLength({})", n),
            Self::MaxLength(n) => write!(formatter, "MaxLength({})", n),
            Self::Custom(_) => write!(formatter, "Custom(fn)"),
        }
    }
}

impl ValidationRule {
    pub fn validate(&self, value: &str) -> Result<(), String> {
        match self {
            Self::Required => {
                if value.trim().is_empty() {
                    Err("This field is required".to_string())
                } else {
                    Ok(())
                }
            }
            Self::MinLength(min) => {
                if value.chars().count() < *min {
                    Err(format!("Minimum {} characters required", min))
                } else {
                    Ok(())
                }
            }
            Self::MaxLength(max) => {
                if value.chars().count() > *max {
                    Err(format!("Maximum {} characters allowed", max))
                } else {
                    Ok(())
                }
            }
            Self::Custom(validator) => validator(value),
        }
    }
}

#[derive(Clone, Debug)]
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>,
    pub undo_stack: UndoStack<TextSnapshot>,
    pub input_mask: InputMask,
    pub max_length: Option<usize>,
}

#[derive(Clone, Debug)]
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)]
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)]
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)]
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)]
pub struct UiPanelData {
    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, Copy, Debug, PartialEq)]
pub enum UiPanelKind {
    Floating,
    DockedLeft,
    DockedRight,
    DockedTop,
    DockedBottom,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ResizeEdge {
    Left,
    Right,
    Top,
    Bottom,
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorPickerMode {
    Rgb,
    Hsv,
}

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

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

pub struct DragValueConfig<'a> {
    pub initial: f32,
    pub min: f32,
    pub max: f32,
    pub speed: f32,
    pub precision: usize,
    pub prefix: &'a str,
    pub suffix: &'a str,
    pub show_arrows: bool,
}

impl<'a> DragValueConfig<'a> {
    pub fn new(min: f32, max: f32, initial: f32) -> Self {
        Self {
            initial,
            min,
            max,
            speed: 0.1,
            precision: 2,
            prefix: "",
            suffix: "",
            show_arrows: true,
        }
    }

    pub fn hide_arrows(mut self) -> Self {
        self.show_arrows = false;
        self
    }

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

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

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

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

pub struct SliderConfig<'a> {
    pub min: f32,
    pub max: f32,
    pub initial: f32,
    pub logarithmic: bool,
    pub precision: usize,
    pub prefix: &'a str,
    pub suffix: &'a str,
}

impl<'a> SliderConfig<'a> {
    pub fn new(min: f32, max: f32, initial: f32) -> Self {
        Self {
            min,
            max,
            initial,
            logarithmic: false,
            precision: 1,
            prefix: "",
            suffix: "",
        }
    }

    pub fn logarithmic(mut self) -> Self {
        self.logarithmic = true;
        self.precision = 3;
        self
    }

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

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

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

#[derive(Clone, Debug)]
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)]
pub struct ContextMenuItem {
    pub label: String,
    pub shortcut: Option<String>,
    pub separator: bool,
}

#[derive(Clone, Debug)]
pub enum ContextMenuItemKind {
    Action,
    Separator,
    Submenu {
        children: Vec<ContextMenuItemDef>,
        popup_entity: freecs::Entity,
        open: bool,
    },
    Widget {
        content_entity: freecs::Entity,
    },
}

#[derive(Clone, Debug)]
pub struct ContextMenuItemDef {
    pub label: String,
    pub shortcut: String,
    pub kind: ContextMenuItemKind,
    pub row_entity: freecs::Entity,
    pub command_id: Option<usize>,
    pub binding: Option<ShortcutBinding>,
}

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

#[derive(Clone, Debug)]
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)]
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>,
}

#[derive(Clone, Debug)]
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)]
pub struct TextSpan {
    pub text: String,
    pub color: Option<Vec4>,
    pub font_size_override: Option<f32>,
    pub bold: bool,
    pub italic: bool,
    pub underline: bool,
    pub font_index: Option<usize>,
}

impl TextSpan {
    pub fn new(text: &str) -> Self {
        Self {
            text: text.to_string(),
            color: None,
            font_size_override: None,
            bold: false,
            italic: false,
            underline: false,
            font_index: None,
        }
    }

    pub fn colored(text: &str, color: Vec4) -> Self {
        Self {
            text: text.to_string(),
            color: Some(color),
            font_size_override: None,
            bold: false,
            italic: false,
            underline: false,
            font_index: None,
        }
    }

    pub fn sized(text: &str, font_size: f32) -> Self {
        Self {
            text: text.to_string(),
            color: None,
            font_size_override: Some(font_size),
            bold: false,
            italic: false,
            underline: false,
            font_index: None,
        }
    }

    pub fn with_color(mut self, color: Vec4) -> Self {
        self.color = Some(color);
        self
    }

    pub fn with_size(mut self, font_size: f32) -> Self {
        self.font_size_override = Some(font_size);
        self
    }

    pub fn with_bold(mut self) -> Self {
        self.bold = true;
        self
    }

    pub fn with_italic(mut self) -> Self {
        self.italic = true;
        self
    }

    pub fn with_underline(mut self) -> Self {
        self.underline = true;
        self
    }

    pub fn with_font(mut self, font_index: usize) -> Self {
        self.font_index = Some(font_index);
        self
    }
}

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

#[derive(Clone, Debug)]
pub struct DataGridColumn {
    pub label: String,
    pub width: f32,
    pub sortable: bool,
    pub alignment: TextAlignment,
    pub editable: bool,
}

impl DataGridColumn {
    pub fn new(label: &str, width: f32) -> Self {
        Self {
            label: label.to_string(),
            width,
            sortable: false,
            alignment: TextAlignment::Left,
            editable: false,
        }
    }

    pub fn sortable(mut self) -> Self {
        self.sortable = true;
        self
    }

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

    pub fn editable(mut self) -> Self {
        self.editable = true;
        self
    }
}

#[derive(Clone, Debug)]
pub struct DataGridPoolRow {
    pub row_entity: freecs::Entity,
    pub cell_text_slots: Vec<usize>,
    pub cell_entities: Vec<freecs::Entity>,
}

#[derive(Clone, Debug)]
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,
}

pub trait DataGridDataSource {
    fn row_count(&self) -> usize;
    fn cell_text(&self, row: usize, column: usize) -> String;
}

pub trait VirtualListDataSource {
    fn item_count(&self) -> usize;
    fn bind_item(
        &self,
        world: &mut crate::ecs::world::World,
        item_index: usize,
        container: freecs::Entity,
    );
}

#[derive(Clone, Debug)]
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)]
pub struct CommandEntry {
    pub label: String,
    pub shortcut: String,
    pub category: String,
    pub enabled: bool,
}

#[derive(Clone, Debug)]
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)]
pub struct VirtualListPoolItem {
    pub container_entity: freecs::Entity,
}

#[derive(Clone, Debug)]
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, Copy, Debug, PartialEq, Eq)]
pub enum ThemeColor {
    Text,
    TextDisabled,
    TextAccent,
    Background,
    BackgroundHover,
    BackgroundActive,
    Panel,
    PanelHeader,
    Border,
    BorderFocused,
    Accent,
    AccentHover,
    AccentActive,
    Success,
    Warning,
    Error,
    SliderTrack,
    SliderFill,
    InputBackground,
    InputBackgroundFocused,
    Selection,
    Scrollbar,
    ScrollbarHover,
}

impl ThemeColor {
    pub fn resolve(self, theme: &UiTheme) -> Vec4 {
        match self {
            Self::Text => theme.text_color,
            Self::TextDisabled => theme.text_color_disabled,
            Self::TextAccent => theme.text_color_accent,
            Self::Background => theme.background_color,
            Self::BackgroundHover => theme.background_color_hovered,
            Self::BackgroundActive => theme.background_color_active,
            Self::Panel => theme.panel_color,
            Self::PanelHeader => theme.panel_header_color,
            Self::Border => theme.border_color,
            Self::BorderFocused => theme.border_color_focused,
            Self::Accent => theme.accent_color,
            Self::AccentHover => theme.accent_color_hovered,
            Self::AccentActive => theme.accent_color_active,
            Self::Success => theme.success_color,
            Self::Warning => theme.warning_color,
            Self::Error => theme.error_color,
            Self::SliderTrack => theme.slider_track_color,
            Self::SliderFill => theme.slider_fill_color,
            Self::InputBackground => theme.input_background_color,
            Self::InputBackgroundFocused => theme.input_background_focused,
            Self::Selection => theme.selection_color,
            Self::Scrollbar => theme.scrollbar_color,
            Self::ScrollbarHover => theme.scrollbar_color_hovered,
        }
    }
}

#[derive(Clone, Debug)]
pub struct UiThemeBinding {
    pub color_roles: Vec<Option<ThemeColor>>,
    pub border_color_role: Option<ThemeColor>,
}

impl Default for UiThemeBinding {
    fn default() -> Self {
        Self {
            color_roles: vec![None; STATE_COUNT],
            border_color_role: None,
        }
    }
}

impl UiThemeBinding {
    pub fn ensure_state_capacity(&mut self, count: usize) {
        if self.color_roles.len() < count {
            self.color_roles.resize(count, None);
        }
    }
}

#[derive(Clone, Debug)]
pub enum CanvasCommand {
    Rect {
        position: Vec2,
        size: Vec2,
        color: Vec4,
        corner_radius: f32,
        id: Option<u32>,
    },
    Circle {
        center: Vec2,
        radius: f32,
        color: Vec4,
        id: Option<u32>,
    },
    Line {
        from: Vec2,
        to: Vec2,
        thickness: f32,
        color: Vec4,
        id: Option<u32>,
    },
    Text {
        text: String,
        position: Vec2,
        font_size: f32,
        color: Vec4,
        id: Option<u32>,
    },
    Polyline {
        points: Vec<Vec2>,
        thickness: f32,
        color: Vec4,
        closed: bool,
        id: Option<u32>,
    },
    RectStroke {
        position: Vec2,
        size: Vec2,
        color: Vec4,
        thickness: f32,
        corner_radius: f32,
        id: Option<u32>,
    },
    CircleStroke {
        center: Vec2,
        radius: f32,
        color: Vec4,
        thickness: f32,
        id: Option<u32>,
    },
    Arc {
        center: Vec2,
        radius: f32,
        start_angle: f32,
        end_angle: f32,
        thickness: f32,
        color: Vec4,
        id: Option<u32>,
    },
    QuadraticBezier {
        start: Vec2,
        control: Vec2,
        end: Vec2,
        thickness: f32,
        color: Vec4,
        id: Option<u32>,
    },
    CubicBezier {
        start: Vec2,
        control1: Vec2,
        control2: Vec2,
        end: Vec2,
        thickness: f32,
        color: Vec4,
        id: Option<u32>,
    },
}

#[derive(Clone, Debug)]
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 syntax_language: Option<String>,
    pub undo_stack: UndoStack<TextSnapshot>,
    pub max_length: Option<usize>,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct CharStyle {
    pub bold: bool,
    pub italic: bool,
    pub underline: bool,
    pub color: Option<Vec4>,
}

#[derive(Clone, Debug)]
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
    }
}

#[derive(Clone, Debug)]
pub enum DragPayload {
    Index(usize),
    Entity(freecs::Entity),
    Text(String),
    Custom(u64),
}

impl Default for DragPayload {
    fn default() -> Self {
        Self::Custom(0)
    }
}

#[derive(Clone, Debug, Default)]
pub struct UiDragSource {
    pub payload: DragPayload,
}

#[derive(Clone, Debug, Default)]
pub enum DragAcceptFilter {
    #[default]
    Any,
    IndexOnly,
    EntityOnly,
    TextOnly,
    CustomOnly,
}

impl DragAcceptFilter {
    pub fn accepts(&self, payload: &DragPayload) -> bool {
        match self {
            Self::Any => true,
            Self::IndexOnly => matches!(payload, DragPayload::Index(_)),
            Self::EntityOnly => matches!(payload, DragPayload::Entity(_)),
            Self::TextOnly => matches!(payload, DragPayload::Text(_)),
            Self::CustomOnly => matches!(payload, DragPayload::Custom(_)),
        }
    }
}

#[derive(Clone, Debug)]
pub struct UiDropTarget {
    pub accepted: bool,
    pub filter: DragAcceptFilter,
}

impl Default for UiDropTarget {
    fn default() -> Self {
        Self {
            accepted: true,
            filter: DragAcceptFilter::Any,
        }
    }
}

#[derive(Clone, Debug)]
pub struct ShortcutBinding {
    pub ctrl: bool,
    pub shift: bool,
    pub alt: bool,
    pub key: winit::keyboard::KeyCode,
}

impl ShortcutBinding {
    pub fn parse(shortcut: &str) -> Option<Self> {
        let mut ctrl = false;
        let mut shift = false;
        let mut alt = false;
        let mut key_part = "";

        for part in shortcut.split('+') {
            let trimmed = part.trim();
            match trimmed.to_lowercase().as_str() {
                "ctrl" => ctrl = true,
                "shift" => shift = true,
                "alt" => alt = true,
                _ => key_part = trimmed,
            }
        }

        let key = match key_part.to_uppercase().as_str() {
            "A" => winit::keyboard::KeyCode::KeyA,
            "B" => winit::keyboard::KeyCode::KeyB,
            "C" => winit::keyboard::KeyCode::KeyC,
            "D" => winit::keyboard::KeyCode::KeyD,
            "E" => winit::keyboard::KeyCode::KeyE,
            "F" => winit::keyboard::KeyCode::KeyF,
            "G" => winit::keyboard::KeyCode::KeyG,
            "H" => winit::keyboard::KeyCode::KeyH,
            "I" => winit::keyboard::KeyCode::KeyI,
            "J" => winit::keyboard::KeyCode::KeyJ,
            "K" => winit::keyboard::KeyCode::KeyK,
            "L" => winit::keyboard::KeyCode::KeyL,
            "M" => winit::keyboard::KeyCode::KeyM,
            "N" => winit::keyboard::KeyCode::KeyN,
            "O" => winit::keyboard::KeyCode::KeyO,
            "P" => winit::keyboard::KeyCode::KeyP,
            "Q" => winit::keyboard::KeyCode::KeyQ,
            "R" => winit::keyboard::KeyCode::KeyR,
            "S" => winit::keyboard::KeyCode::KeyS,
            "T" => winit::keyboard::KeyCode::KeyT,
            "U" => winit::keyboard::KeyCode::KeyU,
            "V" => winit::keyboard::KeyCode::KeyV,
            "W" => winit::keyboard::KeyCode::KeyW,
            "X" => winit::keyboard::KeyCode::KeyX,
            "Y" => winit::keyboard::KeyCode::KeyY,
            "Z" => winit::keyboard::KeyCode::KeyZ,
            "F1" => winit::keyboard::KeyCode::F1,
            "F2" => winit::keyboard::KeyCode::F2,
            "F3" => winit::keyboard::KeyCode::F3,
            "F4" => winit::keyboard::KeyCode::F4,
            "F5" => winit::keyboard::KeyCode::F5,
            "F6" => winit::keyboard::KeyCode::F6,
            "F7" => winit::keyboard::KeyCode::F7,
            "F8" => winit::keyboard::KeyCode::F8,
            "F9" => winit::keyboard::KeyCode::F9,
            "F10" => winit::keyboard::KeyCode::F10,
            "F11" => winit::keyboard::KeyCode::F11,
            "F12" => winit::keyboard::KeyCode::F12,
            "ESCAPE" | "ESC" => winit::keyboard::KeyCode::Escape,
            "ENTER" | "RETURN" => winit::keyboard::KeyCode::Enter,
            "SPACE" => winit::keyboard::KeyCode::Space,
            "DELETE" | "DEL" => winit::keyboard::KeyCode::Delete,
            "BACKSPACE" => winit::keyboard::KeyCode::Backspace,
            "TAB" => winit::keyboard::KeyCode::Tab,
            _ => return None,
        };

        Some(Self {
            ctrl,
            shift,
            alt,
            key,
        })
    }
}

#[derive(Clone, Debug)]
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)]
pub enum UiWidgetState {
    Button(UiButtonData),
    Slider(UiSliderData),
    Toggle(UiToggleData),
    Checkbox(UiCheckboxData),
    Radio(UiRadioData),
    ProgressBar(UiProgressBarData),
    CollapsingHeader(UiCollapsingHeaderData),
    ScrollArea(UiScrollAreaData),
    TabBar(UiTabBarData),
    TextInput(UiTextInputData),
    Dropdown(UiDropdownData),
    Menu(UiMenuData),
    Panel(UiPanelData),
    ColorPicker(UiColorPickerData),
    SelectableLabel(UiSelectableLabelData),
    DragValue(UiDragValueData),
    ContextMenu(UiContextMenuData),
    TreeView(UiTreeViewData),
    TreeNode(UiTreeNodeData),
    ModalDialog(UiModalDialogData),
    RichText(UiRichTextData),
    DataGrid(UiDataGridData),
    PropertyGrid(UiPropertyGridData),
    CommandPalette(UiCommandPaletteData),
    Canvas(UiCanvasData),
    TextArea(UiTextAreaData),
    TileContainer(UiTileContainerData),
    VirtualList(UiVirtualListData),
    RichTextEditor(UiRichTextEditorData),
    RangeSlider(UiRangeSliderData),
    Breadcrumb(UiBreadcrumbData),
    Splitter(UiSplitterData),
    MultiSelect(UiMultiSelectData),
    DatePicker(UiDatePickerData),
}

impl Default for UiWidgetState {
    fn default() -> Self {
        Self::Button(UiButtonData {
            clicked: false,
            text_slot: 0,
        })
    }
}

pub trait FromWidgetState {
    fn from_widget_state(state: &UiWidgetState) -> Option<&Self>;
}

macro_rules! impl_from_widget_state {
    ($variant:ident, $data:ty) => {
        impl FromWidgetState for $data {
            fn from_widget_state(state: &UiWidgetState) -> Option<&Self> {
                match state {
                    UiWidgetState::$variant(d) => Some(d),
                    _ => None,
                }
            }
        }
    };
}

impl_from_widget_state!(Button, UiButtonData);
impl_from_widget_state!(Slider, UiSliderData);
impl_from_widget_state!(Toggle, UiToggleData);
impl_from_widget_state!(Checkbox, UiCheckboxData);
impl_from_widget_state!(Radio, UiRadioData);
impl_from_widget_state!(ProgressBar, UiProgressBarData);
impl_from_widget_state!(CollapsingHeader, UiCollapsingHeaderData);
impl_from_widget_state!(ScrollArea, UiScrollAreaData);
impl_from_widget_state!(TabBar, UiTabBarData);
impl_from_widget_state!(TextInput, UiTextInputData);
impl_from_widget_state!(Dropdown, UiDropdownData);
impl_from_widget_state!(Menu, UiMenuData);
impl_from_widget_state!(Panel, UiPanelData);
impl_from_widget_state!(ColorPicker, UiColorPickerData);
impl_from_widget_state!(SelectableLabel, UiSelectableLabelData);
impl_from_widget_state!(DragValue, UiDragValueData);
impl_from_widget_state!(ContextMenu, UiContextMenuData);
impl_from_widget_state!(TreeView, UiTreeViewData);
impl_from_widget_state!(TreeNode, UiTreeNodeData);
impl_from_widget_state!(ModalDialog, UiModalDialogData);
impl_from_widget_state!(RichText, UiRichTextData);
impl_from_widget_state!(DataGrid, UiDataGridData);
impl_from_widget_state!(PropertyGrid, UiPropertyGridData);
impl_from_widget_state!(CommandPalette, UiCommandPaletteData);
impl_from_widget_state!(Canvas, UiCanvasData);
impl_from_widget_state!(TextArea, UiTextAreaData);
impl_from_widget_state!(TileContainer, UiTileContainerData);
impl_from_widget_state!(VirtualList, UiVirtualListData);
impl_from_widget_state!(RichTextEditor, UiRichTextEditorData);
impl_from_widget_state!(RangeSlider, UiRangeSliderData);
impl_from_widget_state!(Breadcrumb, UiBreadcrumbData);
impl_from_widget_state!(Splitter, UiSplitterData);
impl_from_widget_state!(MultiSelect, UiMultiSelectData);
impl_from_widget_state!(DatePicker, UiDatePickerData);

impl UiWidgetState {
    pub fn changed(&self) -> bool {
        match self {
            Self::Slider(d) => d.changed,
            Self::RangeSlider(d) => d.changed,
            Self::Toggle(d) => d.changed,
            Self::Checkbox(d) => d.changed,
            Self::Radio(d) => d.changed,
            Self::TabBar(d) => d.changed,
            Self::Breadcrumb(d) => d.changed,
            Self::Splitter(d) => d.changed,
            Self::TextInput(d) => d.changed,
            Self::Dropdown(d) => d.changed,
            Self::MultiSelect(d) => d.changed,
            Self::DatePicker(d) => d.changed,
            Self::ColorPicker(d) => d.changed,
            Self::SelectableLabel(d) => d.changed,
            Self::DragValue(d) => d.changed,
            Self::TreeView(d) => d.changed,
            Self::TextArea(d) => d.changed,
            Self::RichTextEditor(d) => d.changed,
            _ => false,
        }
    }

    pub fn child_entities(&self) -> Vec<freecs::Entity> {
        match self {
            Self::Button(_) => Vec::new(),
            Self::Slider(data) => vec![data.fill_entity],
            Self::Toggle(data) => vec![data.knob_entity],
            Self::Checkbox(data) => vec![data.inner_entity],
            Self::Radio(data) => vec![data.inner_entity],
            Self::ProgressBar(data) => vec![data.fill_entity],
            Self::CollapsingHeader(data) => vec![data.content_entity],
            Self::ScrollArea(data) => {
                vec![data.content_entity, data.thumb_entity, data.track_entity]
            }
            Self::TabBar(data) => data.tab_entities.clone(),
            Self::TextInput(data) => {
                let mut entities = vec![data.cursor_entity, data.selection_entity];
                if let Some(placeholder) = data.placeholder_entity {
                    entities.push(placeholder);
                }
                entities
            }
            Self::Dropdown(data) => {
                let mut entities = vec![data.popup_container_entity];
                entities.extend(&data.popup_entities);
                entities
            }
            Self::Menu(data) => {
                let mut entities = vec![data.popup_container_entity];
                entities.extend(&data.popup_entities);
                entities
            }
            Self::Panel(data) => {
                let mut entities = vec![data.content_entity, data.header_entity];
                if let Some(collapse_btn) = data.collapse_button_entity {
                    entities.push(collapse_btn);
                }
                entities
            }
            Self::ColorPicker(data) => {
                let mut entities = vec![data.swatch_entity];
                entities.extend(data.slider_entities);
                entities
            }
            Self::SelectableLabel(_) => Vec::new(),
            Self::DragValue(data) => vec![data.cursor_entity, data.selection_entity],
            Self::ContextMenu(data) => {
                let mut entities = vec![data.popup_entity];
                entities.extend(&data.item_entities);
                entities
            }
            Self::TreeView(data) => {
                let mut entities = vec![data.content_entity];
                entities.extend(&data.node_entities);
                entities
            }
            Self::TreeNode(data) => {
                vec![
                    data.row_entity,
                    data.arrow_entity,
                    data.children_container,
                    data.wrapper_entity,
                ]
            }
            Self::ModalDialog(data) => {
                let mut entities = vec![data.content_entity, data.backdrop_entity];
                if let Some(ok) = data.ok_button {
                    entities.push(ok);
                }
                if let Some(cancel) = data.cancel_button {
                    entities.push(cancel);
                }
                entities
            }
            Self::RichText(data) => data.span_entities.clone(),
            Self::DataGrid(data) => {
                let mut entities = vec![
                    data.scroll_entity,
                    data.body_entity,
                    data.top_spacer,
                    data.bottom_spacer,
                ];
                entities.extend(&data.header_entities);
                entities.extend(&data.header_divider_entities);
                entities.extend(&data.filter_input_entities);
                entities.extend(data.filter_row_entity);
                entities.extend(data.editing_input_entity);
                for row in &data.pool_rows {
                    entities.push(row.row_entity);
                    entities.extend(&row.cell_entities);
                }
                entities
            }
            Self::PropertyGrid(data) => {
                let mut entities = Vec::new();
                entities.extend(&data.row_entities);
                entities.extend(&data.label_entities);
                entities
            }
            Self::CommandPalette(data) => {
                let mut entities = vec![
                    data.text_input_entity,
                    data.backdrop_entity,
                    data.dialog_entity,
                    data.scroll_entity,
                ];
                entities.extend(&data.result_entities);
                entities
            }
            Self::Canvas(_) => Vec::new(),
            Self::TextArea(data) => {
                let mut entities = vec![data.cursor_entity];
                entities.extend(&data.selection_pool);
                if let Some(placeholder) = data.placeholder_entity {
                    entities.push(placeholder);
                }
                entities
            }
            Self::TileContainer(data) => data.collect_pane_entities(),
            Self::VirtualList(data) => {
                let mut entities = vec![
                    data.scroll_entity,
                    data.body_entity,
                    data.top_spacer,
                    data.bottom_spacer,
                ];
                for item in &data.pool_items {
                    entities.push(item.container_entity);
                }
                entities
            }
            Self::RichTextEditor(data) => {
                let mut entities = vec![data.cursor_entity];
                entities.extend(&data.selection_pool);
                entities.extend(data.placeholder_entity);
                entities
            }
            Self::RangeSlider(data) => {
                vec![
                    data.fill_entity,
                    data.low_thumb_entity,
                    data.high_thumb_entity,
                ]
            }
            Self::Breadcrumb(data) => data.segment_entities.clone(),
            Self::Splitter(data) => {
                vec![data.first_pane, data.second_pane, data.divider_entity]
            }
            Self::MultiSelect(data) => {
                let mut entities = vec![data.popup_container_entity];
                entities.extend(&data.popup_entities);
                entities.extend(&data.check_entities);
                entities
            }
            Self::DatePicker(data) => {
                let mut entities = vec![
                    data.popup_entity,
                    data.prev_month_entity,
                    data.next_month_entity,
                ];
                entities.extend(&data.day_entities);
                entities
            }
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UiAnimationType {
    Fade,
    SlideLeft,
    SlideRight,
    SlideUp,
    SlideDown,
    Scale,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UiAnimationPhase {
    Idle,
    IntroPlaying,
    OutroPlaying,
    OutroComplete,
}

#[derive(Clone, Copy, Debug)]
pub struct UiNodeAnimation {
    pub intro: Option<UiAnimationType>,
    pub outro: Option<UiAnimationType>,
    pub duration: f32,
    pub progress: f32,
    pub phase: UiAnimationPhase,
}

#[derive(
    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct TileId(pub usize);

#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SplitDirection {
    Horizontal,
    Vertical,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DropZone {
    Left,
    Right,
    Top,
    Bottom,
    Center,
}

#[derive(Clone, Debug)]
pub struct DropPreview {
    pub target_tile: TileId,
    pub zone: DropZone,
}

#[derive(Clone, Debug)]
pub enum TileNode {
    Pane {
        content_entity: freecs::Entity,
        title: String,
    },
    Split {
        direction: SplitDirection,
        ratio: f32,
        children: [TileId; 2],
    },
    Tabs {
        panes: Vec<TileId>,
        active: usize,
    },
}

#[derive(Clone, Debug)]
pub struct UiTileContainerData {
    pub tiles: Vec<Option<TileNode>>,
    pub root: TileId,
    pub rects: Vec<Rect>,
    pub dragging_splitter: Option<(TileId, f32)>,
    pub pending_tab_drag: Option<(TileId, usize, Vec2)>,
    pub dragging_tab: Option<(TileId, usize, Vec2)>,
    pub drop_preview: Option<DropPreview>,
    pub container_entity: freecs::Entity,
    pub splitter_width: f32,
    pub tab_bar_height: f32,
    pub next_free: Vec<usize>,
    pub hovered_close: Option<(TileId, usize)>,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum TileLayoutNode {
    Pane {
        title: String,
    },
    Split {
        direction: SplitDirection,
        ratio: f32,
        children: [TileId; 2],
    },
    Tabs {
        panes: Vec<TileId>,
        active: usize,
    },
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TileLayout {
    pub nodes: Vec<Option<TileLayoutNode>>,
    pub root: TileId,
}

impl UiTileContainerData {
    pub fn tab_close_width(&self) -> f32 {
        (self.tab_bar_height * 0.75).round()
    }

    pub(crate) fn alloc(&mut self, node: TileNode) -> TileId {
        if let Some(index) = self.next_free.pop() {
            self.tiles[index] = Some(node);
            if index >= self.rects.len() {
                self.rects.resize(self.tiles.len(), Rect::default());
            }
            TileId(index)
        } else {
            let index = self.tiles.len();
            self.tiles.push(Some(node));
            self.rects.push(Rect::default());
            TileId(index)
        }
    }

    pub(crate) fn free(&mut self, tile_id: TileId) {
        self.tiles[tile_id.0] = None;
        self.next_free.push(tile_id.0);
    }

    pub fn get(&self, tile_id: TileId) -> Option<&TileNode> {
        self.tiles.get(tile_id.0).and_then(|t| t.as_ref())
    }

    pub fn get_mut(&mut self, tile_id: TileId) -> Option<&mut TileNode> {
        self.tiles.get_mut(tile_id.0).and_then(|t| t.as_mut())
    }

    fn collect_pane_entities(&self) -> Vec<freecs::Entity> {
        let mut entities = Vec::new();
        for tile in &self.tiles {
            if let Some(TileNode::Pane { content_entity, .. }) = tile {
                entities.push(*content_entity);
            }
        }
        entities
    }

    pub fn find_parent_tabs(&self, pane_id: TileId) -> Option<TileId> {
        for (index, tile) in self.tiles.iter().enumerate() {
            if let Some(TileNode::Tabs { panes, .. }) = tile
                && panes.contains(&pane_id)
            {
                return Some(TileId(index));
            }
        }
        None
    }

    pub(crate) fn find_parent_split(&self, child_id: TileId) -> Option<(TileId, usize)> {
        for (index, tile) in self.tiles.iter().enumerate() {
            if let Some(TileNode::Split { children, .. }) = tile {
                if children[0] == child_id {
                    return Some((TileId(index), 0));
                }
                if children[1] == child_id {
                    return Some((TileId(index), 1));
                }
            }
        }
        None
    }
}

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

    #[test]
    fn text_span_new_defaults() {
        let span = TextSpan::new("hello");
        assert_eq!(span.text, "hello");
        assert!(span.color.is_none());
        assert!(span.font_size_override.is_none());
        assert!(!span.bold);
        assert!(!span.italic);
        assert!(!span.underline);
        assert!(span.font_index.is_none());
    }

    #[test]
    fn text_span_colored() {
        let color = Vec4::new(1.0, 0.0, 0.0, 1.0);
        let span = TextSpan::colored("red", color);
        assert_eq!(span.text, "red");
        assert_eq!(span.color, Some(color));
    }

    #[test]
    fn text_span_builder_chain() {
        let span = TextSpan::new("styled")
            .with_bold()
            .with_italic()
            .with_underline()
            .with_font(2)
            .with_color(Vec4::new(0.0, 1.0, 0.0, 1.0))
            .with_size(24.0);
        assert!(span.bold);
        assert!(span.italic);
        assert!(span.underline);
        assert_eq!(span.font_index, Some(2));
        assert_eq!(span.color, Some(Vec4::new(0.0, 1.0, 0.0, 1.0)));
        assert_eq!(span.font_size_override, Some(24.0));
    }

    #[test]
    fn data_grid_column_defaults() {
        let col = DataGridColumn::new("Name", 100.0);
        assert_eq!(col.label, "Name");
        assert_eq!(col.width, 100.0);
        assert!(!col.sortable);
    }

    #[test]
    fn data_grid_column_sortable() {
        let col = DataGridColumn::new("Name", 100.0).sortable();
        assert!(col.sortable);
    }

    #[test]
    fn data_grid_column_non_sortable() {
        let col = DataGridColumn {
            label: "Actions".to_string(),
            width: 80.0,
            sortable: false,
            alignment: TextAlignment::Left,
            editable: false,
        };
        assert!(!col.sortable);
    }

    #[test]
    fn context_menu_item_separator() {
        let item = ContextMenuItem {
            label: String::new(),
            shortcut: None,
            separator: true,
        };
        assert!(item.separator);
        assert!(item.label.is_empty());
        assert!(item.shortcut.is_none());
    }

    #[test]
    fn selected_rows_hashset_operations() {
        let mut selected: HashSet<usize> = HashSet::new();
        selected.insert(5);
        selected.insert(10);
        selected.insert(5);
        assert_eq!(selected.len(), 2);
        assert!(selected.contains(&5));
        assert!(selected.contains(&10));
        selected.remove(&5);
        assert_eq!(selected.len(), 1);
        assert!(!selected.contains(&5));
    }
}
