use nalgebra_glm::Vec4;

use crate::ecs::text::components::{TextAlignment, TextProperties, VerticalAlignment};
use crate::ecs::text::resources::FontAtlasData;
use crate::ecs::ui::canvas_render::emit_canvas_commands;
use crate::ecs::ui::components::UiNodeContent;
use crate::ecs::ui::text_wrapping::wrap_text;
use crate::ecs::ui::tile_render::emit_tile_containers;
use crate::ecs::ui::types::Rect;
use crate::ecs::ui::types::UiTextInstance;
use crate::ecs::world::World;
use crate::render::wgpu::passes::geometry::{UiImage, UiLayer, UiRect};
use crate::render::wgpu::text_mesh::generate_text_mesh;

pub(crate) fn select_best_font(
    fonts: &[FontAtlasData],
    physical_size: f32,
) -> Option<(usize, &FontAtlasData)> {
    if fonts.is_empty() {
        return None;
    }
    let mut best_index = 0;
    let mut best_deviation = f32::INFINITY;
    for (index, font) in fonts.iter().enumerate() {
        let deviation = (physical_size / font.font_size - 1.0).abs();
        if deviation < best_deviation {
            best_deviation = deviation;
            best_index = index;
        }
    }
    Some((best_index, &fonts[best_index]))
}

struct RenderEntry {
    computed_rect: Rect,
    computed_depth: f32,
    computed_color: Vec4,
    content: UiNodeContent,
    clip_rect: Option<Rect>,
    font_size: f32,
    layer: UiLayer,
    z_index_override: Option<i32>,
}

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

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

    let bitmap_fonts: Vec<FontAtlasData> = world
        .resources
        .text_cache
        .font_manager
        .bitmap_fonts
        .iter()
        .map(|arc| arc.as_ref().clone())
        .collect();
    let has_bitmap_font = !bitmap_fonts.is_empty();

    let dpi_scale = world.resources.window.cached_scale_factor;
    let render_dirty = world.resources.retained_ui.render_dirty;

    if render_dirty {
        world.resources.retained_ui.frame_rects.clear();
        world.resources.retained_ui.frame_ui_images.clear();
        world.resources.retained_ui.frame_text_meshes.clear();

        if let Some(bg_color) = world.resources.retained_ui.background_color {
            world.resources.retained_ui.frame_rects.push(UiRect {
                position: nalgebra_glm::Vec2::new(0.0, 0.0),
                size: viewport_size,
                color: bg_color,
                corner_radius: 0.0,
                border_width: 0.0,
                border_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
                rotation: 0.0,
                clip_rect: None,
                layer: UiLayer::Background,
                z_index: 0,
            });
        }

        let sorted_nodes = std::mem::take(&mut world.resources.retained_ui.z_sorted_nodes);

        let dpi_changed =
            (dpi_scale - world.resources.retained_ui.text_mesh_cache_dpi).abs() > f32::EPSILON;
        if dpi_changed {
            world.resources.retained_ui.text_mesh_cache.clear();
            world.resources.retained_ui.text_mesh_cache_dpi = dpi_scale;
        }

        let mut render_entries: Vec<RenderEntry> = Vec::new();

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

            let computed_rect = node.computed_rect;

            if computed_rect.max.x < 0.0
                || computed_rect.min.x > viewport_size.x
                || computed_rect.max.y < 0.0
                || computed_rect.min.y > viewport_size.y
            {
                continue;
            }

            let computed_depth = node.computed_depth;
            let font_size = node.font_size.unwrap_or(16.0);
            let node_layer = node.computed_layer.unwrap_or(UiLayer::Background);
            let z_index_override = node.z_index;

            let computed_color = world
                .get_ui_node_color(entity)
                .map(|color| color.computed_color)
                .unwrap_or(Vec4::new(1.0, 1.0, 1.0, 1.0));

            let content = match world.get_ui_node_content(entity) {
                Some(content) => *content,
                None => continue,
            };

            let clip_rect = node.computed_clip_rect;

            render_entries.push(RenderEntry {
                computed_rect,
                computed_depth,
                computed_color,
                content,
                clip_rect,
                font_size,
                layer: node_layer,
                z_index_override,
            });
        }

        let mut rects = std::mem::take(&mut world.resources.retained_ui.frame_rects);
        let mut ui_images = std::mem::take(&mut world.resources.retained_ui.frame_ui_images);
        let mut text_meshes = Vec::new();

        for entry in &render_entries {
            let z_index = entry
                .z_index_override
                .unwrap_or((entry.computed_depth * 100.0) as i32);

            match &entry.content {
                UiNodeContent::None => {}
                UiNodeContent::Rect {
                    corner_radius,
                    border_width,
                    border_color,
                } => {
                    rects.push(UiRect {
                        position: entry.computed_rect.min,
                        size: entry.computed_rect.size(),
                        color: entry.computed_color,
                        corner_radius: *corner_radius * dpi_scale,
                        border_width: *border_width * dpi_scale,
                        border_color: *border_color,
                        rotation: 0.0,
                        clip_rect: entry.clip_rect,
                        layer: entry.layer,
                        z_index,
                    });
                }
                UiNodeContent::Text {
                    text_slot,
                    font_index: _,
                    font_size_override,
                    outline_color,
                    outline_width,
                    alignment,
                    vertical_alignment,
                    overflow,
                } => {
                    let text = world
                        .resources
                        .text_cache
                        .get_text(*text_slot)
                        .map(|s| s.to_string());

                    if let Some(text) = text {
                        let actual_font_size =
                            font_size_override.unwrap_or(entry.font_size) * dpi_scale;
                        let Some((font_idx, font_data)) =
                            select_best_font(&bitmap_fonts, actual_font_size)
                        else {
                            continue;
                        };

                        let slot_gen = world
                            .resources
                            .text_cache
                            .slot_generations
                            .get(*text_slot)
                            .copied()
                            .unwrap_or(0);

                        let wrap_width_key: Option<f32> =
                            if *overflow == crate::ecs::ui::components::TextOverflow::Wrap {
                                Some(entry.computed_rect.width().round())
                            } else {
                                None
                            };

                        let (render_text, use_cache) = if *overflow
                            == crate::ecs::ui::components::TextOverflow::Wrap
                        {
                            let available = entry.computed_rect.width();
                            (
                                wrap_text(font_data, &text, actual_font_size, available),
                                true,
                            )
                        } else if *overflow == crate::ecs::ui::components::TextOverflow::Ellipsis {
                            let full_width = crate::ecs::ui::widget_systems::measure_text_width(
                                font_data,
                                &text,
                                actual_font_size,
                            );
                            let available = entry.computed_rect.width();
                            if full_width > available {
                                let ellipsis = "...";
                                let ellipsis_width =
                                    crate::ecs::ui::widget_systems::measure_text_width(
                                        font_data,
                                        ellipsis,
                                        actual_font_size,
                                    );
                                let target_width = (available - ellipsis_width).max(0.0);
                                let mut accumulated = 0.0f32;
                                let mut truncate_at = 0;
                                let scale = actual_font_size / font_data.font_size;
                                let mut prev_char: Option<char> = None;
                                for (index, character) in text.chars().enumerate() {
                                    if let Some(glyph) = font_data.glyphs.get(&character) {
                                        if let Some(previous) = prev_char
                                            && let Some(&kern) =
                                                font_data.kerning.get(&(previous, character))
                                        {
                                            accumulated += kern * scale;
                                        }
                                        accumulated += glyph.advance * scale;
                                        if accumulated > target_width {
                                            truncate_at = index;
                                            break;
                                        }
                                    }
                                    prev_char = Some(character);
                                    truncate_at = index + 1;
                                }
                                let truncated: String =
                                    text.chars().take(truncate_at).collect::<String>() + ellipsis;
                                (truncated, false)
                            } else {
                                (text, true)
                            }
                        } else {
                            (text, true)
                        };

                        let mesh = if use_cache {
                            let cache = &world.resources.retained_ui.text_mesh_cache;
                            let cached =
                                cache.get(*text_slot).and_then(|c| c.as_ref()).filter(|c| {
                                    c.slot_generation == slot_gen
                                        && (c.font_size - actual_font_size).abs() < f32::EPSILON
                                        && c.wrap_width == wrap_width_key
                                });

                            if let Some(hit) = cached {
                                hit.mesh.clone()
                            } else {
                                let properties = TextProperties {
                                    font_size: actual_font_size,
                                    color: entry.computed_color,
                                    alignment: *alignment,
                                    vertical_alignment: *vertical_alignment,
                                    ..TextProperties::default()
                                };
                                let new_mesh =
                                    generate_text_mesh(&render_text, font_data, &properties);
                                let mesh_cache = &mut world.resources.retained_ui.text_mesh_cache;
                                if mesh_cache.len() <= *text_slot {
                                    mesh_cache.resize_with(*text_slot + 1, || None);
                                }
                                mesh_cache[*text_slot] =
                                    Some(crate::ecs::ui::resources::CachedTextMesh {
                                        mesh: new_mesh,
                                        slot_generation: slot_gen,
                                        font_size: actual_font_size,
                                        wrap_width: wrap_width_key,
                                    });
                                mesh_cache[*text_slot].as_ref().unwrap().mesh.clone()
                            }
                        } else {
                            let properties = TextProperties {
                                font_size: actual_font_size,
                                color: entry.computed_color,
                                alignment: *alignment,
                                vertical_alignment: *vertical_alignment,
                                ..TextProperties::default()
                            };
                            generate_text_mesh(&render_text, font_data, &properties)
                        };

                        let position_x = match alignment {
                            TextAlignment::Left => entry.computed_rect.min.x,
                            TextAlignment::Center => entry.computed_rect.center().x,
                            TextAlignment::Right => entry.computed_rect.max.x,
                        };

                        let position_y = match vertical_alignment {
                            VerticalAlignment::Top => entry.computed_rect.min.y,
                            VerticalAlignment::Middle => entry.computed_rect.center().y,
                            VerticalAlignment::Bottom => entry.computed_rect.max.y,
                            VerticalAlignment::Baseline => entry.computed_rect.center().y,
                        };

                        let snapped_position =
                            nalgebra_glm::Vec2::new(position_x.round(), position_y.round());

                        let effective_clip = if matches!(
                            *overflow,
                            crate::ecs::ui::components::TextOverflow::Clip
                                | crate::ecs::ui::components::TextOverflow::Wrap
                        ) {
                            let content_clip = Some(entry.computed_rect);
                            match entry.clip_rect {
                                Some(existing) => entry.computed_rect.intersect(&existing),
                                None => content_clip,
                            }
                        } else {
                            entry.clip_rect
                        };

                        let character_colors = world
                            .resources
                            .retained_ui
                            .text_slot_character_colors
                            .get(text_slot)
                            .cloned();

                        text_meshes.push(UiTextInstance {
                            mesh,
                            position: snapped_position,
                            color: entry.computed_color,
                            outline_color: *outline_color,
                            outline_width: *outline_width,
                            font_size: actual_font_size,
                            font_index: font_idx,
                            clip_rect: effective_clip,
                            layer: entry.layer,
                            z_index,
                            character_colors,
                        });
                    }
                }
                UiNodeContent::Image {
                    texture_index,
                    uv_min,
                    uv_max,
                } => {
                    ui_images.push(UiImage {
                        position: entry.computed_rect.min,
                        size: entry.computed_rect.size(),
                        texture_index: *texture_index,
                        uv_min: *uv_min,
                        uv_max: *uv_max,
                        color: entry.computed_color,
                        clip_rect: entry.clip_rect,
                        layer: entry.layer,
                        z_index,
                    });
                }
            }
        }

        world.resources.retained_ui.z_sorted_nodes = sorted_nodes;
        world.resources.retained_ui.frame_rects = rects;
        world.resources.retained_ui.frame_ui_images = ui_images;
        world.resources.retained_ui.frame_text_meshes = text_meshes;

        world.resources.retained_ui.cached_node_rect_count =
            world.resources.retained_ui.frame_rects.len();
        world.resources.retained_ui.cached_node_image_count =
            world.resources.retained_ui.frame_ui_images.len();
        world.resources.retained_ui.cached_node_text_count =
            world.resources.retained_ui.frame_text_meshes.len();

        let secondary_window_indices: Vec<usize> = world
            .resources
            .retained_ui
            .secondary_buffers
            .keys()
            .copied()
            .collect();
        for window_index in secondary_window_indices {
            let nodes = world
                .resources
                .retained_ui
                .secondary_buffers
                .get_mut(&window_index)
                .map(|b| std::mem::take(&mut b.z_sorted_nodes))
                .unwrap_or_default();
            if nodes.is_empty() {
                if let Some(buffers) = world
                    .resources
                    .retained_ui
                    .secondary_buffers
                    .get_mut(&window_index)
                {
                    buffers.z_sorted_nodes = nodes;
                }
                continue;
            }
            let window_viewport = world
                .resources
                .secondary_windows
                .states
                .iter()
                .find(|s| s.index == window_index)
                .map(|s| nalgebra_glm::Vec2::new(s.size.0 as f32, s.size.1 as f32))
                .unwrap_or(viewport_size);

            let (sec_rects, sec_images, sec_text) = render_sorted_nodes_to_output(
                world,
                &nodes,
                window_viewport,
                dpi_scale,
                &bitmap_fonts,
            );
            if let Some(buffers) = world
                .resources
                .retained_ui
                .secondary_buffers
                .get_mut(&window_index)
            {
                buffers.z_sorted_nodes = nodes;
                buffers.frame_rects = sec_rects;
                buffers.frame_ui_images = sec_images;
                buffers.frame_text_meshes = sec_text;
            }
        }

        world.resources.retained_ui.render_dirty = false;
    } else {
        let rect_count = world.resources.retained_ui.cached_node_rect_count;
        let image_count = world.resources.retained_ui.cached_node_image_count;
        let text_count = world.resources.retained_ui.cached_node_text_count;
        world.resources.retained_ui.frame_rects.truncate(rect_count);
        world
            .resources
            .retained_ui
            .frame_ui_images
            .truncate(image_count);
        world
            .resources
            .retained_ui
            .frame_text_meshes
            .truncate(text_count);
    }

    emit_canvas_commands(world, dpi_scale);

    if let Some(tooltip) = &world.resources.retained_ui.tooltip_state {
        let delay = 0.5;
        if world.resources.retained_ui.current_time - tooltip.hover_start >= delay {
            let mouse_pos = world.resources.input.mouse.position;
            let tooltip_entity = tooltip.tooltip_entity;
            let tooltip_text = tooltip.text.clone();

            if let Some(tooltip_entity) = tooltip_entity {
                let mut tooltip_x = mouse_pos.x + 12.0;
                let mut tooltip_y = mouse_pos.y + 16.0;
                if let Some(node) = world.get_ui_layout_node(tooltip_entity) {
                    let tooltip_w = node.computed_rect.width();
                    let tooltip_h = node.computed_rect.height();
                    if tooltip_x + tooltip_w > viewport_size.x {
                        tooltip_x = viewport_size.x - tooltip_w;
                    }
                    if tooltip_y + tooltip_h > viewport_size.y {
                        tooltip_y = mouse_pos.y - tooltip_h - 4.0;
                    }
                }
                if let Some(node) = world.get_ui_layout_node_mut(tooltip_entity) {
                    node.visible = true;
                    for layout in &mut node.layouts {
                        if let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                            layout
                        {
                            window.position = crate::ecs::ui::units::Ab(nalgebra_glm::Vec2::new(
                                tooltip_x, tooltip_y,
                            ))
                            .into();
                            break;
                        }
                    }
                }
            } else if let Some((tooltip_font_idx, font_data)) =
                select_best_font(&bitmap_fonts, 13.0 * dpi_scale)
            {
                let theme = world.resources.retained_ui.theme_state.active_theme();
                let tooltip_bg = Vec4::new(
                    theme.panel_color.x,
                    theme.panel_color.y,
                    theme.panel_color.z,
                    0.95,
                );
                let tooltip_border = theme.border_color;
                let tooltip_text_color = theme.text_color;
                let tooltip_corner = theme.corner_radius;

                let font_size = 13.0 * dpi_scale;
                let padding = 8.0 * dpi_scale;
                let max_tooltip_width = 300.0 * dpi_scale;
                let max_text_width = max_tooltip_width - padding * 2.0;

                let properties = TextProperties {
                    font_size,
                    color: tooltip_text_color,
                    alignment: TextAlignment::Left,
                    vertical_alignment: VerticalAlignment::Top,
                    ..TextProperties::default()
                };

                let initial_mesh = generate_text_mesh(&tooltip_text, font_data, &properties);
                let initial_text_width = initial_mesh.bounds.x * 100.0;

                let (mesh, text_width, line_count) = if initial_text_width > max_text_width {
                    let wrapped = crate::ecs::ui::text_wrapping::wrap_text(
                        font_data,
                        &tooltip_text,
                        font_size,
                        max_text_width,
                    );
                    let lines = wrapped.lines().count().max(1);
                    let wrapped_mesh = generate_text_mesh(&wrapped, font_data, &properties);
                    let wrapped_width = wrapped_mesh.bounds.x * 100.0;
                    (wrapped_mesh, wrapped_width, lines)
                } else {
                    (initial_mesh, initial_text_width, 1)
                };

                let line_height = font_size * 1.3;
                let tooltip_height = if line_count > 1 {
                    line_height * line_count as f32 + padding * 2.0
                } else {
                    28.0 * dpi_scale
                };
                let tooltip_width = text_width + padding * 2.0;

                let mut tooltip_x = mouse_pos.x + 12.0;
                let mut tooltip_y = mouse_pos.y + 16.0;

                if tooltip_x + tooltip_width > viewport_size.x {
                    tooltip_x = viewport_size.x - tooltip_width;
                }
                if tooltip_y + tooltip_height > viewport_size.y {
                    tooltip_y = mouse_pos.y - tooltip_height - 4.0;
                }

                world.resources.retained_ui.frame_rects.push(UiRect {
                    position: nalgebra_glm::Vec2::new(tooltip_x, tooltip_y),
                    size: nalgebra_glm::Vec2::new(tooltip_width, tooltip_height),
                    color: tooltip_bg,
                    corner_radius: tooltip_corner * dpi_scale,
                    border_width: 1.0 * dpi_scale,
                    border_color: tooltip_border,
                    rotation: 0.0,
                    clip_rect: None,
                    layer: UiLayer::Tooltips,
                    z_index: 0,
                });

                world
                    .resources
                    .retained_ui
                    .frame_text_meshes
                    .push(UiTextInstance {
                        mesh,
                        position: nalgebra_glm::Vec2::new(
                            tooltip_x + padding,
                            if line_count > 1 {
                                tooltip_y + padding + font_size * 0.5
                            } else {
                                tooltip_y + tooltip_height * 0.5
                            },
                        ),
                        color: tooltip_text_color,
                        outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
                        outline_width: 0.0,
                        font_size,
                        font_index: tooltip_font_idx,
                        clip_rect: None,
                        layer: UiLayer::Tooltips,
                        character_colors: None,
                        z_index: 1,
                    });
            }
        }
    }

    if world.resources.retained_ui.focus_ring_visible
        && let Some(focused) = world.resources.retained_ui.focused_entity
        && let Some(node) = world.get_ui_layout_node(focused)
        && node.visible
    {
        let rect = node.computed_rect;
        let accent_color = world
            .resources
            .retained_ui
            .theme_state
            .active_theme()
            .accent_color;
        let ring_color = Vec4::new(accent_color.x, accent_color.y, accent_color.z, 0.8);
        let inset = 1.0 * dpi_scale;

        world.resources.retained_ui.frame_rects.push(UiRect {
            position: nalgebra_glm::Vec2::new(rect.min.x - inset, rect.min.y - inset),
            size: nalgebra_glm::Vec2::new(rect.width() + inset * 2.0, rect.height() + inset * 2.0),
            color: Vec4::new(0.0, 0.0, 0.0, 0.0),
            corner_radius: 4.0 * dpi_scale,
            border_width: 2.0 * dpi_scale,
            border_color: ring_color,
            rotation: 0.0,
            clip_rect: None,
            layer: UiLayer::Tooltips,
            z_index: 0,
        });
    }

    if world.resources.retained_ui.dock_indicator_active {
        let theme = world.resources.retained_ui.theme_state.active_theme();
        let theme_panel_color = theme.panel_color;
        let theme_accent = theme.accent_color;
        let theme_bg_hovered = theme.background_color_hovered;
        let theme_border = theme.border_color;
        let theme_text_color = theme.text_color;

        let indicator_bg = Vec4::new(
            theme_panel_color.x,
            theme_panel_color.y,
            theme_panel_color.z,
            0.95,
        );
        let preview_color = Vec4::new(theme_accent.x, theme_accent.y, theme_accent.z, 0.3);

        let indicator_size = 40.0;
        let indicator_spacing = 4.0;
        let center = viewport_size * 0.5;
        let total_size = indicator_size * 3.0 + indicator_spacing * 2.0;

        let bg_rect_pos =
            center - nalgebra_glm::Vec2::new(total_size + 16.0, total_size + 16.0) * 0.5;
        world.resources.retained_ui.frame_rects.push(UiRect {
            position: bg_rect_pos,
            size: nalgebra_glm::Vec2::new(total_size + 16.0, total_size + 16.0),
            color: indicator_bg,
            corner_radius: 8.0,
            border_width: 1.0,
            border_color: theme_border,
            rotation: 0.0,
            clip_rect: None,
            layer: UiLayer::DockIndicator,
            z_index: 0,
        });

        let mouse_pos = world.resources.input.mouse.position;
        let buttons = [
            (
                nalgebra_glm::Vec2::new(center.x, center.y - indicator_size - indicator_spacing),
                "T",
            ),
            (
                nalgebra_glm::Vec2::new(center.x, center.y + indicator_size + indicator_spacing),
                "B",
            ),
            (
                nalgebra_glm::Vec2::new(center.x - indicator_size - indicator_spacing, center.y),
                "L",
            ),
            (
                nalgebra_glm::Vec2::new(center.x + indicator_size + indicator_spacing, center.y),
                "R",
            ),
            (center, "+"),
        ];

        let mut hovered_index: Option<usize> = None;
        for (index, (btn_center, symbol)) in buttons.iter().enumerate() {
            let half = indicator_size * 0.5;
            let btn_min = *btn_center - nalgebra_glm::Vec2::new(half, half);
            let btn_max = *btn_center + nalgebra_glm::Vec2::new(half, half);
            let is_hovered = mouse_pos.x >= btn_min.x
                && mouse_pos.x <= btn_max.x
                && mouse_pos.y >= btn_min.y
                && mouse_pos.y <= btn_max.y;

            if is_hovered {
                hovered_index = Some(index);
            }

            let btn_color = if is_hovered {
                theme_accent
            } else {
                theme_bg_hovered
            };

            world.resources.retained_ui.frame_rects.push(UiRect {
                position: btn_min,
                size: nalgebra_glm::Vec2::new(indicator_size, indicator_size),
                color: btn_color,
                corner_radius: 4.0,
                border_width: 1.0,
                border_color: theme_border,
                rotation: 0.0,
                clip_rect: None,
                layer: UiLayer::DockIndicator,
                z_index: 1,
            });

            let symbol_font_size = indicator_size * 0.4 * dpi_scale;
            if let Some((dock_font_idx, font_data)) =
                select_best_font(&bitmap_fonts, symbol_font_size)
            {
                let properties = TextProperties {
                    font_size: symbol_font_size,
                    color: theme_text_color,
                    alignment: TextAlignment::Center,
                    vertical_alignment: VerticalAlignment::Middle,
                    ..TextProperties::default()
                };
                let mesh = generate_text_mesh(symbol, font_data, &properties);
                let text_pos =
                    nalgebra_glm::Vec2::new(btn_center.x, btn_center.y + symbol_font_size * 0.15);
                world
                    .resources
                    .retained_ui
                    .frame_text_meshes
                    .push(UiTextInstance {
                        mesh,
                        position: text_pos,
                        color: theme_text_color,
                        outline_color: Vec4::new(0.0, 0.0, 0.0, 0.0),
                        outline_width: 0.0,
                        font_size: symbol_font_size,
                        font_index: dock_font_idx,
                        clip_rect: None,
                        layer: UiLayer::DockIndicator,
                        character_colors: None,
                        z_index: 2,
                    });
            }
        }

        if let Some(index) = hovered_index
            && index < 4
        {
            let reserved = &world.resources.retained_ui.reserved_areas;
            let dock_size = 300.0;
            let (preview_pos, preview_size) = match index {
                0 => {
                    let w = viewport_size.x - reserved.left - reserved.right;
                    (
                        nalgebra_glm::Vec2::new(reserved.left, reserved.top),
                        nalgebra_glm::Vec2::new(w, dock_size),
                    )
                }
                1 => {
                    let w = viewport_size.x - reserved.left - reserved.right;
                    (
                        nalgebra_glm::Vec2::new(
                            reserved.left,
                            viewport_size.y - reserved.bottom - dock_size,
                        ),
                        nalgebra_glm::Vec2::new(w, dock_size),
                    )
                }
                2 => {
                    let h = viewport_size.y - reserved.top - reserved.bottom;
                    (
                        nalgebra_glm::Vec2::new(reserved.left, reserved.top),
                        nalgebra_glm::Vec2::new(dock_size, h),
                    )
                }
                3 => {
                    let h = viewport_size.y - reserved.top - reserved.bottom;
                    (
                        nalgebra_glm::Vec2::new(
                            viewport_size.x - reserved.right - dock_size,
                            reserved.top,
                        ),
                        nalgebra_glm::Vec2::new(dock_size, h),
                    )
                }
                _ => (
                    nalgebra_glm::Vec2::new(0.0, 0.0),
                    nalgebra_glm::Vec2::new(0.0, 0.0),
                ),
            };

            world.resources.retained_ui.frame_rects.push(UiRect {
                position: preview_pos,
                size: preview_size,
                color: preview_color,
                corner_radius: 0.0,
                border_width: 2.0,
                border_color: Vec4::new(theme_accent.x, theme_accent.y, theme_accent.z, 0.8),
                rotation: 0.0,
                clip_rect: None,
                layer: UiLayer::DockIndicator,
                z_index: 0,
            });
        }
    }

    emit_tile_containers(world, dpi_scale);

    let overlay_rects: Vec<_> = world
        .resources
        .retained_ui
        .overlay_rects
        .drain(..)
        .collect();
    world
        .resources
        .retained_ui
        .frame_rects
        .extend(overlay_rects);

    let overlay_text: Vec<_> = world.resources.retained_ui.overlay_text.drain(..).collect();
    if !overlay_text.is_empty() && has_bitmap_font {
        for entry in overlay_text {
            if let Some((overlay_font_idx, overlay_font)) =
                select_best_font(&bitmap_fonts, entry.properties.font_size)
            {
                let mesh = generate_text_mesh(&entry.text, overlay_font, &entry.properties);
                world
                    .resources
                    .retained_ui
                    .frame_text_meshes
                    .push(UiTextInstance {
                        mesh,
                        position: entry.position,
                        color: entry.properties.color,
                        outline_color: entry.properties.outline_color,
                        outline_width: entry.properties.outline_width,
                        font_size: entry.properties.font_size,
                        font_index: overlay_font_idx,
                        clip_rect: entry.clip_rect,
                        layer: entry.layer,
                        z_index: entry.z_index,
                        character_colors: None,
                    });
            }
        }
    }
}

fn render_sorted_nodes_to_output(
    world: &mut World,
    sorted_nodes: &[(freecs::Entity, f32)],
    viewport_size: nalgebra_glm::Vec2,
    dpi_scale: f32,
    bitmap_fonts: &[FontAtlasData],
) -> (Vec<UiRect>, Vec<UiImage>, Vec<UiTextInstance>) {
    let mut render_entries: Vec<RenderEntry> = Vec::new();

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

        let computed_rect = node.computed_rect;
        if computed_rect.max.x < 0.0
            || computed_rect.min.x > viewport_size.x
            || computed_rect.max.y < 0.0
            || computed_rect.min.y > viewport_size.y
        {
            continue;
        }

        let computed_depth = node.computed_depth;
        let font_size = node.font_size.unwrap_or(16.0);
        let node_layer = node.computed_layer.unwrap_or(UiLayer::Background);
        let z_index_override = node.z_index;

        let computed_color = world
            .get_ui_node_color(entity)
            .map(|color| color.computed_color)
            .unwrap_or(Vec4::new(1.0, 1.0, 1.0, 1.0));

        let content = match world.get_ui_node_content(entity) {
            Some(content) => *content,
            None => continue,
        };

        let clip_rect = node.computed_clip_rect;

        render_entries.push(RenderEntry {
            computed_rect,
            computed_depth,
            computed_color,
            content,
            clip_rect,
            font_size,
            layer: node_layer,
            z_index_override,
        });
    }

    let mut rects = Vec::new();
    let mut ui_images = Vec::new();
    let mut text_meshes = Vec::new();

    for entry in &render_entries {
        let z_index = entry
            .z_index_override
            .unwrap_or((entry.computed_depth * 100.0) as i32);

        match &entry.content {
            UiNodeContent::None => {}
            UiNodeContent::Rect {
                corner_radius,
                border_width,
                border_color,
            } => {
                rects.push(UiRect {
                    position: entry.computed_rect.min,
                    size: entry.computed_rect.size(),
                    color: entry.computed_color,
                    corner_radius: *corner_radius * dpi_scale,
                    border_width: *border_width * dpi_scale,
                    border_color: *border_color,
                    rotation: 0.0,
                    clip_rect: entry.clip_rect,
                    layer: entry.layer,
                    z_index,
                });
            }
            UiNodeContent::Text {
                text_slot,
                font_index: _,
                font_size_override,
                outline_color,
                outline_width,
                alignment,
                vertical_alignment,
                overflow: _,
            } => {
                let text = world
                    .resources
                    .text_cache
                    .get_text(*text_slot)
                    .map(|s| s.to_string());

                if let Some(text) = text {
                    let actual_font_size =
                        font_size_override.unwrap_or(entry.font_size) * dpi_scale;
                    let Some((font_idx, font_data)) =
                        select_best_font(bitmap_fonts, actual_font_size)
                    else {
                        continue;
                    };
                    let properties = TextProperties {
                        font_size: actual_font_size,
                        color: entry.computed_color,
                        alignment: *alignment,
                        vertical_alignment: *vertical_alignment,
                        ..TextProperties::default()
                    };
                    let mesh = generate_text_mesh(&text, font_data, &properties);

                    let position_x = match alignment {
                        TextAlignment::Left => entry.computed_rect.min.x,
                        TextAlignment::Center => entry.computed_rect.center().x,
                        TextAlignment::Right => entry.computed_rect.max.x,
                    };

                    let position_y = match vertical_alignment {
                        VerticalAlignment::Top => entry.computed_rect.min.y,
                        VerticalAlignment::Middle => entry.computed_rect.center().y,
                        VerticalAlignment::Bottom => entry.computed_rect.max.y,
                        VerticalAlignment::Baseline => entry.computed_rect.center().y,
                    };

                    let snapped_position =
                        nalgebra_glm::Vec2::new(position_x.round(), position_y.round());

                    let character_colors = world
                        .resources
                        .retained_ui
                        .text_slot_character_colors
                        .get(text_slot)
                        .cloned();

                    text_meshes.push(UiTextInstance {
                        mesh,
                        position: snapped_position,
                        color: entry.computed_color,
                        outline_color: *outline_color,
                        outline_width: *outline_width,
                        font_size: actual_font_size,
                        font_index: font_idx,
                        clip_rect: entry.clip_rect,
                        layer: entry.layer,
                        z_index,
                        character_colors,
                    });
                }
            }
            UiNodeContent::Image {
                texture_index,
                uv_min,
                uv_max,
            } => {
                ui_images.push(UiImage {
                    position: entry.computed_rect.min,
                    size: entry.computed_rect.size(),
                    texture_index: *texture_index,
                    uv_min: *uv_min,
                    uv_max: *uv_max,
                    color: entry.computed_color,
                    clip_rect: entry.clip_rect,
                    layer: entry.layer,
                    z_index,
                });
            }
        }
    }

    (rects, ui_images, text_meshes)
}
