use crate::ecs::world::World;
use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::text::components::{TextAlignment, VerticalAlignment};
use crate::ecs::ui::components::*;
use crate::ecs::ui::state::{UiBase, UiStateTrait};
use crate::ecs::ui::types::Anchor;
use crate::ecs::ui::units::{Ab, Rl};

use super::range_slider_visuals::find_widget_content_in_defs;

/// Marks the retained UI layout as dirty so the next compute pass rebuilds
/// computed rects. Use after mutating any layout-affecting state.
use crate::prelude::*;
pub fn ui_mark_layout_dirty(world: &mut World) {
    world.resources.retained_ui.dirty.layout_dirty = true;
}

/// Marks the retained UI render output as dirty so the next render-sync
/// pass re-emits draw records. Use after mutating colors, content, etc.
pub fn ui_mark_render_dirty(world: &mut World) {
    world.resources.retained_ui.dirty.render_dirty = true;
}

/// Invalidates the children cache (rebuilt lazily on next access) AND
/// marks layout dirty. Use after spawning or reparenting UI entities.
pub fn ui_mark_children_dirty(world: &mut World) {
    world.resources.transform_state.children_cache_valid = false;
    world.resources.retained_ui.dirty.layout_dirty = true;
}

pub fn ui_set_error(world: &mut World, entity: freecs::Entity, error: Option<&str>) {
    if let Some(interaction) = world.ui.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(world: &World, entity: freecs::Entity) -> bool {
    world
        .ui
        .get_ui_node_interaction(entity)
        .is_some_and(|interaction| interaction.error_text.is_some())
}

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

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

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

pub fn ui_validate(world: &mut World, entity: freecs::Entity) -> bool {
    let text = widget::<UiTextInputData>(world, entity)
        .map(|d| d.text.clone())
        .unwrap_or_default();
    let rules: Vec<crate::ecs::ui::components::ValidationRule> = world
        .ui
        .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) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.error_text = error.clone();
        interaction.tooltip_text = error;
    }
    is_valid
}

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

pub fn ui_set_toast_corner(world: &mut World, corner: crate::ecs::ui::resources::ToastCorner) {
    if world.resources.retained_ui.overlays.toast_corner != corner {
        world.resources.retained_ui.overlays.toast_corner = corner;
        if let Some(container) = world.resources.retained_ui.overlays.toast_container.take() {
            ui_despawn_node(world, container);
        }
    }
}

pub fn ui_show_toast(
    world: &mut World,
    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 corner = world.resources.retained_ui.overlays.toast_corner;
    let container = if let Some(entity) = world.resources.retained_ui.overlays.toast_container {
        entity
    } else {
        use crate::ecs::ui::layout_types::{FlowAlignment, FlowDirection};
        use crate::ecs::ui::resources::ToastCorner;
        use crate::ecs::ui::units::UiValue;
        const TOAST_WIDTH: f32 = 320.0;
        const TOAST_MARGIN: f32 = 16.0;
        const TOP_OFFSET: f32 = 60.0;
        let (point_1, point_2, main_alignment): (UiValue<Vec2>, UiValue<Vec2>, FlowAlignment) =
            match corner {
                ToastCorner::TopLeft => (
                    Rl(Vec2::new(0.0, 0.0)) + Ab(Vec2::new(TOAST_MARGIN, TOP_OFFSET)),
                    Rl(Vec2::new(0.0, 100.0))
                        + Ab(Vec2::new(TOAST_MARGIN + TOAST_WIDTH, -TOAST_MARGIN)),
                    FlowAlignment::Start,
                ),
                ToastCorner::TopRight => (
                    Rl(Vec2::new(100.0, 0.0))
                        + Ab(Vec2::new(-(TOAST_MARGIN + TOAST_WIDTH), TOP_OFFSET)),
                    Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-TOAST_MARGIN, -TOAST_MARGIN)),
                    FlowAlignment::Start,
                ),
                ToastCorner::BottomLeft => (
                    Rl(Vec2::new(0.0, 0.0)) + Ab(Vec2::new(TOAST_MARGIN, TOP_OFFSET)),
                    Rl(Vec2::new(0.0, 100.0))
                        + Ab(Vec2::new(TOAST_MARGIN + TOAST_WIDTH, -TOAST_MARGIN)),
                    FlowAlignment::End,
                ),
                ToastCorner::BottomRight => (
                    Rl(Vec2::new(100.0, 0.0))
                        + Ab(Vec2::new(-(TOAST_MARGIN + TOAST_WIDTH), TOP_OFFSET)),
                    Rl(Vec2::new(100.0, 100.0)) + Ab(Vec2::new(-TOAST_MARGIN, -TOAST_MARGIN)),
                    FlowAlignment::End,
                ),
            };
        let mut tree = UiTreeBuilder::new(world);
        let container = tree
            .add_node()
            .boundary(point_1, point_2)
            .flow_with_alignment(
                FlowDirection::Vertical,
                0.0,
                8.0,
                main_alignment,
                FlowAlignment::Start,
            )
            .with_depth(UiDepthMode::Set(50.0))
            .entity();
        tree.finish();
        world.resources.retained_ui.overlays.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 = world.resources.text.cache.add_text(message);
    let spawn_time = world.resources.retained_ui.timing.current_time;

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

    let intro_kind = match corner {
        crate::ecs::ui::resources::ToastCorner::TopLeft
        | crate::ecs::ui::resources::ToastCorner::BottomLeft => UiAnimationType::SlideLeft,
        crate::ecs::ui::resources::ToastCorner::TopRight
        | crate::ecs::ui::resources::ToastCorner::BottomRight => UiAnimationType::SlideRight,
    };
    let toast = tree
        .add_node()
        .flow_child(Ab(Vec2::new(320.0, 44.0)))
        .with_rect(6.0, 1.0, Vec4::new(0.15, 0.15, 0.22, 0.5))
        .color_raw::<UiBase>(Vec4::new(0.06, 0.06, 0.1, 0.95))
        .with_intro(intro_kind, 0.2)
        .with_outro(UiAnimationType::Fade, 0.15)
        .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))
        .color_raw::<UiBase>(accent);

    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)
        .color_raw::<UiBase>(Vec4::new(0.9, 0.9, 0.95, 1.0));

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

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

    ui_mark_layout_dirty(world);
    ui_mark_render_dirty(world);

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

pub fn ui_tick_toasts(world: &mut World) {
    let current_time = world.resources.retained_ui.timing.current_time;
    let entries = std::mem::take(&mut world.resources.retained_ui.overlays.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 = world
                .ui
                .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)
                || !ui_node_visible(world, entry.entity)
            {
                to_despawn.push(entry.entity);
            } else {
                kept.push(entry);
            }
        } else if elapsed >= entry.duration {
            entry.dismissing = true;
            ui_set_visible(world, entry.entity, false);
            kept.push(entry);
        } else {
            kept.push(entry);
        }
    }

    world.resources.retained_ui.overlays.toast_entries = kept;

    for entity in to_despawn {
        ui_despawn_node(world, entity);
    }
}

pub fn ui_set_selected(world: &mut World, entity: freecs::Entity, selected: bool) {
    let accent = world
        .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(data) = world.ui.get_ui_selectable_label_mut(entity) {
        data.selected = selected;
    }
    if let Some(color) = world.ui.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) = world.ui.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(world: &mut World, entity: freecs::Entity, value: f32) {
    let update = if let Some(data) = world.ui.get_ui_drag_value(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);
        world.resources.text.cache.set_text(text_slot, &display);
        if let Some(data) = world.ui.get_ui_drag_value_mut(entity) {
            data.value = clamped;
        }
    }
}

pub fn ui_show_context_menu(world: &mut World, entity: freecs::Entity, position: Vec2) {
    if let Some(old_menu) = world.resources.retained_ui.overlays.active_context_menu
        && old_menu != entity
        && let Some(old_data) = world.ui.get_ui_context_menu(old_menu).cloned().as_ref()
    {
        if let Some(node) = world.ui.get_ui_layout_node_mut(old_data.popup_entity) {
            node.visible = false;
        }
        if let Some(widget_data) = world.ui.get_ui_context_menu_mut(old_menu) {
            widget_data.open = false;
        }
    }

    let popup = world
        .ui
        .get_ui_context_menu(entity)
        .map(|data| data.popup_entity);

    if let Some(popup_entity) = popup {
        let dpi_scale = crate::ecs::window::resources::window_scale_factor(world);
        if let Some(node) = world.ui.get_ui_layout_node_mut(popup_entity)
            && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                node.base_layout.as_mut()
        {
            window.position = crate::ecs::ui::units::Ab(position / dpi_scale).into();
            node.visible = true;
        }
        if let Some(data) = world.ui.get_ui_context_menu_mut(entity) {
            data.open = true;
            data.clicked_item = None;
        }
        world.resources.retained_ui.overlays.active_context_menu = Some(entity);
        ui_mark_layout_dirty(world);
    }
}

pub fn ui_context_menu_set_checked(
    world: &mut World,
    entity: freecs::Entity,
    command_id: usize,
    checked: bool,
) {
    let Some(data) = world.ui.get_ui_context_menu_mut(entity) else {
        return;
    };
    set_checkable_in_defs(
        &mut data.item_defs,
        command_id,
        checked,
        &mut world.resources.text.cache,
    );
}

pub fn ui_context_menu_get_checked(
    world: &World,
    entity: freecs::Entity,
    command_id: usize,
) -> Option<bool> {
    let data = world.ui.get_ui_context_menu(entity)?;
    find_checkable_in_defs(&data.item_defs, command_id)
}

pub fn ui_context_menu_widget_content(
    world: &World,
    entity: freecs::Entity,
    command_id: usize,
) -> Option<freecs::Entity> {
    if let Some(data) = world.ui.get_ui_context_menu(entity) {
        find_widget_content_in_defs(&data.item_defs, command_id)
    } else {
        None
    }
}

pub fn ui_close_context_menus(world: &mut World) {
    if let Some(menu_entity) = world.resources.retained_ui.overlays.active_context_menu {
        let popup = world
            .ui
            .get_ui_context_menu(menu_entity)
            .map(|data| data.popup_entity);
        if let Some(popup_entity) = popup
            && let Some(node) = world.ui.get_ui_layout_node_mut(popup_entity)
        {
            node.visible = false;
        }
        if let Some(data) = world.ui.get_ui_context_menu_mut(menu_entity) {
            data.open = false;
        }
        world.resources.retained_ui.overlays.active_context_menu = None;
        ui_mark_layout_dirty(world);
    }
}

pub fn ui_tree_view_set_filter(world: &mut World, tree: freecs::Entity, text: &str) {
    let tree_data = if let Some(data) = world.ui.get_ui_tree_view(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(node_data) = world.ui.get_ui_tree_node(node_entity) {
                pre_filter.insert(node_entity, node_data.expanded);
            }
        }
        if let Some(data) = world.ui.get_ui_tree_view_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(node_data) = world.ui.get_ui_tree_node(node_entity)
                && node_data.label.to_lowercase().contains(&filter_lower)
            {
                matching_nodes.insert(node_entity);
                let mut ancestor = node_data.parent_node;
                while let Some(ancestor_entity) = ancestor {
                    matching_nodes.insert(ancestor_entity);
                    ancestor = if let Some(anc_data) = world.ui.get_ui_tree_node(ancestor_entity) {
                        anc_data.parent_node
                    } else {
                        None
                    };
                }
            }
        }

        for &node_entity in &tree_data.node_entities {
            let node_info = world
                .ui
                .get_ui_tree_node(node_entity)
                .map(|node_data| (node_data.wrapper_entity, node_data.parent_node.is_some()));

            if let Some((wrapper, _has_parent)) = node_info {
                let visible = matching_nodes.contains(&node_entity);
                if let Some(node) = world.ui.get_ui_layout_node_mut(wrapper) {
                    node.visible = visible;
                }
                if visible {
                    ui_tree_node_set_expanded(world, 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 = world
                .ui
                .get_ui_tree_node(node_entity)
                .map(|node_data| node_data.wrapper_entity);
            if let Some(wrapper) = wrapper
                && let Some(node) = world.ui.get_ui_layout_node_mut(wrapper)
            {
                node.visible = true;
            }
            let was_expanded = pre_filter.get(&node_entity).copied().unwrap_or(false);
            ui_tree_node_set_expanded(world, node_entity, was_expanded);
        }
    }

    if let Some(data) = world.ui.get_ui_tree_view_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(world: &mut World, tree: freecs::Entity) {
    ui_tree_view_set_filter(world, tree, "");
}

pub fn ui_tree_node_set_expanded(world: &mut World, entity: freecs::Entity, expanded: bool) {
    let update = world
        .ui
        .get_ui_tree_node(entity)
        .map(|data| (data.arrow_text_slot, data.children_container));

    if let Some((arrow_slot, children)) = update {
        world
            .resources
            .text
            .cache
            .set_text(arrow_slot, if expanded { "\u{25BC}" } else { "\u{25B6}" });
        if let Some(node) = world.ui.get_ui_layout_node_mut(children) {
            node.visible = expanded;
        }
        if let Some(data) = world.ui.get_ui_tree_node_mut(entity) {
            data.expanded = expanded;
        }
    }
}

pub fn ui_tree_node_mark_loaded(world: &mut World, entity: freecs::Entity) {
    if let Some(data) = world.ui.get_ui_tree_node_mut(entity) {
        data.lazy_loaded = true;
    }
}

pub fn ui_tree_view_select_node(world: &mut World, tree: freecs::Entity, node: freecs::Entity) {
    let tree_data = if let Some(data) = world.ui.get_ui_tree_view(tree) {
        data.clone()
    } else {
        return;
    };
    if !tree_data.node_entities.contains(&node) {
        return;
    }

    for prev in &tree_data.selected_nodes {
        if *prev == node {
            continue;
        }
        if let Some(prev_node) = world.ui.get_ui_tree_node(*prev).cloned() {
            if let Some(color) = world.ui.get_ui_node_color_mut(prev_node.row_entity) {
                color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
            }
            if let Some(weights) = world.ui.get_ui_state_weights_mut(prev_node.row_entity) {
                weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
            }
            if let Some(widget_data) = world.ui.get_ui_tree_node_mut(*prev) {
                widget_data.selected = false;
            }
        }
    }

    let row_entity = if let Some(target) = world.ui.get_ui_tree_node(node) {
        target.row_entity
    } else {
        return;
    };

    let accent = world
        .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(color) = world.ui.get_ui_node_color_mut(row_entity) {
        color.colors[crate::ecs::ui::state::UiSelected::INDEX] = Some(selected_bg);
    }
    if let Some(weights) = world.ui.get_ui_state_weights_mut(row_entity) {
        weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 1.0;
    }
    if let Some(widget_data) = world.ui.get_ui_tree_node_mut(node) {
        widget_data.selected = true;
    }
    if let Some(widget_data) = world.ui.get_ui_tree_view_mut(tree) {
        widget_data.selected_nodes = vec![node];
    }

    let mut ancestor = world.ui.get_ui_tree_node(node).and_then(|d| d.parent_node);
    while let Some(ancestor_entity) = ancestor {
        ui_tree_node_set_expanded(world, ancestor_entity, true);
        ancestor = world
            .ui
            .get_ui_tree_node(ancestor_entity)
            .and_then(|d| d.parent_node);
    }
}

pub fn ui_tree_view_clear_selection(world: &mut World, tree: freecs::Entity) {
    let selected = if let Some(data) = world.ui.get_ui_tree_view(tree) {
        data.selected_nodes.clone()
    } else {
        return;
    };
    for prev in selected {
        if let Some(prev_node) = world.ui.get_ui_tree_node(prev).cloned() {
            if let Some(color) = world.ui.get_ui_node_color_mut(prev_node.row_entity) {
                color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
            }
            if let Some(weights) = world.ui.get_ui_state_weights_mut(prev_node.row_entity) {
                weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
            }
            if let Some(widget_data) = world.ui.get_ui_tree_node_mut(prev) {
                widget_data.selected = false;
            }
        }
    }
    if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
        data.selected_nodes.clear();
    }
}

pub fn ui_tree_view_scroll_to_node(world: &mut World, tree: freecs::Entity, node: freecs::Entity) {
    let (current_offset, node_entities) = if let Some(data) = world.ui.get_ui_tree_view(tree) {
        (data.scroll_offset, data.node_entities.clone())
    } else {
        return;
    };
    if !node_entities.contains(&node) {
        return;
    }

    let row_entity = if let Some(target) = world.ui.get_ui_tree_node(node) {
        target.row_entity
    } else {
        return;
    };

    let row_rect = if let Some(layout) = world.ui.get_ui_layout_node(row_entity) {
        layout.computed_rect
    } else {
        return;
    };
    let view_rect = if let Some(layout) = world.ui.get_ui_layout_node(tree) {
        layout.computed_rect
    } else {
        return;
    };
    if view_rect.height() <= 0.0 || row_rect.height() <= 0.0 {
        return;
    }

    let dpi_scale = crate::ecs::window::resources::window_scale_factor(world).max(1.0);
    let mut scroll_offset = current_offset;
    if row_rect.min.y < view_rect.min.y {
        scroll_offset = (scroll_offset - (view_rect.min.y - row_rect.min.y) / dpi_scale).max(0.0);
    } else if row_rect.max.y > view_rect.max.y {
        scroll_offset += (row_rect.max.y - view_rect.max.y) / dpi_scale;
    }

    if let Some(data) = world.ui.get_ui_tree_view_mut(tree) {
        data.scroll_offset = scroll_offset;
    }
}

pub fn ui_show_modal(world: &mut World, entity: freecs::Entity) {
    let backdrop = world
        .ui
        .get_ui_modal_dialog(entity)
        .map(|data| data.backdrop_entity);

    if let Some(backdrop_entity) = backdrop {
        if let Some(node) = world.ui.get_ui_layout_node_mut(backdrop_entity) {
            node.visible = true;
        }
        if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
            node.visible = true;
        }
        if let Some(data) = world.ui.get_ui_modal_dialog_mut(entity) {
            data.result = None;
        }
        world.resources.retained_ui.overlays.active_modal = Some(entity);
        ui_mark_layout_dirty(world);
    }
}

/// 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 = ui_widget_content(world, header_entity).unwrap();
/// ```
pub fn ui_widget_content(world: &World, entity: freecs::Entity) -> Option<freecs::Entity> {
    if let Some(data) = world.ui.get_ui_collapsing_header(entity) {
        return Some(data.content_entity);
    }
    if let Some(data) = world.ui.get_ui_scroll_area(entity) {
        return Some(data.content_entity);
    }
    if let Some(data) = world.ui.get_ui_panel(entity) {
        return Some(data.content_entity);
    }
    if let Some(data) = world.ui.get_ui_modal_dialog(entity) {
        return Some(data.content_entity);
    }
    if let Some(data) = world.ui.get_ui_tree_view(entity) {
        return Some(data.content_entity);
    }
    if let Some(data) = world.ui.get_ui_tile_container(entity) {
        return Some(data.container_entity);
    }
    Some(entity)
}

pub fn ui_show_command_palette(world: &mut World, entity: freecs::Entity) {
    let (backdrop, text_input) = if let Some(data) = world.ui.get_ui_command_palette(entity) {
        (data.backdrop_entity, data.text_input_entity)
    } else {
        return;
    };

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

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

    if let Some(data) = world.ui.get_ui_command_palette_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();
    }

    ui_command_palette_rebuild_results(world, entity);
    ui_mark_layout_dirty(world);
}

pub fn ui_command_palette_register(
    world: &mut World,
    entity: freecs::Entity,
    label: &str,
    shortcut: &str,
    category: &str,
) {
    if let Some(data) = world.ui.get_ui_command_palette_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(world: &mut World, entity: freecs::Entity) {
    if let Some(data) = world.ui.get_ui_command_palette_mut(entity) {
        data.commands.clear();
        data.filtered_indices.clear();
        data.selected_index = 0;
    }
}

pub fn ui_command_palette_rebuild_results(world: &mut World, entity: freecs::Entity) {
    let data = if let Some(data) = world.ui.get_ui_command_palette(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)
            };
            world.resources.text.cache.set_text(label_slot, &display);
            world
                .resources
                .text
                .cache
                .set_text(shortcut_slot, &cmd.shortcut);

            if let Some(node) = world.ui.get_ui_layout_node_mut(row_entity) {
                node.visible = true;
            }
        } else if let Some(node) = world.ui.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(
    world: &mut World,
    entity: freecs::Entity,
    span_index: usize,
    text: &str,
) {
    if let Some(data) = world.ui.get_ui_rich_text(entity)
        && let Some(&text_slot) = data.span_text_slots.get(span_index)
    {
        world.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(
    world: &mut World,
    entity: freecs::Entity,
    span_index: usize,
    color: Vec4,
) {
    if let Some(data) = world.ui.get_ui_rich_text(entity)
        && let Some(&span_entity) = data.span_entities.get(span_index)
        && let Some(node_color) = world.ui.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 ui_bubbled_events_for(world, form_container) {
///     if let UiEvent::TextInputChanged { entity, text } = &event.event {
///         // Some text input inside the form changed
///     }
/// }
/// ```
pub fn ui_bubbled_events_for(
    world: &World,
    entity: freecs::Entity,
) -> Vec<crate::ecs::ui::resources::BubbledUiEvent> {
    world
        .resources
        .retained_ui
        .frame
        .bubbled_events
        .iter()
        .filter(|event| event.ancestor == entity && !event.stopped)
        .cloned()
        .collect()
}

fn set_checkable_in_defs(
    defs: &mut [crate::ecs::ui::components::ContextMenuItemDef],
    command_id: usize,
    new_checked: bool,
    text_cache: &mut crate::ecs::text::resources::TextCache,
) -> bool {
    for def in defs.iter_mut() {
        let id = def.command_id;
        if let crate::ecs::ui::components::ContextMenuItemKind::Checkable {
            checked,
            check_text_slot,
        } = &mut def.kind
        {
            if id == Some(command_id) {
                if *checked != new_checked {
                    *checked = new_checked;
                    text_cache
                        .set_text(*check_text_slot, if new_checked { "\u{2713}" } else { "" });
                }
                return true;
            }
        } else if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } =
            &mut def.kind
            && set_checkable_in_defs(children, command_id, new_checked, text_cache)
        {
            return true;
        }
    }
    false
}

fn find_checkable_in_defs(
    defs: &[crate::ecs::ui::components::ContextMenuItemDef],
    command_id: usize,
) -> Option<bool> {
    for def in defs {
        let id = def.command_id;
        if let crate::ecs::ui::components::ContextMenuItemKind::Checkable { checked, .. } =
            &def.kind
        {
            if id == Some(command_id) {
                return Some(*checked);
            }
        } else if let crate::ecs::ui::components::ContextMenuItemKind::Submenu { children, .. } =
            &def.kind
            && let Some(found) = find_checkable_in_defs(children, command_id)
        {
            return Some(found);
        }
    }
    None
}
