use nalgebra_glm::Vec2;

use crate::ecs::ui::color::blend_state_colors;
use crate::ecs::ui::components::{AutoSizeMode, UiAnimationPhase, UiDepthMode, UiNodeContent};
use crate::ecs::ui::layout_types::{
    FlowAlignment, FlowDirection, FlowLayout, GridLayout, compute_layout_rect,
};
use crate::ecs::ui::state::{
    STATE_COUNT, UiBase, UiDisabled, UiFocused, UiHover, UiPressed, UiStateTrait,
};
use crate::ecs::ui::types::Rect;
use crate::ecs::ui::units::UiEvalContext;
use crate::ecs::ui::widget_systems::measure_text_width;
use crate::ecs::world::World;
use crate::render::wgpu::passes::geometry::UiLayer;

pub const UI_LAYOUT_PICKING: &str = "ui_layout_picking";
pub const UI_LAYOUT_STATE_UPDATE: &str = "ui_layout_state_update";
pub const UI_LAYOUT_COMPUTE: &str = "ui_layout_compute";
pub const UI_THEME_APPLY: &str = "ui_theme_apply";
pub const UI_LAYOUT_COLOR_BLEND: &str = "ui_layout_color_blend";
pub const UI_LAYOUT_RENDER_SYNC: &str = "ui_layout_render_sync";

pub fn ui_layout_state_update_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let delta_time = world.resources.window.timing.delta_time;
    let reduced_motion = world.resources.retained_ui.reduced_motion;
    let total_state_count = world.resources.retained_ui.total_state_count();

    let entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_STATE_WEIGHTS)
        .collect();

    let mut any_weight_changed = false;

    for entity in entities {
        let (hover_target, pressed_target, focused_target, disabled_target) = {
            match world.get_ui_node_interaction(entity) {
                Some(interaction) => (
                    if interaction.hovered { 1.0 } else { 0.0 },
                    if interaction.pressed { 1.0 } else { 0.0 },
                    if interaction.focused { 1.0 } else { 0.0 },
                    if interaction.disabled { 1.0 } else { 0.0 },
                ),
                None => (0.0, 0.0, 0.0, 0.0),
            }
        };

        if let Some(state_weights) = world.get_ui_state_weights_mut(entity) {
            state_weights.ensure_state_capacity(total_state_count);

            let targets = [
                (UiHover::INDEX, hover_target),
                (UiPressed::INDEX, pressed_target),
                (UiFocused::INDEX, focused_target),
                (UiDisabled::INDEX, disabled_target),
            ];

            for (index, target) in targets {
                let previous = state_weights.weights[index];

                if (state_weights.targets[index] - target).abs() > f32::EPSILON {
                    state_weights.targets[index] = target;
                    state_weights.start_weights[index] = previous;
                    state_weights.progress[index] = 0.0;
                }

                if reduced_motion {
                    state_weights.weights[index] = state_weights.targets[index];
                    state_weights.progress[index] = 1.0;
                } else if state_weights.progress[index] < 1.0 {
                    let transition = state_weights.transitions[index].unwrap_or_default();
                    let speed = if target > state_weights.start_weights[index] {
                        transition.enter_speed
                    } else {
                        transition.exit_speed
                    };

                    state_weights.progress[index] =
                        (state_weights.progress[index] + speed * delta_time).min(1.0);

                    let eased = transition.easing.evaluate(state_weights.progress[index]);
                    let start = state_weights.start_weights[index];
                    state_weights.weights[index] =
                        (start + (target - start) * eased).clamp(0.0, 1.0);
                }

                if (state_weights.weights[index] - previous).abs() > f32::EPSILON {
                    any_weight_changed = true;
                }
            }

            for index in STATE_COUNT..state_weights.weights.len() {
                let previous = state_weights.weights[index];
                let target = state_weights.targets[index];

                if reduced_motion {
                    state_weights.weights[index] = target;
                    state_weights.progress[index] = 1.0;
                } else if state_weights.progress[index] < 1.0 {
                    let transition = state_weights.transitions[index].unwrap_or_default();
                    let speed = if target > state_weights.start_weights[index] {
                        transition.enter_speed
                    } else {
                        transition.exit_speed
                    };

                    state_weights.progress[index] =
                        (state_weights.progress[index] + speed * delta_time).min(1.0);

                    let eased = transition.easing.evaluate(state_weights.progress[index]);
                    let start = state_weights.start_weights[index];
                    state_weights.weights[index] =
                        (start + (target - start) * eased).clamp(0.0, 1.0);
                }

                if (state_weights.weights[index] - previous).abs() > f32::EPSILON {
                    any_weight_changed = true;
                }
            }

            let non_base_sum: f32 = state_weights.weights[1..].iter().sum();
            state_weights.weights[UiBase::INDEX] = (1.0 - non_base_sum).clamp(0.0, 1.0);
        }
    }

    if any_weight_changed {
        world.resources.retained_ui.layout_dirty = true;
    }

    let anim_entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_LAYOUT_NODE)
        .collect();

    for entity in anim_entities {
        let should_hide = if let Some(node) = world.get_ui_layout_node_mut(entity)
            && let Some(animation) = &mut node.animation
        {
            match animation.phase {
                UiAnimationPhase::IntroPlaying => {
                    animation.progress += delta_time / animation.duration;
                    if animation.progress >= 1.0 {
                        animation.progress = 1.0;
                        animation.phase = UiAnimationPhase::Idle;
                    }
                    world.resources.retained_ui.layout_dirty = true;
                    false
                }
                UiAnimationPhase::OutroPlaying => {
                    animation.progress += delta_time / animation.duration;
                    if animation.progress >= 1.0 {
                        animation.progress = 1.0;
                        animation.phase = UiAnimationPhase::OutroComplete;
                        world.resources.retained_ui.layout_dirty = true;
                        true
                    } else {
                        world.resources.retained_ui.layout_dirty = true;
                        false
                    }
                }
                _ => false,
            }
        } else {
            false
        };

        if should_hide && let Some(node) = world.get_ui_layout_node_mut(entity) {
            node.visible = false;
        }
    }

    world.ui_tick_toasts();
}

struct StackEntry {
    entity: freecs::Entity,
    parent_rect: Rect,
    parent_depth: f32,
    parent_font_size: f32,
    parent_clip_rect: Option<Rect>,
    parent_layer: Option<UiLayer>,
    flow_override_rect: Option<Rect>,
}

pub fn ui_docked_panel_layout_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let viewport_size = world
        .resources
        .window
        .cached_viewport_size
        .map(|(width, height)| Vec2::new(width as f32, height as f32))
        .unwrap_or(Vec2::new(800.0, 600.0));
    let dpi_scale = world.resources.window.cached_scale_factor;

    world.resources.retained_ui.reserved_areas =
        crate::ecs::ui::resources::ReservedAreas::default();

    let entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_WIDGET_STATE)
        .collect();

    let mut docked_panels: Vec<(
        freecs::Entity,
        crate::ecs::ui::components::UiPanelKind,
        f32,
        i32,
    )> = Vec::new();
    for entity in &entities {
        if let Some(crate::ecs::ui::components::UiWidgetState::Panel(data)) =
            world.get_ui_widget_state(*entity)
            && data.panel_kind != crate::ecs::ui::components::UiPanelKind::Floating
        {
            docked_panels.push((
                *entity,
                data.panel_kind,
                data.default_dock_size,
                data.focus_order,
            ));
        }
    }
    docked_panels.sort_by_key(|(_, _, _, order)| *order);

    for (entity, panel_kind, dock_size, _) in &docked_panels {
        let reserved = &world.resources.retained_ui.reserved_areas;
        let (position, size, resize_edge_kind) = match panel_kind {
            crate::ecs::ui::components::UiPanelKind::DockedLeft => {
                let h = viewport_size.y - reserved.top - reserved.bottom;
                (
                    Vec2::new(reserved.left, reserved.top),
                    Vec2::new(dock_size * dpi_scale, h),
                    Some(crate::ecs::ui::components::ResizeEdge::Right),
                )
            }
            crate::ecs::ui::components::UiPanelKind::DockedRight => {
                let h = viewport_size.y - reserved.top - reserved.bottom;
                (
                    Vec2::new(
                        viewport_size.x - reserved.right - dock_size * dpi_scale,
                        reserved.top,
                    ),
                    Vec2::new(dock_size * dpi_scale, h),
                    Some(crate::ecs::ui::components::ResizeEdge::Left),
                )
            }
            crate::ecs::ui::components::UiPanelKind::DockedTop => {
                let w = viewport_size.x - reserved.left - reserved.right;
                (
                    Vec2::new(reserved.left, reserved.top),
                    Vec2::new(w, dock_size * dpi_scale),
                    Some(crate::ecs::ui::components::ResizeEdge::Bottom),
                )
            }
            crate::ecs::ui::components::UiPanelKind::DockedBottom => {
                let w = viewport_size.x - reserved.left - reserved.right;
                (
                    Vec2::new(
                        reserved.left,
                        viewport_size.y - reserved.bottom - dock_size * dpi_scale,
                    ),
                    Vec2::new(w, dock_size * dpi_scale),
                    Some(crate::ecs::ui::components::ResizeEdge::Top),
                )
            }
            crate::ecs::ui::components::UiPanelKind::Floating => continue,
        };

        let _ = resize_edge_kind;

        if let Some(node) = world.get_ui_layout_node_mut(*entity)
            && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                node.layouts[UiBase::INDEX].as_mut()
        {
            let current_ab = window.position.absolute.unwrap_or(Vec2::new(0.0, 0.0));
            let parent_min = node.computed_rect.min - current_ab * dpi_scale;
            window.position = crate::ecs::ui::units::Ab((position - parent_min) / dpi_scale).into();
            window.size = crate::ecs::ui::units::Ab(size / dpi_scale).into();
        }

        match panel_kind {
            crate::ecs::ui::components::UiPanelKind::DockedLeft => {
                world.resources.retained_ui.reserved_areas.left += dock_size * dpi_scale;
            }
            crate::ecs::ui::components::UiPanelKind::DockedRight => {
                world.resources.retained_ui.reserved_areas.right += dock_size * dpi_scale;
            }
            crate::ecs::ui::components::UiPanelKind::DockedTop => {
                world.resources.retained_ui.reserved_areas.top += dock_size * dpi_scale;
            }
            crate::ecs::ui::components::UiPanelKind::DockedBottom => {
                world.resources.retained_ui.reserved_areas.bottom += dock_size * dpi_scale;
            }
            crate::ecs::ui::components::UiPanelKind::Floating => {}
        }
    }
}

pub fn ui_layout_compute_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let viewport_raw = world.resources.window.cached_viewport_size;
    let dpi_scale = world.resources.window.cached_scale_factor;
    let text_generation = world.resources.text_cache.generation;

    let viewport_changed = world.resources.retained_ui.last_compute_viewport != viewport_raw;
    let dpi_changed =
        (dpi_scale - world.resources.retained_ui.last_compute_dpi).abs() > f32::EPSILON;
    let text_changed = text_generation != world.resources.retained_ui.last_compute_text_generation;
    let layout_dirty = world.resources.retained_ui.layout_dirty;

    if !viewport_changed && !dpi_changed && !text_changed && !layout_dirty {
        world.resources.retained_ui.render_dirty = false;
        return;
    }

    world.resources.retained_ui.last_compute_viewport = viewport_raw;
    world.resources.retained_ui.last_compute_dpi = dpi_scale;
    world.resources.retained_ui.last_compute_text_generation = text_generation;
    world.resources.retained_ui.layout_dirty = false;
    world.resources.retained_ui.render_dirty = true;

    let viewport_size = viewport_raw
        .map(|(width, height)| Vec2::new(width as f32, height as f32))
        .unwrap_or(Vec2::new(800.0, 600.0));

    let root_entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_LAYOUT_ROOT)
        .collect();

    let mut all_sorted_nodes: Vec<(freecs::Entity, f32)> = Vec::new();
    let mut secondary_sorted: std::collections::HashMap<usize, Vec<(freecs::Entity, f32)>> =
        std::collections::HashMap::new();

    for root_entity in root_entities {
        let (absolute_scale, default_font_size, target_window) = {
            match world.get_ui_layout_root(root_entity) {
                Some(root) => (
                    root.absolute_scale * dpi_scale,
                    root.default_font_size,
                    root.target_window,
                ),
                None => continue,
            }
        };

        let root_viewport_size = if let Some(window_index) = target_window {
            world
                .resources
                .secondary_windows
                .states
                .iter()
                .find(|s| s.index == window_index)
                .map(|s| Vec2::new(s.size.0 as f32, s.size.1 as f32))
                .unwrap_or(viewport_size)
        } else {
            viewport_size
        };

        let flow_ctx = FlowContext {
            absolute_scale,
            viewport_size: root_viewport_size,
            dpi_scale,
        };

        let root_rect = Rect::new(0.0, 0.0, root_viewport_size.x, root_viewport_size.y);
        let root_children: Vec<freecs::Entity> = world
            .resources
            .children_cache
            .get(&root_entity)
            .map(|v| v.to_vec())
            .unwrap_or_default();

        let mut stack: Vec<StackEntry> = Vec::new();

        for child in root_children.iter().rev() {
            if world.get_ui_layout_node(*child).is_some() {
                stack.push(StackEntry {
                    entity: *child,
                    parent_rect: root_rect,
                    parent_depth: 0.0,
                    parent_font_size: default_font_size,
                    parent_clip_rect: None,
                    parent_layer: None,
                    flow_override_rect: None,
                });
            }
        }

        while let Some(entry) = stack.pop() {
            let (
                layouts,
                flow_layout,
                responsive_flow,
                grid_layout,
                depth_mode,
                node_font_size,
                visible,
                clip_content,
                node_layer,
                node_min_size,
                node_max_size,
            ) = {
                match world.get_ui_layout_node(entry.entity) {
                    Some(node) => (
                        node.layouts.clone(),
                        node.flow_layout,
                        node.responsive_flow,
                        node.grid_layout,
                        node.depth,
                        node.font_size,
                        node.visible,
                        node.clip_content,
                        node.layer,
                        node.min_size,
                        node.max_size,
                    ),
                    None => continue,
                }
            };

            if !visible {
                continue;
            }

            let font_size = node_font_size.unwrap_or(entry.parent_font_size);

            let context = UiEvalContext {
                parent_width: entry.parent_rect.width(),
                parent_height: entry.parent_rect.height(),
                viewport_width: root_viewport_size.x,
                viewport_height: root_viewport_size.y,
                font_size,
                absolute_scale,
            };

            let has_non_base_layouts = layouts[1..].iter().any(Option::is_some);

            let computed_rect = if let Some(flow_rect) = entry.flow_override_rect {
                flow_rect
            } else if !has_non_base_layouts {
                if let Some(base_layout) = &layouts[UiBase::INDEX] {
                    compute_layout_rect(base_layout, &context, &entry.parent_rect)
                } else {
                    entry.parent_rect
                }
            } else {
                let weights = world
                    .get_ui_state_weights(entry.entity)
                    .map(|state_weights| state_weights.weights.clone())
                    .unwrap_or_else(|| vec![0.0; STATE_COUNT]);
                let total_weight: f32 = weights.iter().sum();
                if total_weight < f32::EPSILON {
                    if let Some(base_layout) = &layouts[UiBase::INDEX] {
                        compute_layout_rect(base_layout, &context, &entry.parent_rect)
                    } else {
                        entry.parent_rect
                    }
                } else {
                    let mut blended_min = Vec2::new(0.0, 0.0);
                    let mut blended_max = Vec2::new(0.0, 0.0);

                    let blend_count = weights.len().min(layouts.len());
                    for (index, weight) in weights[..blend_count].iter().enumerate() {
                        let layout = layouts[index].as_ref().or(layouts[UiBase::INDEX].as_ref());

                        if let Some(layout) = layout {
                            let rect = compute_layout_rect(layout, &context, &entry.parent_rect);
                            let normalized = *weight / total_weight;
                            blended_min += rect.min * normalized;
                            blended_max += rect.max * normalized;
                        }
                    }

                    Rect::from_min_max(blended_min, blended_max)
                }
            };

            let computed_rect = if node_min_size.is_some() || node_max_size.is_some() {
                let mut width = computed_rect.width();
                let mut height = computed_rect.height();
                if let Some(min) = node_min_size {
                    width = width.max(min.x);
                    height = height.max(min.y);
                }
                if let Some(max) = node_max_size {
                    width = width.min(max.x);
                    height = height.min(max.y);
                }
                Rect::from_min_max(
                    computed_rect.min,
                    Vec2::new(computed_rect.min.x + width, computed_rect.min.y + height),
                )
            } else {
                computed_rect
            };

            let depth = match depth_mode {
                UiDepthMode::Add(delta) => entry.parent_depth + delta,
                UiDepthMode::Set(absolute) => absolute,
            };

            let effective_layer = node_layer.or(entry.parent_layer);

            let is_popup_layer = matches!(
                effective_layer,
                Some(UiLayer::Popups) | Some(UiLayer::Tooltips)
            );

            let (node_clip_rect, child_clip_rect) = if is_popup_layer {
                (None, None)
            } else if clip_content {
                let clipped = match &entry.parent_clip_rect {
                    Some(parent_clip) => computed_rect.intersect(parent_clip),
                    None => Some(computed_rect),
                };
                (clipped, clipped)
            } else {
                (entry.parent_clip_rect, entry.parent_clip_rect)
            };

            let computed_rect = if let Some(node) = world.get_ui_layout_node(entry.entity)
                && let Some(animation) = &node.animation
            {
                apply_animation_to_rect(computed_rect, animation)
            } else {
                computed_rect
            };

            if let Some(node) = world.get_ui_layout_node_mut(entry.entity) {
                node.computed_rect = computed_rect;
                node.computed_depth = depth;
                node.computed_clip_rect = node_clip_rect;
                node.computed_layer = effective_layer;
            }

            if let Some(window_index) = target_window {
                secondary_sorted
                    .entry(window_index)
                    .or_default()
                    .push((entry.entity, depth));
            } else {
                all_sorted_nodes.push((entry.entity, depth));
            }

            let children: Vec<freecs::Entity> = world
                .resources
                .children_cache
                .get(&entry.entity)
                .map(|v| v.to_vec())
                .unwrap_or_default();

            if let Some(flow) = &flow_layout {
                let effective_flow = if let Some(resp) = responsive_flow
                    && computed_rect.width() < resp.max_width * dpi_scale
                {
                    FlowLayout {
                        direction: resp.direction,
                        ..*flow
                    }
                } else {
                    *flow
                };
                let flow_rects = compute_flow_children(
                    world,
                    &children,
                    &effective_flow,
                    &computed_rect,
                    font_size,
                    &flow_ctx,
                );

                for (child, child_rect) in flow_rects.iter().rev() {
                    stack.push(StackEntry {
                        entity: *child,
                        parent_rect: computed_rect,
                        parent_depth: depth,
                        parent_font_size: font_size,
                        parent_clip_rect: child_clip_rect,
                        parent_layer: effective_layer,
                        flow_override_rect: Some(*child_rect),
                    });
                }
            } else if let Some(grid) = &grid_layout {
                let grid_rects =
                    compute_grid_children(world, &children, grid, &computed_rect, dpi_scale);

                for (child, child_rect) in grid_rects.iter().rev() {
                    stack.push(StackEntry {
                        entity: *child,
                        parent_rect: computed_rect,
                        parent_depth: depth,
                        parent_font_size: font_size,
                        parent_clip_rect: child_clip_rect,
                        parent_layer: effective_layer,
                        flow_override_rect: Some(*child_rect),
                    });
                }
            } else {
                for child in children.iter().rev() {
                    if world.get_ui_layout_node(*child).is_some() {
                        stack.push(StackEntry {
                            entity: *child,
                            parent_rect: computed_rect,
                            parent_depth: depth,
                            parent_font_size: font_size,
                            parent_clip_rect: child_clip_rect,
                            parent_layer: effective_layer,
                            flow_override_rect: None,
                        });
                    }
                }
            }
        }
    }

    all_sorted_nodes.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
    world.resources.retained_ui.z_sorted_nodes = all_sorted_nodes;
    world.resources.retained_ui.picking_grid =
        Some(crate::ecs::ui::picking::build_picking_grid(world));

    for (window_index, mut nodes) in secondary_sorted {
        nodes.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
        let buffers = world
            .resources
            .retained_ui
            .secondary_buffers
            .entry(window_index)
            .or_insert_with(|| crate::ecs::ui::resources::SecondaryWindowUiBuffers {
                z_sorted_nodes: Vec::new(),
                frame_rects: Vec::new(),
                frame_ui_images: Vec::new(),
                frame_text_meshes: Vec::new(),
            });
        buffers.z_sorted_nodes = nodes;
    }
}

struct FlowContext {
    absolute_scale: f32,
    viewport_size: Vec2,
    dpi_scale: f32,
}

fn compute_natural_height(
    world: &World,
    entity: freecs::Entity,
    available_width: f32,
    font_size: f32,
    flow_ctx: &FlowContext,
) -> f32 {
    let node = match world.get_ui_layout_node(entity) {
        Some(node) => node,
        None => return 0.0,
    };

    if !node.visible {
        return 0.0;
    }

    let flow = match node.flow_layout {
        Some(flow) => flow,
        None => return 0.0,
    };

    let children: Vec<freecs::Entity> = world
        .resources
        .children_cache
        .get(&entity)
        .map(|v| v.to_vec())
        .unwrap_or_default();

    let scaled_padding = flow.padding * flow_ctx.dpi_scale;
    let scaled_spacing = flow.spacing * flow_ctx.dpi_scale;
    let inner_width = (available_width - scaled_padding * 2.0).max(0.0);

    let context = UiEvalContext {
        parent_width: inner_width,
        parent_height: 0.0,
        viewport_width: flow_ctx.viewport_size.x,
        viewport_height: flow_ctx.viewport_size.y,
        font_size,
        absolute_scale: flow_ctx.absolute_scale,
    };

    let mut child_heights: Vec<f32> = Vec::new();

    for child_entity in &children {
        let child_node = match world.get_ui_layout_node(*child_entity) {
            Some(node) => node,
            None => continue,
        };

        if !child_node.visible {
            continue;
        }

        let child_font_size = child_node.font_size.unwrap_or(font_size);

        let height = if let Some(ref child_size) = child_node.flow_child_size {
            let evaluated = child_size.evaluate(&context);
            if evaluated.y.abs() < f32::EPSILON && child_node.flow_layout.is_some() {
                compute_natural_height(world, *child_entity, inner_width, child_font_size, flow_ctx)
            } else {
                evaluated.y
            }
        } else {
            0.0
        };

        child_heights.push(height);
    }

    let visible_count = child_heights.len();

    match flow.direction {
        FlowDirection::Vertical => {
            let total: f32 = child_heights.iter().sum();
            let spacing = if visible_count > 1 {
                scaled_spacing * (visible_count - 1) as f32
            } else {
                0.0
            };
            total + spacing + scaled_padding * 2.0
        }
        FlowDirection::Horizontal => {
            let max_height = child_heights.iter().cloned().fold(0.0f32, f32::max);
            max_height + scaled_padding * 2.0
        }
    }
}

fn compute_grid_children(
    world: &World,
    children: &[freecs::Entity],
    grid: &GridLayout,
    parent_rect: &Rect,
    dpi_scale: f32,
) -> Vec<(freecs::Entity, Rect)> {
    let scaled_padding = grid.padding * dpi_scale;
    let scaled_col_spacing = grid.column_spacing * dpi_scale;
    let scaled_row_spacing = grid.row_spacing * dpi_scale;
    let scaled_row_height = grid.row_height * dpi_scale;

    let inner_width = (parent_rect.width() - scaled_padding * 2.0).max(0.0);
    let columns = if let Some(min_width) = grid.min_column_width {
        let scaled_min = min_width * dpi_scale;
        if scaled_min > 0.0 {
            ((inner_width + scaled_col_spacing) / (scaled_min + scaled_col_spacing))
                .floor()
                .max(1.0) as usize
        } else {
            grid.columns.max(1)
        }
    } else {
        grid.columns.max(1)
    };
    let col_width =
        (inner_width - scaled_col_spacing * (columns as f32 - 1.0).max(0.0)) / columns as f32;

    let visible_children: Vec<freecs::Entity> = children
        .iter()
        .filter(|child| {
            world
                .get_ui_layout_node(**child)
                .is_some_and(|node| node.visible)
        })
        .copied()
        .collect();

    let mut result = Vec::with_capacity(visible_children.len());

    for (index, entity) in visible_children.iter().enumerate() {
        let col = index % columns;
        let row = index / columns;

        let x = parent_rect.min.x + scaled_padding + col as f32 * (col_width + scaled_col_spacing);
        let y = parent_rect.min.y
            + scaled_padding
            + row as f32 * (scaled_row_height + scaled_row_spacing);

        let rect = Rect::from_min_max(
            Vec2::new(x, y),
            Vec2::new(x + col_width, y + scaled_row_height),
        );

        result.push((*entity, rect));
    }

    result
}

fn measure_auto_size(
    world: &World,
    entity: freecs::Entity,
    auto_size: AutoSizeMode,
    padding: Vec2,
    font_size: f32,
    dpi_scale: f32,
) -> Option<(Option<f32>, Option<f32>)> {
    if auto_size == AutoSizeMode::None {
        return None;
    }

    let best_idx = world
        .resources
        .text_cache
        .font_manager
        .best_bitmap_font_for_size(font_size);
    let font_arc = world
        .resources
        .text_cache
        .font_manager
        .get_bitmap_font_arc(best_idx);
    let atlas = font_arc?;

    let (text, text_font_size) = if let Some(content) = world.get_ui_node_content(entity)
        && let UiNodeContent::Text {
            text_slot,
            font_size_override,
            ..
        } = content
    {
        let resolved_size = font_size_override.unwrap_or(font_size);
        if let Some(text) = world.resources.text_cache.get_text(*text_slot) {
            (text.to_string(), resolved_size)
        } else {
            return None;
        }
    } else {
        let children: Vec<freecs::Entity> = world
            .resources
            .children_cache
            .get(&entity)
            .map(|v| v.to_vec())
            .unwrap_or_default();

        let mut found_text = None;
        for child in &children {
            if let Some(content) = world.get_ui_node_content(*child)
                && let UiNodeContent::Text {
                    text_slot,
                    font_size_override,
                    ..
                } = content
            {
                let child_font_size = world
                    .get_ui_layout_node(*child)
                    .and_then(|n| n.font_size)
                    .unwrap_or(font_size);
                let resolved_size = font_size_override.unwrap_or(child_font_size);
                if let Some(text) = world.resources.text_cache.get_text(*text_slot) {
                    found_text = Some((text.to_string(), resolved_size));
                    break;
                }
            }
        }

        if let Some(found) = found_text {
            found
        } else if let Some(flow) = world.get_ui_layout_node(entity).and_then(|n| n.flow_layout) {
            let mut total_width = 0.0f32;
            let mut total_height = 0.0f32;
            let mut max_cross_width = 0.0f32;
            let mut max_cross_height = 0.0f32;
            let visible_count = children
                .iter()
                .filter(|c| world.get_ui_layout_node(**c).is_some_and(|n| n.visible))
                .count();
            let total_spacing = flow.spacing * visible_count.saturating_sub(1) as f32;

            for child in &children {
                let child_node = match world.get_ui_layout_node(*child) {
                    Some(n) if n.visible => n,
                    _ => continue,
                };
                if let Some(ref child_size) = child_node.flow_child_size {
                    if child_size.relative.is_some()
                        || child_size.relative_width.is_some()
                        || child_size.relative_height.is_some()
                    {
                        continue;
                    }
                    let abs = child_size.absolute.unwrap_or(Vec2::zeros());
                    match flow.direction {
                        FlowDirection::Vertical => {
                            total_height += abs.y;
                            max_cross_width = max_cross_width.max(abs.x);
                        }
                        FlowDirection::Horizontal => {
                            total_width += abs.x;
                            max_cross_height = max_cross_height.max(abs.y);
                        }
                    }
                }
            }

            let (content_w, content_h) = match flow.direction {
                FlowDirection::Vertical => (
                    max_cross_width + flow.padding * 2.0,
                    total_height + total_spacing + flow.padding * 2.0,
                ),
                FlowDirection::Horizontal => (
                    total_width + total_spacing + flow.padding * 2.0,
                    max_cross_height + flow.padding * 2.0,
                ),
            };

            let auto_width = matches!(auto_size, AutoSizeMode::Width | AutoSizeMode::Both)
                .then_some(content_w + padding.x * 2.0);
            let auto_height = matches!(auto_size, AutoSizeMode::Height | AutoSizeMode::Both)
                .then_some(content_h + padding.y * 2.0);

            return Some((auto_width, auto_height));
        } else {
            return None;
        }
    };

    let measured_width = measure_text_width(&atlas, &text, text_font_size) / dpi_scale;

    let auto_width = matches!(auto_size, AutoSizeMode::Width | AutoSizeMode::Both)
        .then_some(measured_width + padding.x * 2.0);
    let auto_height = matches!(auto_size, AutoSizeMode::Height | AutoSizeMode::Both)
        .then_some(text_font_size * 1.5 + padding.y * 2.0);

    Some((auto_width, auto_height))
}

fn compute_flow_children(
    world: &World,
    children: &[freecs::Entity],
    flow: &FlowLayout,
    parent_rect: &Rect,
    parent_font_size: f32,
    flow_ctx: &FlowContext,
) -> Vec<(freecs::Entity, Rect)> {
    let scaled_padding = flow.padding * flow_ctx.dpi_scale;
    let scaled_spacing = flow.spacing * flow_ctx.dpi_scale;
    let inner_rect = Rect::from_min_max(
        parent_rect.min + Vec2::new(scaled_padding, scaled_padding),
        parent_rect.max - Vec2::new(scaled_padding, scaled_padding),
    );

    let flow_children: Vec<freecs::Entity> = children
        .iter()
        .filter(|child| {
            world
                .get_ui_layout_node(**child)
                .is_some_and(|node| node.visible)
        })
        .copied()
        .collect();

    if flow_children.is_empty() {
        return Vec::new();
    }

    struct ChildMeasure {
        entity: freecs::Entity,
        width: f32,
        height: f32,
        flex_grow: Option<f32>,
        flex_shrink: Option<f32>,
    }

    let context = UiEvalContext {
        parent_width: inner_rect.width(),
        parent_height: inner_rect.height(),
        viewport_width: flow_ctx.viewport_size.x,
        viewport_height: flow_ctx.viewport_size.y,
        font_size: parent_font_size,
        absolute_scale: flow_ctx.absolute_scale,
    };

    let mut measures: Vec<ChildMeasure> = Vec::new();

    for entity in &flow_children {
        let node = match world.get_ui_layout_node(*entity) {
            Some(node) => node,
            None => continue,
        };

        let child_font_size = node.font_size.unwrap_or(parent_font_size);
        let auto_size = node.auto_size;
        let auto_size_padding = node.auto_size_padding;
        let min_size = node.min_size;
        let max_size = node.max_size;
        let flex_shrink = node.flex_shrink;

        let (width, height) = if let Some(ref child_size) = node.flow_child_size {
            let evaluated = child_size.evaluate(&context);
            let final_height = if evaluated.y.abs() < f32::EPSILON && node.flow_layout.is_some() {
                compute_natural_height(world, *entity, evaluated.x, child_font_size, flow_ctx)
            } else {
                evaluated.y
            };
            (evaluated.x, final_height)
        } else if let Some(base_layout) = &node.layouts[UiBase::INDEX] {
            let rect = compute_layout_rect(base_layout, &context, &inner_rect);
            (rect.width(), rect.height())
        } else {
            (inner_rect.width(), 0.0)
        };

        let (width, height) = if auto_size != AutoSizeMode::None {
            if let Some((auto_w, auto_h)) = measure_auto_size(
                world,
                *entity,
                auto_size,
                auto_size_padding,
                child_font_size,
                flow_ctx.dpi_scale,
            ) {
                (auto_w.unwrap_or(width), auto_h.unwrap_or(height))
            } else {
                (width, height)
            }
        } else {
            (width, height)
        };

        let mut width = width;
        let mut height = height;
        if let Some(min) = min_size {
            width = width.max(min.x);
            height = height.max(min.y);
        }
        if let Some(max) = max_size {
            width = width.min(max.x);
            height = height.min(max.y);
        }

        measures.push(ChildMeasure {
            entity: *entity,
            width,
            height,
            flex_grow: node.flex_grow,
            flex_shrink,
        });
    }

    let total_spacing = scaled_spacing * (measures.len().saturating_sub(1)) as f32;
    let fixed_size_sum: f32 = match flow.direction {
        FlowDirection::Vertical => measures
            .iter()
            .filter(|measure| measure.flex_grow.is_none())
            .map(|measure| measure.height)
            .sum(),
        FlowDirection::Horizontal => measures
            .iter()
            .filter(|measure| measure.flex_grow.is_none())
            .map(|measure| measure.width)
            .sum(),
    };

    let available_for_flex = match flow.direction {
        FlowDirection::Vertical => (inner_rect.height() - total_spacing - fixed_size_sum).max(0.0),
        FlowDirection::Horizontal => (inner_rect.width() - total_spacing - fixed_size_sum).max(0.0),
    };

    let total_flex: f32 = measures
        .iter()
        .filter_map(|measure| measure.flex_grow)
        .sum();

    let total_content_size: f32 = match flow.direction {
        FlowDirection::Vertical => {
            measures
                .iter()
                .map(|measure| {
                    if let Some(grow) = measure.flex_grow {
                        if total_flex > 0.0 {
                            available_for_flex * (grow / total_flex)
                        } else {
                            measure.height
                        }
                    } else {
                        measure.height
                    }
                })
                .sum::<f32>()
                + total_spacing
        }
        FlowDirection::Horizontal => {
            measures
                .iter()
                .map(|measure| {
                    if let Some(grow) = measure.flex_grow {
                        if total_flex > 0.0 {
                            available_for_flex * (grow / total_flex)
                        } else {
                            measure.width
                        }
                    } else {
                        measure.width
                    }
                })
                .sum::<f32>()
                + total_spacing
        }
    };

    let main_axis_start_offset = match flow.direction {
        FlowDirection::Vertical => match flow.alignment {
            FlowAlignment::Start => 0.0,
            FlowAlignment::Center => (inner_rect.height() - total_content_size) * 0.5,
            FlowAlignment::End => inner_rect.height() - total_content_size,
        },
        FlowDirection::Horizontal => match flow.alignment {
            FlowAlignment::Start => 0.0,
            FlowAlignment::Center => (inner_rect.width() - total_content_size) * 0.5,
            FlowAlignment::End => inner_rect.width() - total_content_size,
        },
    };

    if flow.wrap && flow.direction == FlowDirection::Horizontal {
        let entities: Vec<freecs::Entity> = measures.iter().map(|m| m.entity).collect();
        let widths: Vec<f32> = measures.iter().map(|m| m.width).collect();
        let heights: Vec<f32> = measures.iter().map(|m| m.height).collect();
        return compute_flow_children_wrap(
            &entities,
            &widths,
            &heights,
            flow.cross_alignment,
            &inner_rect,
            scaled_spacing,
        );
    }

    let mut result = Vec::with_capacity(measures.len());
    let mut cursor = main_axis_start_offset;

    let total_fixed_size: f32 = match flow.direction {
        FlowDirection::Vertical => measures.iter().map(|m| m.height).sum::<f32>(),
        FlowDirection::Horizontal => measures.iter().map(|m| m.width).sum::<f32>(),
    };
    let available_main = match flow.direction {
        FlowDirection::Vertical => inner_rect.height(),
        FlowDirection::Horizontal => inner_rect.width(),
    };
    let overflow_amount = (total_fixed_size + total_spacing - available_main).max(0.0);
    let total_shrink: f32 = measures.iter().filter_map(|m| m.flex_shrink).sum();

    for measure in &measures {
        let shrink_delta = if overflow_amount > 0.0 && total_shrink > 0.0 {
            if let Some(shrink) = measure.flex_shrink {
                overflow_amount * (shrink / total_shrink)
            } else {
                0.0
            }
        } else {
            0.0
        };

        let (final_width, final_height) = if let Some(grow) = measure.flex_grow {
            if total_flex > 0.0 {
                let flex_amount = available_for_flex * (grow / total_flex);
                match flow.direction {
                    FlowDirection::Vertical => (measure.width, flex_amount),
                    FlowDirection::Horizontal => (flex_amount, measure.height),
                }
            } else {
                (measure.width, measure.height)
            }
        } else {
            match flow.direction {
                FlowDirection::Vertical => {
                    (measure.width, (measure.height - shrink_delta).max(0.0))
                }
                FlowDirection::Horizontal => {
                    ((measure.width - shrink_delta).max(0.0), measure.height)
                }
            }
        };

        let child_rect = match flow.direction {
            FlowDirection::Vertical => {
                let cross_offset = match flow.cross_alignment {
                    FlowAlignment::Start => 0.0,
                    FlowAlignment::Center => (inner_rect.width() - final_width) * 0.5,
                    FlowAlignment::End => inner_rect.width() - final_width,
                };
                let rect = Rect::from_min_max(
                    Vec2::new(inner_rect.min.x + cross_offset, inner_rect.min.y + cursor),
                    Vec2::new(
                        inner_rect.min.x + cross_offset + final_width,
                        inner_rect.min.y + cursor + final_height,
                    ),
                );
                cursor += final_height + scaled_spacing;
                rect
            }
            FlowDirection::Horizontal => {
                let cross_offset = match flow.cross_alignment {
                    FlowAlignment::Start => 0.0,
                    FlowAlignment::Center => (inner_rect.height() - final_height) * 0.5,
                    FlowAlignment::End => inner_rect.height() - final_height,
                };
                let rect = Rect::from_min_max(
                    Vec2::new(inner_rect.min.x + cursor, inner_rect.min.y + cross_offset),
                    Vec2::new(
                        inner_rect.min.x + cursor + final_width,
                        inner_rect.min.y + cross_offset + final_height,
                    ),
                );
                cursor += final_width + scaled_spacing;
                rect
            }
        };

        result.push((measure.entity, child_rect));
    }

    result
}

fn compute_flow_children_wrap(
    entities: &[freecs::Entity],
    widths: &[f32],
    heights: &[f32],
    cross_alignment: FlowAlignment,
    inner_rect: &Rect,
    scaled_spacing: f32,
) -> Vec<(freecs::Entity, Rect)> {
    let available_width = inner_rect.width();
    let mut result = Vec::with_capacity(entities.len());
    let mut cursor_x = 0.0f32;
    let mut cursor_y = 0.0f32;
    let mut line_height = 0.0f32;

    for index in 0..entities.len() {
        let width = widths[index];
        let height = heights[index];

        if cursor_x > 0.0 && cursor_x + width > available_width {
            cursor_y += line_height + scaled_spacing;
            cursor_x = 0.0;
            line_height = 0.0;
        }

        if height > line_height {
            line_height = height;
        }

        let cross_offset = match cross_alignment {
            FlowAlignment::Start => 0.0,
            FlowAlignment::Center => (line_height - height).max(0.0) * 0.5,
            FlowAlignment::End => (line_height - height).max(0.0),
        };

        let rect = Rect::from_min_max(
            Vec2::new(
                inner_rect.min.x + cursor_x,
                inner_rect.min.y + cursor_y + cross_offset,
            ),
            Vec2::new(
                inner_rect.min.x + cursor_x + width,
                inner_rect.min.y + cursor_y + cross_offset + height,
            ),
        );

        result.push((entities[index], rect));
        cursor_x += width + scaled_spacing;
    }

    result
}

pub fn ui_theme_apply_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let generation = world.resources.retained_ui.theme_state.generation;
    if generation
        == world
            .resources
            .retained_ui
            .theme_state
            .last_applied_generation
    {
        return;
    }
    world
        .resources
        .retained_ui
        .theme_state
        .last_applied_generation = generation;

    let theme = world
        .resources
        .retained_ui
        .theme_state
        .active_theme()
        .clone();

    let entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_THEME_BINDING)
        .collect();

    for entity in entities {
        let binding = match world.get_ui_theme_binding(entity) {
            Some(binding) => binding.clone(),
            None => continue,
        };

        if let Some(node_color) = world.get_ui_node_color_mut(entity) {
            for (state_index, role) in binding.color_roles.iter().enumerate() {
                if let Some(role) = role {
                    node_color.colors[state_index] = Some(role.resolve(&theme));
                }
            }
        }

        if let Some(border_role) = binding.border_color_role
            && let Some(content) = world.get_ui_node_content_mut(entity)
            && let UiNodeContent::Rect { border_color, .. } = content
        {
            *border_color = border_role.resolve(&theme);
        }
    }
}

pub fn ui_layout_color_blend_system(world: &mut World) {
    if !world.resources.retained_ui.enabled {
        return;
    }

    let entities: Vec<freecs::Entity> = world
        .query_entities(crate::ecs::world::UI_NODE_COLOR)
        .collect();

    for entity in entities {
        let (colors, weights) = {
            let color_comp = match world.get_ui_node_color(entity) {
                Some(color) => color.colors.clone(),
                None => continue,
            };
            let weight_comp = world
                .get_ui_state_weights(entity)
                .map(|state_weights| state_weights.weights.clone())
                .unwrap_or_else(|| vec![0.0; STATE_COUNT]);
            (color_comp, weight_comp)
        };

        let blended = blend_state_colors(&colors, &weights);

        let alpha_multiplier = if let Some(node) = world.get_ui_layout_node(entity)
            && let Some(animation) = &node.animation
        {
            animation_alpha(animation)
        } else {
            1.0
        };

        if let Some(color_comp) = world.get_ui_node_color_mut(entity) {
            let mut final_color = blended;
            if alpha_multiplier < 1.0 {
                final_color.w *= alpha_multiplier;
            }
            let old = color_comp.computed_color;
            color_comp.computed_color = final_color;
            let diff = old - final_color;
            if diff.x.abs() > 0.001
                || diff.y.abs() > 0.001
                || diff.z.abs() > 0.001
                || diff.w.abs() > 0.001
            {
                world.resources.retained_ui.render_dirty = true;
            }
        }
    }
}

fn animation_alpha(animation: &crate::ecs::ui::components::UiNodeAnimation) -> f32 {
    use crate::ecs::ui::components::UiAnimationType;

    let anim_type = match animation.phase {
        UiAnimationPhase::IntroPlaying => animation.intro,
        UiAnimationPhase::OutroPlaying => animation.outro,
        _ => return 1.0,
    };

    let uses_fade = matches!(anim_type, Some(UiAnimationType::Fade));
    let t = animation_progress_eased(animation);

    if uses_fade {
        match animation.phase {
            UiAnimationPhase::IntroPlaying => t,
            UiAnimationPhase::OutroPlaying => 1.0 - t,
            _ => 1.0,
        }
    } else {
        1.0
    }
}

fn animation_progress_eased(animation: &crate::ecs::ui::components::UiNodeAnimation) -> f32 {
    let t = animation.progress.clamp(0.0, 1.0);
    match animation.phase {
        UiAnimationPhase::IntroPlaying => ease_out_cubic(t),
        UiAnimationPhase::OutroPlaying => ease_in_cubic(t),
        _ => 1.0,
    }
}

fn ease_out_cubic(t: f32) -> f32 {
    1.0 - (1.0 - t).powi(3)
}

fn ease_in_cubic(t: f32) -> f32 {
    t * t * t
}

fn apply_animation_to_rect(
    rect: Rect,
    animation: &crate::ecs::ui::components::UiNodeAnimation,
) -> Rect {
    let anim_type = match animation.phase {
        UiAnimationPhase::IntroPlaying => animation.intro,
        UiAnimationPhase::OutroPlaying => animation.outro,
        _ => return rect,
    };

    let anim_type = match anim_type {
        Some(t) => t,
        None => return rect,
    };

    let t = animation_progress_eased(animation);

    match animation.phase {
        UiAnimationPhase::IntroPlaying => apply_intro_transform(rect, anim_type, t),
        UiAnimationPhase::OutroPlaying => apply_outro_transform(rect, anim_type, t),
        _ => rect,
    }
}

fn apply_intro_transform(
    rect: Rect,
    anim_type: crate::ecs::ui::components::UiAnimationType,
    t: f32,
) -> Rect {
    use crate::ecs::ui::components::UiAnimationType;

    match anim_type {
        UiAnimationType::Fade => rect,
        UiAnimationType::SlideLeft => {
            let offset = rect.width() * (1.0 - t);
            Rect::new(
                rect.min.x - offset,
                rect.min.y,
                rect.max.x - offset,
                rect.max.y,
            )
        }
        UiAnimationType::SlideRight => {
            let offset = rect.width() * (1.0 - t);
            Rect::new(
                rect.min.x + offset,
                rect.min.y,
                rect.max.x + offset,
                rect.max.y,
            )
        }
        UiAnimationType::SlideUp => {
            let offset = rect.height() * (1.0 - t);
            Rect::new(
                rect.min.x,
                rect.min.y - offset,
                rect.max.x,
                rect.max.y - offset,
            )
        }
        UiAnimationType::SlideDown => {
            let offset = rect.height() * (1.0 - t);
            Rect::new(
                rect.min.x,
                rect.min.y + offset,
                rect.max.x,
                rect.max.y + offset,
            )
        }
        UiAnimationType::Scale => {
            let center = rect.center();
            let half_w = rect.width() * 0.5 * t;
            let half_h = rect.height() * 0.5 * t;
            Rect::new(
                center.x - half_w,
                center.y - half_h,
                center.x + half_w,
                center.y + half_h,
            )
        }
    }
}

fn apply_outro_transform(
    rect: Rect,
    anim_type: crate::ecs::ui::components::UiAnimationType,
    t: f32,
) -> Rect {
    use crate::ecs::ui::components::UiAnimationType;

    match anim_type {
        UiAnimationType::Fade => rect,
        UiAnimationType::SlideLeft => {
            let offset = rect.width() * t;
            Rect::new(
                rect.min.x - offset,
                rect.min.y,
                rect.max.x - offset,
                rect.max.y,
            )
        }
        UiAnimationType::SlideRight => {
            let offset = rect.width() * t;
            Rect::new(
                rect.min.x + offset,
                rect.min.y,
                rect.max.x + offset,
                rect.max.y,
            )
        }
        UiAnimationType::SlideUp => {
            let offset = rect.height() * t;
            Rect::new(
                rect.min.x,
                rect.min.y - offset,
                rect.max.x,
                rect.max.y - offset,
            )
        }
        UiAnimationType::SlideDown => {
            let offset = rect.height() * t;
            Rect::new(
                rect.min.x,
                rect.min.y + offset,
                rect.max.x,
                rect.max.y + offset,
            )
        }
        UiAnimationType::Scale => {
            let center = rect.center();
            let scale = 1.0 - t;
            let half_w = rect.width() * 0.5 * scale;
            let half_h = rect.height() * 0.5 * scale;
            Rect::new(
                center.x - half_w,
                center.y - half_h,
                center.x + half_w,
                center.y + half_h,
            )
        }
    }
}
