use std::collections::HashMap;

use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::builder::UiTreeBuilder;
use crate::ecs::ui::components::{
    AccessibleRole, CanvasCommand, CharStyle, CommandEntry, ContextMenuItem, ContextMenuItemDef,
    ContextMenuItemKind, DataGridColumn, DataGridPoolRow, DragValueConfig, ShortcutBinding,
    TextSpan, ThemeColor, UiButtonData, UiCanvasData, UiCheckboxData, UiCollapsingHeaderData,
    UiColorPickerData, UiCommandPaletteData, UiContextMenuData, UiDataGridData, UiDatePickerData,
    UiDepthMode, UiDragValueData, UiDropdownData, UiMenuData, UiModalDialogData, UiMultiSelectData,
    UiNodeInteraction, UiPanelData, UiPanelKind, UiProgressBarData, UiPropertyGridData,
    UiRadioData, UiRangeSliderData, UiRichTextData, UiRichTextEditorData, UiScrollAreaData,
    UiSelectableLabelData, UiSliderData, UiTabBarData, UiTextAreaData, UiTextInputData,
    UiTileContainerData, UiToggleData, UiTreeNodeData, UiTreeViewData, UiWidgetState, UndoStack,
};
use crate::ecs::ui::components::{SplitDirection, TileId, TileNode};
use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection};
use crate::ecs::ui::state::{UiBase, UiFocused, UiHover, UiPressed, UiStateTrait};
use crate::ecs::ui::types::{Anchor, Rect};
use crate::ecs::ui::units::{Ab, Rl};
use crate::render::wgpu::passes::geometry::UiLayer;
use std::collections::HashSet;

struct ContextMenuTheme {
    font_size: f32,
    corner_radius: f32,
    button_height: f32,
}

enum ContextMenuBuilderEntry {
    Item {
        label: String,
        shortcut: String,
    },
    Separator,
    Submenu {
        label: String,
        children: Vec<ContextMenuBuilderEntry>,
    },
    WidgetRow {
        label: String,
    },
}

pub struct ContextMenuBuilder {
    entries: Vec<ContextMenuBuilderEntry>,
}

impl ContextMenuBuilder {
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

    pub fn item(mut self, label: &str, shortcut: &str) -> Self {
        self.entries.push(ContextMenuBuilderEntry::Item {
            label: label.to_string(),
            shortcut: shortcut.to_string(),
        });
        self
    }

    pub fn separator(mut self) -> Self {
        self.entries.push(ContextMenuBuilderEntry::Separator);
        self
    }

    pub fn submenu(
        mut self,
        label: &str,
        f: impl FnOnce(ContextMenuBuilder) -> ContextMenuBuilder,
    ) -> Self {
        let sub = f(ContextMenuBuilder::new());
        self.entries.push(ContextMenuBuilderEntry::Submenu {
            label: label.to_string(),
            children: sub.entries,
        });
        self
    }

    pub fn widget_row(mut self, label: &str) -> Self {
        self.entries.push(ContextMenuBuilderEntry::WidgetRow {
            label: label.to_string(),
        });
        self
    }
}

impl Default for ContextMenuBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> UiTreeBuilder<'a> {
    pub fn add_label(&mut self, text: &str) -> freecs::Entity {
        let font_size = self.active_theme().font_size;
        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done()
    }

    pub fn add_label_with_slot(&mut self, slot: usize) -> freecs::Entity {
        let font_size = self.active_theme().font_size;

        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
            .with_text_slot(slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done()
    }

    pub fn add_label_wrapped(&mut self, text: &str) -> freecs::Entity {
        let font_size = self.active_theme().font_size;
        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)))
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
            .with_text_wrap()
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done()
    }

    pub fn add_dynamic_label(&mut self, text: &str) -> (freecs::Entity, usize) {
        let slot = self.world_mut().resources.text_cache.add_text(text);
        let entity = self.add_label_with_slot(slot);
        (entity, slot)
    }

    pub fn add_label_colored(&mut self, text: &str, color: Vec4) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_color::<UiBase>(color)
            .without_pointer_events()
            .done()
    }

    pub fn add_heading(&mut self, text: &str) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size * 1.4;

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::TextAccent)
            .without_pointer_events()
            .done()
    }

    pub fn add_separator(&mut self) -> freecs::Entity {
        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
            .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::Border)
            .without_pointer_events()
            .done()
    }

    pub fn add_spacing(&mut self, amount: f32) -> freecs::Entity {
        self.add_node()
            .flow_child(Ab(Vec2::new(amount, amount)))
            .without_pointer_events()
            .done()
    }

    pub fn add_spring(&mut self) -> freecs::Entity {
        self.add_node()
            .flow_child(Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .without_pointer_events()
            .done()
    }

    /// Creates a horizontal flow container (row). Children are laid out
    /// left-to-right with 4 px spacing. Returns the row entity which can
    /// be used as a parent for nested children.
    ///
    /// ```ignore
    /// let row = tree.add_row();
    /// tree.push_parent(row);
    /// tree.add_label("Left");
    /// tree.add_label("Right");
    /// tree.pop_parent();
    /// ```
    pub fn add_row(&mut self) -> freecs::Entity {
        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)))
            .flow(FlowDirection::Horizontal, 0.0, 4.0)
            .without_pointer_events()
            .entity()
    }

    /// Creates a vertical flow container (column). Children are laid out
    /// top-to-bottom with 4 px spacing. Returns the column entity.
    ///
    /// ```ignore
    /// let col = tree.add_column();
    /// tree.push_parent(col);
    /// tree.add_toggle(true);
    /// tree.add_checkbox("Enable", false);
    /// tree.pop_parent();
    /// ```
    pub fn add_column(&mut self) -> freecs::Entity {
        self.add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)))
            .flow(FlowDirection::Vertical, 0.0, 4.0)
            .without_pointer_events()
            .entity()
    }

    pub fn add_button(&mut self, text: &str) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let button_height = theme.button_height;
        let corner_radius = theme.corner_radius;
        let border_width = theme.border_width;
        let border_color = theme.border_color;

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        let button_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, border_width, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .with_text_slot(text_slot, font_size)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(button_entity) {
            interaction.accessible_role = Some(AccessibleRole::Button);
        }
        self.assign_tab_index(button_entity);

        button_entity
    }

    pub fn add_button_colored(&mut self, text: &str, color: Vec4) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let button_height = theme.button_height;
        let corner_radius = theme.corner_radius;
        let border_width = theme.border_width;
        let border_color = theme.border_color;

        let hover_color = Vec4::new(
            (color.x + 0.1).min(1.0),
            (color.y + 0.1).min(1.0),
            (color.z + 0.1).min(1.0),
            color.w,
        );
        let active_color = Vec4::new(
            (color.x - 0.1).max(0.0),
            (color.y - 0.1).max(0.0),
            (color.z - 0.1).max(0.0),
            color.w,
        );

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        let button_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, border_width, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_color::<UiBase>(color)
            .with_color::<UiHover>(hover_color)
            .with_color::<UiPressed>(active_color)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .with_text_slot(text_slot, font_size)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot,
            }),
        );
        self.assign_tab_index(button_entity);

        button_entity
    }

    pub fn add_slider(&mut self, min: f32, max: f32, initial: f32) -> freecs::Entity {
        self.add_slider_configured(crate::ecs::ui::components::SliderConfig::new(
            min, max, initial,
        ))
    }

    pub fn add_slider_logarithmic(&mut self, min: f32, max: f32, initial: f32) -> freecs::Entity {
        self.add_slider_configured(
            crate::ecs::ui::components::SliderConfig::new(min, max, initial).logarithmic(),
        )
    }

    pub fn add_slider_configured(
        &mut self,
        config: crate::ecs::ui::components::SliderConfig<'_>,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let slider_height = theme.slider_height;
        let corner_radius = theme.corner_radius;
        let font_size = theme.font_size;

        let normalized = if config.logarithmic && config.min > 0.0 && config.max > config.min {
            ((config.initial / config.min).ln() / (config.max / config.min).ln()).clamp(0.0, 1.0)
        } else {
            ((config.initial - config.min) / (config.max - config.min)).clamp(0.0, 1.0)
        };
        let text_slot = self.world_mut().resources.text_cache.add_text(format!(
            "{}{:.prec$}{}",
            config.prefix,
            config.initial,
            config.suffix,
            prec = config.precision
        ));

        let mut fill_entity = freecs::Entity::default();

        let track_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, slider_height)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                fill_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Rl(Vec2::new(normalized * 100.0, 100.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::SliderFill)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .with_text_slot(text_slot, font_size * 0.85)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            track_entity,
            UiWidgetState::Slider(UiSliderData {
                min: config.min,
                max: config.max,
                value: config.initial,
                changed: false,
                fill_entity,
                text_slot,
                logarithmic: config.logarithmic,
                precision: config.precision,
                prefix: config.prefix.to_string(),
                suffix: config.suffix.to_string(),
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(track_entity) {
            interaction.accessible_role = Some(AccessibleRole::Slider);
        }
        self.assign_tab_index(track_entity);

        track_entity
    }

    pub fn add_range_slider(
        &mut self,
        min: f32,
        max: f32,
        initial_low: f32,
        initial_high: f32,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let slider_height = theme.slider_height;
        let corner_radius = theme.corner_radius;
        let font_size = theme.font_size;
        let thumb_size = slider_height;

        let range = max - min;
        let low_normalized = if range.abs() > f32::EPSILON {
            ((initial_low - min) / range).clamp(0.0, 1.0)
        } else {
            0.0
        };
        let high_normalized = if range.abs() > f32::EPSILON {
            ((initial_high - min) / range).clamp(0.0, 1.0)
        } else {
            1.0
        };

        let text_slot = self
            .world_mut()
            .resources
            .text_cache
            .add_text(format!("{:.1} - {:.1}", initial_low, initial_high));

        let mut fill_entity = freecs::Entity::default();
        let mut low_thumb_entity = freecs::Entity::default();
        let mut high_thumb_entity = freecs::Entity::default();

        let track_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, slider_height)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                fill_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(low_normalized * 100.0, 0.0)),
                        Rl(Vec2::new(high_normalized * 100.0, 100.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::SliderFill)
                    .without_pointer_events()
                    .done();

                low_thumb_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(low_normalized * 100.0, 0.0))
                            + Ab(Vec2::new(-thumb_size * 0.5, 0.0)),
                        Rl(Vec2::new(low_normalized * 100.0, 100.0))
                            + Ab(Vec2::new(thumb_size * 0.5, 0.0)),
                    )
                    .with_rect(thumb_size * 0.5, 1.0, Vec4::new(1.0, 1.0, 1.0, 0.3))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                high_thumb_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(high_normalized * 100.0, 0.0))
                            + Ab(Vec2::new(-thumb_size * 0.5, 0.0)),
                        Rl(Vec2::new(high_normalized * 100.0, 100.0))
                            + Ab(Vec2::new(thumb_size * 0.5, 0.0)),
                    )
                    .with_rect(thumb_size * 0.5, 1.0, Vec4::new(1.0, 1.0, 1.0, 0.3))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .with_text_slot(text_slot, font_size * 0.85)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            track_entity,
            UiWidgetState::RangeSlider(UiRangeSliderData {
                min,
                max,
                low_value: initial_low,
                high_value: initial_high,
                changed: false,
                fill_entity,
                low_thumb_entity,
                high_thumb_entity,
                text_slot,
                active_thumb: None,
                precision: 1,
                thumb_half_size: thumb_size * 0.5,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(track_entity) {
            interaction.accessible_role = Some(AccessibleRole::Slider);
        }
        self.assign_tab_index(track_entity);

        track_entity
    }

    pub fn add_canvas(&mut self, size: Vec2) -> freecs::Entity {
        let canvas_entity = self
            .add_node()
            .flow_child(Ab(size))
            .with_clip()
            .without_pointer_events()
            .done();

        self.world_mut().set_ui_widget_state(
            canvas_entity,
            UiWidgetState::Canvas(UiCanvasData {
                commands: Vec::new(),
                hit_test_enabled: false,
                command_bounds: Vec::new(),
            }),
        );

        canvas_entity
    }

    pub fn add_canvas_interactive(&mut self, size: Vec2) -> freecs::Entity {
        let canvas_entity = self
            .add_node()
            .flow_child(Ab(size))
            .with_clip()
            .with_interaction()
            .done();

        self.world_mut().set_ui_widget_state(
            canvas_entity,
            UiWidgetState::Canvas(UiCanvasData {
                commands: Vec::new(),
                hit_test_enabled: true,
                command_bounds: Vec::new(),
            }),
        );

        canvas_entity
    }

    pub fn add_toggle(&mut self, initial: bool) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let bg_color = if initial {
            theme.accent_color
        } else {
            theme.background_color
        };
        let toggle_width = theme.toggle_width;
        let toggle_height = theme.toggle_height;
        let knob_padding = 2.0;
        let knob_size = toggle_height - knob_padding * 2.0;
        let knob_travel = toggle_width - knob_size - knob_padding * 2.0;

        let initial_x = if initial {
            knob_padding + knob_travel
        } else {
            knob_padding
        };
        let mut knob_entity = freecs::Entity::default();

        let toggle_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(toggle_width, toggle_height)))
            .with_rect(toggle_height / 2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(bg_color)
            .with_interaction()
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                knob_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(initial_x, knob_padding)),
                        Ab(Vec2::new(knob_size, knob_size)),
                        Anchor::TopLeft,
                    )
                    .with_rect(knob_size / 2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            toggle_entity,
            UiWidgetState::Toggle(UiToggleData {
                value: initial,
                changed: false,
                animated_position: if initial { 1.0 } else { 0.0 },
                knob_entity,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(toggle_entity) {
            interaction.accessible_role = Some(AccessibleRole::Toggle);
        }
        self.assign_tab_index(toggle_entity);

        toggle_entity
    }

    pub fn add_checkbox(&mut self, label: &str, initial: bool) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;

        let text_slot = self.world_mut().resources.text_cache.add_text(label);
        let mut inner_entity = freecs::Entity::default();

        let row_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.8)))
            .flow(FlowDirection::Horizontal, 0.0, 8.0)
            .entity();

        self.push_parent(row_entity);

        let box_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(20.0, 20.0)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                inner_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(3.0, 3.0)),
                        Ab(Vec2::new(-3.0, -3.0)) + Rl(Vec2::new(100.0, 100.0)),
                    )
                    .with_rect(corner_radius * 0.5, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Accent)
                    .with_visible(initial)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.add_node()
            .flow_child(Ab(Vec2::new(0.0, 20.0)))
            .flex_grow(1.0)
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done();

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            box_entity,
            UiWidgetState::Checkbox(UiCheckboxData {
                value: initial,
                changed: false,
                inner_entity,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(box_entity) {
            interaction.accessible_role = Some(AccessibleRole::Checkbox);
        }
        self.assign_tab_index(box_entity);

        box_entity
    }

    pub fn add_radio(&mut self, label: &str, group_id: u32, option_index: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let border_color = theme.border_color;

        let text_slot = self.world_mut().resources.text_cache.add_text(label);
        let mut inner_entity = freecs::Entity::default();
        let selected = option_index == 0;

        let row_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.8)))
            .flow(FlowDirection::Horizontal, 0.0, 8.0)
            .entity();

        self.push_parent(row_entity);

        let circle_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(20.0, 20.0)))
            .with_rect(10.0, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                inner_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(5.0, 5.0)),
                        Ab(Vec2::new(-5.0, -5.0)) + Rl(Vec2::new(100.0, 100.0)),
                    )
                    .with_rect(5.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Accent)
                    .with_visible(selected)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.add_node()
            .flow_child(Ab(Vec2::new(0.0, 20.0)))
            .flex_grow(1.0)
            .with_text_slot(text_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done();

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            circle_entity,
            UiWidgetState::Radio(UiRadioData {
                group_id,
                option_index,
                selected,
                changed: false,
                inner_entity,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(circle_entity) {
            interaction.accessible_role = Some(AccessibleRole::Button);
        }
        self.world_mut()
            .resources
            .retained_ui
            .radio_groups
            .entry(group_id)
            .or_default()
            .push(circle_entity);
        self.assign_tab_index(circle_entity);

        circle_entity
    }

    pub fn add_progress_bar(&mut self, initial: f32) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let slider_height = theme.slider_height;
        let corner_radius = theme.corner_radius;

        let normalized = initial.clamp(0.0, 1.0);
        let mut fill_entity = freecs::Entity::default();

        let bar_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, slider_height)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .without_pointer_events()
            .with_children(|tree| {
                fill_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Rl(Vec2::new(normalized * 100.0, 100.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Accent)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            bar_entity,
            UiWidgetState::ProgressBar(UiProgressBarData {
                value: initial,
                fill_entity,
            }),
        );
        self.world_mut().set_ui_node_interaction(
            bar_entity,
            UiNodeInteraction {
                accessible_role: Some(AccessibleRole::ProgressBar),
                ..UiNodeInteraction::default()
            },
        );

        bar_entity
    }

    pub fn add_collapsing_header(&mut self, label: &str, default_open: bool) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;

        let arrow_text = if default_open { "\u{25BC}" } else { "\u{25B6}" };
        let arrow_slot = self.world_mut().resources.text_cache.add_text(arrow_text);
        let label_slot = self.world_mut().resources.text_cache.add_text(label);

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(0.0)
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .entity();

        self.push_parent(root_entity);

        let header_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 2.0)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 8.0, 4.0)
            .with_children(|tree| {
                tree.add_node()
                    .flow_child(Ab(Vec2::new(16.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                    .with_text_slot(arrow_slot, font_size)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                    .flex_grow(1.0)
                    .with_text_slot(label_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        let content_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(0.0)
            .flow(FlowDirection::Vertical, 8.0, 4.0)
            .with_visible(default_open)
            .entity();

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            header_entity,
            UiWidgetState::CollapsingHeader(UiCollapsingHeaderData {
                open: default_open,
                changed: false,
                content_entity,
                arrow_text_slot: arrow_slot,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(header_entity) {
            interaction.accessible_role = Some(AccessibleRole::Button);
        }

        header_entity
    }

    pub fn add_scroll_area(&mut self, size: Vec2) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let corner_radius = theme.corner_radius;

        let mut content_entity = freecs::Entity::default();
        let mut thumb_entity = freecs::Entity::default();
        let mut track_entity = freecs::Entity::default();

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, size.y)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_clip()
            .with_interaction()
            .with_children(|tree| {
                content_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(-12.0, 0.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .flow(FlowDirection::Vertical, 4.0, 4.0)
                    .without_pointer_events()
                    .entity();

                track_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(-10.0, 2.0)) + Rl(Vec2::new(100.0, 0.0)),
                        Ab(Vec2::new(-2.0, -2.0)) + Rl(Vec2::new(100.0, 100.0)),
                    )
                    .with_rect(4.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.2))
                    .with_visible(false)
                    .without_pointer_events()
                    .entity();

                tree.push_parent(track_entity);

                thumb_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(8.0, 30.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(4.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Scrollbar)
                    .with_theme_color::<UiHover>(ThemeColor::ScrollbarHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .done();

                tree.pop_parent();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            root_entity,
            UiWidgetState::ScrollArea(UiScrollAreaData {
                scroll_offset: 0.0,
                content_entity,
                content_height: 0.0,
                visible_height: size.y,
                thumb_entity,
                track_entity,
                thumb_dragging: false,
                thumb_drag_start_offset: 0.0,
                snap_interval: None,
            }),
        );

        root_entity
    }

    pub fn add_scroll_area_fill(&mut self, padding: f32, spacing: f32) -> freecs::Entity {
        let mut content_entity = freecs::Entity::default();
        let mut thumb_entity = freecs::Entity::default();
        let mut track_entity = freecs::Entity::default();

        let root_entity = self
            .add_node()
            .boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_clip()
            .with_interaction()
            .with_children(|tree| {
                content_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(-12.0, 0.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .flow(FlowDirection::Vertical, padding, spacing)
                    .without_pointer_events()
                    .entity();

                track_entity = tree
                    .add_node()
                    .boundary(
                        Ab(Vec2::new(-10.0, 2.0)) + Rl(Vec2::new(100.0, 0.0)),
                        Ab(Vec2::new(-2.0, -2.0)) + Rl(Vec2::new(100.0, 100.0)),
                    )
                    .with_rect(4.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.2))
                    .with_visible(false)
                    .without_pointer_events()
                    .entity();

                tree.push_parent(track_entity);

                thumb_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(8.0, 30.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(4.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Scrollbar)
                    .with_theme_color::<UiHover>(ThemeColor::ScrollbarHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .done();

                tree.pop_parent();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            root_entity,
            UiWidgetState::ScrollArea(UiScrollAreaData {
                scroll_offset: 0.0,
                content_entity,
                content_height: 0.0,
                visible_height: 0.0,
                thumb_entity,
                track_entity,
                thumb_dragging: false,
                thumb_drag_start_offset: 0.0,
                snap_interval: None,
            }),
        );

        root_entity
    }

    pub fn add_virtual_list(&mut self, item_height: f32, pool_size: usize) -> freecs::Entity {
        use crate::ecs::ui::components::{UiVirtualListData, VirtualListPoolItem};

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .entity();

        self.push_parent(root_entity);

        let scroll_entity = self.add_scroll_area_fill(0.0, 0.0);
        let body_entity = self
            .world_mut()
            .widget::<UiScrollAreaData>(scroll_entity)
            .map(|d| d.content_entity)
            .unwrap();

        self.push_parent(body_entity);

        let top_spacer = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .entity();

        let mut pool_items = Vec::with_capacity(pool_size);
        for _ in 0..pool_size {
            let container = self
                .add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, item_height)))
                .with_interaction()
                .with_visible(false)
                .entity();
            pool_items.push(VirtualListPoolItem {
                container_entity: container,
            });
        }

        let bottom_spacer = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .entity();

        self.pop_parent();
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            root_entity,
            UiWidgetState::VirtualList(UiVirtualListData {
                item_height,
                pool_size,
                total_items: 0,
                visible_start: 0,
                scroll_entity,
                body_entity,
                top_spacer,
                bottom_spacer,
                pool_items,
                selection: None,
                selection_changed: false,
            }),
        );

        root_entity
    }

    pub fn add_tab_bar(&mut self, labels: &[&str], initial: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let active_bg = theme.accent_color;
        let inactive_bg = theme.background_color;
        let corner_radius = theme.corner_radius;
        let tab_height = theme.button_height;

        let mut tab_entities = Vec::new();
        let mut tab_text_slots = Vec::new();

        let bar_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, tab_height)))
            .flow(FlowDirection::Horizontal, 0.0, 2.0)
            .entity();

        self.push_parent(bar_entity);

        for (index, label) in labels.iter().enumerate() {
            let is_active = index == initial;
            let bg = if is_active { active_bg } else { inactive_bg };
            let text_slot = self.world_mut().resources.text_cache.add_text(*label);
            tab_text_slots.push(text_slot);

            let tab_entity = self
                .add_node()
                .flow_child(Ab(Vec2::new(0.0, tab_height)))
                .flex_grow(1.0)
                .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                .with_color::<UiBase>(bg)
                .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                .with_interaction()
                .with_transition::<UiHover>(8.0, 6.0)
                .with_cursor_icon(winit::window::CursorIcon::Pointer)
                .with_children(|tree| {
                    tree.add_node()
                        .with_text_slot(text_slot, font_size)
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();
                })
                .done();

            if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(tab_entity) {
                interaction.accessible_role = Some(AccessibleRole::Tab);
            }

            tab_entities.push(tab_entity);
        }

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            bar_entity,
            UiWidgetState::TabBar(UiTabBarData {
                selected_tab: initial,
                changed: false,
                tab_entities,
                tab_text_slots,
            }),
        );

        bar_entity
    }

    pub fn add_breadcrumb(&mut self, segments: &[&str]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let text_color = theme.text_color;
        let accent_color = theme.accent_color;
        let separator_color = Vec4::new(
            text_color.x * 0.5,
            text_color.y * 0.5,
            text_color.z * 0.5,
            text_color.w,
        );

        let mut segment_entities = Vec::new();
        let mut segment_text_slots = Vec::new();

        let bar_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 28.0)))
            .flow(FlowDirection::Horizontal, 0.0, 2.0)
            .entity();

        self.push_parent(bar_entity);

        for (index, segment) in segments.iter().enumerate() {
            if index > 0 {
                let separator_slot = self.world_mut().resources.text_cache.add_text("\u{203A}");
                self.add_node()
                    .with_text_slot(separator_slot, font_size)
                    .with_color::<UiBase>(separator_color)
                    .flow_child(Ab(Vec2::new(0.0, 28.0)))
                    .without_pointer_events()
                    .done();
            }

            let text_slot = self.world_mut().resources.text_cache.add_text(*segment);
            segment_text_slots.push(text_slot);

            let is_last = index == segments.len() - 1;
            let color = if is_last { text_color } else { accent_color };

            let segment_entity = self
                .add_node()
                .flow_child(Ab(Vec2::new(0.0, 28.0)))
                .with_children(|tree| {
                    tree.add_node()
                        .with_text_slot(text_slot, font_size)
                        .with_color::<UiBase>(color)
                        .without_pointer_events()
                        .done();
                });

            let segment_entity = if is_last {
                segment_entity.done()
            } else {
                segment_entity
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .done()
            };

            segment_entities.push(segment_entity);
        }

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            bar_entity,
            UiWidgetState::Breadcrumb(crate::ecs::ui::components::UiBreadcrumbData {
                segments: segments.iter().map(|s| s.to_string()).collect(),
                clicked_segment: None,
                changed: false,
                segment_entities,
                segment_text_slots,
            }),
        );

        bar_entity
    }

    pub fn add_splitter(
        &mut self,
        direction: crate::ecs::ui::components::SplitDirection,
        initial_ratio: f32,
    ) -> freecs::Entity {
        use crate::ecs::ui::components::SplitDirection;

        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let divider_color = theme.border_color;
        let divider_hover_color = theme.accent_color;
        let divider_thickness = 4.0;
        let ratio = initial_ratio.clamp(0.05, 0.95);

        let container_entity = self
            .add_node()
            .boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .entity();

        self.push_parent(container_entity);

        let (p1_1, p1_2, d1, d2, p2_1, p2_2) = match direction {
            SplitDirection::Horizontal => (
                Rl(Vec2::new(0.0, 0.0)),
                Rl(Vec2::new(ratio * 100.0, 100.0)) + Ab(Vec2::new(-divider_thickness * 0.5, 0.0)),
                Rl(Vec2::new(ratio * 100.0, 0.0)) + Ab(Vec2::new(-divider_thickness * 0.5, 0.0)),
                Rl(Vec2::new(ratio * 100.0, 100.0)) + Ab(Vec2::new(divider_thickness * 0.5, 0.0)),
                Rl(Vec2::new(ratio * 100.0, 0.0)) + Ab(Vec2::new(divider_thickness * 0.5, 0.0)),
                Rl(Vec2::new(100.0, 100.0)),
            ),
            SplitDirection::Vertical => (
                Rl(Vec2::new(0.0, 0.0)),
                Rl(Vec2::new(100.0, ratio * 100.0)) + Ab(Vec2::new(0.0, -divider_thickness * 0.5)),
                Rl(Vec2::new(0.0, ratio * 100.0)) + Ab(Vec2::new(0.0, -divider_thickness * 0.5)),
                Rl(Vec2::new(100.0, ratio * 100.0)) + Ab(Vec2::new(0.0, divider_thickness * 0.5)),
                Rl(Vec2::new(0.0, ratio * 100.0)) + Ab(Vec2::new(0.0, divider_thickness * 0.5)),
                Rl(Vec2::new(100.0, 100.0)),
            ),
        };

        let first_pane = self.add_node().boundary(p1_1, p1_2).with_clip().entity();

        let cursor_icon = match direction {
            SplitDirection::Horizontal => winit::window::CursorIcon::ColResize,
            SplitDirection::Vertical => winit::window::CursorIcon::RowResize,
        };

        let divider_entity = self
            .add_node()
            .boundary(d1, d2)
            .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(divider_color)
            .with_color::<UiHover>(divider_hover_color)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(cursor_icon)
            .done();

        let second_pane = self.add_node().boundary(p2_1, p2_2).with_clip().entity();

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            container_entity,
            UiWidgetState::Splitter(crate::ecs::ui::components::UiSplitterData {
                direction,
                ratio,
                changed: false,
                first_pane,
                second_pane,
                divider_entity,
                min_ratio: 0.05,
                max_ratio: 0.95,
            }),
        );

        container_entity
    }

    pub fn add_text_input(&mut self, placeholder: &str) -> freecs::Entity {
        self.add_text_input_inner(placeholder, "")
    }

    pub fn add_text_input_with_value(
        &mut self,
        placeholder: &str,
        initial_text: &str,
    ) -> freecs::Entity {
        self.add_text_input_inner(placeholder, initial_text)
    }

    pub fn add_text_input_max_length(
        &mut self,
        placeholder: &str,
        max_length: usize,
    ) -> freecs::Entity {
        let entity = self.add_text_input_inner(placeholder, "");
        if let Some(UiWidgetState::TextInput(data)) =
            self.world_mut().get_ui_widget_state_mut(entity)
        {
            data.max_length = Some(max_length);
        }
        entity
    }

    fn add_text_input_inner(&mut self, placeholder: &str, initial_text: &str) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let accent_color = theme.accent_color;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let input_height = theme.button_height;

        let text_slot = self.world_mut().resources.text_cache.add_text(initial_text);
        let placeholder_text = placeholder.to_string();
        let has_placeholder = !placeholder_text.is_empty();
        let mut cursor_entity = freecs::Entity::default();
        let mut selection_entity = freecs::Entity::default();
        let mut placeholder_entity_out = None;

        let input_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, input_height)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiFocused>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Text)
            .with_clip()
            .with_children(|tree| {
                selection_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 4.0)),
                        Ab(Vec2::new(0.0, input_height - 8.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_color::<UiBase>(Vec4::new(
                        accent_color.x,
                        accent_color.y,
                        accent_color.z,
                        0.3,
                    ))
                    .with_visible(false)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .window(
                        Ab(Vec2::new(8.0, 0.0)),
                        Ab(Vec2::new(0.0, input_height)) + Rl(Vec2::new(100.0, 0.0)),
                        Anchor::TopLeft,
                    )
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                if has_placeholder {
                    let placeholder_slot = tree
                        .world_mut()
                        .resources
                        .text_cache
                        .add_text(&placeholder_text);
                    placeholder_entity_out = Some(
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 0.0)),
                                Ab(Vec2::new(0.0, input_height)) + Rl(Vec2::new(100.0, 0.0)),
                                Anchor::TopLeft,
                            )
                            .with_text_slot(placeholder_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                            .without_pointer_events()
                            .done(),
                    );
                }

                cursor_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 4.0)),
                        Ab(Vec2::new(2.0, input_height - 8.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .with_visible(false)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            input_entity,
            UiWidgetState::TextInput(UiTextInputData {
                text: initial_text.to_string(),
                cursor_position: initial_text.chars().count(),
                selection_start: None,
                changed: false,
                text_slot,
                cursor_entity,
                selection_entity,
                scroll_offset: 0.0,
                cursor_blink_timer: 0.0,
                placeholder_entity: placeholder_entity_out,
                undo_stack: UndoStack::new(100),
                input_mask: crate::ecs::ui::components::InputMask::None,
                max_length: None,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(input_entity) {
            interaction.accessible_role = Some(AccessibleRole::TextInput);
        }
        if !initial_text.is_empty()
            && let Some(ph_entity) = placeholder_entity_out
            && let Some(node) = self.world_mut().get_ui_layout_node_mut(ph_entity)
        {
            node.visible = false;
        }
        self.assign_tab_index(input_entity);

        input_entity
    }

    pub fn add_text_area(&mut self, placeholder: &str, rows: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let accent_color = theme.accent_color;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let line_height = font_size * 1.4;
        let area_height = line_height * rows as f32 + 16.0;

        let text_slot = self.world_mut().resources.text_cache.add_text("");
        let placeholder_text = placeholder.to_string();
        let has_placeholder = !placeholder_text.is_empty();
        let mut cursor_entity = freecs::Entity::default();
        let mut selection_pool = Vec::new();
        let mut placeholder_entity_out = None;

        let input_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, area_height)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiFocused>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Text)
            .with_clip()
            .with_children(|tree| {
                for _ in 0..rows + 1 {
                    let sel = tree
                        .add_node()
                        .window(
                            Ab(Vec2::new(8.0, 0.0)),
                            Ab(Vec2::new(0.0, line_height)),
                            Anchor::TopLeft,
                        )
                        .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_color::<UiBase>(Vec4::new(
                            accent_color.x,
                            accent_color.y,
                            accent_color.z,
                            0.3,
                        ))
                        .with_visible(false)
                        .without_pointer_events()
                        .done();
                    selection_pool.push(sel);
                }

                tree.add_node()
                    .window(
                        Ab(Vec2::new(8.0, 8.0)),
                        Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
                        Anchor::TopLeft,
                    )
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                if has_placeholder {
                    let placeholder_slot = tree
                        .world_mut()
                        .resources
                        .text_cache
                        .add_text(&placeholder_text);
                    placeholder_entity_out = Some(
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 8.0)),
                                Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
                                Anchor::TopLeft,
                            )
                            .with_text_slot(placeholder_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
                            .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                            .without_pointer_events()
                            .done(),
                    );
                }

                cursor_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 8.0)),
                        Ab(Vec2::new(2.0, line_height)),
                        Anchor::TopLeft,
                    )
                    .with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .with_visible(false)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            input_entity,
            UiWidgetState::TextArea(UiTextAreaData {
                text: String::new(),
                cursor_position: 0,
                selection_start: None,
                changed: false,
                text_slot,
                cursor_entity,
                selection_pool,
                scroll_offset_y: 0.0,
                cursor_blink_timer: 0.0,
                placeholder_entity: placeholder_entity_out,
                line_height,
                visible_rows: rows,
                syntax_language: None,
                undo_stack: UndoStack::new(100),
                max_length: None,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(input_entity) {
            interaction.accessible_role = Some(AccessibleRole::TextArea);
        }
        self.assign_tab_index(input_entity);

        input_entity
    }

    pub fn add_text_area_with_value(
        &mut self,
        placeholder: &str,
        rows: usize,
        initial_text: &str,
    ) -> freecs::Entity {
        let entity = self.add_text_area(placeholder, rows);
        self.world_mut()
            .ui_text_area_set_value(entity, initial_text);
        entity
    }

    #[cfg(feature = "syntax_highlighting")]
    pub fn add_text_area_with_syntax(
        &mut self,
        placeholder: &str,
        rows: usize,
        language: &str,
    ) -> freecs::Entity {
        let entity = self.add_text_area(placeholder, rows);
        if let Some(UiWidgetState::TextArea(data)) =
            self.world_mut().get_ui_widget_state_mut(entity)
        {
            data.syntax_language = Some(language.to_string());
        }
        entity
    }

    #[cfg(feature = "syntax_highlighting")]
    pub fn add_text_area_with_syntax_and_value(
        &mut self,
        placeholder: &str,
        rows: usize,
        language: &str,
        initial_text: &str,
    ) -> freecs::Entity {
        let entity = self.add_text_area_with_syntax(placeholder, rows, language);
        self.world_mut()
            .ui_text_area_set_value(entity, initial_text);
        entity
    }

    pub fn add_rich_text_editor(&mut self, placeholder: &str, rows: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let accent_color = theme.accent_color;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let line_height = font_size * 1.4;
        let area_height = line_height * rows as f32 + 16.0;

        let text_slot = self.world_mut().resources.text_cache.add_text("");
        let placeholder_text = placeholder.to_string();
        let has_placeholder = !placeholder_text.is_empty();
        let mut cursor_entity = freecs::Entity::default();
        let mut selection_pool = Vec::new();
        let mut placeholder_entity_out = None;

        let input_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, area_height)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiFocused>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Text)
            .with_clip()
            .with_children(|tree| {
                for _ in 0..rows + 1 {
                    let sel = tree
                        .add_node()
                        .window(
                            Ab(Vec2::new(8.0, 0.0)),
                            Ab(Vec2::new(0.0, line_height)),
                            Anchor::TopLeft,
                        )
                        .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_color::<UiBase>(Vec4::new(
                            accent_color.x,
                            accent_color.y,
                            accent_color.z,
                            0.3,
                        ))
                        .with_visible(false)
                        .without_pointer_events()
                        .done();
                    selection_pool.push(sel);
                }

                tree.add_node()
                    .window(
                        Ab(Vec2::new(8.0, 8.0)),
                        Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
                        Anchor::TopLeft,
                    )
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                if has_placeholder {
                    let placeholder_slot = tree
                        .world_mut()
                        .resources
                        .text_cache
                        .add_text(&placeholder_text);
                    placeholder_entity_out = Some(
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 8.0)),
                                Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-16.0, 0.0)),
                                Anchor::TopLeft,
                            )
                            .with_text_slot(placeholder_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Top)
                            .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                            .without_pointer_events()
                            .done(),
                    );
                }

                cursor_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 8.0)),
                        Ab(Vec2::new(2.0, line_height)),
                        Anchor::TopLeft,
                    )
                    .with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .with_visible(false)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            input_entity,
            UiWidgetState::RichTextEditor(UiRichTextEditorData {
                text: String::new(),
                char_styles: Vec::new(),
                current_style: CharStyle::default(),
                cursor_position: 0,
                selection_start: None,
                changed: false,
                text_slot,
                cursor_entity,
                selection_pool,
                scroll_offset_y: 0.0,
                cursor_blink_timer: 0.0,
                line_height,
                visible_rows: rows,
                placeholder_entity: placeholder_entity_out,
                undo_stack: UndoStack::new(100),
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(input_entity) {
            interaction.accessible_role = Some(AccessibleRole::TextArea);
        }
        self.assign_tab_index(input_entity);

        input_entity
    }

    pub fn add_rich_text_editor_with_value(
        &mut self,
        placeholder: &str,
        rows: usize,
        initial_text: &str,
    ) -> freecs::Entity {
        let entity = self.add_rich_text_editor(placeholder, rows);
        self.world_mut()
            .ui_rich_text_editor_set_value(entity, initial_text);
        entity
    }

    pub fn add_dropdown(&mut self, options: &[&str], initial: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let button_height = theme.button_height;
        let accent_color = theme.accent_color;
        let bg_color = theme.input_background_color;

        let selected_text = options.get(initial).copied().unwrap_or("");
        let header_text_slot = self
            .world_mut()
            .resources
            .text_cache
            .add_text(selected_text);
        let arrow_slot = self.world_mut().resources.text_cache.add_text("\u{25BC}");

        let mut popup_entities = Vec::new();
        let option_strings: Vec<String> = options.iter().map(|s| s.to_string()).collect();
        let popup_height = options.len() as f32 * button_height;

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .without_pointer_events()
            .entity();

        self.push_parent(wrapper_entity);

        let header_entity = self
            .add_node()
            .boundary(Ab(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 8.0, 0.0)
            .with_children(|tree| {
                tree.add_node()
                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                    .flex_grow(1.0)
                    .with_text_slot(header_text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .flow_child(Ab(Vec2::new(20.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                    .with_text_slot(arrow_slot, font_size * 0.8)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        let popup_container_entity = self
            .add_node()
            .window(
                Rl(Vec2::new(0.0, 100.0)),
                Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, popup_height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(30.0))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .entity();

        self.push_parent(popup_container_entity);

        for (index, option) in options.iter().enumerate() {
            let option_slot = self.world_mut().resources.text_cache.add_text(*option);
            let is_selected = index == initial;

            let option_entity = self
                .add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                .with_color::<UiBase>(if is_selected { accent_color } else { bg_color })
                .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                .with_interaction()
                .with_transition::<UiHover>(8.0, 6.0)
                .with_cursor_icon(winit::window::CursorIcon::Pointer)
                .with_children(|tree| {
                    tree.add_node()
                        .with_text_slot(option_slot, font_size)
                        .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();
                })
                .done();

            popup_entities.push(option_entity);
        }

        self.pop_parent();
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            header_entity,
            UiWidgetState::Dropdown(UiDropdownData {
                options: option_strings,
                selected_index: initial,
                changed: false,
                open: false,
                header_text_slot,
                popup_entities,
                popup_container_entity,
                hovered_index: None,
                is_theme_dropdown: false,
                searchable: false,
                filter_text: String::new(),
                filter_input_entity: None,
                filtered_indices: Vec::new(),
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(header_entity) {
            interaction.accessible_role = Some(AccessibleRole::Dropdown);
        }
        self.assign_tab_index(header_entity);

        header_entity
    }

    pub fn add_dropdown_searchable(&mut self, options: &[&str], initial: usize) -> freecs::Entity {
        let entity = self.add_dropdown(options, initial);
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let button_height = theme.button_height;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;

        let popup_container = {
            let widget = self.world_mut().get_ui_widget_state(entity).cloned();
            if let Some(UiWidgetState::Dropdown(data)) = widget {
                data.popup_container_entity
            } else {
                return entity;
            }
        };

        let filter_text_slot = self.world_mut().resources.text_cache.add_text("");
        let placeholder_slot = self.world_mut().resources.text_cache.add_text("Search...");

        self.push_parent(popup_container);

        let filter_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_interaction()
            .with_cursor_icon(winit::window::CursorIcon::Text)
            .with_children(|tree| {
                tree.add_node()
                    .boundary(
                        Ab(Vec2::new(8.0, 0.0)),
                        Ab(Vec2::new(0.0, button_height)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_text_slot(filter_text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
                tree.add_node()
                    .boundary(
                        Ab(Vec2::new(8.0, 0.0)),
                        Ab(Vec2::new(0.0, button_height)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_text_slot(placeholder_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.pop_parent();

        self.world_mut().resources.children_cache_valid = false;
        if let Some(children) = self
            .world_mut()
            .resources
            .children_cache
            .get_mut(&popup_container)
            && let Some(pos) = children.iter().position(|e| *e == filter_entity)
        {
            children.remove(pos);
            children.insert(0, filter_entity);
        }

        let all_indices: Vec<usize> = (0..options.len()).collect();
        if let Some(UiWidgetState::Dropdown(data)) =
            self.world_mut().get_ui_widget_state_mut(entity)
        {
            data.searchable = true;
            data.filter_input_entity = Some(filter_entity);
            data.filtered_indices = all_indices;
        }

        self.world_mut().set_ui_widget_state(
            filter_entity,
            UiWidgetState::TextInput(UiTextInputData {
                text: String::new(),
                cursor_position: 0,
                selection_start: None,
                changed: false,
                text_slot: filter_text_slot,
                cursor_entity: freecs::Entity::default(),
                selection_entity: freecs::Entity::default(),
                scroll_offset: 0.0,
                cursor_blink_timer: 0.0,
                placeholder_entity: None,
                undo_stack: UndoStack::new(50),
                input_mask: crate::ecs::ui::components::InputMask::None,
                max_length: None,
            }),
        );

        entity
    }

    pub fn add_multi_select(&mut self, options: &[&str]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let button_height = theme.button_height;
        let bg_color = theme.input_background_color;

        let header_text_slot = self.world_mut().resources.text_cache.add_text("0 selected");
        let arrow_slot = self.world_mut().resources.text_cache.add_text("\u{25BC}");

        let mut popup_entities = Vec::new();
        let mut check_entities = Vec::new();
        let option_strings: Vec<String> = options.iter().map(|s| s.to_string()).collect();
        let popup_height = options.len() as f32 * button_height;

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .without_pointer_events()
            .entity();

        self.push_parent(wrapper_entity);

        let header_entity = self
            .add_node()
            .boundary(Ab(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 8.0, 0.0)
            .with_children(|tree| {
                tree.add_node()
                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                    .flex_grow(1.0)
                    .with_text_slot(header_text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .flow_child(Ab(Vec2::new(20.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                    .with_text_slot(arrow_slot, font_size * 0.8)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        let popup_container_entity = self
            .add_node()
            .window(
                Rl(Vec2::new(0.0, 100.0)),
                Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, popup_height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(30.0))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .entity();

        self.push_parent(popup_container_entity);

        for option in options.iter() {
            let option_slot = self.world_mut().resources.text_cache.add_text(*option);
            let check_slot = self.world_mut().resources.text_cache.add_text(" ");

            let mut check_entity = freecs::Entity::default();

            let option_entity = self
                .add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                .with_color::<UiBase>(bg_color)
                .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                .with_interaction()
                .with_transition::<UiHover>(8.0, 6.0)
                .with_cursor_icon(winit::window::CursorIcon::Pointer)
                .flow(FlowDirection::Horizontal, 4.0, 0.0)
                .with_children(|tree| {
                    check_entity = tree
                        .add_node()
                        .flow_child(Ab(Vec2::new(20.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                        .with_text_slot(check_slot, font_size)
                        .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();

                    tree.add_node()
                        .flow_child(Rl(Vec2::new(0.0, 100.0)))
                        .flex_grow(1.0)
                        .with_text_slot(option_slot, font_size)
                        .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();
                })
                .done();

            popup_entities.push(option_entity);
            check_entities.push(check_entity);
        }

        self.pop_parent();
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            header_entity,
            UiWidgetState::MultiSelect(UiMultiSelectData {
                options: option_strings,
                selected_indices: HashSet::new(),
                changed: false,
                open: false,
                header_text_slot,
                popup_entities,
                popup_container_entity,
                hovered_index: None,
                check_entities,
            }),
        );
        self.assign_tab_index(header_entity);

        header_entity
    }

    pub fn add_date_picker(&mut self, year: i32, month: u32, day: u32) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let button_height = theme.button_height;
        let accent_color = theme.accent_color;

        let header_text = format!("{year:04}-{month:02}-{day:02}");
        let header_text_slot = self.world_mut().resources.text_cache.add_text(&header_text);
        let arrow_slot = self.world_mut().resources.text_cache.add_text("\u{1F4C5}");
        let month_label = format_month_year(year, month);
        let month_label_slot = self.world_mut().resources.text_cache.add_text(&month_label);

        let cell_size = 30.0;
        let grid_width = cell_size * 7.0;
        let nav_height = button_height;
        let grid_height = cell_size * 6.0;
        let popup_height = nav_height + grid_height;

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .without_pointer_events()
            .entity();
        self.push_parent(wrapper_entity);

        let header_entity = self
            .add_node()
            .boundary(Ab(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 8.0, 0.0)
            .with_children(|tree| {
                tree.add_node()
                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                    .flex_grow(1.0)
                    .with_text_slot(header_text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .flow_child(Ab(Vec2::new(24.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                    .with_text_slot(arrow_slot, font_size * 0.8)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        let popup_entity = self
            .add_node()
            .window(
                Rl(Vec2::new(0.0, 100.0)),
                Ab(Vec2::new(grid_width + 8.0, popup_height + 8.0)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(30.0))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 4.0, 4.0)
            .entity();

        self.push_parent(popup_entity);

        let nav_row = self
            .add_node()
            .flow_child(Ab(Vec2::new(grid_width, nav_height)))
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .without_pointer_events()
            .entity();
        self.push_parent(nav_row);

        let prev_slot = self.world_mut().resources.text_cache.add_text("\u{25C0}");
        let prev_month_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(cell_size, nav_height)))
            .with_rect(corner_radius, 0.0, Vec4::zeros())
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .boundary(Ab(Vec2::zeros()), Rl(Vec2::new(100.0, 100.0)))
                    .with_text_slot(prev_slot, font_size)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.add_node()
            .flow_child(Rl(Vec2::new(0.0, 100.0)))
            .flex_grow(1.0)
            .with_text_slot(month_label_slot, font_size)
            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done();

        let next_slot = self.world_mut().resources.text_cache.add_text("\u{25B6}");
        let next_month_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(cell_size, nav_height)))
            .with_rect(corner_radius, 0.0, Vec4::zeros())
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .boundary(Ab(Vec2::zeros()), Rl(Vec2::new(100.0, 100.0)))
                    .with_text_slot(next_slot, font_size)
                    .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.pop_parent();

        let mut day_entities = Vec::with_capacity(42);
        let mut day_text_slots = Vec::with_capacity(42);

        let grid_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(grid_width, grid_height)))
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .flow_wrap()
            .without_pointer_events()
            .entity();
        self.push_parent(grid_entity);

        for _ in 0..42 {
            let slot = self.world_mut().resources.text_cache.add_text("");
            day_text_slots.push(slot);

            let cell = self
                .add_node()
                .flow_child(Ab(Vec2::new(cell_size, cell_size)))
                .with_rect(corner_radius, 0.0, Vec4::zeros())
                .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                .with_interaction()
                .with_transition::<UiHover>(8.0, 6.0)
                .with_cursor_icon(winit::window::CursorIcon::Pointer)
                .with_children(|tree| {
                    tree.add_node()
                        .boundary(Ab(Vec2::zeros()), Rl(Vec2::new(100.0, 100.0)))
                        .with_text_slot(slot, font_size * 0.85)
                        .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();
                })
                .done();
            day_entities.push(cell);
        }

        self.pop_parent();
        self.pop_parent();
        self.pop_parent();

        populate_calendar_grid(
            self.world_mut(),
            year,
            month,
            day,
            &day_text_slots,
            &day_entities,
            accent_color,
        );

        self.world_mut().set_ui_widget_state(
            header_entity,
            UiWidgetState::DatePicker(UiDatePickerData {
                year,
                month,
                day,
                changed: false,
                open: false,
                header_text_slot,
                popup_entity,
                day_entities,
                day_text_slots,
                month_label_slot,
                prev_month_entity,
                next_month_entity,
                selected_day_entity: None,
            }),
        );
        self.assign_tab_index(header_entity);

        header_entity
    }

    pub fn add_menu(&mut self, label: &str, items: &[&str]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let button_height = theme.button_height;
        let border_color = theme.border_color;

        let label_slot = self.world_mut().resources.text_cache.add_text(label);

        let mut popup_entities = Vec::new();
        let mut popup_container_entity = freecs::Entity::default();
        let item_strings: Vec<String> = items.iter().map(|s| s.to_string()).collect();
        let popup_height = items.len() as f32 * button_height;

        let menu_button = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .with_text_slot(label_slot, font_size)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                popup_container_entity = tree
                    .add_node()
                    .window(
                        Rl(Vec2::new(0.0, 100.0)),
                        Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, popup_height)),
                        Anchor::TopLeft,
                    )
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::Background)
                    .with_layer(UiLayer::Popups)
                    .with_depth(UiDepthMode::Set(30.0))
                    .with_visible(false)
                    .flow(FlowDirection::Vertical, 0.0, 0.0)
                    .entity();

                tree.push_parent(popup_container_entity);

                for item in items {
                    let item_slot = tree.world_mut().resources.text_cache.add_text(*item);

                    let item_entity = tree
                        .add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                        .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_theme_color::<UiBase>(ThemeColor::Background)
                        .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                        .with_interaction()
                        .with_transition::<UiHover>(8.0, 6.0)
                        .with_cursor_icon(winit::window::CursorIcon::Pointer)
                        .with_children(|tree| {
                            tree.add_node()
                                .with_text_slot(item_slot, font_size)
                                .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                                .with_theme_color::<UiBase>(ThemeColor::Text)
                                .without_pointer_events()
                                .done();
                        })
                        .done();

                    popup_entities.push(item_entity);
                }

                tree.pop_parent();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            menu_button,
            UiWidgetState::Menu(UiMenuData {
                items: item_strings,
                clicked_item: None,
                open: false,
                label_text_slot: label_slot,
                popup_entities,
                popup_container_entity,
            }),
        );

        menu_button
    }

    pub fn add_floating_panel(&mut self, title: &str, rect: Rect) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;

        let title_slot = self.world_mut().resources.text_cache.add_text(title);
        let collapse_slot = self.world_mut().resources.text_cache.add_text("-");

        let focus_order = self.world_mut().resources.retained_ui.next_focus_order;
        self.world_mut().resources.retained_ui.next_focus_order += 1;

        let mut header_entity = freecs::Entity::default();
        let mut content_entity = freecs::Entity::default();
        let mut collapse_button_entity = freecs::Entity::default();

        let panel_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(rect.min.x, rect.min.y)),
                Ab(Vec2::new(rect.width(), rect.height())),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::FloatingPanels)
            .with_clip()
            .with_depth(crate::ecs::ui::components::UiDepthMode::Set(
                20.0 + focus_order as f32 * 10.0,
            ))
            .with_children(|tree| {
                header_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(0.0, 32.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
                    .with_clip()
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Grab)
                    .with_children(|tree| {
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 16.0)),
                                Ab(Vec2::new(200.0, 20.0)),
                                Anchor::CenterLeft,
                            )
                            .with_text_slot(title_slot, font_size * 0.85)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Accent)
                            .without_pointer_events()
                            .done();

                        collapse_button_entity = tree
                            .add_node()
                            .window(
                                Rl(Vec2::new(100.0, 50.0)) + Ab(Vec2::new(-8.0, 0.0)),
                                Ab(Vec2::new(20.0, 20.0)),
                                Anchor::CenterRight,
                            )
                            .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                            .with_theme_color::<UiBase>(ThemeColor::BackgroundHover)
                            .with_theme_color::<UiHover>(ThemeColor::Accent)
                            .with_interaction()
                            .with_transition::<UiHover>(8.0, 6.0)
                            .with_cursor_icon(winit::window::CursorIcon::Pointer)
                            .with_children(|tree| {
                                tree.add_node()
                                    .window(
                                        Rl(Vec2::new(50.0, 50.0)),
                                        Ab(Vec2::new(20.0, 20.0)),
                                        Anchor::Center,
                                    )
                                    .with_text_slot(collapse_slot, font_size * 0.8)
                                    .with_text_alignment(
                                        TextAlignment::Center,
                                        VerticalAlignment::Middle,
                                    )
                                    .with_theme_color::<UiBase>(ThemeColor::Text)
                                    .without_pointer_events()
                                    .done();
                            })
                            .done();
                    })
                    .done();

                content_entity = tree
                    .add_node()
                    .boundary(Ab(Vec2::new(0.0, 32.0)), Rl(Vec2::new(100.0, 100.0)))
                    .flow(FlowDirection::Vertical, 8.0, 4.0)
                    .with_clip()
                    .without_pointer_events()
                    .entity();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            panel_entity,
            UiWidgetState::Panel(UiPanelData {
                title: title.to_string(),
                title_text_slot: title_slot,
                content_entity,
                header_entity,
                collapsed: false,
                panel_kind: UiPanelKind::Floating,
                focus_order,
                pinned: false,
                min_size: Vec2::new(150.0, 100.0),
                drag_offset: None,
                resize_edge: None,
                resize_start_rect: None,
                resize_start_mouse: None,
                undocked_rect: None,
                default_dock_size: 300.0,
                collapse_button_entity: Some(collapse_button_entity),
                collapse_button_text_slot: Some(collapse_slot),
                header_visible: true,
                resizable: true,
            }),
        );

        panel_entity
    }

    fn build_docked_panel(
        &mut self,
        title: &str,
        panel_kind: UiPanelKind,
        default_size: f32,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let border_color = theme.border_color;

        let title_slot = self.world_mut().resources.text_cache.add_text(title);

        let focus_order = self.world_mut().resources.retained_ui.next_focus_order;
        self.world_mut().resources.retained_ui.next_focus_order += 1;

        let mut header_entity = freecs::Entity::default();
        let mut content_entity = freecs::Entity::default();

        let panel_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(0.0, 0.0)),
                Ab(Vec2::new(default_size, default_size)),
                Anchor::TopLeft,
            )
            .with_rect(0.0, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::DockedPanels)
            .with_clip()
            .with_depth(crate::ecs::ui::components::UiDepthMode::Set(
                10.0 + focus_order as f32 * 10.0,
            ))
            .with_children(|tree| {
                header_entity = tree
                    .add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(0.0, 32.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
                    .with_clip()
                    .with_interaction()
                    .with_cursor_icon(winit::window::CursorIcon::Grab)
                    .with_children(|tree| {
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(8.0, 16.0)),
                                Ab(Vec2::new(200.0, 20.0)),
                                Anchor::CenterLeft,
                            )
                            .with_text_slot(title_slot, font_size * 0.85)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Accent)
                            .without_pointer_events()
                            .done();
                    })
                    .done();

                content_entity = tree
                    .add_node()
                    .boundary(Ab(Vec2::new(0.0, 32.0)), Rl(Vec2::new(100.0, 100.0)))
                    .flow(FlowDirection::Vertical, 8.0, 4.0)
                    .with_clip()
                    .without_pointer_events()
                    .entity();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            panel_entity,
            UiWidgetState::Panel(UiPanelData {
                title: title.to_string(),
                title_text_slot: title_slot,
                content_entity,
                header_entity,
                collapsed: false,
                panel_kind,
                focus_order,
                pinned: false,
                min_size: Vec2::new(100.0, 60.0),
                drag_offset: None,
                resize_edge: None,
                resize_start_rect: None,
                resize_start_mouse: None,
                undocked_rect: None,
                default_dock_size: default_size,
                collapse_button_entity: None,
                collapse_button_text_slot: None,
                header_visible: true,
                resizable: true,
            }),
        );

        panel_entity
    }

    pub fn add_docked_panel_left(&mut self, title: &str, default_width: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedLeft, default_width)
    }

    pub fn add_docked_panel_right(&mut self, title: &str, default_width: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedRight, default_width)
    }

    pub fn add_docked_panel_top(&mut self, title: &str, default_height: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedTop, default_height)
    }

    pub fn add_docked_panel_bottom(&mut self, title: &str, default_height: f32) -> freecs::Entity {
        self.build_docked_panel(title, UiPanelKind::DockedBottom, default_height)
    }

    pub fn add_color_picker(&mut self, initial_color: Vec4) -> freecs::Entity {
        self.add_color_picker_with_mode(
            initial_color,
            crate::ecs::ui::components::ColorPickerMode::Rgb,
        )
    }

    pub fn add_color_picker_hsv(&mut self, initial_color: Vec4) -> freecs::Entity {
        self.add_color_picker_with_mode(
            initial_color,
            crate::ecs::ui::components::ColorPickerMode::Hsv,
        )
    }

    pub fn add_color_picker_with_mode(
        &mut self,
        initial_color: Vec4,
        mode: crate::ecs::ui::components::ColorPickerMode,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let corner_radius = theme.corner_radius;

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Horizontal, 4.0, 8.0)
            .entity();

        self.push_parent(root_entity);

        let swatch_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(48.0, 48.0)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(initial_color)
            .without_pointer_events()
            .done();

        let sliders_container = self
            .add_node()
            .flow_child(Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .flow(FlowDirection::Vertical, 0.0, 4.0)
            .entity();

        self.push_parent(sliders_container);

        let slider_entities = match mode {
            crate::ecs::ui::components::ColorPickerMode::Rgb => {
                let slider_r = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.x)
                        .precision(2)
                        .prefix("R: "),
                );
                let slider_g = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.y)
                        .precision(2)
                        .prefix("G: "),
                );
                let slider_b = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.z)
                        .precision(2)
                        .prefix("B: "),
                );
                let slider_a = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.w)
                        .precision(2)
                        .prefix("A: "),
                );
                [slider_r, slider_g, slider_b, slider_a]
            }
            crate::ecs::ui::components::ColorPickerMode::Hsv => {
                let hsv = crate::ecs::ui::color::Hsva::from_rgba(initial_color);
                let slider_h = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 360.0, hsv.hue)
                        .precision(0)
                        .prefix("H: ")
                        .suffix("\u{00b0}"),
                );
                let slider_s = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, hsv.saturation)
                        .precision(2)
                        .prefix("S: "),
                );
                let slider_v = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, hsv.value)
                        .precision(2)
                        .prefix("V: "),
                );
                let slider_a = self.add_slider_configured(
                    crate::ecs::ui::components::SliderConfig::new(0.0, 1.0, initial_color.w)
                        .precision(2)
                        .prefix("A: "),
                );
                [slider_h, slider_s, slider_v, slider_a]
            }
        };

        self.pop_parent();
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            root_entity,
            UiWidgetState::ColorPicker(UiColorPickerData {
                color: initial_color,
                changed: false,
                swatch_entity,
                slider_entities,
                mode,
            }),
        );

        root_entity
    }

    pub fn add_icon_button(
        &mut self,
        texture_index: u32,
        icon_size: Vec2,
        text: &str,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let button_height = theme.button_height;
        let corner_radius = theme.corner_radius;
        let border_width = theme.border_width;
        let border_color = theme.border_color;

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        let button_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, border_width, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 8.0, 8.0)
            .with_children(|tree| {
                tree.add_node()
                    .flow_child(Ab(icon_size))
                    .with_image(texture_index)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .flow_child(Ab(Vec2::new(0.0, button_height)))
                    .flex_grow(1.0)
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot,
            }),
        );
        self.assign_tab_index(button_entity);

        button_entity
    }

    pub fn add_button_rich(&mut self, spans: &[TextSpan]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let button_height = theme.button_height;
        let corner_radius = theme.corner_radius;
        let border_width = theme.border_width;
        let border_color = theme.border_color;
        let text_color = theme.text_color;

        let button_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .with_rect(corner_radius, border_width, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_theme_color::<UiPressed>(ThemeColor::BackgroundActive)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_transition::<UiPressed>(12.0, 8.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .entity();

        self.push_parent(button_entity);

        let span_container = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
            .flow(FlowDirection::Horizontal, 0.0, 8.0)
            .flow_wrap()
            .without_pointer_events()
            .entity();

        self.push_parent(span_container);

        for span in spans {
            let text_slot = self.world_mut().resources.text_cache.add_text(&span.text);
            let mut span_font_size = span.font_size_override.unwrap_or(font_size);
            let span_color = span.color.unwrap_or(text_color);
            let span_font_index = span.font_index.unwrap_or(0);

            if span.bold {
                span_font_size *= 1.05;
            }

            let outline_width = if span.bold { 0.4 } else { 0.0 };
            let outline_color = if span.bold {
                span_color
            } else {
                Vec4::new(0.0, 0.0, 0.0, 0.0)
            };

            let span_wrapper = self
                .add_node()
                .flow_child(Ab(Vec2::new(0.0, button_height)))
                .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                .flow(FlowDirection::Vertical, 0.0, 0.0)
                .without_pointer_events()
                .entity();

            self.push_parent(span_wrapper);

            let entity = self.add_node().done();
            if let Some(content) = self.world_mut().get_ui_node_content_mut(entity) {
                *content = crate::ecs::ui::components::UiNodeContent::Text {
                    text_slot,
                    font_index: span_font_index,
                    font_size_override: Some(span_font_size),
                    outline_color,
                    outline_width,
                    alignment: TextAlignment::Left,
                    vertical_alignment: VerticalAlignment::Middle,
                    overflow: crate::ecs::ui::components::TextOverflow::default(),
                };
            }
            if let Some(node) = self.world_mut().get_ui_layout_node_mut(entity) {
                node.flow_child_size = Some(Ab(Vec2::new(0.0, button_height)).into());
                node.auto_size = crate::ecs::ui::components::AutoSizeMode::Width;
                node.pointer_events = false;
            }
            if let Some(color) = self.world_mut().get_ui_node_color_mut(entity) {
                color.colors[UiBase::INDEX] = Some(span_color);
            }
            if span.color.is_none() {
                let mut binding = crate::ecs::ui::components::UiThemeBinding::default();
                binding.color_roles[UiBase::INDEX] = Some(ThemeColor::Text);
                self.world_mut().set_ui_theme_binding(entity, binding);
            }

            self.pop_parent();
        }

        self.pop_parent();
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            button_entity,
            UiWidgetState::Button(UiButtonData {
                clicked: false,
                text_slot: 0,
            }),
        );
        self.assign_tab_index(button_entity);

        button_entity
    }

    pub fn add_image_node(&mut self, texture_index: u32, size: Vec2) -> freecs::Entity {
        self.add_node()
            .flow_child(Ab(size))
            .with_image(texture_index)
            .with_color::<UiBase>(Vec4::new(1.0, 1.0, 1.0, 1.0))
            .without_pointer_events()
            .done()
    }

    pub fn add_theme_dropdown(&mut self) -> freecs::Entity {
        let preset_names: Vec<String> = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .presets
            .iter()
            .map(|theme| theme.name.clone())
            .collect();

        let selected = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .selected_preset_index;

        let labels: Vec<&str> = preset_names.iter().map(|s| s.as_str()).collect();
        let entity = self.add_dropdown(&labels, selected.unwrap_or(0));
        if let Some(UiWidgetState::Dropdown(data)) =
            self.world_mut().get_ui_widget_state_mut(entity)
        {
            data.is_theme_dropdown = true;
        }
        entity
    }

    pub fn add_selectable_label(&mut self, text: &str, group_id: Option<u32>) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;

        let text_slot = self.world_mut().resources.text_cache.add_text(text);

        let label_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, font_size * 1.5)))
            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .with_children(|tree| {
                tree.add_node()
                    .boundary(Ab(Vec2::new(8.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            label_entity,
            UiWidgetState::SelectableLabel(UiSelectableLabelData {
                selected: false,
                changed: false,
                text_slot,
                group_id,
            }),
        );
        if let Some(gid) = group_id {
            self.world_mut()
                .resources
                .retained_ui
                .selectable_label_groups
                .entry(gid)
                .or_default()
                .push(label_entity);
        }

        label_entity
    }

    pub fn add_drag_value(&mut self, min: f32, max: f32, initial: f32) -> freecs::Entity {
        self.add_drag_value_configured(DragValueConfig::new(min, max, initial))
    }

    pub fn add_drag_value_configured(&mut self, config: DragValueConfig<'_>) -> freecs::Entity {
        let initial = config.initial;
        let min = config.min;
        let max = config.max;
        let speed = config.speed;
        let precision = config.precision;
        let prefix = config.prefix;
        let suffix = config.suffix;
        let show_arrows = config.show_arrows;
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let accent_color = theme.accent_color;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let input_height = theme.button_height;

        let display_text = format!("{prefix}{initial:.prec$}{suffix}", prec = precision);
        let text_slot = self
            .world_mut()
            .resources
            .text_cache
            .add_text(&display_text);
        let mut cursor_entity = freecs::Entity::default();
        let mut selection_entity = freecs::Entity::default();

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, input_height)))
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .without_pointer_events()
            .entity();

        self.push_parent(wrapper_entity);

        let root_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(0.0, 100.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flex_grow(1.0)
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::InputBackground)
            .with_theme_color::<UiFocused>(ThemeColor::InputBackgroundFocused)
            .with_interaction()
            .with_transition::<UiFocused>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::EwResize)
            .with_clip()
            .with_children(|tree| {
                selection_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 4.0)),
                        Ab(Vec2::new(0.0, input_height - 8.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_color::<UiBase>(Vec4::new(
                        accent_color.x,
                        accent_color.y,
                        accent_color.z,
                        0.3,
                    ))
                    .with_visible(false)
                    .without_pointer_events()
                    .done();

                tree.add_node()
                    .window(
                        Ab(Vec2::new(8.0, 0.0)),
                        Ab(Vec2::new(0.0, input_height)) + Rl(Vec2::new(100.0, 0.0)),
                        Anchor::TopLeft,
                    )
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done();

                cursor_entity = tree
                    .add_node()
                    .window(
                        Ab(Vec2::new(8.0, 4.0)),
                        Ab(Vec2::new(2.0, input_height - 8.0)),
                        Anchor::TopLeft,
                    )
                    .with_rect(1.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .with_visible(false)
                    .without_pointer_events()
                    .done();
            })
            .done();

        let mut up_entity = None;
        let mut down_entity = None;
        if show_arrows {
            let arrow_width = 18.0;
            let half_height = input_height / 2.0;

            let arrow_col = self
                .add_node()
                .flow_child(Ab(Vec2::new(arrow_width, input_height)))
                .flow(FlowDirection::Vertical, 0.0, 0.0)
                .without_pointer_events()
                .entity();
            self.push_parent(arrow_col);

            let up_slot = self.world_mut().resources.text_cache.add_text("\u{25B2}");
            up_entity = Some(
                self.add_node()
                    .flow_child(Ab(Vec2::new(arrow_width, half_height)))
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::InputBackground)
                    .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .with_children(|tree| {
                        tree.add_node()
                            .boundary(Ab(Vec2::zeros()), Rl(Vec2::new(100.0, 100.0)))
                            .with_text_slot(up_slot, font_size * 0.5)
                            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                    })
                    .done(),
            );

            let down_slot = self.world_mut().resources.text_cache.add_text("\u{25BC}");
            down_entity = Some(
                self.add_node()
                    .flow_child(Ab(Vec2::new(arrow_width, half_height)))
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_theme_border_color(ThemeColor::Border)
                    .with_theme_color::<UiBase>(ThemeColor::InputBackground)
                    .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .with_children(|tree| {
                        tree.add_node()
                            .boundary(Ab(Vec2::zeros()), Rl(Vec2::new(100.0, 100.0)))
                            .with_text_slot(down_slot, font_size * 0.5)
                            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                    })
                    .done(),
            );

            self.pop_parent();
        }

        self.pop_parent();

        let step_val = speed * 10.0;
        self.world_mut().set_ui_widget_state(
            root_entity,
            UiWidgetState::DragValue(UiDragValueData {
                value: initial,
                min,
                max,
                speed,
                precision,
                prefix: prefix.to_string(),
                suffix: suffix.to_string(),
                changed: false,
                text_slot,
                editing: false,
                cursor_entity,
                selection_entity,
                edit_text: String::new(),
                cursor_position: 0,
                selection_start: None,
                cursor_blink_timer: 0.0,
                scroll_offset: 0.0,
                drag_start_value: initial,
                undo_stack: UndoStack::new(100),
                up_entity,
                down_entity,
                step: step_val,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(root_entity) {
            interaction.accessible_role = Some(AccessibleRole::Slider);
        }
        self.assign_tab_index(root_entity);

        root_entity
    }

    pub fn add_context_menu(&mut self, items: &[(&str, Option<&str>)]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let button_height = theme.button_height;

        let mut item_entities = Vec::new();
        let context_items: Vec<ContextMenuItem> = items
            .iter()
            .map(|(label, shortcut)| ContextMenuItem {
                label: label.to_string(),
                shortcut: shortcut.map(|s| s.to_string()),
                separator: label.is_empty(),
            })
            .collect();

        let popup_height = items.iter().fold(0.0f32, |acc, (label, _)| {
            if label.is_empty() {
                acc + 5.0
            } else {
                acc + button_height
            }
        });

        let popup_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(0.0, 0.0)),
                Ab(Vec2::new(200.0, popup_height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(35.0))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 2.0, 0.0)
            .entity();

        self.push_parent(popup_entity);

        for (label, shortcut) in items {
            if label.is_empty() {
                self.add_node()
                    .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
                    .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Border)
                    .without_pointer_events()
                    .done();
                item_entities.push(freecs::Entity::default());
                continue;
            }

            let label_slot = self.world_mut().resources.text_cache.add_text(*label);

            let item_entity = if let Some(sc) = shortcut {
                let sc_slot = self.world_mut().resources.text_cache.add_text(*sc);
                self.add_node()
                    .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Background)
                    .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .flow(FlowDirection::Horizontal, 8.0, 0.0)
                    .with_children(|tree| {
                        tree.add_node()
                            .flow_child(Rl(Vec2::new(0.0, 100.0)))
                            .flex_grow(1.0)
                            .with_text_slot(label_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                        tree.add_node()
                            .flow_child(Rl(Vec2::new(0.0, 100.0)))
                            .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                            .auto_size_padding(Vec2::new(4.0, 0.0))
                            .with_text_slot(sc_slot, font_size * 0.85)
                            .with_text_alignment(TextAlignment::Right, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                            .without_pointer_events()
                            .done();
                    })
                    .done()
            } else {
                self.add_node()
                    .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::Background)
                    .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                    .with_interaction()
                    .with_transition::<UiHover>(8.0, 6.0)
                    .with_cursor_icon(winit::window::CursorIcon::Pointer)
                    .with_children(|tree| {
                        tree.add_node()
                            .boundary(Ab(Vec2::new(8.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
                            .with_text_slot(label_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Text)
                            .without_pointer_events()
                            .done();
                    })
                    .done()
            };

            if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(item_entity) {
                interaction.accessible_role = Some(AccessibleRole::MenuItem);
            }
            item_entities.push(item_entity);
        }

        self.pop_parent();

        let menu_entity = popup_entity;
        self.world_mut()
            .set_ui_node_interaction(menu_entity, UiNodeInteraction::default());
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(menu_entity) {
            interaction.accessible_role = Some(AccessibleRole::Menu);
        }
        self.world_mut().set_ui_widget_state(
            menu_entity,
            UiWidgetState::ContextMenu(UiContextMenuData {
                items: context_items,
                open: false,
                clicked_item: None,
                popup_entity,
                item_entities,
                item_defs: Vec::new(),
            }),
        );

        menu_entity
    }

    pub fn add_context_menu_from_builder(&mut self, builder: ContextMenuBuilder) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let button_height = theme.button_height;

        let mut command_counter = 0usize;
        let cm_theme = ContextMenuTheme {
            font_size,
            corner_radius,
            button_height,
        };

        let popup_entity =
            self.build_context_menu_popup(&builder.entries, &mut command_counter, &cm_theme, 0);

        let item_defs = if let Some(UiWidgetState::ContextMenu(data)) =
            self.world_mut().get_ui_widget_state(popup_entity)
        {
            data.item_defs.clone()
        } else {
            Vec::new()
        };

        let item_entities: Vec<freecs::Entity> =
            item_defs.iter().map(|def| def.row_entity).collect();

        self.world_mut()
            .set_ui_node_interaction(popup_entity, UiNodeInteraction::default());
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(popup_entity) {
            interaction.accessible_role = Some(AccessibleRole::Menu);
        }
        self.world_mut().set_ui_widget_state(
            popup_entity,
            UiWidgetState::ContextMenu(UiContextMenuData {
                items: Vec::new(),
                open: false,
                clicked_item: None,
                popup_entity,
                item_entities,
                item_defs,
            }),
        );

        popup_entity
    }

    fn build_context_menu_popup(
        &mut self,
        entries: &[ContextMenuBuilderEntry],
        command_counter: &mut usize,
        theme: &ContextMenuTheme,
        depth_level: u32,
    ) -> freecs::Entity {
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let button_height = theme.button_height;

        let border_color = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .border_color;

        let popup_height = entries.iter().fold(0.0f32, |acc, entry| match entry {
            ContextMenuBuilderEntry::Separator => acc + 5.0,
            _ => acc + button_height,
        });

        let popup_entity = self
            .add_node()
            .window(
                Ab(Vec2::new(0.0, 0.0)),
                Ab(Vec2::new(200.0, popup_height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(36.0 + depth_level as f32))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 2.0, 0.0)
            .entity();

        self.push_parent(popup_entity);

        let mut item_defs = Vec::new();

        for entry in entries {
            match entry {
                ContextMenuBuilderEntry::Separator => {
                    self.add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
                        .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_theme_color::<UiBase>(ThemeColor::Border)
                        .without_pointer_events()
                        .done();
                    item_defs.push(ContextMenuItemDef {
                        label: String::new(),
                        shortcut: String::new(),
                        kind: ContextMenuItemKind::Separator,
                        row_entity: freecs::Entity::default(),
                        command_id: None,
                        binding: None,
                    });
                }
                ContextMenuBuilderEntry::Item { label, shortcut } => {
                    let cmd_id = *command_counter;
                    *command_counter += 1;

                    let label_slot = self.world_mut().resources.text_cache.add_text(label);
                    let row_entity = if shortcut.is_empty() {
                        self.add_node()
                            .flow_child(
                                Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)),
                            )
                            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                            .with_theme_color::<UiBase>(ThemeColor::Background)
                            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                            .with_interaction()
                            .with_transition::<UiHover>(8.0, 6.0)
                            .with_cursor_icon(winit::window::CursorIcon::Pointer)
                            .with_children(|tree| {
                                tree.add_node()
                                    .boundary(Ab(Vec2::new(8.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
                                    .with_text_slot(label_slot, font_size)
                                    .with_text_alignment(
                                        TextAlignment::Left,
                                        VerticalAlignment::Middle,
                                    )
                                    .with_theme_color::<UiBase>(ThemeColor::Text)
                                    .without_pointer_events()
                                    .done();
                            })
                            .done()
                    } else {
                        let sc_slot = self.world_mut().resources.text_cache.add_text(shortcut);
                        self.add_node()
                            .flow_child(
                                Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)),
                            )
                            .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                            .with_theme_color::<UiBase>(ThemeColor::Background)
                            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                            .with_interaction()
                            .with_transition::<UiHover>(8.0, 6.0)
                            .with_cursor_icon(winit::window::CursorIcon::Pointer)
                            .flow(FlowDirection::Horizontal, 8.0, 0.0)
                            .with_children(|tree| {
                                tree.add_node()
                                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                                    .flex_grow(1.0)
                                    .with_text_slot(label_slot, font_size)
                                    .with_text_alignment(
                                        TextAlignment::Left,
                                        VerticalAlignment::Middle,
                                    )
                                    .with_theme_color::<UiBase>(ThemeColor::Text)
                                    .without_pointer_events()
                                    .done();
                                tree.add_node()
                                    .flow_child(Rl(Vec2::new(0.0, 100.0)))
                                    .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                                    .auto_size_padding(Vec2::new(4.0, 0.0))
                                    .with_text_slot(sc_slot, font_size * 0.85)
                                    .with_text_alignment(
                                        TextAlignment::Right,
                                        VerticalAlignment::Middle,
                                    )
                                    .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                                    .without_pointer_events()
                                    .done();
                            })
                            .done()
                    };

                    item_defs.push(ContextMenuItemDef {
                        label: label.clone(),
                        shortcut: shortcut.clone(),
                        kind: ContextMenuItemKind::Action,
                        row_entity,
                        command_id: Some(cmd_id),
                        binding: ShortcutBinding::parse(shortcut),
                    });
                }
                ContextMenuBuilderEntry::Submenu { label, children } => {
                    let label_slot = self.world_mut().resources.text_cache.add_text(label);
                    let arrow_slot = self.world_mut().resources.text_cache.add_text("\u{25B6}");

                    let row_entity = self
                        .add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                        .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_theme_color::<UiBase>(ThemeColor::Background)
                        .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
                        .with_interaction()
                        .with_transition::<UiHover>(8.0, 6.0)
                        .with_cursor_icon(winit::window::CursorIcon::Pointer)
                        .flow(FlowDirection::Horizontal, 8.0, 0.0)
                        .with_children(|tree| {
                            tree.add_node()
                                .flow_child(Rl(Vec2::new(0.0, 100.0)))
                                .flex_grow(1.0)
                                .with_text_slot(label_slot, font_size)
                                .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                                .with_theme_color::<UiBase>(ThemeColor::Text)
                                .without_pointer_events()
                                .done();
                            tree.add_node()
                                .flow_child(Ab(Vec2::new(16.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)))
                                .with_text_slot(arrow_slot, font_size * 0.85)
                                .with_text_alignment(
                                    TextAlignment::Center,
                                    VerticalAlignment::Middle,
                                )
                                .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                                .without_pointer_events()
                                .done();
                        })
                        .done();

                    self.pop_parent();
                    let child_popup = self.build_context_menu_popup(
                        children,
                        command_counter,
                        theme,
                        depth_level + 1,
                    );
                    self.push_parent(popup_entity);

                    let child_defs = if let Some(UiWidgetState::ContextMenu(data)) =
                        self.world_mut().get_ui_widget_state(child_popup)
                    {
                        data.item_defs.clone()
                    } else {
                        Vec::new()
                    };

                    item_defs.push(ContextMenuItemDef {
                        label: label.clone(),
                        shortcut: String::new(),
                        kind: ContextMenuItemKind::Submenu {
                            children: child_defs,
                            popup_entity: child_popup,
                            open: false,
                        },
                        row_entity,
                        command_id: None,
                        binding: None,
                    });
                }
                ContextMenuBuilderEntry::WidgetRow { label } => {
                    let cmd_id = *command_counter;
                    *command_counter += 1;

                    let label_slot = self.world_mut().resources.text_cache.add_text(label);

                    let mut content_entity = freecs::Entity::default();

                    let row_entity = self
                        .add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, button_height)))
                        .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_theme_color::<UiBase>(ThemeColor::Background)
                        .with_interaction()
                        .flow_with_alignment(
                            FlowDirection::Horizontal,
                            8.0,
                            0.0,
                            FlowAlignment::Start,
                            FlowAlignment::Center,
                        )
                        .with_children(|tree| {
                            tree.add_node()
                                .flow_child(Ab(Vec2::new(0.0, button_height)))
                                .flex_grow(1.0)
                                .with_text_slot(label_slot, font_size)
                                .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                                .with_theme_color::<UiBase>(ThemeColor::Text)
                                .without_pointer_events()
                                .done();
                            content_entity = tree
                                .add_node()
                                .flow_child(Ab(Vec2::new(0.0, button_height)))
                                .flow_with_alignment(
                                    FlowDirection::Horizontal,
                                    0.0,
                                    4.0,
                                    FlowAlignment::End,
                                    FlowAlignment::Center,
                                )
                                .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                                .entity();
                        })
                        .done();

                    item_defs.push(ContextMenuItemDef {
                        label: label.clone(),
                        shortcut: String::new(),
                        kind: ContextMenuItemKind::Widget { content_entity },
                        row_entity,
                        command_id: Some(cmd_id),
                        binding: None,
                    });
                }
            }
        }

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            popup_entity,
            UiWidgetState::ContextMenu(UiContextMenuData {
                items: Vec::new(),
                open: false,
                clicked_item: None,
                popup_entity,
                item_entities: item_defs.iter().map(|d| d.row_entity).collect(),
                item_defs,
            }),
        );

        popup_entity
    }

    pub fn add_tree_view(&mut self, multi_select: bool) -> freecs::Entity {
        let scroll_entity = self.add_scroll_area_fill(2.0, 0.0);
        let content_entity = self
            .world_mut()
            .widget::<UiScrollAreaData>(scroll_entity)
            .map(|d| d.content_entity)
            .unwrap_or(scroll_entity);

        self.world_mut().set_ui_widget_state(
            scroll_entity,
            UiWidgetState::TreeView(UiTreeViewData {
                selected_nodes: Vec::new(),
                multi_select,
                node_entities: Vec::new(),
                changed: false,
                content_entity,
                context_menu_node: None,
                filter_text: String::new(),
                filter_active: false,
                pre_filter_expanded: std::collections::HashMap::new(),
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(scroll_entity) {
            interaction.accessible_role = Some(AccessibleRole::Tree);
        }

        scroll_entity
    }

    pub fn add_tree_node(
        &mut self,
        tree: freecs::Entity,
        parent_container: freecs::Entity,
        label: &str,
        depth: usize,
        user_data: u64,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let row_height = font_size * 1.4;
        let indent = depth as f32 * 16.0;

        let arrow_slot = self.world_mut().resources.text_cache.add_text("\u{25B6}");
        let label_slot = self.world_mut().resources.text_cache.add_text(label);

        let wrapper_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .without_pointer_events()
            .entity();

        if let Some(parent) = self.world_mut().get_parent_mut(wrapper_entity) {
            *parent = crate::ecs::transform::components::Parent(Some(parent_container));
        }
        self.world_mut().resources.children_cache_valid = false;

        self.push_parent(wrapper_entity);

        let row_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)))
            .with_rect(2.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiHover>(ThemeColor::BackgroundHover)
            .with_interaction()
            .with_transition::<UiHover>(8.0, 6.0)
            .with_cursor_icon(winit::window::CursorIcon::Pointer)
            .flow(FlowDirection::Horizontal, 4.0, 2.0)
            .entity();

        self.push_parent(row_entity);

        if indent > 0.0 {
            self.add_node()
                .flow_child(Ab(Vec2::new(indent, row_height)))
                .without_pointer_events()
                .done();
        }

        let arrow_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(16.0, row_height)))
            .with_text_slot(arrow_slot, font_size * 0.8)
            .with_text_alignment(TextAlignment::Center, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done();

        self.add_node()
            .flow_child(Rl(Vec2::new(0.0, 100.0)))
            .flex_grow(1.0)
            .with_text_slot(label_slot, font_size)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::Text)
            .without_pointer_events()
            .done();

        self.pop_parent();

        let children_container = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .with_visible(false)
            .without_pointer_events()
            .entity();

        self.pop_parent();

        let node_entity = row_entity;

        let existing_nodes = if let Some(UiWidgetState::TreeView(tree_data)) =
            self.world_mut().get_ui_widget_state(tree)
        {
            tree_data.node_entities.clone()
        } else {
            Vec::new()
        };

        let parent_node = existing_nodes.iter().find_map(|&candidate| {
            if let Some(UiWidgetState::TreeNode(nd)) =
                self.world_mut().get_ui_widget_state(candidate)
                && nd.children_container == parent_container
            {
                return Some(candidate);
            }
            None
        });

        self.world_mut().set_ui_widget_state(
            node_entity,
            UiWidgetState::TreeNode(UiTreeNodeData {
                label: label.to_string(),
                text_slot: label_slot,
                depth,
                expanded: false,
                selected: false,
                row_entity,
                arrow_entity,
                arrow_text_slot: arrow_slot,
                children_container,
                user_data,
                parent_node,
                wrapper_entity,
                lazy: false,
                lazy_loaded: false,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(node_entity) {
            interaction.accessible_role = Some(AccessibleRole::TreeItem);
        }

        if let Some(UiWidgetState::TreeView(tree_data)) =
            self.world_mut().get_ui_widget_state_mut(tree)
        {
            tree_data.node_entities.push(node_entity);
        }

        node_entity
    }

    pub fn add_tree_node_lazy(
        &mut self,
        tree: freecs::Entity,
        parent_container: freecs::Entity,
        label: &str,
        depth: usize,
        user_data: u64,
    ) -> freecs::Entity {
        let node = self.add_tree_node(tree, parent_container, label, depth, user_data);
        if let Some(UiWidgetState::TreeNode(data)) = self.world_mut().get_ui_widget_state_mut(node)
        {
            data.lazy = true;
        }
        node
    }

    pub fn add_modal_dialog(&mut self, title: &str, width: f32, height: f32) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;

        let title_slot = self.world_mut().resources.text_cache.add_text(title);

        let backdrop_entity = self
            .add_node()
            .boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.5))
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(40.0))
            .with_interaction()
            .with_visible(false)
            .done();

        let mut content_entity = freecs::Entity::default();

        let dialog_entity = self
            .add_node()
            .window(
                Rl(Vec2::new(50.0, 50.0)) + Ab(Vec2::new(-width / 2.0, -height / 2.0)),
                Ab(Vec2::new(width, height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(41.0))
            .with_visible(false)
            .with_children(|tree| {
                tree.add_node()
                    .boundary(
                        Rl(Vec2::new(0.0, 0.0)),
                        Ab(Vec2::new(0.0, 36.0)) + Rl(Vec2::new(100.0, 0.0)),
                    )
                    .with_rect(corner_radius, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
                    .with_theme_color::<UiBase>(ThemeColor::BackgroundActive)
                    .without_pointer_events()
                    .with_children(|tree| {
                        tree.add_node()
                            .window(
                                Ab(Vec2::new(12.0, 18.0)),
                                Ab(Vec2::new(200.0, 20.0)),
                                Anchor::CenterLeft,
                            )
                            .with_text_slot(title_slot, font_size)
                            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                            .with_theme_color::<UiBase>(ThemeColor::Accent)
                            .without_pointer_events()
                            .done();
                    })
                    .done();

                content_entity = tree
                    .add_node()
                    .boundary(Ab(Vec2::new(0.0, 36.0)), Rl(Vec2::new(100.0, 100.0)))
                    .flow(FlowDirection::Vertical, 12.0, 8.0)
                    .without_pointer_events()
                    .entity();
            })
            .done();

        self.world_mut().set_ui_widget_state(
            dialog_entity,
            UiWidgetState::ModalDialog(UiModalDialogData {
                title_text_slot: title_slot,
                content_entity,
                backdrop_entity,
                ok_button: None,
                cancel_button: None,
                result: None,
            }),
        );

        dialog_entity
    }

    pub fn add_confirm_dialog(&mut self, title: &str, message: &str) -> freecs::Entity {
        let dialog_entity = self.add_modal_dialog(title, 350.0, 180.0);

        let content = self
            .world_mut()
            .widget::<UiModalDialogData>(dialog_entity)
            .map(|d| d.content_entity)
            .unwrap_or(dialog_entity);

        let accent_color = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .accent_color;

        let ok_button;
        let cancel_button;

        {
            let mut subtree =
                crate::ecs::ui::builder::UiTreeBuilder::from_parent(self.world_mut(), content);

            subtree.add_label(message);

            subtree.add_spacing(8.0);

            let button_row = subtree
                .add_node()
                .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 36.0)))
                .flow(FlowDirection::Horizontal, 0.0, 8.0)
                .without_pointer_events()
                .entity();

            subtree.push_parent(button_row);
            cancel_button = subtree.add_button("Cancel");
            ok_button = subtree.add_button_colored("OK", accent_color);
            subtree.pop_parent();

            if let Some(node) = subtree.world_mut().get_ui_layout_node_mut(cancel_button) {
                node.flow_child_size = Some(Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-4.0, 36.0)));
            }
            if let Some(node) = subtree.world_mut().get_ui_layout_node_mut(ok_button) {
                node.flow_child_size = Some(Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-4.0, 36.0)));
            }
            subtree.finish_subtree();
        }

        if let Some(UiWidgetState::ModalDialog(data)) =
            self.world_mut().get_ui_widget_state_mut(dialog_entity)
        {
            data.ok_button = Some(ok_button);
            data.cancel_button = Some(cancel_button);
        }

        dialog_entity
    }

    pub fn add_command_palette(&mut self, pool_size: usize) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let accent_color = theme.accent_color;
        let corner_radius = theme.corner_radius;
        let border_color = theme.border_color;
        let row_height = font_size * 1.8;

        let backdrop_entity = self
            .add_node()
            .boundary(Rl(Vec2::new(0.0, 0.0)), Rl(Vec2::new(100.0, 100.0)))
            .with_rect(0.0, 0.0, Vec4::zeros())
            .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.3))
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(42.0))
            .with_interaction()
            .with_visible(false)
            .done();

        let dialog_width = 500.0f32;
        let dialog_height = (pool_size as f32 * row_height) + row_height + 8.0;

        let mut text_input_entity = freecs::Entity::default();
        let mut scroll_entity = freecs::Entity::default();
        let mut result_entities = Vec::with_capacity(pool_size);
        let mut result_text_slots = Vec::with_capacity(pool_size);

        let dialog_entity = self
            .add_node()
            .window(
                Rl(Vec2::new(50.0, 0.0)) + Ab(Vec2::new(-dialog_width / 2.0, 80.0)),
                Ab(Vec2::new(dialog_width, dialog_height)),
                Anchor::TopLeft,
            )
            .with_rect(corner_radius, 1.0, border_color)
            .with_theme_border_color(ThemeColor::Border)
            .with_theme_color::<UiBase>(ThemeColor::Background)
            .with_layer(UiLayer::Popups)
            .with_depth(UiDepthMode::Set(43.0))
            .with_visible(false)
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .with_children(|tree| {
                text_input_entity = tree.add_text_input("Type a command...");
                if let Some(node) = tree.world_mut().get_ui_layout_node_mut(text_input_entity) {
                    node.flow_child_size =
                        Some(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)));
                }

                scroll_entity = tree.add_scroll_area_fill(0.0, 0.0);
                let content = tree
                    .world_mut()
                    .widget::<UiScrollAreaData>(scroll_entity)
                    .map(|d| d.content_entity)
                    .unwrap_or(scroll_entity);

                tree.push_parent(content);

                for _ in 0..pool_size {
                    let label_slot = tree.world_mut().resources.text_cache.add_text("");
                    let shortcut_slot = tree.world_mut().resources.text_cache.add_text("");

                    let row = tree
                        .add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)))
                        .with_rect(2.0, 0.0, Vec4::zeros())
                        .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
                        .with_color::<UiHover>(Vec4::new(
                            accent_color.x,
                            accent_color.y,
                            accent_color.z,
                            0.2,
                        ))
                        .with_interaction()
                        .with_transition::<UiHover>(8.0, 6.0)
                        .with_cursor_icon(winit::window::CursorIcon::Pointer)
                        .flow(FlowDirection::Horizontal, 8.0, 0.0)
                        .with_visible(false)
                        .with_children(|tree| {
                            tree.add_node()
                                .flow_child(Rl(Vec2::new(0.0, 100.0)))
                                .flex_grow(1.0)
                                .with_text_slot(label_slot, font_size)
                                .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                                .with_theme_color::<UiBase>(ThemeColor::Text)
                                .without_pointer_events()
                                .done();
                            tree.add_node()
                                .flow_child(Rl(Vec2::new(0.0, 100.0)))
                                .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                                .auto_size_padding(Vec2::new(8.0, 0.0))
                                .with_text_slot(shortcut_slot, font_size * 0.85)
                                .with_text_alignment(
                                    TextAlignment::Right,
                                    VerticalAlignment::Middle,
                                )
                                .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
                                .without_pointer_events()
                                .done();
                        })
                        .done();

                    result_entities.push(row);
                    result_text_slots.push(label_slot);
                    result_text_slots.push(shortcut_slot);
                }

                tree.pop_parent();
            })
            .done();

        let palette_entity = dialog_entity;
        self.world_mut()
            .set_ui_node_interaction(palette_entity, UiNodeInteraction::default());
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(palette_entity) {
            interaction.accessible_role = Some(AccessibleRole::Dialog);
        }
        self.world_mut().set_ui_widget_state(
            palette_entity,
            UiWidgetState::CommandPalette(UiCommandPaletteData {
                commands: Vec::new(),
                filter_text: String::new(),
                filtered_indices: Vec::new(),
                selected_index: 0,
                open: false,
                executed_command: None,
                text_input_entity,
                result_entities,
                result_text_slots,
                backdrop_entity,
                dialog_entity,
                pool_size,
                scroll_entity,
            }),
        );

        palette_entity
    }

    pub fn add_property_grid(&mut self, label_width: f32) -> freecs::Entity {
        let grid_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 0.0)))
            .flow(FlowDirection::Vertical, 4.0, 2.0)
            .with_interaction()
            .entity();

        self.world_mut().set_ui_widget_state(
            grid_entity,
            UiWidgetState::PropertyGrid(UiPropertyGridData {
                label_width,
                row_entities: Vec::new(),
                label_entities: Vec::new(),
                resize_active: false,
                resize_start_x: 0.0,
                resize_start_width: 0.0,
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(grid_entity) {
            interaction.accessible_role = Some(AccessibleRole::Grid);
        }

        grid_entity
    }

    pub fn add_property_row(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
    ) -> freecs::Entity {
        let label_width = if let Some(UiWidgetState::PropertyGrid(data)) =
            self.world_mut().get_ui_widget_state(grid)
        {
            data.label_width
        } else {
            100.0
        };

        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let row_height = theme.button_height;

        let label_slot = self.world_mut().resources.text_cache.add_text(label);

        let row_entity = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, row_height)))
            .flow_with_alignment(
                FlowDirection::Horizontal,
                4.0,
                4.0,
                FlowAlignment::Start,
                FlowAlignment::Center,
            )
            .without_pointer_events()
            .entity();

        if let Some(p) = self.world_mut().get_parent_mut(row_entity) {
            *p = crate::ecs::transform::components::Parent(Some(parent));
        }
        self.world_mut().resources.children_cache_valid = false;

        self.push_parent(row_entity);

        let label_entity = self
            .add_node()
            .flow_child(Ab(Vec2::new(label_width, row_height)))
            .with_text_slot(label_slot, font_size * 0.9)
            .with_text_alignment(TextAlignment::Right, VerticalAlignment::Middle)
            .with_theme_color::<UiBase>(ThemeColor::TextDisabled)
            .without_pointer_events()
            .done();

        let widget_area = self
            .add_node()
            .flow_child(Ab(Vec2::new(0.0, row_height)))
            .flex_grow(1.0)
            .flow_with_alignment(
                FlowDirection::Horizontal,
                0.0,
                4.0,
                FlowAlignment::Start,
                FlowAlignment::Center,
            )
            .without_pointer_events()
            .entity();

        self.pop_parent();

        if let Some(UiWidgetState::PropertyGrid(data)) =
            self.world_mut().get_ui_widget_state_mut(grid)
        {
            data.row_entities.push(row_entity);
            data.label_entities.push(label_entity);
        }

        widget_area
    }

    pub fn add_property_slider(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        min: f32,
        max: f32,
        initial: f32,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_slider(min, max, initial);
        self.pop_parent();
        widget
    }

    pub fn add_property_toggle(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        initial: bool,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_toggle(initial);
        self.pop_parent();
        widget
    }

    pub fn add_property_text_input(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        placeholder: &str,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_text_input(placeholder);
        self.pop_parent();
        widget
    }

    pub fn add_property_dropdown(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        options: &[&str],
        initial: usize,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_dropdown(options, initial);
        self.pop_parent();
        widget
    }

    pub fn add_property_checkbox(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        initial: bool,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_checkbox(label, initial);
        self.pop_parent();
        widget
    }

    pub fn add_property_drag_value(
        &mut self,
        grid: freecs::Entity,
        parent: freecs::Entity,
        label: &str,
        min: f32,
        max: f32,
        initial: f32,
    ) -> freecs::Entity {
        let widget_area = self.add_property_row(grid, parent, label);
        self.push_parent(widget_area);
        let widget = self.add_drag_value(min, max, initial);
        self.pop_parent();
        widget
    }

    /// Creates a rich text node composed of multiple inline spans, each with
    /// independent color and font-size overrides.
    ///
    /// Returns the container entity. Use
    /// [`ui_rich_text_set_span_text`](crate::ecs::world::World::ui_rich_text_set_span_text)
    /// and related methods to update spans at runtime.
    pub fn add_rich_text(&mut self, spans: &[TextSpan]) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let text_color = theme.text_color;

        let container = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)))
            .auto_size(crate::ecs::ui::components::AutoSizeMode::Height)
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .flow_wrap()
            .without_pointer_events()
            .entity();

        self.push_parent(container);

        let mut span_entities = Vec::with_capacity(spans.len());
        let mut span_text_slots = Vec::with_capacity(spans.len());

        for span in spans {
            let text_slot = self.world_mut().resources.text_cache.add_text(&span.text);
            let mut span_font_size = span.font_size_override.unwrap_or(font_size);
            let span_color = span.color.unwrap_or(text_color);
            let span_font_index = span.font_index.unwrap_or(0);

            if span.bold {
                span_font_size *= 1.05;
            }

            let outline_width = if span.bold { 0.4 } else { 0.0 };
            let outline_color = if span.bold {
                span_color
            } else {
                Vec4::new(0.0, 0.0, 0.0, 0.0)
            };

            let span_wrapper = self
                .add_node()
                .flow_child(Ab(Vec2::new(0.0, span_font_size * 1.5)))
                .auto_size(crate::ecs::ui::components::AutoSizeMode::Width)
                .flow(FlowDirection::Vertical, 0.0, 0.0)
                .without_pointer_events()
                .entity();

            self.push_parent(span_wrapper);

            let entity = self.add_node().done();
            if let Some(content) = self.world_mut().get_ui_node_content_mut(entity) {
                *content = crate::ecs::ui::components::UiNodeContent::Text {
                    text_slot,
                    font_index: span_font_index,
                    font_size_override: Some(span_font_size),
                    outline_color,
                    outline_width,
                    alignment: TextAlignment::Left,
                    vertical_alignment: VerticalAlignment::Middle,
                    overflow: crate::ecs::ui::components::TextOverflow::default(),
                };
            }
            if let Some(node) = self.world_mut().get_ui_layout_node_mut(entity) {
                node.flow_child_size = Some(Ab(Vec2::new(0.0, span_font_size * 1.5)).into());
                node.auto_size = crate::ecs::ui::components::AutoSizeMode::Width;
                node.pointer_events = false;
            }
            if let Some(color) = self.world_mut().get_ui_node_color_mut(entity) {
                color.colors[UiBase::INDEX] = Some(span_color);
            }
            if span.color.is_none() {
                let mut binding = crate::ecs::ui::components::UiThemeBinding::default();
                binding.color_roles[UiBase::INDEX] = Some(ThemeColor::Text);
                self.world_mut().set_ui_theme_binding(entity, binding);
            }

            if span.underline {
                if span.color.is_none() {
                    self.add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
                        .with_rect(0.0, 0.0, Vec4::zeros())
                        .with_theme_color::<UiBase>(ThemeColor::Text)
                        .without_pointer_events()
                        .done();
                } else {
                    self.add_node()
                        .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 1.0)))
                        .with_rect(0.0, 0.0, Vec4::zeros())
                        .with_color::<UiBase>(span_color)
                        .without_pointer_events()
                        .done();
                }
            }

            self.pop_parent();

            span_entities.push(span_wrapper);
            span_text_slots.push(text_slot);
        }

        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            container,
            UiWidgetState::RichText(UiRichTextData {
                span_entities,
                span_text_slots,
            }),
        );

        container
    }

    pub fn add_property_section(&mut self, parent: freecs::Entity, label: &str) -> freecs::Entity {
        let header = self.add_collapsing_header(label, true);

        let reparent_entity = self
            .world_mut()
            .get_parent(header)
            .and_then(|p| p.0)
            .unwrap_or(header);

        if let Some(p) = self.world_mut().get_parent_mut(reparent_entity) {
            *p = crate::ecs::transform::components::Parent(Some(parent));
        }
        self.world_mut().resources.children_cache_valid = false;

        self.world_mut()
            .widget::<UiCollapsingHeaderData>(header)
            .map(|d| d.content_entity)
            .unwrap_or(header)
    }

    pub fn add_table(&mut self, headers: &[&str], widths: &[f32]) -> freecs::Entity {
        let columns: Vec<DataGridColumn> = headers
            .iter()
            .zip(widths.iter())
            .map(|(header, width)| DataGridColumn::new(header, *width))
            .collect();
        self.add_data_grid(&columns, 20)
    }

    /// Creates a data grid with virtual scrolling, sortable column headers,
    /// and multi-selection support.
    ///
    /// The grid uses a pool of pre-allocated row nodes. Only visible rows are
    /// populated. Each frame, call
    /// [`ui_data_grid_set_row_count`](crate::ecs::world::World::ui_data_grid_set_row_count),
    /// query [`ui_data_grid_visible_range`](crate::ecs::world::World::ui_data_grid_visible_range),
    /// and set cell text with
    /// [`ui_data_grid_set_cell`](crate::ecs::world::World::ui_data_grid_set_cell).
    ///
    /// # Arguments
    ///
    /// * `columns` - Column definitions with labels, widths, and sort flags.
    /// * `pool_size` - Maximum number of simultaneously visible rows.
    pub fn add_data_grid(
        &mut self,
        columns: &[DataGridColumn],
        pool_size: usize,
    ) -> freecs::Entity {
        let theme = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme();
        let font_size = theme.font_size;
        let row_height = font_size * 1.8;

        let total_width: f32 = columns.iter().map(|c| c.width).sum();

        let root = self
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 100.0)))
            .flow(FlowDirection::Vertical, 0.0, 0.0)
            .with_rect(0.0, 0.0, Vec4::zeros())
            .with_theme_color::<UiBase>(ThemeColor::Panel)
            .entity();

        self.push_parent(root);

        let header_row = self
            .add_node()
            .flow_child(Ab(Vec2::new(total_width, row_height)))
            .flow(FlowDirection::Horizontal, 0.0, 0.0)
            .with_rect(0.0, 0.0, Vec4::zeros())
            .with_theme_color::<UiBase>(ThemeColor::PanelHeader)
            .without_pointer_events()
            .entity();

        self.push_parent(header_row);

        let mut header_entities = Vec::with_capacity(columns.len());
        let mut header_text_slots = Vec::with_capacity(columns.len());
        for column in columns {
            let text_slot = self
                .world_mut()
                .resources
                .text_cache
                .add_text(&column.label);
            header_text_slots.push(text_slot);

            let header_cell = if column.sortable {
                let entity = self
                    .add_node()
                    .flow_child(Ab(Vec2::new(column.width, row_height)))
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .with_theme_color::<UiHover>(ThemeColor::TextAccent)
                    .with_interaction()
                    .entity();
                if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(entity) {
                    interaction.cursor_icon = Some(winit::window::CursorIcon::Pointer);
                }
                entity
            } else {
                self.add_node()
                    .flow_child(Ab(Vec2::new(column.width, row_height)))
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .done()
            };

            header_entities.push(header_cell);
        }

        self.pop_parent();

        let scroll = self.add_scroll_area_fill(0.0, 0.0);
        let scroll_entity = scroll;
        let body_entity = self
            .world_mut()
            .widget::<UiScrollAreaData>(scroll)
            .map(|d| d.content_entity)
            .unwrap_or(scroll);

        self.push_parent(body_entity);

        let top_spacer = self
            .add_node()
            .flow_child(Ab(Vec2::new(total_width, 0.0)))
            .without_pointer_events()
            .done();

        let mut pool_rows = Vec::with_capacity(pool_size);
        let accent_color = self
            .world_mut()
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .accent_color;
        let selected_bg = Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.3);
        let has_editable = columns.iter().any(|c| c.editable);

        for _ in 0..pool_size {
            let mut row_builder = self
                .add_node()
                .flow_child(Ab(Vec2::new(total_width, row_height)))
                .flow(FlowDirection::Horizontal, 0.0, 0.0)
                .with_rect(0.0, 0.0, Vec4::zeros())
                .with_color::<UiBase>(Vec4::new(0.0, 0.0, 0.0, 0.0))
                .with_color::<crate::ecs::ui::state::UiSelected>(selected_bg)
                .with_interaction();
            if has_editable {
                row_builder = row_builder.with_cursor_icon(winit::window::CursorIcon::Cell);
            }
            let row_entity = row_builder.entity();

            self.push_parent(row_entity);

            let mut cell_text_slots = Vec::with_capacity(columns.len());
            let mut cell_entities = Vec::with_capacity(columns.len());
            for column in columns {
                let text_slot = self.world_mut().resources.text_cache.add_text("");

                let cell_entity = self
                    .add_node()
                    .flow_child(Ab(Vec2::new(column.width, row_height)))
                    .with_text_slot(text_slot, font_size)
                    .with_text_alignment(column.alignment, VerticalAlignment::Middle)
                    .with_theme_color::<UiBase>(ThemeColor::Text)
                    .without_pointer_events()
                    .entity();

                cell_text_slots.push(text_slot);
                cell_entities.push(cell_entity);
            }

            self.pop_parent();

            if let Some(node) = self.world_mut().get_ui_layout_node_mut(row_entity) {
                node.visible = false;
            }

            pool_rows.push(DataGridPoolRow {
                row_entity,
                cell_text_slots,
                cell_entities,
            });
        }

        let bottom_spacer = self
            .add_node()
            .flow_child(Ab(Vec2::new(total_width, 0.0)))
            .without_pointer_events()
            .done();

        self.pop_parent();
        self.pop_parent();

        self.push_parent(root);
        let editing_input_entity = self.add_text_input("");
        if let Some(node) = self
            .world_mut()
            .get_ui_layout_node_mut(editing_input_entity)
        {
            node.visible = false;
            node.flow_child_size = None;
            node.layouts[crate::ecs::ui::state::UiBase::INDEX] =
                Some(crate::ecs::ui::layout_types::UiLayoutType::Window(
                    crate::ecs::ui::layout_types::WindowLayout {
                        position: Ab(Vec2::new(0.0, 0.0)).into(),
                        size: Ab(Vec2::new(100.0, row_height)).into(),
                        anchor: crate::ecs::ui::types::Anchor::TopLeft,
                    },
                ));
        }
        self.pop_parent();

        self.world_mut().set_ui_widget_state(
            root,
            UiWidgetState::DataGrid(UiDataGridData {
                columns: columns.to_vec(),
                row_height,
                pool_size,
                total_rows: 0,
                scroll_entity,
                body_entity,
                header_entities,
                header_text_slots,
                top_spacer,
                bottom_spacer,
                pool_rows,
                visible_start: 0,
                sort_column: None,
                sort_ascending: true,
                sort_changed: false,
                selected_rows: std::collections::HashSet::new(),
                selection_anchor: None,
                selection_changed: false,
                focused: false,
                resize_column: None,
                resize_start_x: 0.0,
                resize_start_width: 0.0,
                filtered_indices: None,
                header_divider_entities: Vec::new(),
                filter_row_entity: None,
                filter_input_entities: Vec::new(),
                filter_texts: Vec::new(),
                editing_cell: None,
                editing_input_entity: Some(editing_input_entity),
            }),
        );
        if let Some(interaction) = self.world_mut().get_ui_node_interaction_mut(root) {
            interaction.accessible_role = Some(AccessibleRole::Grid);
        }

        root
    }

    pub fn add_tile_container(&mut self, size: Vec2) -> freecs::Entity {
        let container = self
            .add_node()
            .window(Ab(Vec2::new(0.0, 0.0)), Ab(size), Anchor::TopLeft)
            .with_rect(0.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_theme_color::<UiBase>(ThemeColor::Panel)
            .with_clip()
            .done();

        let root_tabs = TileNode::Tabs {
            panes: Vec::new(),
            active: 0,
        };

        let tiles: Vec<Option<TileNode>> = vec![Some(root_tabs)];

        let data = UiTileContainerData {
            tiles,
            root: TileId(0),
            rects: vec![Rect::default()],
            dragging_splitter: None,
            pending_tab_drag: None,
            dragging_tab: None,
            drop_preview: None,
            container_entity: container,
            splitter_width: 4.0,
            tab_bar_height: 26.0,
            next_free: Vec::new(),
            hovered_close: None,
        };

        self.world_mut()
            .set_ui_widget_state(container, UiWidgetState::TileContainer(data));

        container
    }

    pub fn build_tiles(
        &mut self,
        container: freecs::Entity,
        f: impl FnOnce(&mut TileBuilder<'_, 'a>),
    ) {
        let mut builder = TileBuilder {
            tree: self,
            container,
        };
        f(&mut builder);
    }
}

pub struct TileBuilder<'t, 'a> {
    tree: &'t mut UiTreeBuilder<'a>,
    container: freecs::Entity,
}

impl<'t, 'a> TileBuilder<'t, 'a> {
    pub fn add_text(&mut self, text: impl Into<String>) -> usize {
        self.tree.world_mut().resources.text_cache.add_text(text)
    }

    pub fn pane(&mut self, title: &str) -> Option<(TileId, freecs::Entity)> {
        self.tree
            .world_mut()
            .ui_tile_add_pane(self.container, title)
    }

    pub fn pane_sibling(
        &mut self,
        sibling: TileId,
        title: &str,
    ) -> Option<(TileId, freecs::Entity)> {
        let tabs_id = match self.tree.world_mut().get_ui_widget_state(self.container) {
            Some(UiWidgetState::TileContainer(data)) => data.find_parent_tabs(sibling)?,
            _ => return None,
        };
        self.tree
            .world_mut()
            .ui_tile_add_pane_to(self.container, tabs_id, title)
    }

    pub fn split_from(
        &mut self,
        pane: TileId,
        direction: SplitDirection,
        ratio: f32,
        title: &str,
    ) -> Option<(TileId, freecs::Entity)> {
        let target = match self.tree.world_mut().get_ui_widget_state(self.container) {
            Some(UiWidgetState::TileContainer(data)) => {
                data.find_parent_tabs(pane).unwrap_or(data.root)
            }
            _ => return None,
        };
        self.tree
            .world_mut()
            .ui_tile_split(self.container, target, direction, ratio, title)
    }

    pub fn content(
        &mut self,
        content_entity: freecs::Entity,
        f: impl FnOnce(&mut UiTreeBuilder<'a>),
    ) {
        self.tree.push_parent(content_entity);
        f(self.tree);
        self.tree.pop_parent();
    }
}

pub(crate) struct RangeSliderVisualUpdate {
    pub fill_entity: freecs::Entity,
    pub low_thumb: freecs::Entity,
    pub high_thumb: freecs::Entity,
    pub text_slot: usize,
    pub precision: usize,
    pub low_normalized: f32,
    pub high_normalized: f32,
    pub low_value: f32,
    pub high_value: f32,
    pub thumb_half: f32,
}

pub(crate) fn update_range_slider_visuals(
    world: &mut crate::ecs::world::World,
    update: &RangeSliderVisualUpdate,
) {
    let RangeSliderVisualUpdate {
        fill_entity,
        low_thumb,
        high_thumb,
        text_slot,
        precision,
        low_normalized,
        high_normalized,
        low_value,
        high_value,
        thumb_half,
    } = *update;
    use crate::ecs::ui::layout_types::UiLayoutType;
    use crate::ecs::ui::state::UiBase;
    use crate::ecs::ui::units::{Ab, Rl};

    if let Some(fill_node) = world.get_ui_layout_node_mut(fill_entity)
        && let Some(UiLayoutType::Boundary(boundary)) = fill_node.layouts[UiBase::INDEX].as_mut()
    {
        boundary.position_1 = Rl(Vec2::new(low_normalized * 100.0, 0.0)).into();
        boundary.position_2 = Rl(Vec2::new(high_normalized * 100.0, 100.0)).into();
    }

    if let Some(low_node) = world.get_ui_layout_node_mut(low_thumb)
        && let Some(UiLayoutType::Boundary(boundary)) = low_node.layouts[UiBase::INDEX].as_mut()
    {
        boundary.position_1 =
            Rl(Vec2::new(low_normalized * 100.0, 0.0)) + Ab(Vec2::new(-thumb_half, 0.0));
        boundary.position_2 =
            Rl(Vec2::new(low_normalized * 100.0, 100.0)) + Ab(Vec2::new(thumb_half, 0.0));
    }

    if let Some(high_node) = world.get_ui_layout_node_mut(high_thumb)
        && let Some(UiLayoutType::Boundary(boundary)) = high_node.layouts[UiBase::INDEX].as_mut()
    {
        boundary.position_1 =
            Rl(Vec2::new(high_normalized * 100.0, 0.0)) + Ab(Vec2::new(-thumb_half, 0.0));
        boundary.position_2 =
            Rl(Vec2::new(high_normalized * 100.0, 100.0)) + Ab(Vec2::new(thumb_half, 0.0));
    }

    world.resources.text_cache.set_text(
        text_slot,
        format!(
            "{:.prec$} - {:.prec$}",
            low_value,
            high_value,
            prec = precision,
        ),
    );
}

fn find_widget_content_in_defs(
    defs: &[ContextMenuItemDef],
    command_id: usize,
) -> Option<freecs::Entity> {
    for def in defs {
        if def.command_id == Some(command_id)
            && let ContextMenuItemKind::Widget { content_entity } = &def.kind
        {
            return Some(*content_entity);
        }
        if let ContextMenuItemKind::Submenu { children, .. } = &def.kind
            && let Some(found) = find_widget_content_in_defs(children, command_id)
        {
            return Some(found);
        }
    }
    None
}

impl crate::ecs::world::World {
    pub fn measure_ui_text_width(&self, text: &str, font_size: f32) -> f32 {
        let best_idx = self
            .resources
            .text_cache
            .font_manager
            .best_bitmap_font_for_size(font_size);
        let font_arc = self
            .resources
            .text_cache
            .font_manager
            .get_bitmap_font_arc(best_idx);
        if let Some(atlas) = font_arc {
            crate::ecs::ui::widget_systems::measure_text_width(&atlas, text, font_size)
        } else {
            0.0
        }
    }

    pub fn widget<T: crate::ecs::ui::components::FromWidgetState>(
        &self,
        entity: freecs::Entity,
    ) -> Option<&T> {
        T::from_widget_state(self.get_ui_widget_state(entity)?)
    }

    pub fn ui_changed(&self, entity: freecs::Entity) -> bool {
        self.get_ui_widget_state(entity)
            .is_some_and(|s| s.changed())
    }

    pub fn ui_button_set_text(&mut self, entity: freecs::Entity, text: &str) {
        if let Some(UiWidgetState::Button(data)) = self.get_ui_widget_state(entity) {
            let slot = data.text_slot;
            self.resources.text_cache.set_text(slot, text);
        }
    }

    pub fn ui_slider_set_value(&mut self, entity: freecs::Entity, value: f32) {
        let update = if let Some(UiWidgetState::Slider(data)) = self.get_ui_widget_state(entity) {
            let normalized = if data.logarithmic && data.min > 0.0 && data.max > data.min {
                ((value / data.min).ln() / (data.max / data.min).ln()).clamp(0.0, 1.0)
            } else {
                ((value - data.min) / (data.max - data.min)).clamp(0.0, 1.0)
            };
            Some((
                data.fill_entity,
                data.text_slot,
                data.min,
                data.max,
                normalized,
                data.logarithmic,
            ))
        } else {
            None
        };

        if let Some((fill_entity, text_slot, min, max, normalized, logarithmic)) = update {
            let clamped = if logarithmic && min > 0.0 && max > min {
                min * (max / min).powf(normalized)
            } else {
                min + normalized * (max - min)
            };
            if let Some(fill_node) = self.get_ui_layout_node_mut(fill_entity)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
                    fill_node.layouts[UiBase::INDEX].as_mut()
            {
                boundary.position_2 = Rl(Vec2::new(normalized * 100.0, 100.0)).into();
            }
            let precision = if logarithmic { 3 } else { 1 };
            self.resources
                .text_cache
                .set_text(text_slot, format!("{:.prec$}", clamped, prec = precision));
            if let Some(UiWidgetState::Slider(data)) = self.get_ui_widget_state_mut(entity) {
                data.value = clamped;
            }
        }
    }

    pub fn ui_range_slider_set_values(&mut self, entity: freecs::Entity, low: f32, high: f32) {
        let update =
            if let Some(UiWidgetState::RangeSlider(data)) = self.get_ui_widget_state(entity) {
                let range = data.max - data.min;
                if range.abs() > f32::EPSILON {
                    let low_n = ((low - data.min) / range).clamp(0.0, 1.0);
                    let high_n = ((high - data.min) / range).clamp(0.0, 1.0);
                    Some((
                        data.fill_entity,
                        data.low_thumb_entity,
                        data.high_thumb_entity,
                        data.text_slot,
                        data.precision,
                        data.thumb_half_size,
                        low_n,
                        high_n,
                        data.min + low_n * range,
                        data.min + high_n * range,
                    ))
                } else {
                    None
                }
            } else {
                None
            };

        if let Some((
            fill_entity,
            low_thumb,
            high_thumb,
            text_slot,
            precision,
            thumb_half,
            low_n,
            high_n,
            clamped_low,
            clamped_high,
        )) = update
        {
            update_range_slider_visuals(
                self,
                &RangeSliderVisualUpdate {
                    fill_entity,
                    low_thumb,
                    high_thumb,
                    text_slot,
                    precision,
                    low_normalized: low_n,
                    high_normalized: high_n,
                    low_value: clamped_low,
                    high_value: clamped_high,
                    thumb_half,
                },
            );
            if let Some(UiWidgetState::RangeSlider(data)) = self.get_ui_widget_state_mut(entity) {
                data.low_value = clamped_low;
                data.high_value = clamped_high;
            }
        }
    }

    pub fn ui_breadcrumb_set_segments(&mut self, entity: freecs::Entity, segments: &[&str]) {
        let text_slots: Vec<usize> =
            if let Some(UiWidgetState::Breadcrumb(data)) = self.get_ui_widget_state_mut(entity) {
                data.segments = segments.iter().map(|s| s.to_string()).collect();
                data.segment_text_slots.clone()
            } else {
                return;
            };
        for (index, text_slot) in text_slots.iter().enumerate() {
            if index < segments.len() {
                self.resources
                    .text_cache
                    .set_text(*text_slot, segments[index]);
            }
        }
    }

    pub fn ui_toggle_set_value(&mut self, entity: freecs::Entity, value: bool) {
        if let Some(UiWidgetState::Toggle(data)) = self.get_ui_widget_state_mut(entity) {
            data.value = value;
        }
    }

    pub fn ui_radio_group_value(&self, group_id: u32) -> Option<usize> {
        let entities: Vec<freecs::Entity> = self
            .query_entities(crate::ecs::world::UI_WIDGET_STATE)
            .collect();

        for entity in entities {
            if let Some(UiWidgetState::Radio(data)) = self.get_ui_widget_state(entity)
                && data.group_id == group_id
                && data.selected
            {
                return Some(data.option_index);
            }
        }
        None
    }

    pub fn ui_progress_bar_set_value(&mut self, entity: freecs::Entity, value: f32) {
        let update =
            if let Some(UiWidgetState::ProgressBar(data)) = self.get_ui_widget_state(entity) {
                Some(data.fill_entity)
            } else {
                None
            };

        if let Some(fill_entity) = update {
            let normalized = value.clamp(0.0, 1.0);
            if let Some(fill_node) = self.get_ui_layout_node_mut(fill_entity)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(boundary)) =
                    fill_node.layouts[UiBase::INDEX].as_mut()
            {
                boundary.position_2 = Rl(Vec2::new(normalized * 100.0, 100.0)).into();
            }
            if let Some(UiWidgetState::ProgressBar(data)) = self.get_ui_widget_state_mut(entity) {
                data.value = normalized;
            }
        }
    }

    pub fn ui_scroll_area_set_offset(&mut self, entity: freecs::Entity, offset: f32) {
        if let Some(UiWidgetState::ScrollArea(data)) = self.get_ui_widget_state_mut(entity) {
            data.scroll_offset = offset;
        }
    }

    pub fn ui_scroll_area_set_snap(&mut self, entity: freecs::Entity, interval: Option<f32>) {
        if let Some(UiWidgetState::ScrollArea(data)) = self.get_ui_widget_state_mut(entity) {
            data.snap_interval = interval;
        }
    }

    pub fn ui_virtual_list_set_count(&mut self, entity: freecs::Entity, count: usize) {
        if let Some(UiWidgetState::VirtualList(data)) = self.get_ui_widget_state_mut(entity) {
            data.total_items = count;
        }
    }

    pub fn ui_virtual_list_selection_changed(&self, entity: freecs::Entity) -> bool {
        if let Some(UiWidgetState::VirtualList(data)) = self.get_ui_widget_state(entity) {
            data.selection_changed
        } else {
            false
        }
    }

    pub fn ui_tab_bar_set_value(&mut self, entity: freecs::Entity, index: usize) {
        let update = if let Some(UiWidgetState::TabBar(data)) = self.get_ui_widget_state(entity) {
            if index < data.tab_entities.len() && index != data.selected_tab {
                Some((
                    data.tab_entities[data.selected_tab],
                    data.tab_entities[index],
                ))
            } else {
                None
            }
        } else {
            None
        };

        if let Some((old_entity, new_entity)) = update {
            let theme = self.resources.retained_ui.theme_state.active_theme();
            let active_bg = theme.accent_color;
            let inactive_bg = theme.background_color;
            if let Some(color) = self.get_ui_node_color_mut(old_entity) {
                color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(inactive_bg);
            }
            if let Some(color) = self.get_ui_node_color_mut(new_entity) {
                color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(active_bg);
            }
            if let Some(UiWidgetState::TabBar(data)) = self.get_ui_widget_state_mut(entity) {
                data.selected_tab = index;
            }
        }
    }

    pub fn ui_text_input_set_value(&mut self, entity: freecs::Entity, text: &str) {
        let (text_slot, placeholder_entity) =
            if let Some(UiWidgetState::TextInput(data)) = self.get_ui_widget_state(entity) {
                (Some(data.text_slot), data.placeholder_entity)
            } else {
                (None, None)
            };

        if let Some(slot) = text_slot {
            self.resources.text_cache.set_text(slot, text);
            if let Some(UiWidgetState::TextInput(data)) = self.get_ui_widget_state_mut(entity) {
                data.text = text.to_string();
                data.cursor_position = text.chars().count();
                data.selection_start = None;
            }
            if let Some(ph_entity) = placeholder_entity
                && let Some(node) = self.get_ui_layout_node_mut(ph_entity)
            {
                node.visible = text.is_empty();
            }
        }
    }

    pub fn ui_text_input_set_mask(
        &mut self,
        entity: freecs::Entity,
        mask: crate::ecs::ui::components::InputMask,
    ) {
        if let Some(UiWidgetState::TextInput(data)) = self.get_ui_widget_state_mut(entity) {
            data.input_mask = mask;
        }
    }

    pub fn ui_text_input_submitted(&self, entity: freecs::Entity) -> Option<String> {
        for event in &self.resources.retained_ui.frame_events {
            if let crate::ecs::ui::resources::UiEvent::TextInputSubmitted {
                entity: event_entity,
                text,
            } = event
                && *event_entity == entity
            {
                return Some(text.clone());
            }
        }
        None
    }

    pub fn ui_dropdown_set_value(&mut self, entity: freecs::Entity, index: usize) {
        let update = if let Some(UiWidgetState::Dropdown(data)) = self.get_ui_widget_state(entity) {
            if index < data.options.len() && index != data.selected_index {
                Some((data.header_text_slot, data.options[index].clone()))
            } else {
                None
            }
        } else {
            None
        };

        if let Some((text_slot, selected_text)) = update {
            self.resources.text_cache.set_text(text_slot, selected_text);
            if let Some(UiWidgetState::Dropdown(data)) = self.get_ui_widget_state_mut(entity) {
                data.selected_index = index;
            }
        }
    }

    pub fn ui_panel_set_pinned(&mut self, entity: freecs::Entity, pinned: bool) {
        if let Some(UiWidgetState::Panel(data)) = self.get_ui_widget_state_mut(entity) {
            data.pinned = pinned;
        }
    }

    pub fn ui_panel_set_header_visible(&mut self, entity: freecs::Entity, visible: bool) {
        if let Some(UiWidgetState::Panel(data)) = self.get_ui_widget_state_mut(entity) {
            let header = data.header_entity;
            let content = data.content_entity;
            data.header_visible = visible;
            self.ui_set_visible(header, visible);
            if let Some(node) = self.get_ui_layout_node_mut(content)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Boundary(ref mut boundary)) =
                    node.layouts[UiBase::INDEX]
            {
                let offset_y = if visible { 32.0 } else { 0.0 };
                boundary.position_1 = crate::ecs::ui::units::Ab(Vec2::new(0.0, offset_y)).into();
            }
        }
    }

    pub fn ui_reserved_areas(&self) -> &crate::ecs::ui::resources::ReservedAreas {
        &self.resources.retained_ui.reserved_areas
    }

    pub fn ui_color_picker_set_value(&mut self, entity: freecs::Entity, color: Vec4) {
        let update =
            if let Some(UiWidgetState::ColorPicker(data)) = self.get_ui_widget_state(entity) {
                let sliders = data.slider_entities;
                let mode = data.mode;
                let swatch = data.swatch_entity;
                Some((sliders, mode, swatch))
            } else {
                None
            };

        if let Some((sliders, mode, swatch)) = update {
            match mode {
                crate::ecs::ui::components::ColorPickerMode::Rgb => {
                    self.ui_slider_set_value(sliders[0], color.x);
                    self.ui_slider_set_value(sliders[1], color.y);
                    self.ui_slider_set_value(sliders[2], color.z);
                    self.ui_slider_set_value(sliders[3], color.w);
                }
                crate::ecs::ui::components::ColorPickerMode::Hsv => {
                    let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
                    self.ui_slider_set_value(sliders[0], hsv.hue);
                    self.ui_slider_set_value(sliders[1], hsv.saturation);
                    self.ui_slider_set_value(sliders[2], hsv.value);
                    self.ui_slider_set_value(sliders[3], color.w);
                }
            }

            if let Some(swatch_color) = self.get_ui_node_color_mut(swatch) {
                swatch_color.colors[UiBase::INDEX] = Some(color);
            }

            if let Some(UiWidgetState::ColorPicker(data)) = self.get_ui_widget_state_mut(entity) {
                data.color = color;
            }
        }
    }

    pub fn ui_clipboard_text(&self) -> &str {
        &self.resources.retained_ui.clipboard_text
    }

    pub fn ui_set_clipboard_text(&mut self, text: impl Into<String>) {
        self.resources.retained_ui.clipboard_text = text.into();
    }

    pub fn ui_events(&self) -> &[crate::ecs::ui::resources::UiEvent] {
        &self.resources.retained_ui.frame_events
    }

    pub fn ui_events_for(
        &self,
        entity: freecs::Entity,
    ) -> impl Iterator<Item = &crate::ecs::ui::resources::UiEvent> {
        use crate::ecs::ui::resources::UiEvent;
        self.resources
            .retained_ui
            .frame_events
            .iter()
            .filter(move |event| match event {
                UiEvent::ButtonClicked(event_entity)
                | UiEvent::SliderChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::ToggleChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::CheckboxChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::RadioChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::TabChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::TextInputChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::TextInputSubmitted {
                    entity: event_entity,
                    ..
                }
                | UiEvent::DropdownChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::MenuItemClicked {
                    entity: event_entity,
                    ..
                }
                | UiEvent::ColorPickerChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::SelectableLabelClicked {
                    entity: event_entity,
                    ..
                }
                | UiEvent::DragValueChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::ContextMenuItemClicked {
                    entity: event_entity,
                    ..
                }
                | UiEvent::ModalClosed {
                    entity: event_entity,
                    ..
                }
                | UiEvent::CommandPaletteExecuted {
                    entity: event_entity,
                    ..
                } => *event_entity == entity,
                UiEvent::TreeNodeSelected {
                    tree: event_entity, ..
                }
                | UiEvent::TreeNodeToggled {
                    tree: event_entity, ..
                }
                | UiEvent::TreeNodeContextMenu {
                    tree: event_entity, ..
                }
                | UiEvent::TreeNodeExpandRequested {
                    tree: event_entity, ..
                } => *event_entity == entity,
                UiEvent::TileTabActivated {
                    container: event_entity,
                    ..
                }
                | UiEvent::TileTabClosed {
                    container: event_entity,
                    ..
                }
                | UiEvent::TileSplitterMoved {
                    container: event_entity,
                    ..
                } => *event_entity == entity,
                UiEvent::DragStarted {
                    source: event_entity,
                    ..
                }
                | UiEvent::DragCancelled {
                    source: event_entity,
                    ..
                } => *event_entity == entity,
                UiEvent::DragDropped {
                    target: event_entity,
                    ..
                }
                | UiEvent::CanvasClicked {
                    entity: event_entity,
                    ..
                } => *event_entity == entity,
                UiEvent::DragEnter {
                    target: event_entity,
                    ..
                }
                | UiEvent::DragOver {
                    target: event_entity,
                    ..
                }
                | UiEvent::DragLeave {
                    target: event_entity,
                    ..
                } => *event_entity == entity,
                UiEvent::ShortcutTriggered { .. } => false,
                UiEvent::VirtualListItemClicked {
                    entity: event_entity,
                    ..
                }
                | UiEvent::TextAreaChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::RichTextEditorChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::DataGridFilterChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::RangeSliderChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::DataGridCellEdited {
                    entity: event_entity,
                    ..
                }
                | UiEvent::BreadcrumbClicked {
                    entity: event_entity,
                    ..
                }
                | UiEvent::SplitterChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::MultiSelectChanged {
                    entity: event_entity,
                    ..
                }
                | UiEvent::DatePickerChanged {
                    entity: event_entity,
                    ..
                } => *event_entity == entity,
            })
    }

    pub fn ui_despawn_node(&mut self, entity: freecs::Entity) {
        if !self.resources.children_cache_valid {
            self.validate_and_rebuild_children_cache();
        }

        let mut stack = vec![entity];
        let mut all_entities = Vec::new();
        while let Some(current) = stack.pop() {
            all_entities.push(current);
            if let Some(children) = self.resources.children_cache.get(&current) {
                for child in children {
                    stack.push(*child);
                }
            }
        }

        for descendant in &all_entities {
            if let Some(content) = self.get_ui_node_content(*descendant)
                && let crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. } = content
            {
                let slot = *text_slot;
                self.resources.text_cache.remove_text(slot);
            }

            if let Some(widget) = self.get_ui_widget_state(*descendant).cloned() {
                match &widget {
                    UiWidgetState::Radio(data) => {
                        if let Some(group) = self
                            .resources
                            .retained_ui
                            .radio_groups
                            .get_mut(&data.group_id)
                        {
                            group.retain(|e| *e != *descendant);
                        }
                    }
                    UiWidgetState::SelectableLabel(data) => {
                        if let Some(gid) = data.group_id
                            && let Some(group) = self
                                .resources
                                .retained_ui
                                .selectable_label_groups
                                .get_mut(&gid)
                        {
                            group.retain(|e| *e != *descendant);
                        }
                    }
                    _ => {}
                }
                let slots: Vec<usize> = match &widget {
                    UiWidgetState::Button(data) => vec![data.text_slot],
                    UiWidgetState::Slider(data) => vec![data.text_slot],
                    UiWidgetState::TextInput(data) => vec![data.text_slot],
                    UiWidgetState::CollapsingHeader(data) => vec![data.arrow_text_slot],
                    UiWidgetState::Dropdown(data) => vec![data.header_text_slot],
                    UiWidgetState::Menu(data) => vec![data.label_text_slot],
                    UiWidgetState::Panel(data) => {
                        let mut s = vec![data.title_text_slot];
                        if let Some(slot) = data.collapse_button_text_slot {
                            s.push(slot);
                        }
                        s
                    }
                    UiWidgetState::SelectableLabel(data) => vec![data.text_slot],
                    UiWidgetState::DragValue(data) => vec![data.text_slot],
                    UiWidgetState::TreeNode(data) => {
                        vec![data.text_slot, data.arrow_text_slot]
                    }
                    UiWidgetState::ModalDialog(data) => vec![data.title_text_slot],
                    UiWidgetState::TabBar(data) => data.tab_text_slots.clone(),
                    UiWidgetState::TextArea(data) => vec![data.text_slot],
                    UiWidgetState::RichText(data) => data.span_text_slots.clone(),
                    UiWidgetState::DataGrid(data) => {
                        let mut s = data.header_text_slots.clone();
                        for row in &data.pool_rows {
                            s.extend_from_slice(&row.cell_text_slots);
                        }
                        s
                    }
                    UiWidgetState::CommandPalette(data) => data.result_text_slots.clone(),
                    UiWidgetState::RichTextEditor(data) => vec![data.text_slot],
                    UiWidgetState::Breadcrumb(data) => data.segment_text_slots.clone(),
                    UiWidgetState::MultiSelect(data) => vec![data.header_text_slot],
                    UiWidgetState::DatePicker(data) => {
                        let mut s = vec![data.header_text_slot, data.month_label_slot];
                        s.extend_from_slice(&data.day_text_slots);
                        s
                    }
                    _ => vec![],
                };
                for slot in slots {
                    self.resources.text_cache.remove_text(slot);
                }
            }
        }

        crate::ecs::world::commands::despawn_recursive_immediate(self, entity);
        self.resources.children_cache_valid = false;
        self.resources.retained_ui.layout_dirty = true;
    }

    pub fn ui_clear_children(&mut self, parent: freecs::Entity) {
        if !self.resources.children_cache_valid {
            self.validate_and_rebuild_children_cache();
        }

        let children: Vec<freecs::Entity> = self
            .resources
            .children_cache
            .get(&parent)
            .cloned()
            .unwrap_or_default();

        for child in children {
            self.ui_despawn_node(child);
        }
        self.resources.retained_ui.layout_dirty = true;
    }

    pub fn ui_rebuild_children(
        &mut self,
        parent: freecs::Entity,
        build: impl FnOnce(&mut crate::ecs::ui::builder::UiTreeBuilder),
    ) {
        self.ui_clear_children(parent);
        let content = self.ui_widget_content(parent).unwrap_or(parent);
        let mut builder = crate::ecs::ui::builder::UiTreeBuilder::from_parent(self, content);
        build(&mut builder);
        builder.finish_subtree();
    }

    pub fn ui_focus(&mut self, entity: freecs::Entity) {
        self.resources.retained_ui.focused_entity = Some(entity);
    }

    pub fn ui_set_disabled(&mut self, entity: freecs::Entity, disabled: bool) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.disabled = disabled;
        }

        if disabled {
            let base_color = self
                .get_ui_node_color(entity)
                .and_then(|color| color.colors[UiBase::INDEX]);
            if let Some(base) = base_color
                && let Some(color_comp) = self.get_ui_node_color_mut(entity)
            {
                color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = Some(
                    nalgebra_glm::Vec4::new(base.x, base.y, base.z, base.w * 0.4),
                );
            }
        } else if let Some(color_comp) = self.get_ui_node_color_mut(entity) {
            color_comp.colors[crate::ecs::ui::state::UiDisabled::INDEX] = None;
        }
    }

    pub fn ui_set_disabled_recursive(&mut self, entity: freecs::Entity, disabled: bool) {
        if !self.resources.children_cache_valid {
            self.validate_and_rebuild_children_cache();
        }
        let mut stack = vec![entity];
        while let Some(current) = stack.pop() {
            self.ui_set_disabled(current, disabled);
            if let Some(children) = self.resources.children_cache.get(&current) {
                for child in children {
                    stack.push(*child);
                }
            }
        }
    }

    pub fn ui_is_disabled(&self, entity: freecs::Entity) -> bool {
        self.get_ui_node_interaction(entity)
            .is_some_and(|interaction| interaction.disabled)
    }

    pub fn ui_set_visible(&mut self, entity: freecs::Entity, visible: bool) {
        if let Some(node) = self.get_ui_layout_node_mut(entity) {
            if let Some(animation) = &mut node.animation {
                if visible {
                    if animation.intro.is_some() {
                        animation.phase =
                            crate::ecs::ui::components::UiAnimationPhase::IntroPlaying;
                        animation.progress = 0.0;
                    }
                    node.visible = true;
                } else if animation.outro.is_some() {
                    animation.phase = crate::ecs::ui::components::UiAnimationPhase::OutroPlaying;
                    animation.progress = 0.0;
                } else {
                    animation.phase = crate::ecs::ui::components::UiAnimationPhase::Idle;
                    node.visible = false;
                }
            } else {
                node.visible = visible;
            }
        }
    }

    pub fn ui_set_visible_exclusive(
        &mut self,
        entities: &[freecs::Entity],
        active: freecs::Entity,
    ) {
        for &entity in entities {
            self.ui_set_visible(entity, entity == active);
        }
    }

    pub fn ui_node_visible(&self, entity: freecs::Entity) -> bool {
        self.get_ui_layout_node(entity)
            .is_some_and(|node| node.visible)
    }

    pub fn ui_node_effectively_visible(&self, entity: freecs::Entity) -> bool {
        let Some(node) = self.get_ui_layout_node(entity) else {
            return false;
        };
        if !node.visible {
            return false;
        }
        let mut current = entity;
        while let Some(crate::ecs::transform::components::Parent(Some(parent))) =
            self.get_parent(current)
        {
            let parent = *parent;
            if let Some(parent_node) = self.get_ui_layout_node(parent)
                && !parent_node.visible
            {
                return false;
            }
            current = parent;
        }
        true
    }

    pub fn ui_rect(&self, entity: freecs::Entity) -> Option<crate::ecs::ui::types::Rect> {
        self.get_ui_layout_node(entity)
            .map(|node| node.computed_rect)
    }

    pub fn ui_size(&self, entity: freecs::Entity) -> Option<Vec2> {
        self.get_ui_layout_node(entity)
            .map(|node| node.computed_rect.size())
    }

    pub fn ui_set_text(&mut self, entity: freecs::Entity, text: &str) {
        let slot = self.ui_text_slot_for_entity(entity);
        if let Some(text_slot) = slot {
            self.resources.text_cache.set_text(text_slot, text);
        }
    }

    pub fn ui_set_label_text(&mut self, entity: freecs::Entity, text: &str) {
        self.ui_set_text(entity, text);
    }

    pub fn ui_label_text(&self, entity: freecs::Entity) -> Option<String> {
        self.ui_text_slot_for_entity(entity)
            .and_then(|slot| self.resources.text_cache.get_text(slot).map(String::from))
    }

    pub(crate) fn ui_text_slot_for_entity(&self, entity: freecs::Entity) -> Option<usize> {
        if let Some(crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. }) =
            self.get_ui_node_content(entity)
        {
            return Some(*text_slot);
        }
        match self.get_ui_widget_state(entity) {
            Some(UiWidgetState::Button(data)) => Some(data.text_slot),
            Some(UiWidgetState::Slider(data)) => Some(data.text_slot),
            Some(UiWidgetState::TextInput(data)) => Some(data.text_slot),
            Some(UiWidgetState::SelectableLabel(data)) => Some(data.text_slot),
            Some(UiWidgetState::DragValue(data)) => Some(data.text_slot),
            _ => None,
        }
    }

    pub fn ui_set_tooltip_entity(
        &mut self,
        entity: freecs::Entity,
        tooltip_entity: Option<freecs::Entity>,
    ) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.tooltip_entity = tooltip_entity;
        }
    }

    pub fn ui_set_tooltip_text(&mut self, entity: freecs::Entity, text: Option<&str>) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.tooltip_text = text.map(String::from);
        }
    }

    pub fn ui_set_error(&mut self, entity: freecs::Entity, error: Option<&str>) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.error_text = error.map(String::from);
            interaction.tooltip_text = error.map(String::from);
        }
    }

    pub fn ui_has_error(&self, entity: freecs::Entity) -> bool {
        self.get_ui_node_interaction(entity)
            .is_some_and(|interaction| interaction.error_text.is_some())
    }

    pub fn ui_clear_error(&mut self, entity: freecs::Entity) {
        self.ui_set_error(entity, None);
    }

    pub fn ui_add_validation_rule(
        &mut self,
        entity: freecs::Entity,
        rule: crate::ecs::ui::components::ValidationRule,
    ) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.validation_rules.push(rule);
        }
    }

    pub fn ui_set_validation_rules(
        &mut self,
        entity: freecs::Entity,
        rules: Vec<crate::ecs::ui::components::ValidationRule>,
    ) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.validation_rules = rules;
        }
    }

    pub fn ui_validate(&mut self, entity: freecs::Entity) -> bool {
        let text = self
            .widget::<UiTextInputData>(entity)
            .map(|d| d.text.clone())
            .unwrap_or_default();
        let rules: Vec<crate::ecs::ui::components::ValidationRule> = self
            .get_ui_node_interaction(entity)
            .map(|interaction| interaction.validation_rules.clone())
            .unwrap_or_default();

        let mut error: Option<String> = None;
        for rule in &rules {
            if let Err(message) = rule.validate(&text) {
                error = Some(message);
                break;
            }
        }

        let is_valid = error.is_none();
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.error_text = error.clone();
            interaction.tooltip_text = error;
        }
        is_valid
    }

    pub fn ui_is_valid(&self, entity: freecs::Entity) -> bool {
        self.get_ui_node_interaction(entity)
            .is_some_and(|interaction| interaction.error_text.is_none())
    }

    pub fn ui_show_toast(
        &mut self,
        message: &str,
        severity: crate::ecs::ui::resources::ToastSeverity,
        duration: f32,
    ) {
        use crate::ecs::ui::builder::UiTreeBuilder;
        use crate::ecs::ui::components::{UiAnimationPhase, UiAnimationType};
        use crate::ecs::ui::resources::{ToastEntry, ToastSeverity};

        let container = if let Some(entity) = self.resources.retained_ui.toast_container {
            entity
        } else {
            let mut tree = UiTreeBuilder::new(self);
            let container = tree
                .add_node()
                .boundary(
                    Rl(Vec2::new(70.0, 0.0)) + Ab(Vec2::new(0.0, 60.0)),
                    Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-12.0, -50.0)),
                )
                .flow(
                    crate::ecs::ui::layout_types::FlowDirection::Vertical,
                    0.0,
                    8.0,
                )
                .with_depth(UiDepthMode::Set(50.0))
                .without_pointer_events()
                .entity();
            tree.finish();
            self.resources.retained_ui.toast_container = Some(container);
            container
        };

        let accent = match severity {
            ToastSeverity::Info => Vec4::new(0.3, 0.6, 1.0, 1.0),
            ToastSeverity::Success => Vec4::new(0.2, 0.8, 0.3, 1.0),
            ToastSeverity::Warning => Vec4::new(1.0, 0.7, 0.1, 1.0),
            ToastSeverity::Error => Vec4::new(1.0, 0.25, 0.25, 1.0),
        };

        let text_slot = self.resources.text_cache.add_text(message);
        let spawn_time = self.resources.retained_ui.current_time;

        let mut tree = UiTreeBuilder::from_parent(self, container);

        let toast = tree
            .add_node()
            .flow_child(Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(0.0, 36.0)))
            .with_rect(6.0, 1.0, Vec4::new(0.15, 0.15, 0.22, 0.5))
            .with_color::<UiBase>(Vec4::new(0.06, 0.06, 0.1, 0.95))
            .with_intro(UiAnimationType::SlideRight, 0.2)
            .with_outro(UiAnimationType::Fade, 0.15)
            .without_pointer_events()
            .entity();

        tree.push_parent(toast);

        tree.add_node()
            .boundary(
                Rl(Vec2::new(0.0, 0.0)),
                Ab(Vec2::new(4.0, 0.0)) + Rl(Vec2::new(0.0, 100.0)),
            )
            .with_rect(6.0, 0.0, Vec4::new(0.0, 0.0, 0.0, 0.0))
            .with_color::<UiBase>(accent)
            .without_pointer_events();

        tree.add_node()
            .window(
                Ab(Vec2::new(14.0, 18.0)),
                Rl(Vec2::new(100.0, 0.0)) + Ab(Vec2::new(-20.0, 20.0)),
                Anchor::CenterLeft,
            )
            .with_text_slot(text_slot, 13.0)
            .with_text_alignment(TextAlignment::Left, VerticalAlignment::Middle)
            .with_color::<UiBase>(Vec4::new(0.9, 0.9, 0.95, 1.0))
            .without_pointer_events();

        tree.pop_parent();
        tree.finish_subtree();

        if let Some(node) = self.get_ui_layout_node_mut(toast) {
            if let Some(animation) = &mut node.animation {
                animation.phase = UiAnimationPhase::IntroPlaying;
                animation.progress = 0.0;
            }
            node.visible = true;
        }

        self.resources.retained_ui.toast_entries.push(ToastEntry {
            entity: toast,
            spawn_time,
            duration,
            dismissing: false,
        });
    }

    pub fn ui_tick_toasts(&mut self) {
        let current_time = self.resources.retained_ui.current_time;
        let entries = std::mem::take(&mut self.resources.retained_ui.toast_entries);
        let mut kept = Vec::new();
        let mut to_despawn = Vec::new();

        for mut entry in entries {
            let elapsed = (current_time - entry.spawn_time) as f32;

            if entry.dismissing {
                let phase = self
                    .get_ui_layout_node(entry.entity)
                    .and_then(|node| node.animation.as_ref())
                    .map(|anim| anim.phase);

                if phase == Some(crate::ecs::ui::components::UiAnimationPhase::OutroComplete)
                    || !self.ui_node_visible(entry.entity)
                {
                    to_despawn.push(entry.entity);
                } else {
                    kept.push(entry);
                }
            } else if elapsed >= entry.duration {
                entry.dismissing = true;
                self.ui_set_visible(entry.entity, false);
                kept.push(entry);
            } else {
                kept.push(entry);
            }
        }

        self.resources.retained_ui.toast_entries = kept;

        for entity in to_despawn {
            self.ui_despawn_node(entity);
        }
    }

    pub fn ui_set_selected(&mut self, entity: freecs::Entity, selected: bool) {
        let accent = self
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .accent_color;
        let selected_bg = Vec4::new(accent.x, accent.y, accent.z, 0.3);

        if let Some(UiWidgetState::SelectableLabel(data)) = self.get_ui_widget_state_mut(entity) {
            data.selected = selected;
        }
        if let Some(color) = self.get_ui_node_color_mut(entity) {
            color.colors[crate::ecs::ui::state::UiSelected::INDEX] =
                if selected { Some(selected_bg) } else { None };
        }
        if let Some(weights) = self.get_ui_state_weights_mut(entity) {
            weights.weights[crate::ecs::ui::state::UiSelected::INDEX] =
                if selected { 1.0 } else { 0.0 };
        }
    }

    pub fn ui_drag_value_set_value(&mut self, entity: freecs::Entity, value: f32) {
        let update = if let Some(UiWidgetState::DragValue(data)) = self.get_ui_widget_state(entity)
        {
            let clamped = value.clamp(data.min, data.max);
            Some((
                data.text_slot,
                data.prefix.clone(),
                data.suffix.clone(),
                data.precision,
                clamped,
            ))
        } else {
            None
        };

        if let Some((text_slot, prefix, suffix, precision, clamped)) = update {
            let display = format!("{prefix}{clamped:.prec$}{suffix}", prec = precision);
            self.resources.text_cache.set_text(text_slot, &display);
            if let Some(UiWidgetState::DragValue(data)) = self.get_ui_widget_state_mut(entity) {
                data.value = clamped;
            }
        }
    }

    pub fn ui_show_context_menu(&mut self, entity: freecs::Entity, position: Vec2) {
        if let Some(old_menu) = self.resources.retained_ui.active_context_menu
            && old_menu != entity
            && let Some(UiWidgetState::ContextMenu(old_data)) =
                self.get_ui_widget_state(old_menu).cloned().as_ref()
        {
            if let Some(node) = self.get_ui_layout_node_mut(old_data.popup_entity) {
                node.visible = false;
            }
            if let Some(UiWidgetState::ContextMenu(wd)) = self.get_ui_widget_state_mut(old_menu) {
                wd.open = false;
            }
        }

        let popup = if let Some(UiWidgetState::ContextMenu(data)) = self.get_ui_widget_state(entity)
        {
            Some(data.popup_entity)
        } else {
            None
        };

        if let Some(popup_entity) = popup {
            let dpi_scale = self.resources.window.cached_scale_factor;
            if let Some(node) = self.get_ui_layout_node_mut(popup_entity)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                    node.layouts[crate::ecs::ui::state::UiBase::INDEX].as_mut()
            {
                window.position = crate::ecs::ui::units::Ab(position / dpi_scale).into();
                node.visible = true;
            }
            if let Some(UiWidgetState::ContextMenu(data)) = self.get_ui_widget_state_mut(entity) {
                data.open = true;
                data.clicked_item = None;
            }
            self.resources.retained_ui.active_context_menu = Some(entity);
        }
    }

    pub fn ui_context_menu_widget_content(
        &self,
        entity: freecs::Entity,
        command_id: usize,
    ) -> Option<freecs::Entity> {
        if let Some(UiWidgetState::ContextMenu(data)) = self.get_ui_widget_state(entity) {
            find_widget_content_in_defs(&data.item_defs, command_id)
        } else {
            None
        }
    }

    pub fn ui_close_context_menus(&mut self) {
        if let Some(menu_entity) = self.resources.retained_ui.active_context_menu {
            let popup = if let Some(UiWidgetState::ContextMenu(data)) =
                self.get_ui_widget_state(menu_entity)
            {
                Some(data.popup_entity)
            } else {
                None
            };
            if let Some(popup_entity) = popup
                && let Some(node) = self.get_ui_layout_node_mut(popup_entity)
            {
                node.visible = false;
            }
            if let Some(UiWidgetState::ContextMenu(data)) =
                self.get_ui_widget_state_mut(menu_entity)
            {
                data.open = false;
            }
            self.resources.retained_ui.active_context_menu = None;
        }
    }

    pub fn ui_tree_view_set_filter(&mut self, tree: freecs::Entity, text: &str) {
        let tree_data = if let Some(UiWidgetState::TreeView(data)) = self.get_ui_widget_state(tree)
        {
            data.clone()
        } else {
            return;
        };

        let filter_lower = text.to_lowercase();
        let filtering = !filter_lower.is_empty();

        if filtering && !tree_data.filter_active {
            let mut pre_filter = std::collections::HashMap::new();
            for &node_entity in &tree_data.node_entities {
                if let Some(UiWidgetState::TreeNode(nd)) = self.get_ui_widget_state(node_entity) {
                    pre_filter.insert(node_entity, nd.expanded);
                }
            }
            if let Some(UiWidgetState::TreeView(data)) = self.get_ui_widget_state_mut(tree) {
                data.pre_filter_expanded = pre_filter;
            }
        }

        if filtering {
            let mut matching_nodes: std::collections::HashSet<freecs::Entity> =
                std::collections::HashSet::new();

            for &node_entity in &tree_data.node_entities {
                if let Some(UiWidgetState::TreeNode(nd)) = self.get_ui_widget_state(node_entity)
                    && nd.label.to_lowercase().contains(&filter_lower)
                {
                    matching_nodes.insert(node_entity);
                    let mut ancestor = nd.parent_node;
                    while let Some(anc) = ancestor {
                        matching_nodes.insert(anc);
                        ancestor = if let Some(UiWidgetState::TreeNode(anc_data)) =
                            self.get_ui_widget_state(anc)
                        {
                            anc_data.parent_node
                        } else {
                            None
                        };
                    }
                }
            }

            for &node_entity in &tree_data.node_entities {
                let node_info = if let Some(UiWidgetState::TreeNode(nd)) =
                    self.get_ui_widget_state(node_entity)
                {
                    Some((nd.wrapper_entity, nd.parent_node.is_some()))
                } else {
                    None
                };

                if let Some((wrapper, _has_parent)) = node_info {
                    let visible = matching_nodes.contains(&node_entity);
                    if let Some(node) = self.get_ui_layout_node_mut(wrapper) {
                        node.visible = visible;
                    }
                    if visible {
                        self.ui_tree_node_set_expanded(node_entity, true);
                    }
                }
            }
        } else if tree_data.filter_active {
            let pre_filter = tree_data.pre_filter_expanded.clone();
            for &node_entity in &tree_data.node_entities {
                let wrapper = if let Some(UiWidgetState::TreeNode(nd)) =
                    self.get_ui_widget_state(node_entity)
                {
                    Some(nd.wrapper_entity)
                } else {
                    None
                };
                if let Some(wrapper) = wrapper
                    && let Some(node) = self.get_ui_layout_node_mut(wrapper)
                {
                    node.visible = true;
                }
                let was_expanded = pre_filter.get(&node_entity).copied().unwrap_or(false);
                self.ui_tree_node_set_expanded(node_entity, was_expanded);
            }
        }

        if let Some(UiWidgetState::TreeView(data)) = self.get_ui_widget_state_mut(tree) {
            data.filter_text = text.to_string();
            data.filter_active = filtering;
            if !filtering {
                data.pre_filter_expanded.clear();
            }
        }
    }

    pub fn ui_tree_view_clear_filter(&mut self, tree: freecs::Entity) {
        self.ui_tree_view_set_filter(tree, "");
    }

    pub fn ui_tree_node_set_expanded(&mut self, entity: freecs::Entity, expanded: bool) {
        let update = if let Some(UiWidgetState::TreeNode(data)) = self.get_ui_widget_state(entity) {
            Some((data.arrow_text_slot, data.children_container))
        } else {
            None
        };

        if let Some((arrow_slot, children)) = update {
            self.resources
                .text_cache
                .set_text(arrow_slot, if expanded { "\u{25BC}" } else { "\u{25B6}" });
            if let Some(node) = self.get_ui_layout_node_mut(children) {
                node.visible = expanded;
            }
            if let Some(UiWidgetState::TreeNode(data)) = self.get_ui_widget_state_mut(entity) {
                data.expanded = expanded;
            }
        }
    }

    pub fn ui_tree_node_mark_loaded(&mut self, entity: freecs::Entity) {
        if let Some(UiWidgetState::TreeNode(data)) = self.get_ui_widget_state_mut(entity) {
            data.lazy_loaded = true;
        }
    }

    pub fn ui_show_modal(&mut self, entity: freecs::Entity) {
        let backdrop =
            if let Some(UiWidgetState::ModalDialog(data)) = self.get_ui_widget_state(entity) {
                Some(data.backdrop_entity)
            } else {
                None
            };

        if let Some(backdrop_entity) = backdrop {
            if let Some(node) = self.get_ui_layout_node_mut(backdrop_entity) {
                node.visible = true;
            }
            if let Some(node) = self.get_ui_layout_node_mut(entity) {
                node.visible = true;
            }
            if let Some(UiWidgetState::ModalDialog(data)) = self.get_ui_widget_state_mut(entity) {
                data.result = None;
            }
            self.resources.retained_ui.active_modal = Some(entity);
        }
    }

    /// Returns the content entity of any container widget (collapsing header,
    /// panel, scroll area, tree view, modal dialog). For non-widget entities
    /// (such as a row or column), returns the entity itself so it can be used
    /// as a parent for nested children.
    ///
    /// ```ignore
    /// let content = world.ui_widget_content(header_entity).unwrap();
    /// ```
    pub fn ui_widget_content(&self, entity: freecs::Entity) -> Option<freecs::Entity> {
        match self.get_ui_widget_state(entity) {
            Some(UiWidgetState::CollapsingHeader(data)) => Some(data.content_entity),
            Some(UiWidgetState::ScrollArea(data)) => Some(data.content_entity),
            Some(UiWidgetState::Panel(data)) => Some(data.content_entity),
            Some(UiWidgetState::ModalDialog(data)) => Some(data.content_entity),
            Some(UiWidgetState::TreeView(data)) => Some(data.content_entity),
            Some(UiWidgetState::TileContainer(data)) => Some(data.container_entity),
            _ => Some(entity),
        }
    }

    pub fn ui_show_command_palette(&mut self, entity: freecs::Entity) {
        let (backdrop, text_input) =
            if let Some(UiWidgetState::CommandPalette(data)) = self.get_ui_widget_state(entity) {
                (data.backdrop_entity, data.text_input_entity)
            } else {
                return;
            };

        if let Some(node) = self.get_ui_layout_node_mut(backdrop) {
            node.visible = true;
        }
        if let Some(node) = self.get_ui_layout_node_mut(entity) {
            node.visible = true;
        }

        self.ui_text_input_set_value(text_input, "");
        self.resources.retained_ui.focused_entity = Some(text_input);

        if let Some(UiWidgetState::CommandPalette(data)) = self.get_ui_widget_state_mut(entity) {
            data.open = true;
            data.executed_command = None;
            data.filter_text.clear();
            data.selected_index = 0;
            data.filtered_indices = (0..data.commands.len()).collect();
        }

        self.ui_command_palette_rebuild_results(entity);
    }

    pub fn ui_command_palette_register(
        &mut self,
        entity: freecs::Entity,
        label: &str,
        shortcut: &str,
        category: &str,
    ) {
        if let Some(UiWidgetState::CommandPalette(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CommandEntry {
                label: label.to_string(),
                shortcut: shortcut.to_string(),
                category: category.to_string(),
                enabled: true,
            });
            data.filtered_indices = (0..data.commands.len()).collect();
        }
    }

    pub fn ui_command_palette_clear(&mut self, entity: freecs::Entity) {
        if let Some(UiWidgetState::CommandPalette(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.clear();
            data.filtered_indices.clear();
            data.selected_index = 0;
        }
    }

    pub(crate) fn ui_command_palette_rebuild_results(&mut self, entity: freecs::Entity) {
        let data =
            if let Some(UiWidgetState::CommandPalette(data)) = self.get_ui_widget_state(entity) {
                data.clone()
            } else {
                return;
            };

        for (pool_index, &row_entity) in data.result_entities.iter().enumerate() {
            if pool_index < data.filtered_indices.len() {
                let cmd_index = data.filtered_indices[pool_index];
                let cmd = &data.commands[cmd_index];
                let label_slot = data.result_text_slots[pool_index * 2];
                let shortcut_slot = data.result_text_slots[pool_index * 2 + 1];

                let display = if cmd.category.is_empty() {
                    cmd.label.clone()
                } else {
                    format!("{}: {}", cmd.category, cmd.label)
                };
                self.resources.text_cache.set_text(label_slot, &display);
                self.resources
                    .text_cache
                    .set_text(shortcut_slot, &cmd.shortcut);

                if let Some(node) = self.get_ui_layout_node_mut(row_entity) {
                    node.visible = true;
                }
            } else if let Some(node) = self.get_ui_layout_node_mut(row_entity) {
                node.visible = false;
            }
        }
    }

    /// Updates the text of a specific span in a rich text widget.
    pub fn ui_rich_text_set_span_text(
        &mut self,
        entity: freecs::Entity,
        span_index: usize,
        text: &str,
    ) {
        if let Some(UiWidgetState::RichText(data)) = self.get_ui_widget_state(entity)
            && let Some(&text_slot) = data.span_text_slots.get(span_index)
        {
            self.resources.text_cache.set_text(text_slot, text);
        }
    }

    /// Updates the color of a specific span in a rich text widget.
    pub fn ui_rich_text_set_span_color(
        &mut self,
        entity: freecs::Entity,
        span_index: usize,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::RichText(data)) = self.get_ui_widget_state(entity)
            && let Some(&span_entity) = data.span_entities.get(span_index)
            && let Some(node_color) = self.get_ui_node_color_mut(span_entity)
        {
            node_color.colors[0] = Some(color);
        }
    }

    /// Returns all events that have bubbled through `entity` from a descendant.
    /// This allows a container to observe events from any of its children
    /// without polling each child individually.
    ///
    /// ```ignore
    /// for event in world.ui_bubbled_events_for(form_container) {
    ///     if let UiEvent::TextInputChanged { entity, text } = &event.event {
    ///         // Some text input inside the form changed
    ///     }
    /// }
    /// ```
    pub fn ui_bubbled_events_for(
        &self,
        entity: freecs::Entity,
    ) -> Vec<crate::ecs::ui::resources::BubbledUiEvent> {
        self.resources
            .retained_ui
            .bubbled_events
            .iter()
            .filter(|event| event.ancestor == entity && !event.stopped)
            .cloned()
            .collect()
    }

    /// Sets the total number of data rows in the grid. Call this every frame
    /// (or when data changes) before setting cell contents.
    pub fn ui_data_grid_set_row_count(&mut self, entity: freecs::Entity, count: usize) {
        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state_mut(entity) {
            data.total_rows = count;
            data.selected_rows.retain(|&row| row < count);
        }
    }

    /// Sets the text of a cell in the data grid. `data_row` is the absolute
    /// row index in the data set (not the pool index).
    pub fn ui_data_grid_set_cell(
        &mut self,
        entity: freecs::Entity,
        data_row: usize,
        column: usize,
        text: &str,
    ) {
        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
            if data_row < data.visible_start {
                return;
            }
            let pool_index = data_row - data.visible_start;
            if let Some(pool_row) = data.pool_rows.get(pool_index)
                && let Some(&text_slot) = pool_row.cell_text_slots.get(column)
            {
                self.resources.text_cache.set_text(text_slot, text);
            }
        }
    }

    /// Returns whether the sort state changed this frame.
    pub fn ui_data_grid_sort_changed(&self, entity: freecs::Entity) -> bool {
        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
            data.sort_changed
        } else {
            false
        }
    }

    /// Returns whether the selection changed this frame.
    pub fn ui_data_grid_selection_changed(&self, entity: freecs::Entity) -> bool {
        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
            data.selection_changed
        } else {
            false
        }
    }

    pub fn ui_data_grid_set_filter(&mut self, entity: freecs::Entity, indices: &[usize]) {
        if let Some(UiWidgetState::DataGrid(grid)) = self.get_ui_widget_state_mut(entity) {
            grid.filtered_indices = Some(indices.to_vec());
            grid.selected_rows.clear();
            grid.selection_anchor = None;
            grid.selection_changed = true;
        }
    }

    pub fn ui_data_grid_clear_filter(&mut self, entity: freecs::Entity) {
        if let Some(UiWidgetState::DataGrid(grid)) = self.get_ui_widget_state_mut(entity) {
            grid.filtered_indices = None;
            grid.selected_rows.clear();
            grid.selection_anchor = None;
            grid.selection_changed = true;
        }
    }

    pub fn ui_data_grid_populate(
        &mut self,
        entity: freecs::Entity,
        source: &dyn crate::ecs::ui::components::DataGridDataSource,
    ) {
        let row_count = source.row_count();
        self.ui_data_grid_set_row_count(entity, row_count);
        let Some(data) = self.widget::<UiDataGridData>(entity) else {
            return;
        };
        let end = (data.visible_start + data.pool_size).min(data.total_rows);
        let range = data.visible_start..end;
        let col_count = data.columns.len();
        let filtered = data.filtered_indices.clone();
        for visible_row in range {
            let data_row = filtered
                .as_ref()
                .and_then(|indices| indices.get(visible_row).copied())
                .unwrap_or(visible_row);
            for column in 0..col_count {
                let text = source.cell_text(data_row, column);
                self.ui_data_grid_set_cell(entity, visible_row, column, &text);
            }
        }
    }

    pub fn ui_data_grid_populate_fn(
        &mut self,
        entity: freecs::Entity,
        row_count: usize,
        cell_fn: impl Fn(usize, usize) -> String,
    ) {
        self.ui_data_grid_set_row_count(entity, row_count);
        let Some(data) = self.widget::<UiDataGridData>(entity) else {
            return;
        };
        let end = (data.visible_start + data.pool_size).min(data.total_rows);
        let range = data.visible_start..end;
        let col_count = data.columns.len();
        let filtered = data.filtered_indices.clone();
        for visible_row in range {
            let data_row = filtered
                .as_ref()
                .and_then(|indices| indices.get(visible_row).copied())
                .unwrap_or(visible_row);
            for column in 0..col_count {
                let text = cell_fn(data_row, column);
                self.ui_data_grid_set_cell(entity, visible_row, column, &text);
            }
        }
    }

    pub fn ui_data_grid_start_edit(&mut self, entity: freecs::Entity, row: usize, column: usize) {
        let info = if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
            if column < data.columns.len()
                && data.columns[column].editable
                && let Some(input_entity) = data.editing_input_entity
            {
                let pool_row_idx = if row >= data.visible_start {
                    row - data.visible_start
                } else {
                    return;
                };
                if pool_row_idx >= data.pool_rows.len() {
                    return;
                }
                let cell_entity = data.pool_rows[pool_row_idx].cell_entities[column];
                let cell_text_slot = data.pool_rows[pool_row_idx].cell_text_slots[column];
                let cell_text = self
                    .resources
                    .text_cache
                    .get_text(cell_text_slot)
                    .unwrap_or_default()
                    .to_string();
                Some((input_entity, cell_entity, cell_text))
            } else {
                None
            }
        } else {
            None
        };

        if let Some((input_entity, cell_entity, cell_text)) = info {
            self.ui_text_input_set_value(input_entity, &cell_text);
            if let Some(UiWidgetState::TextInput(input_data)) =
                self.get_ui_widget_state_mut(input_entity)
            {
                input_data.selection_start = Some(0);
            }

            let root_rect = self
                .get_ui_layout_node(entity)
                .map(|n| n.computed_rect)
                .unwrap_or_default();
            let cell_rect = self
                .get_ui_layout_node(cell_entity)
                .map(|n| n.computed_rect)
                .unwrap_or_default();

            let rel_x = cell_rect.min.x - root_rect.min.x;
            let rel_y = cell_rect.min.y - root_rect.min.y;
            let width = cell_rect.width();
            let height = cell_rect.height();

            if let Some(node) = self.get_ui_layout_node_mut(input_entity) {
                node.visible = true;
                if let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                    node.layouts[UiBase::INDEX].as_mut()
                {
                    window.position = Ab(Vec2::new(rel_x, rel_y)).into();
                    window.size = Ab(Vec2::new(width, height)).into();
                }
            }
            self.resources.retained_ui.focused_entity = Some(input_entity);
            if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state_mut(entity) {
                data.editing_cell = Some((row, column));
            }
        }
    }

    pub fn ui_data_grid_stop_edit(&mut self, entity: freecs::Entity, commit: bool) {
        let info = if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
            if let Some((row, column)) = data.editing_cell
                && let Some(input_entity) = data.editing_input_entity
            {
                Some((input_entity, row, column))
            } else {
                None
            }
        } else {
            None
        };

        if let Some((input_entity, row, column)) = info {
            if commit {
                let text = self
                    .widget::<UiTextInputData>(input_entity)
                    .map(|d| d.text.clone())
                    .unwrap_or_default();
                self.resources.retained_ui.frame_events.push(
                    crate::ecs::ui::resources::UiEvent::DataGridCellEdited {
                        entity,
                        row,
                        column,
                        text,
                    },
                );
            }
            if let Some(node) = self.get_ui_layout_node_mut(input_entity) {
                node.visible = false;
            }
            if self.resources.retained_ui.focused_entity == Some(input_entity) {
                self.resources.retained_ui.focused_entity = None;
            }
            if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state_mut(entity) {
                data.editing_cell = None;
            }
        }
    }

    pub fn ui_text_area_set_value(&mut self, entity: freecs::Entity, text: &str) {
        let extract = if let Some(UiWidgetState::TextArea(data)) = self.get_ui_widget_state(entity)
        {
            Some((
                data.text_slot,
                data.placeholder_entity,
                data.syntax_language.clone(),
            ))
        } else {
            None
        };

        if let Some((slot, placeholder_entity, syntax_language)) = extract {
            self.resources.text_cache.set_text(slot, text);
            if let Some(UiWidgetState::TextArea(data)) = self.get_ui_widget_state_mut(entity) {
                data.text = text.to_string();
                data.cursor_position = text.chars().count();
                data.selection_start = None;
            }
            if let Some(ph_entity) = placeholder_entity
                && let Some(node) = self.get_ui_layout_node_mut(ph_entity)
            {
                node.visible = text.is_empty();
            }

            #[cfg(feature = "syntax_highlighting")]
            if let Some(language) = &syntax_language {
                crate::ecs::ui::syntax::highlight_text_area(self, text, language, slot);
            }

            #[cfg(not(feature = "syntax_highlighting"))]
            let _ = syntax_language;
        }
    }

    pub fn ui_text_area_set_syntax(&mut self, entity: freecs::Entity, language: Option<&str>) {
        let info = if let Some(UiWidgetState::TextArea(data)) = self.get_ui_widget_state(entity) {
            Some((data.text_slot, data.text.clone()))
        } else {
            None
        };

        if let Some(UiWidgetState::TextArea(data)) = self.get_ui_widget_state_mut(entity) {
            data.syntax_language = language.map(|s| s.to_string());
        }

        if language.is_some()
            && let Some((text_slot, text)) = info
        {
            #[cfg(feature = "syntax_highlighting")]
            crate::ecs::ui::syntax::highlight_text_area(self, &text, language.unwrap(), text_slot);

            #[cfg(not(feature = "syntax_highlighting"))]
            {
                let _ = (text_slot, text);
            }
        } else if let Some((text_slot, _)) = info {
            self.resources
                .retained_ui
                .text_slot_character_colors
                .remove(&text_slot);
        }
    }

    pub fn ui_rich_text_editor_set_value(&mut self, entity: freecs::Entity, text: &str) {
        let slot =
            if let Some(UiWidgetState::RichTextEditor(data)) = self.get_ui_widget_state(entity) {
                Some(data.text_slot)
            } else {
                None
            };

        if let Some(slot) = slot {
            self.resources.text_cache.set_text(slot, text);
            let char_count = text.chars().count();
            if let Some(UiWidgetState::RichTextEditor(data)) = self.get_ui_widget_state_mut(entity)
            {
                data.text = text.to_string();
                data.char_styles = vec![CharStyle::default(); char_count];
                data.cursor_position = char_count;
                data.selection_start = None;
            }
        }
    }

    pub fn ui_rich_text_editor_toggle_bold(&mut self, entity: freecs::Entity) {
        self.ui_rich_text_editor_toggle_style(entity, |style| &mut style.bold);
    }

    pub fn ui_rich_text_editor_toggle_italic(&mut self, entity: freecs::Entity) {
        self.ui_rich_text_editor_toggle_style(entity, |style| &mut style.italic);
    }

    pub fn ui_rich_text_editor_toggle_underline(&mut self, entity: freecs::Entity) {
        self.ui_rich_text_editor_toggle_style(entity, |style| &mut style.underline);
    }

    pub fn ui_rich_text_editor_set_color(&mut self, entity: freecs::Entity, color: Option<Vec4>) {
        let update_info = if let Some(UiWidgetState::RichTextEditor(data)) =
            self.get_ui_widget_state_mut(entity)
        {
            if let Some(sel_start) = data.selection_start {
                let start = sel_start.min(data.cursor_position);
                let end = sel_start.max(data.cursor_position);
                for index in start..end {
                    if index < data.char_styles.len() {
                        data.char_styles[index].color = color;
                    }
                }
            }
            data.current_style.color = color;
            data.changed = true;
            Some((data.char_styles.clone(), data.text_slot))
        } else {
            None
        };

        if let Some((char_styles, text_slot)) = update_info {
            Self::update_rich_text_char_colors(
                &char_styles,
                text_slot,
                &mut self.resources.retained_ui.text_slot_character_colors,
            );
        }
    }

    fn ui_rich_text_editor_toggle_style(
        &mut self,
        entity: freecs::Entity,
        accessor: fn(&mut CharStyle) -> &mut bool,
    ) {
        let update_info = if let Some(UiWidgetState::RichTextEditor(data)) =
            self.get_ui_widget_state_mut(entity)
        {
            if let Some(sel_start) = data.selection_start {
                let start = sel_start.min(data.cursor_position);
                let end = sel_start.max(data.cursor_position);
                let all_set = (start..end).all(|index| {
                    data.char_styles
                        .get(index)
                        .map(|s| *accessor(&mut s.clone()))
                        .unwrap_or(false)
                });
                let new_value = !all_set;
                for index in start..end {
                    if index < data.char_styles.len() {
                        *accessor(&mut data.char_styles[index]) = new_value;
                    }
                }
                *accessor(&mut data.current_style) = new_value;
            } else {
                let field = accessor(&mut data.current_style);
                *field = !*field;
            }
            data.changed = true;
            Some((data.char_styles.clone(), data.text_slot))
        } else {
            None
        };

        if let Some((char_styles, text_slot)) = update_info {
            Self::update_rich_text_char_colors(
                &char_styles,
                text_slot,
                &mut self.resources.retained_ui.text_slot_character_colors,
            );
        }
    }

    fn update_rich_text_char_colors(
        char_styles: &[CharStyle],
        text_slot: usize,
        slot_colors: &mut HashMap<usize, Vec<Option<Vec4>>>,
    ) {
        let has_colors = char_styles.iter().any(|s| s.color.is_some());
        if has_colors {
            let colors: Vec<Option<Vec4>> = char_styles.iter().map(|s| s.color).collect();
            slot_colors.insert(text_slot, colors);
        } else {
            slot_colors.remove(&text_slot);
        }
    }

    pub fn ui_canvas_hit_test(&self, entity: freecs::Entity, position: Vec2) -> Option<u32> {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state(entity) {
            for &(command_id, ref rect) in data.command_bounds.iter().rev() {
                if rect.contains(position) {
                    return Some(command_id);
                }
            }
        }
        None
    }

    pub fn ui_canvas_clear(&mut self, entity: freecs::Entity) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.clear();
        }
    }

    pub fn ui_canvas_rect(
        &mut self,
        entity: freecs::Entity,
        position: Vec2,
        size: Vec2,
        color: Vec4,
        corner_radius: f32,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Rect {
                position,
                size,
                color,
                corner_radius,
                id: None,
            });
        }
    }

    pub fn ui_canvas_circle(
        &mut self,
        entity: freecs::Entity,
        center: Vec2,
        radius: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Circle {
                center,
                radius,
                color,
                id: None,
            });
        }
    }

    pub fn ui_canvas_line(
        &mut self,
        entity: freecs::Entity,
        from: Vec2,
        to: Vec2,
        thickness: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Line {
                from,
                to,
                thickness,
                color,
                id: None,
            });
        }
    }

    pub fn ui_canvas_text(
        &mut self,
        entity: freecs::Entity,
        text: &str,
        position: Vec2,
        font_size: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Text {
                text: text.to_string(),
                position,
                font_size,
                color,
                id: None,
            });
        }
    }

    pub fn ui_canvas_polyline(
        &mut self,
        entity: freecs::Entity,
        points: &[Vec2],
        thickness: f32,
        color: Vec4,
        closed: bool,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Polyline {
                points: points.to_vec(),
                thickness,
                color,
                closed,
                id: None,
            });
        }
    }

    pub fn ui_canvas_rect_stroke(
        &mut self,
        entity: freecs::Entity,
        position: Vec2,
        size: Vec2,
        color: Vec4,
        thickness: f32,
        corner_radius: f32,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::RectStroke {
                position,
                size,
                color,
                thickness,
                corner_radius,
                id: None,
            });
        }
    }

    pub fn ui_canvas_circle_stroke(
        &mut self,
        entity: freecs::Entity,
        center: Vec2,
        radius: f32,
        color: Vec4,
        thickness: f32,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::CircleStroke {
                center,
                radius,
                color,
                thickness,
                id: None,
            });
        }
    }

    pub fn ui_canvas_arc(
        &mut self,
        entity: freecs::Entity,
        center: Vec2,
        radius: f32,
        angle_range: (f32, f32),
        thickness: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::Arc {
                center,
                radius,
                start_angle: angle_range.0,
                end_angle: angle_range.1,
                thickness,
                color,
                id: None,
            });
        }
    }

    pub fn ui_canvas_quadratic_bezier(
        &mut self,
        entity: freecs::Entity,
        start: Vec2,
        control: Vec2,
        end: Vec2,
        thickness: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::QuadraticBezier {
                start,
                control,
                end,
                thickness,
                color,
                id: None,
            });
        }
    }

    pub fn ui_canvas_cubic_bezier(
        &mut self,
        entity: freecs::Entity,
        start: Vec2,
        controls: (Vec2, Vec2),
        end: Vec2,
        thickness: f32,
        color: Vec4,
    ) {
        if let Some(UiWidgetState::Canvas(data)) = self.get_ui_widget_state_mut(entity) {
            data.commands.push(CanvasCommand::CubicBezier {
                start,
                control1: controls.0,
                control2: controls.1,
                end,
                thickness,
                color,
                id: None,
            });
        }
    }

    pub fn ui_tile_add_pane(
        &mut self,
        container: freecs::Entity,
        title: &str,
    ) -> Option<(TileId, freecs::Entity)> {
        let content_entity = self.spawn_tile_pane_entity(container);

        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container)
        else {
            return None;
        };

        let pane = TileNode::Pane {
            content_entity,
            title: title.to_string(),
        };
        let pane_id = data.alloc(pane);

        let mut current = data.root;
        let target_tabs = loop {
            match data.get(current) {
                Some(TileNode::Tabs { .. }) => break Some(current),
                Some(TileNode::Split { children, .. }) => current = children[0],
                _ => break None,
            }
        };
        if let Some(tabs_id) = target_tabs
            && let Some(TileNode::Tabs { panes, .. }) = data.get_mut(tabs_id)
        {
            panes.push(pane_id);
        }

        Some((pane_id, content_entity))
    }

    pub fn ui_tile_add_pane_to(
        &mut self,
        container: freecs::Entity,
        tabs_id: TileId,
        title: &str,
    ) -> Option<(TileId, freecs::Entity)> {
        let (pane_id, content_entity) = self.ui_tile_add_pane(container, title)?;

        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container)
        else {
            return None;
        };

        if let Some(parent_tabs_id) = data.find_parent_tabs(pane_id)
            && parent_tabs_id != tabs_id
            && let Some(TileNode::Tabs { panes, active }) = data.get_mut(parent_tabs_id)
        {
            panes.retain(|id| *id != pane_id);
            if *active >= panes.len() && !panes.is_empty() {
                *active = panes.len() - 1;
            }
        }

        if let Some(TileNode::Tabs { panes, .. }) = data.get_mut(tabs_id)
            && !panes.contains(&pane_id)
        {
            panes.push(pane_id);
        }

        Some((pane_id, content_entity))
    }

    pub fn ui_tile_split(
        &mut self,
        container: freecs::Entity,
        target: TileId,
        direction: SplitDirection,
        ratio: f32,
        title: &str,
    ) -> Option<(TileId, freecs::Entity)> {
        let (pane_id, content_entity) = self.ui_tile_add_pane(container, title)?;

        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container)
        else {
            return None;
        };

        if let Some(parent_tabs_id) = data.find_parent_tabs(pane_id)
            && let Some(TileNode::Tabs { panes, .. }) = data.get_mut(parent_tabs_id)
        {
            panes.retain(|id| *id != pane_id);
        }

        let new_tabs = TileNode::Tabs {
            panes: vec![pane_id],
            active: 0,
        };
        let new_tabs_id = data.alloc(new_tabs);

        let old_node = data.tiles[target.0].take()?;
        let old_id = data.alloc(old_node);

        let split = TileNode::Split {
            direction,
            ratio,
            children: [old_id, new_tabs_id],
        };
        data.tiles[target.0] = Some(split);

        Some((pane_id, content_entity))
    }

    pub fn ui_tile_remove(&mut self, container: freecs::Entity, tile_id: TileId) {
        let content_to_hide =
            if let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) {
                let pane_count = data
                    .tiles
                    .iter()
                    .filter(|t| matches!(t, Some(TileNode::Pane { .. })))
                    .count();
                if pane_count < 2 {
                    return;
                }
                if let Some(TileNode::Pane { content_entity, .. }) = data.get(tile_id) {
                    Some(*content_entity)
                } else {
                    None
                }
            } else {
                return;
            };

        if let Some(content) = content_to_hide
            && let Some(node) = self.get_ui_layout_node_mut(content)
        {
            node.visible = false;
        }

        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container)
        else {
            return;
        };

        if let Some(tabs_id) = data.find_parent_tabs(tile_id) {
            if let Some(TileNode::Tabs { panes, active }) = data.get_mut(tabs_id) {
                panes.retain(|id| *id != tile_id);
                if *active >= panes.len() && !panes.is_empty() {
                    *active = panes.len() - 1;
                }
            }
            data.free(tile_id);

            let remaining = if let Some(TileNode::Tabs { panes, .. }) = data.get(tabs_id) {
                panes.len()
            } else {
                0
            };

            if remaining == 0 {
                if let Some((parent_split_id, child_index)) = data.find_parent_split(tabs_id) {
                    let sibling_index = 1 - child_index;
                    let sibling_id =
                        if let Some(TileNode::Split { children, .. }) = data.get(parent_split_id) {
                            children[sibling_index]
                        } else {
                            return;
                        };

                    let sibling_node = data.tiles[sibling_id.0].take();
                    data.tiles[parent_split_id.0] = sibling_node;
                    data.free(tabs_id);
                    data.free(sibling_id);
                } else {
                    data.free(tabs_id);
                }
            }
        }
    }

    pub fn ui_tile_pane_content(
        &self,
        container: freecs::Entity,
        tile_id: TileId,
    ) -> Option<freecs::Entity> {
        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) else {
            return None;
        };
        if let Some(TileNode::Pane { content_entity, .. }) = data.get(tile_id) {
            Some(*content_entity)
        } else {
            None
        }
    }

    pub fn ui_tile_pane_title(&self, container: freecs::Entity, pane_id: TileId) -> Option<String> {
        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) else {
            return None;
        };
        if let Some(TileNode::Pane { title, .. }) = data.get(pane_id) {
            Some(title.clone())
        } else {
            None
        }
    }

    pub fn ui_tile_active_pane(&self, container: freecs::Entity, pane_id: TileId) -> bool {
        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) else {
            return false;
        };
        if let Some(tabs_id) = data.find_parent_tabs(pane_id)
            && let Some(TileNode::Tabs { panes, active }) = data.get(tabs_id)
            && panes.get(*active) == Some(&pane_id)
        {
            return true;
        }
        false
    }

    pub fn ui_tile_tab_activated(&self, container: freecs::Entity) -> Option<TileId> {
        for event in &self.resources.retained_ui.frame_events {
            if let crate::ecs::ui::resources::UiEvent::TileTabActivated {
                container: event_container,
                pane_id,
            } = event
                && *event_container == container
            {
                return Some(*pane_id);
            }
        }
        None
    }

    pub fn ui_tile_tab_closed(&self, container: freecs::Entity) -> Option<(TileId, String)> {
        for event in &self.resources.retained_ui.frame_events {
            if let crate::ecs::ui::resources::UiEvent::TileTabClosed {
                container: event_container,
                pane_id,
                title,
            } = event
                && *event_container == container
            {
                return Some((*pane_id, title.clone()));
            }
        }
        None
    }

    pub fn ui_tile_splitter_moved(&self, container: freecs::Entity) -> Option<(TileId, f32)> {
        for event in &self.resources.retained_ui.frame_events {
            if let crate::ecs::ui::resources::UiEvent::TileSplitterMoved {
                container: event_container,
                split_id,
                ratio,
            } = event
                && *event_container == container
            {
                return Some((*split_id, *ratio));
            }
        }
        None
    }

    pub fn ui_tile_set_active(&mut self, container: freecs::Entity, pane_id: TileId) {
        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container)
        else {
            return;
        };
        if let Some(tabs_id) = data.find_parent_tabs(pane_id)
            && let Some(TileNode::Tabs { panes, active }) = data.get_mut(tabs_id)
            && let Some(index) = panes.iter().position(|id| *id == pane_id)
        {
            *active = index;
        }
    }

    pub fn ui_tile_save_layout(
        &self,
        container: freecs::Entity,
    ) -> Option<crate::ecs::ui::components::TileLayout> {
        use crate::ecs::ui::components::{TileLayout, TileLayoutNode};
        let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) else {
            return None;
        };
        let nodes = data
            .tiles
            .iter()
            .map(|tile| {
                tile.as_ref().map(|node| match node {
                    TileNode::Pane { title, .. } => TileLayoutNode::Pane {
                        title: title.clone(),
                    },
                    TileNode::Split {
                        direction,
                        ratio,
                        children,
                    } => TileLayoutNode::Split {
                        direction: *direction,
                        ratio: *ratio,
                        children: *children,
                    },
                    TileNode::Tabs { panes, active } => TileLayoutNode::Tabs {
                        panes: panes.clone(),
                        active: *active,
                    },
                })
            })
            .collect();
        Some(TileLayout {
            nodes,
            root: data.root,
        })
    }

    pub fn ui_tile_load_layout(
        &mut self,
        container: freecs::Entity,
        layout: &crate::ecs::ui::components::TileLayout,
    ) -> Vec<(TileId, freecs::Entity)> {
        use crate::ecs::ui::components::TileLayoutNode;

        let old_pane_entities: Vec<freecs::Entity> =
            if let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state(container) {
                data.tiles
                    .iter()
                    .filter_map(|tile| {
                        if let Some(TileNode::Pane { content_entity, .. }) = tile {
                            Some(*content_entity)
                        } else {
                            None
                        }
                    })
                    .collect()
            } else {
                return Vec::new();
            };

        for entity in &old_pane_entities {
            if let Some(node) = self.get_ui_layout_node_mut(*entity) {
                node.visible = false;
            }
        }

        let mut pane_mappings = Vec::new();
        let mut new_tiles: Vec<Option<TileNode>> = Vec::with_capacity(layout.nodes.len());
        for layout_node in &layout.nodes {
            match layout_node {
                Some(TileLayoutNode::Pane { title }) => {
                    let content_entity = self.spawn_tile_pane_entity(container);
                    let tile_id = TileId(new_tiles.len());
                    pane_mappings.push((tile_id, content_entity));
                    new_tiles.push(Some(TileNode::Pane {
                        content_entity,
                        title: title.clone(),
                    }));
                }
                Some(TileLayoutNode::Split {
                    direction,
                    ratio,
                    children,
                }) => {
                    new_tiles.push(Some(TileNode::Split {
                        direction: *direction,
                        ratio: *ratio,
                        children: *children,
                    }));
                }
                Some(TileLayoutNode::Tabs { panes, active }) => {
                    new_tiles.push(Some(TileNode::Tabs {
                        panes: panes.clone(),
                        active: *active,
                    }));
                }
                None => {
                    new_tiles.push(None);
                }
            }
        }

        let mut next_free = Vec::new();
        for (index, tile) in new_tiles.iter().enumerate() {
            if tile.is_none() {
                next_free.push(index);
            }
        }

        let rects_len = new_tiles.len();
        if let Some(UiWidgetState::TileContainer(data)) = self.get_ui_widget_state_mut(container) {
            data.tiles = new_tiles;
            data.root = layout.root;
            data.next_free = next_free;
            data.rects
                .resize(rects_len, crate::ecs::ui::types::Rect::default());
            data.dragging_splitter = None;
            data.pending_tab_drag = None;
            data.dragging_tab = None;
            data.drop_preview = None;
            data.hovered_close = None;
        }

        self.resources.children_cache_valid = false;
        self.resources.retained_ui.layout_dirty = true;

        pane_mappings
    }

    fn spawn_tile_pane_entity(&mut self, container: freecs::Entity) -> freecs::Entity {
        use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection, FlowLayout};
        use crate::ecs::ui::state::UiBase;

        let theme = self.resources.retained_ui.theme_state.active_theme();
        let panel_color = theme.panel_color;

        let entities = self.spawn_entities(
            crate::ecs::world::UI_LAYOUT_NODE
                | crate::ecs::world::UI_NODE_COLOR
                | crate::ecs::world::UI_NODE_CONTENT
                | crate::ecs::world::UI_STATE_WEIGHTS
                | crate::ecs::world::PARENT,
            1,
        );
        let content_entity = entities[0];

        if let Some(parent) = self.get_parent_mut(content_entity) {
            *parent = crate::ecs::transform::components::Parent(Some(container));
        }
        if let Some(node) = self.get_ui_layout_node_mut(content_entity) {
            *node = crate::ecs::ui::components::UiLayoutNode::default();
            node.layouts[UiBase::INDEX] = Some(crate::ecs::ui::layout_types::UiLayoutType::Window(
                crate::ecs::ui::layout_types::WindowLayout {
                    position: Ab(Vec2::new(0.0, 0.0)).into(),
                    size: Ab(Vec2::new(100.0, 100.0)).into(),
                    anchor: Anchor::TopLeft,
                },
            ));
            node.depth = crate::ecs::ui::components::UiDepthMode::Add(0.0);
            node.clip_content = true;
            node.flow_layout = Some(FlowLayout {
                direction: FlowDirection::Vertical,
                padding: 4.0,
                spacing: 4.0,
                alignment: FlowAlignment::Start,
                cross_alignment: FlowAlignment::Start,
                wrap: false,
            });
        }
        if let Some(color) = self.get_ui_node_color_mut(content_entity) {
            color.colors[UiBase::INDEX] = Some(panel_color);
        }
        if let Some(content) = self.get_ui_node_content_mut(content_entity) {
            *content = crate::ecs::ui::components::UiNodeContent::Rect {
                corner_radius: 0.0,
                border_width: 0.0,
                border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
            };
        }

        self.resources.children_cache_valid = false;
        self.resources.retained_ui.layout_dirty = true;

        content_entity
    }

    pub fn build_tiles(
        &mut self,
        container: freecs::Entity,
        f: impl FnOnce(&mut TileBuilder<'_, '_>),
    ) {
        let mut tree = UiTreeBuilder::from_parent(self, container);
        tree.build_tiles(container, f);
    }

    pub fn ui_on<F>(&mut self, entity: freecs::Entity, handler: F)
    where
        F: FnMut(&mut crate::ecs::world::World, &crate::ecs::ui::resources::UiEvent) + 'static,
    {
        self.resources
            .retained_ui
            .event_handlers
            .entry(entity)
            .or_default()
            .push(Box::new(handler));
    }

    pub fn ui_off(&mut self, entity: freecs::Entity) {
        self.resources.retained_ui.event_handlers.remove(&entity);
    }

    pub fn ui_data_grid_enable_filters(&mut self, entity: freecs::Entity) {
        let columns = if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity)
        {
            data.columns.clone()
        } else {
            return;
        };

        let theme = self.resources.retained_ui.theme_state.active_theme();
        let font_size = theme.font_size;
        let input_bg = theme.input_background_color;
        let text_color = theme.text_color;
        let border_color = theme.border_color;
        let corner_radius = theme.corner_radius;
        let row_height = 28.0;

        let header_entity =
            if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state(entity) {
                data.header_entities
                    .first()
                    .and_then(|header| self.get_parent(*header).and_then(|p| p.0))
            } else {
                None
            };

        let header_parent = if let Some(he) = header_entity {
            self.get_parent(he).and_then(|p| p.0)
        } else {
            None
        };

        let filter_row_parent = header_parent.unwrap_or(entity);

        let mut filter_input_entities = Vec::new();
        let mut filter_texts = Vec::new();

        let filter_row = {
            let mut tree = crate::ecs::ui::builder::UiTreeBuilder::new(self);
            tree.push_parent(filter_row_parent);
            let row_entity = tree
                .add_node()
                .flow_child(
                    crate::ecs::ui::units::Rl(Vec2::new(100.0, 0.0))
                        + crate::ecs::ui::units::Ab(Vec2::new(0.0, row_height)),
                )
                .flow(FlowDirection::Horizontal, 0.0, 2.0)
                .entity();

            tree.push_parent(row_entity);

            for column in &columns {
                let text_slot = tree.world_mut().resources.text_cache.add_text("");
                let input = tree
                    .add_node()
                    .flow_child(crate::ecs::ui::units::Ab(Vec2::new(
                        column.width,
                        row_height,
                    )))
                    .with_rect(corner_radius, 1.0, border_color)
                    .with_color::<UiBase>(input_bg)
                    .with_interaction()
                    .with_children(|inner| {
                        inner
                            .add_node()
                            .with_text_slot(text_slot, font_size)
                            .with_color::<UiBase>(text_color)
                            .without_pointer_events()
                            .done();
                    })
                    .done();
                filter_input_entities.push(input);
                filter_texts.push(String::new());
            }

            tree.pop_parent();
            tree.pop_parent();

            row_entity
        };

        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state_mut(entity) {
            data.filter_row_entity = Some(filter_row);
            data.filter_input_entities = filter_input_entities;
            data.filter_texts = filter_texts;
        }
    }

    pub fn ui_data_grid_set_filtered_indices(
        &mut self,
        entity: freecs::Entity,
        indices: Option<Vec<usize>>,
    ) {
        if let Some(UiWidgetState::DataGrid(data)) = self.get_ui_widget_state_mut(entity) {
            data.filtered_indices = indices;
        }
        self.resources.retained_ui.layout_dirty = true;
    }

    pub fn ui_set_state_active(
        &mut self,
        entity: freecs::Entity,
        state_index: usize,
        active: bool,
    ) {
        if let Some(weights) = self.get_ui_state_weights_mut(entity) {
            weights.ensure_state_capacity(state_index + 1);
            weights.targets[state_index] = if active { 1.0 } else { 0.0 };
            weights.start_weights[state_index] = weights.weights[state_index];
            weights.progress[state_index] = 0.0;
        }
    }

    pub(crate) fn ui_create_property<T: crate::ecs::ui::resources::IntoPropertyValue>(
        &mut self,
        entity: freecs::Entity,
        initial: T,
    ) -> crate::ecs::ui::resources::PropertyId {
        let id = crate::ecs::ui::resources::PropertyId(self.resources.retained_ui.next_property_id);
        self.resources.retained_ui.next_property_id += 1;
        self.resources.retained_ui.bound_properties.insert(
            id,
            crate::ecs::ui::resources::BoundProperty {
                value: initial.into_property_value(),
                entity,
                dirty_from_widget: false,
                dirty_from_code: false,
            },
        );
        id
    }

    pub fn ui_register_named<T: crate::ecs::ui::resources::IntoPropertyValue>(
        &mut self,
        name: &str,
        entity: freecs::Entity,
        initial: T,
    ) {
        let property_id = self.ui_create_property(entity, initial);
        self.resources
            .retained_ui
            .register_named_property(name, property_id);
    }

    pub(crate) fn ui_property<T: crate::ecs::ui::resources::FromPropertyValue>(
        &self,
        id: crate::ecs::ui::resources::PropertyId,
    ) -> T {
        self.resources
            .retained_ui
            .bound_properties
            .get(&id)
            .and_then(|prop| T::from_property_value(&prop.value))
            .unwrap_or_else(T::default_value)
    }

    pub(crate) fn ui_set_property<T: crate::ecs::ui::resources::IntoPropertyValue>(
        &mut self,
        id: crate::ecs::ui::resources::PropertyId,
        value: T,
    ) {
        if let Some(prop) = self.resources.retained_ui.bound_properties.get_mut(&id) {
            prop.value = value.into_property_value();
            prop.dirty_from_code = true;
        }
    }

    pub fn ui_prop<T: crate::ecs::ui::resources::FromPropertyValue>(&self, name: &str) -> T {
        if let Some(&property_id) = self.resources.retained_ui.named_properties.get(name) {
            self.ui_property(property_id)
        } else {
            #[cfg(debug_assertions)]
            panic!("Unknown UI property: {name}");
            #[cfg(not(debug_assertions))]
            T::default_value()
        }
    }

    pub fn ui_set_prop<T: crate::ecs::ui::resources::IntoPropertyValue>(
        &mut self,
        name: &str,
        value: T,
    ) {
        if let Some(&property_id) = self.resources.retained_ui.named_properties.get(name) {
            self.ui_set_property(property_id, value);
        } else {
            #[cfg(debug_assertions)]
            panic!("Unknown UI property: {name}");
        }
    }

    pub fn ui_prop_entity(&self, name: &str) -> Option<freecs::Entity> {
        self.resources
            .retained_ui
            .named_properties
            .get(name)
            .and_then(|id| self.resources.retained_ui.bound_properties.get(id))
            .map(|bp| bp.entity)
    }

    pub fn ui_clicked(&self, entity: freecs::Entity) -> bool {
        self.widget::<crate::ecs::ui::components::UiButtonData>(entity)
            .is_some_and(|d| d.clicked)
    }

    pub fn ui_react<T, F>(&mut self, name: &str, mut handler: F)
    where
        T: crate::ecs::ui::resources::FromPropertyValue + 'static,
        F: FnMut(T, &mut Self) + 'static,
    {
        let wrapped: crate::ecs::ui::resources::PropertyReaction = Box::new(move |value, world| {
            if let Some(typed) = T::from_property_value(value) {
                handler(typed, world);
            }
        });
        self.resources
            .retained_ui
            .property_reactions
            .entry(name.to_string())
            .or_default()
            .push(wrapped);
    }

    pub fn ui_react_clicked<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(&mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .click_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |world| handler(world)));
    }

    pub fn ui_react_submitted<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(String, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .submit_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |text, world| handler(text, world)));
    }

    pub fn ui_react_confirmed<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(bool, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .confirm_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |confirmed, world| {
                handler(confirmed, world);
            }));
    }

    pub fn ui_react_menu_selected<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(usize, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .menu_select_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |index, world| handler(index, world)));
    }

    pub fn ui_react_command<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(usize, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .command_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |index, world| handler(index, world)));
    }

    pub fn ui_react_tree_selected<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(freecs::Entity, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .tree_select_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |node, world| handler(node, world)));
    }

    pub fn ui_react_tree_context_menu<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(freecs::Entity, nalgebra_glm::Vec2, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .tree_context_menu_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |node, position, world| {
                handler(node, position, world);
            }));
    }

    pub fn ui_react_multi_select_changed<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(Vec<usize>, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .multi_select_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |indices, world| handler(indices, world)));
    }

    pub fn ui_react_date_changed<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(i32, u32, u32, &mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .date_changed_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |year, month, day, world| {
                handler(year, month, day, world);
            }));
    }

    pub fn ui_react_changed<F>(&mut self, entity: freecs::Entity, mut handler: F)
    where
        F: FnMut(&mut Self) + 'static,
    {
        self.resources
            .retained_ui
            .changed_reactions
            .entry(entity)
            .or_default()
            .push(Box::new(move |world| handler(world)));
    }

    pub fn ui_react_any<F>(&mut self, names: &[&str], handler: F)
    where
        F: FnMut(&mut Self) + 'static,
    {
        let handler = std::rc::Rc::new(std::cell::RefCell::new(handler));
        for name in names {
            let handler = handler.clone();
            self.resources
                .retained_ui
                .property_reactions
                .entry(name.to_string())
                .or_default()
                .push(Box::new(move |_value, world| {
                    (handler.borrow_mut())(world);
                }));
        }
    }

    pub fn ui_cleanup_entity(&mut self, entity: freecs::Entity) {
        let ui = &mut self.resources.retained_ui;
        ui.click_reactions.remove(&entity);
        ui.submit_reactions.remove(&entity);
        ui.confirm_reactions.remove(&entity);
        ui.menu_select_reactions.remove(&entity);
        ui.command_reactions.remove(&entity);
        ui.tree_select_reactions.remove(&entity);
        ui.tree_context_menu_reactions.remove(&entity);
        ui.multi_select_reactions.remove(&entity);
        ui.date_changed_reactions.remove(&entity);
        ui.changed_reactions.remove(&entity);
        ui.event_handlers.remove(&entity);

        let stale_ids: Vec<crate::ecs::ui::resources::PropertyId> = ui
            .bound_properties
            .iter()
            .filter(|(_, prop)| prop.entity == entity)
            .map(|(&id, _)| id)
            .collect();
        for id in &stale_ids {
            ui.bound_properties.remove(id);
        }
        let stale_names: Vec<String> = ui
            .named_properties
            .iter()
            .filter(|(_, id)| stale_ids.contains(id))
            .map(|(name, _)| name.clone())
            .collect();
        for name in &stale_names {
            ui.named_properties.remove(name);
            ui.property_reactions.remove(name);
        }
    }

    pub fn ui_remove_property(&mut self, name: &str) {
        let ui = &mut self.resources.retained_ui;
        if let Some(property_id) = ui.named_properties.remove(name) {
            ui.bound_properties.remove(&property_id);
        }
        ui.property_reactions.remove(name);
    }

    pub fn ui_set_reduced_motion(&mut self, enabled: bool) {
        self.resources.retained_ui.reduced_motion = enabled;
    }

    pub fn ui_announce(&mut self, message: &str) {
        self.resources
            .retained_ui
            .announce_queue
            .push(message.to_string());
    }

    pub fn ui_announcements(&self) -> &[String] {
        &self.resources.retained_ui.announce_queue
    }

    pub fn ui_set_accessible_label(&mut self, entity: freecs::Entity, label: &str) {
        if let Some(interaction) = self.get_ui_node_interaction_mut(entity) {
            interaction.accessible_label = Some(label.to_string());
        }
    }

    pub fn ui_multi_select_set_selected(&mut self, entity: freecs::Entity, indices: &[usize]) {
        let update =
            if let Some(UiWidgetState::MultiSelect(data)) = self.get_ui_widget_state_mut(entity) {
                data.selected_indices = indices.iter().copied().collect();
                let count = data.selected_indices.len();
                let header_slot = data.header_text_slot;
                let header_text = format!("{count} selected");
                let check_updates: Vec<(freecs::Entity, bool)> = data
                    .check_entities
                    .iter()
                    .enumerate()
                    .map(|(index, &e)| (e, data.selected_indices.contains(&index)))
                    .collect();
                Some((header_slot, header_text, check_updates))
            } else {
                None
            };
        if let Some((header_slot, header_text, check_updates)) = update {
            for (check_entity, selected) in check_updates {
                let check_text = if selected { "\u{2713}" } else { " " };
                if let Some(crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. }) =
                    self.get_ui_node_content(check_entity)
                {
                    let slot = *text_slot;
                    self.resources.text_cache.set_text(slot, check_text);
                }
            }
            self.resources
                .text_cache
                .set_text(header_slot, &header_text);
        }
    }

    pub fn ui_date_picker_set_value(
        &mut self,
        entity: freecs::Entity,
        year: i32,
        month: u32,
        day: u32,
    ) {
        let header_text = format!("{year:04}-{month:02}-{day:02}");
        let update = if let Some(UiWidgetState::DatePicker(data)) = self.get_ui_widget_state(entity)
        {
            Some((
                data.header_text_slot,
                data.month_label_slot,
                data.day_text_slots.clone(),
                data.day_entities.clone(),
            ))
        } else {
            None
        };
        if let Some((header_slot, month_slot, day_slots, day_ents)) = update {
            self.resources
                .text_cache
                .set_text(header_slot, &header_text);
            let month_label = format_month_year(year, month);
            self.resources.text_cache.set_text(month_slot, &month_label);
            let theme = self.resources.retained_ui.theme_state.active_theme();
            let accent_color = theme.accent_color;
            populate_calendar_grid(self, year, month, day, &day_slots, &day_ents, accent_color);
            if let Some(UiWidgetState::DatePicker(data)) = self.get_ui_widget_state_mut(entity) {
                data.year = year;
                data.month = month;
                data.day = day;
            }
        }
    }
}

pub(crate) fn format_month_year(year: i32, month: u32) -> String {
    let month_name = match month {
        1 => "January",
        2 => "February",
        3 => "March",
        4 => "April",
        5 => "May",
        6 => "June",
        7 => "July",
        8 => "August",
        9 => "September",
        10 => "October",
        11 => "November",
        12 => "December",
        _ => "???",
    };
    format!("{month_name} {year}")
}

pub(crate) fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
                29
            } else {
                28
            }
        }
        _ => 30,
    }
}

pub(crate) fn day_of_week(year: i32, month: u32, day: u32) -> u32 {
    let (adjusted_year, adjusted_month) = if month <= 2 {
        (year - 1, month + 12)
    } else {
        (year, month)
    };
    let q = day as i32;
    let m = adjusted_month as i32;
    let k = adjusted_year % 100;
    let j = adjusted_year / 100;
    let h = (q + (13 * (m + 1)) / 5 + k + k / 4 + j / 4 - 2 * j).rem_euclid(7);
    ((h + 6) % 7) as u32
}

pub(crate) fn populate_calendar_grid(
    world: &mut crate::ecs::world::World,
    year: i32,
    month: u32,
    selected_day: u32,
    day_text_slots: &[usize],
    day_entities: &[freecs::Entity],
    accent_color: nalgebra_glm::Vec4,
) {
    let dim_color = nalgebra_glm::Vec4::new(0.5, 0.5, 0.5, 0.3);
    let clear_color = nalgebra_glm::Vec4::new(0.0, 0.0, 0.0, 0.0);

    let first_dow = day_of_week(year, month, 1);
    let days = days_in_month(year, month);

    let (prev_year, prev_month) = if month == 1 {
        (year - 1, 12)
    } else {
        (year, month - 1)
    };
    let prev_days = days_in_month(prev_year, prev_month);

    for cell_index in 0..42 {
        let (day_num, is_current_month) = if (cell_index as u32) < first_dow {
            let d = prev_days - first_dow + cell_index as u32 + 1;
            (d, false)
        } else {
            let d = cell_index as u32 - first_dow + 1;
            if d <= days {
                (d, true)
            } else {
                let d = d - days;
                (d, false)
            }
        };

        world
            .resources
            .text_cache
            .set_text(day_text_slots[cell_index], day_num.to_string());

        let is_selected = is_current_month && day_num == selected_day;
        let base_color = if is_selected {
            accent_color
        } else if is_current_month {
            clear_color
        } else {
            dim_color
        };
        if let Some(color) = world.get_ui_node_color_mut(day_entities[cell_index]) {
            color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(base_color);
        }
    }
}
