use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::{Vec2, Vec4};

use crate::ecs::ui::components::UiPanelKind;
use crate::ecs::world::World;

use super::snapshot_interaction;

use crate::prelude::*;
pub(super) struct PanelContext {
    pub(super) mouse_position: Vec2,
    pub(super) mouse_just_pressed: bool,
    pub(super) mouse_just_released: bool,
    pub(super) mouse_down: bool,
    pub(super) dpi_scale: f32,
    pub(super) viewport_size: Vec2,
    pub(super) mouse_occluded: bool,
}

pub(super) fn handle_panel(
    world: &mut World,
    entity: freecs::Entity,
    data: &crate::ecs::ui::components::UiPanelData,
    ctx: &PanelContext,
) {
    use crate::ecs::ui::components::ResizeEdge;
    use crate::ecs::ui::units::Ab;

    let mouse_position = ctx.mouse_position;
    let mouse_just_pressed = ctx.mouse_just_pressed;
    let mouse_just_released = ctx.mouse_just_released;
    let mouse_down = ctx.mouse_down;
    let dpi_scale = ctx.dpi_scale;
    let viewport_size = ctx.viewport_size;

    let header_interaction = snapshot_interaction(world, data.header_entity);

    let mut drag_offset = data.drag_offset;
    let mut resize_edge = data.resize_edge;
    let mut resize_start_rect = data.resize_start_rect;
    let mut resize_start_mouse = data.resize_start_mouse;
    let mut panel_kind = data.panel_kind;
    let mut undocked_rect = data.undocked_rect;
    let mut default_dock_size = data.default_dock_size;
    let resizable = data.resizable;

    if let Some(collapse_entity) = data.collapse_button_entity {
        let collapse_interaction = snapshot_interaction(world, collapse_entity);
        if collapse_interaction.clicked {
            if panel_kind == UiPanelKind::Floating {
                if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
                    node.visible = false;
                }
            } else {
                let new_collapsed = !data.collapsed;
                if let Some(node) = world.ui.get_ui_layout_node_mut(data.content_entity) {
                    node.visible = !new_collapsed;
                }
                if let Some(text_slot) = data.collapse_button_text_slot {
                    world
                        .resources
                        .text
                        .cache
                        .set_text(text_slot, if new_collapsed { "+" } else { "-" });
                }
                if let Some(widget_data) = world.ui.get_ui_panel_mut(entity) {
                    widget_data.collapsed = new_collapsed;
                }
            }
        }
    }

    if header_interaction.dragging && panel_kind == UiPanelKind::Floating {
        if drag_offset.is_none() {
            let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
            if let Some(rect) = panel_rect
                && let Some(drag_start) = header_interaction.drag_start
            {
                drag_offset = Some(drag_start - rect.min);
            }
        }

        if let Some(offset) = drag_offset
            && let Some(node) = world.ui.get_ui_layout_node_mut(entity)
            && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                node.base_layout.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;
            let new_pos = (mouse_position - offset - parent_min) / dpi_scale;
            let rounded_pos = Vec2::new(new_pos.x.round(), new_pos.y.round());
            window.position = Ab(rounded_pos).into();
        }

        world.resources.retained_ui.overlays.dock_indicator_active = true;
        world.resources.retained_ui.overlays.dock_indicator_panel = Some(entity);
    }

    if header_interaction.dragging
        && panel_kind != UiPanelKind::Floating
        && !data.pinned
        && let Some(drag_start) = header_interaction.drag_start
    {
        let drag_distance = (mouse_position - drag_start).magnitude();
        if drag_distance > 50.0 {
            panel_kind = UiPanelKind::Floating;

            let restored_size = undocked_rect
                .map(|r| r.size())
                .unwrap_or(Vec2::new(300.0, 400.0));
            let new_pos = mouse_position - Vec2::new(restored_size.x * 0.5, 14.0);

            if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
                node.layer = Some(crate::render::wgpu::passes::geometry::UiLayer::FloatingPanels);
                if let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                    node.base_layout.as_mut()
                {
                    window.position = Ab(new_pos / dpi_scale).into();
                    window.size = Ab(restored_size / dpi_scale).into();
                }
            }

            drag_offset = Some(Vec2::new(restored_size.x * 0.5, 14.0));
            world.resources.retained_ui.overlays.dock_indicator_active = true;
            world.resources.retained_ui.overlays.dock_indicator_panel = Some(entity);
        }
    }

    if mouse_just_released
        && world.resources.retained_ui.overlays.dock_indicator_panel == Some(entity)
        && panel_kind == UiPanelKind::Floating
    {
        let indicator_size = 40.0;
        let indicator_spacing = 4.0;
        let center = viewport_size * 0.5;
        let dock_buttons = [
            (
                crate::ecs::ui::types::Rect::from_center_size(
                    Vec2::new(center.x, center.y - indicator_size - indicator_spacing),
                    Vec2::new(indicator_size, indicator_size),
                ),
                UiPanelKind::DockedTop,
            ),
            (
                crate::ecs::ui::types::Rect::from_center_size(
                    Vec2::new(center.x, center.y + indicator_size + indicator_spacing),
                    Vec2::new(indicator_size, indicator_size),
                ),
                UiPanelKind::DockedBottom,
            ),
            (
                crate::ecs::ui::types::Rect::from_center_size(
                    Vec2::new(center.x - indicator_size - indicator_spacing, center.y),
                    Vec2::new(indicator_size, indicator_size),
                ),
                UiPanelKind::DockedLeft,
            ),
            (
                crate::ecs::ui::types::Rect::from_center_size(
                    Vec2::new(center.x + indicator_size + indicator_spacing, center.y),
                    Vec2::new(indicator_size, indicator_size),
                ),
                UiPanelKind::DockedRight,
            ),
        ];

        let mut target_dock = None;
        for (btn_rect, dock_kind) in &dock_buttons {
            if btn_rect.contains(mouse_position) {
                target_dock = Some(*dock_kind);
                break;
            }
        }
        if target_dock.is_none() {
            let edge_threshold = 80.0;
            target_dock = if mouse_position.y < edge_threshold {
                Some(UiPanelKind::DockedTop)
            } else if mouse_position.y > viewport_size.y - edge_threshold {
                Some(UiPanelKind::DockedBottom)
            } else if mouse_position.x < edge_threshold {
                Some(UiPanelKind::DockedLeft)
            } else if mouse_position.x > viewport_size.x - edge_threshold {
                Some(UiPanelKind::DockedRight)
            } else {
                None
            };
        }

        if let Some(new_kind) = target_dock {
            let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);
            if let Some(rect) = panel_rect {
                undocked_rect = Some(rect);
            }
            panel_kind = new_kind;
            default_dock_size = match new_kind {
                UiPanelKind::DockedLeft | UiPanelKind::DockedRight => {
                    undocked_rect.map(|r| r.width()).unwrap_or(300.0) / dpi_scale
                }
                UiPanelKind::DockedTop | UiPanelKind::DockedBottom => {
                    undocked_rect.map(|r| r.height()).unwrap_or(300.0) / dpi_scale
                }
                UiPanelKind::Floating => 300.0,
            };

            if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
                node.layer = Some(crate::render::wgpu::passes::geometry::UiLayer::DockedPanels);
            }
        }

        world.resources.retained_ui.overlays.dock_indicator_active = false;
        world.resources.retained_ui.overlays.dock_indicator_panel = None;
    }

    if !header_interaction.pressed && !header_interaction.dragging {
        drag_offset = None;
        if world.resources.retained_ui.overlays.dock_indicator_panel == Some(entity) {
            world.resources.retained_ui.overlays.dock_indicator_active = false;
            world.resources.retained_ui.overlays.dock_indicator_panel = None;
        }
    }

    if !ctx.mouse_occluded && resizable {
        let panel_rect = world.ui.get_ui_layout_node(entity).map(|n| n.computed_rect);

        if let Some(rect) = panel_rect {
            let edge_margin = 6.0 * dpi_scale;
            let near_left = (mouse_position.x - rect.min.x).abs() < edge_margin;
            let near_right = (mouse_position.x - rect.max.x).abs() < edge_margin;
            let near_top = (mouse_position.y - rect.min.y).abs() < edge_margin;
            let near_bottom = (mouse_position.y - rect.max.y).abs() < edge_margin;
            let inside_x = mouse_position.x >= rect.min.x - edge_margin
                && mouse_position.x <= rect.max.x + edge_margin;
            let inside_y = mouse_position.y >= rect.min.y - edge_margin
                && mouse_position.y <= rect.max.y + edge_margin;

            let detected_edge = match panel_kind {
                UiPanelKind::DockedLeft => {
                    if near_right && inside_y {
                        Some(ResizeEdge::Right)
                    } else {
                        None
                    }
                }
                UiPanelKind::DockedRight => {
                    if near_left && inside_y {
                        Some(ResizeEdge::Left)
                    } else {
                        None
                    }
                }
                UiPanelKind::DockedTop => {
                    if near_bottom && inside_x {
                        Some(ResizeEdge::Bottom)
                    } else {
                        None
                    }
                }
                UiPanelKind::DockedBottom => {
                    if near_top && inside_x {
                        Some(ResizeEdge::Top)
                    } else {
                        None
                    }
                }
                UiPanelKind::Floating => {
                    if near_top && near_left && inside_x && inside_y {
                        Some(ResizeEdge::TopLeft)
                    } else if near_top && near_right && inside_x && inside_y {
                        Some(ResizeEdge::TopRight)
                    } else if near_bottom && near_left && inside_x && inside_y {
                        Some(ResizeEdge::BottomLeft)
                    } else if near_bottom && near_right && inside_x && inside_y {
                        Some(ResizeEdge::BottomRight)
                    } else if near_left && inside_y {
                        Some(ResizeEdge::Left)
                    } else if near_right && inside_y {
                        Some(ResizeEdge::Right)
                    } else if near_top && inside_x {
                        Some(ResizeEdge::Top)
                    } else if near_bottom && inside_x {
                        Some(ResizeEdge::Bottom)
                    } else {
                        None
                    }
                }
            };

            if let Some(edge) = detected_edge {
                world
                    .resources
                    .retained_ui
                    .interaction_for_active_mut()
                    .requested_cursor = Some(match edge {
                    ResizeEdge::Left | ResizeEdge::Right => winit::window::CursorIcon::EwResize,
                    ResizeEdge::Top | ResizeEdge::Bottom => winit::window::CursorIcon::NsResize,
                    ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
                        winit::window::CursorIcon::NwseResize
                    }
                    ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
                        winit::window::CursorIcon::NeswResize
                    }
                });
            }

            if !header_interaction.dragging && mouse_just_pressed && detected_edge.is_some() {
                resize_edge = detected_edge;
                resize_start_rect = Some(rect);
                resize_start_mouse = Some(mouse_position);
            }
        }
    }

    if let Some(edge) = resize_edge {
        world
            .resources
            .retained_ui
            .interaction_for_active_mut()
            .requested_cursor = Some(match edge {
            ResizeEdge::Left | ResizeEdge::Right => winit::window::CursorIcon::EwResize,
            ResizeEdge::Top | ResizeEdge::Bottom => winit::window::CursorIcon::NsResize,
            ResizeEdge::TopLeft | ResizeEdge::BottomRight => winit::window::CursorIcon::NwseResize,
            ResizeEdge::TopRight | ResizeEdge::BottomLeft => winit::window::CursorIcon::NeswResize,
        });
        if mouse_down {
            if let (Some(start_rect), Some(start_mouse)) = (resize_start_rect, resize_start_mouse) {
                let delta = mouse_position - start_mouse;
                let min_size = data.min_size;
                let mut new_pos = start_rect.min;
                let mut new_size = start_rect.size();

                match edge {
                    ResizeEdge::Right => {
                        new_size.x = (start_rect.width() + delta.x).max(min_size.x);
                    }
                    ResizeEdge::Bottom => {
                        new_size.y = (start_rect.height() + delta.y).max(min_size.y);
                    }
                    ResizeEdge::Left => {
                        let new_width = (start_rect.width() - delta.x).max(min_size.x);
                        new_pos.x = start_rect.max.x - new_width;
                        new_size.x = new_width;
                    }
                    ResizeEdge::Top => {
                        let new_height = (start_rect.height() - delta.y).max(min_size.y);
                        new_pos.y = start_rect.max.y - new_height;
                        new_size.y = new_height;
                    }
                    ResizeEdge::TopLeft => {
                        let new_width = (start_rect.width() - delta.x).max(min_size.x);
                        let new_height = (start_rect.height() - delta.y).max(min_size.y);
                        new_pos.x = start_rect.max.x - new_width;
                        new_pos.y = start_rect.max.y - new_height;
                        new_size.x = new_width;
                        new_size.y = new_height;
                    }
                    ResizeEdge::TopRight => {
                        let new_height = (start_rect.height() - delta.y).max(min_size.y);
                        new_pos.y = start_rect.max.y - new_height;
                        new_size.x = (start_rect.width() + delta.x).max(min_size.x);
                        new_size.y = new_height;
                    }
                    ResizeEdge::BottomLeft => {
                        let new_width = (start_rect.width() - delta.x).max(min_size.x);
                        new_pos.x = start_rect.max.x - new_width;
                        new_size.x = new_width;
                        new_size.y = (start_rect.height() + delta.y).max(min_size.y);
                    }
                    ResizeEdge::BottomRight => {
                        new_size.x = (start_rect.width() + delta.x).max(min_size.x);
                        new_size.y = (start_rect.height() + delta.y).max(min_size.y);
                    }
                }

                if panel_kind != UiPanelKind::Floating {
                    match panel_kind {
                        UiPanelKind::DockedLeft | UiPanelKind::DockedRight => {
                            default_dock_size = new_size.x / dpi_scale;
                        }
                        UiPanelKind::DockedTop | UiPanelKind::DockedBottom => {
                            default_dock_size = new_size.y / dpi_scale;
                        }
                        UiPanelKind::Floating => {}
                    }
                } else if let Some(node) = world.ui.get_ui_layout_node_mut(entity)
                    && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                        node.base_layout.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;
                    let logical_pos = (new_pos - parent_min) / dpi_scale;
                    let logical_size = new_size / dpi_scale;
                    window.position =
                        Ab(Vec2::new(logical_pos.x.round(), logical_pos.y.round())).into();
                    window.size =
                        Ab(Vec2::new(logical_size.x.round(), logical_size.y.round())).into();
                }
            }
        } else {
            resize_edge = None;
            resize_start_rect = None;
            resize_start_mouse = None;
        }
    }

    if let Some(widget_data) = world.ui.get_ui_panel_mut(entity) {
        widget_data.drag_offset = drag_offset;
        widget_data.resize_edge = resize_edge;
        widget_data.resize_start_rect = resize_start_rect;
        widget_data.resize_start_mouse = resize_start_mouse;
        widget_data.panel_kind = panel_kind;
        widget_data.undocked_rect = undocked_rect;
        widget_data.default_dock_size = default_dock_size;
    }
}

pub(super) fn handle_color_wheel(
    world: &mut World,
    entity: freecs::Entity,
    data: &crate::ecs::ui::components::UiColorWheelData,
    mouse_position: Vec2,
) {
    let mut hue = data.hue;
    let mut saturation = data.saturation;
    let mut changed_position = false;

    let wheel_interaction = snapshot_interaction(world, data.wheel_entity);
    if (wheel_interaction.pressed || wheel_interaction.dragging)
        && let Some(rect) = world
            .ui
            .get_ui_layout_node(data.wheel_entity)
            .map(|node| node.computed_rect)
    {
        let center = rect.center();
        let half = (rect.width().min(rect.height())) * 0.5;
        if half > 0.0 {
            let offset = mouse_position - center;
            let raw_radius = nalgebra_glm::length(&offset);
            let new_saturation = (raw_radius / half).clamp(0.0, 1.0);
            let angle = offset.y.atan2(offset.x);
            let new_hue = ((angle / std::f32::consts::TAU) + 0.5).rem_euclid(1.0);
            if (new_hue - hue).abs() > f32::EPSILON
                || (new_saturation - saturation).abs() > f32::EPSILON
            {
                hue = new_hue;
                saturation = new_saturation;
                changed_position = true;
            }
        }
    }

    let mut value = data.value;
    let mut alpha = data.alpha;
    let mut changed_value = false;
    let mut changed_alpha = false;

    if let Some(slider_data) = world.ui.get_ui_slider(data.value_slider_entity) {
        if slider_data.changed && (slider_data.value - value).abs() > f32::EPSILON {
            changed_value = true;
        }
        value = slider_data.value;
    }
    if let Some(slider_data) = world.ui.get_ui_slider(data.alpha_slider_entity) {
        if slider_data.changed && (slider_data.value - alpha).abs() > f32::EPSILON {
            changed_alpha = true;
        }
        alpha = slider_data.value;
    }

    let any_changed = changed_position || changed_value || changed_alpha;

    let hsva = crate::ecs::ui::color::Hsva {
        hue: hue * 360.0,
        saturation,
        value,
        alpha,
    };
    let rgba = hsva.to_rgba();

    if changed_value && let Some(effect) = world.ui.get_ui_node_effect_mut(data.wheel_entity) {
        let (kind, params) =
            crate::ecs::ui::theme::RectEffect::ColorWheel { value, alpha: 1.0 }.encode();
        effect.kind = kind;
        effect.params = params;
    }

    if any_changed {
        if let Some(swatch_color) = world.ui.get_ui_node_color_mut(data.swatch_entity) {
            swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(rgba);
        }

        if changed_position {
            let dot_size = 12.0_f32;
            if let Some(rect) = world
                .ui
                .get_ui_layout_node(data.wheel_entity)
                .map(|node| node.computed_rect)
            {
                let half = (rect.width().min(rect.height())) * 0.5;
                if half > 0.0 {
                    let dpi = crate::ecs::window::resources::window_scale_factor(world).max(0.0001);
                    let logical_half = half / dpi;
                    let logical_dot = dot_size;
                    let theta = hue * std::f32::consts::TAU - std::f32::consts::PI;
                    let local_x =
                        logical_half + saturation * logical_half * theta.cos() - logical_dot * 0.5;
                    let local_y =
                        logical_half + saturation * logical_half * theta.sin() - logical_dot * 0.5;
                    if let Some(node) = world.ui.get_ui_layout_node_mut(data.dot_entity)
                        && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                            node.base_layout.as_mut()
                    {
                        window.position =
                            crate::ecs::ui::units::Ab(Vec2::new(local_x, local_y)).into();
                    }
                }
            }
        }
    }

    if let Some(widget_data) = world.ui.get_ui_color_wheel_mut(entity) {
        widget_data.hue = hue;
        widget_data.saturation = saturation;
        widget_data.value = value;
        widget_data.alpha = alpha;
        widget_data.color = rgba;
        widget_data.changed = any_changed;
    }
    if any_changed {
        world.resources.retained_ui.events_for_active_mut().push(
            crate::ecs::ui::resources::UiEvent::ColorPickerChanged {
                entity,
                color: rgba,
            },
        );
    }
}

pub(super) fn handle_color_picker(
    world: &mut World,
    entity: freecs::Entity,
    data: &crate::ecs::ui::components::UiColorPickerData,
) {
    let mode = data.mode;
    let mut any_changed = false;

    let mut slider_values = [0.0_f32; 4];
    for (index, slider_entity) in data.slider_entities.iter().enumerate() {
        if let Some(slider_data) = world.ui.get_ui_slider(*slider_entity) {
            if slider_data.changed {
                any_changed = true;
            }
            slider_values[index] = slider_data.value;
        }
    }

    let mut color = match mode {
        crate::ecs::ui::components::ColorPickerMode::Rgb => Vec4::new(
            slider_values[0],
            slider_values[1],
            slider_values[2],
            slider_values[3],
        ),
        crate::ecs::ui::components::ColorPickerMode::Hsv => {
            let hsv = crate::ecs::ui::color::Hsva {
                hue: slider_values[0],
                saturation: slider_values[1],
                value: slider_values[2],
                alpha: slider_values[3],
            };
            hsv.to_rgba()
        }
    };

    let mut popup_open = data.popup_open;
    let swatch_interaction = snapshot_interaction(world, data.swatch_entity);
    if swatch_interaction.clicked {
        popup_open = !popup_open;
    }

    let popup_wheel_changed = world
        .ui
        .get_ui_color_wheel(data.popup_wheel_entity)
        .map(|wheel| (wheel.changed, wheel.color))
        .unwrap_or((false, color));
    if popup_wheel_changed.0 {
        color = popup_wheel_changed.1;
        any_changed = true;
        sync_color_picker_sliders(world, data, color);
    }

    if popup_open && !data.popup_open {
        sync_color_wheel_to_color(world, data.popup_wheel_entity, color);
    }

    if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_entity) {
        node.visible = popup_open;
    }

    if popup_open {
        let mouse_position = world.resources.input.mouse.position;
        let mouse_just_pressed = world
            .resources
            .input
            .mouse
            .state
            .contains(crate::ecs::input::resources::MouseState::LEFT_JUST_PRESSED);
        if mouse_just_pressed && !swatch_interaction.clicked {
            let popup_rect = world
                .ui
                .get_ui_layout_node(data.popup_entity)
                .map(|node| node.computed_rect);
            let inside_popup = popup_rect.is_some_and(|rect| rect.contains(mouse_position));
            if !inside_popup {
                popup_open = false;
                if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_entity) {
                    node.visible = false;
                }
            }
        }
    }

    if any_changed && let Some(swatch_color) = world.ui.get_ui_node_color_mut(data.swatch_entity) {
        swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(color);
    }

    if let Some(widget_data) = world.ui.get_ui_color_picker_mut(entity) {
        widget_data.color = color;
        widget_data.changed = any_changed;
        widget_data.popup_open = popup_open;
    }
    if any_changed {
        world
            .resources
            .retained_ui
            .frame
            .events
            .push(crate::ecs::ui::resources::UiEvent::ColorPickerChanged { entity, color });
    }
}

fn sync_color_picker_sliders(
    world: &mut World,
    data: &crate::ecs::ui::components::UiColorPickerData,
    color: Vec4,
) {
    match data.mode {
        crate::ecs::ui::components::ColorPickerMode::Rgb => {
            ui_slider_set_value(world, data.slider_entities[0], color.x);
            ui_slider_set_value(world, data.slider_entities[1], color.y);
            ui_slider_set_value(world, data.slider_entities[2], color.z);
            ui_slider_set_value(world, data.slider_entities[3], color.w);
        }
        crate::ecs::ui::components::ColorPickerMode::Hsv => {
            let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
            ui_slider_set_value(world, data.slider_entities[0], hsv.hue);
            ui_slider_set_value(world, data.slider_entities[1], hsv.saturation);
            ui_slider_set_value(world, data.slider_entities[2], hsv.value);
            ui_slider_set_value(world, data.slider_entities[3], color.w);
        }
    }
}

fn sync_color_wheel_to_color(world: &mut World, wheel_entity: freecs::Entity, color: Vec4) {
    let wheel_data = match world.ui.get_ui_color_wheel(wheel_entity) {
        Some(data) => data.clone(),
        None => return,
    };
    let hsv = crate::ecs::ui::color::Hsva::from_rgba(color);
    let hue_normalized = (hsv.hue / 360.0).clamp(0.0, 1.0);
    let saturation = hsv.saturation.clamp(0.0, 1.0);
    let value = hsv.value.clamp(0.0, 1.0);
    let alpha = color.w.clamp(0.0, 1.0);

    ui_slider_set_value(world, wheel_data.value_slider_entity, value);
    ui_slider_set_value(world, wheel_data.alpha_slider_entity, alpha);

    if let Some(swatch_color) = world.ui.get_ui_node_color_mut(wheel_data.swatch_entity) {
        swatch_color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(color);
    }
    if let Some(effect) = world.ui.get_ui_node_effect_mut(wheel_data.wheel_entity) {
        let (kind, params) =
            crate::ecs::ui::theme::RectEffect::ColorWheel { value, alpha: 1.0 }.encode();
        effect.kind = kind;
        effect.params = params;
    }
    if let Some(rect) = world
        .ui
        .get_ui_layout_node(wheel_data.wheel_entity)
        .map(|node| node.computed_rect)
    {
        let half = (rect.width().min(rect.height())) * 0.5;
        if half > 0.0 {
            let dpi = crate::ecs::window::resources::window_scale_factor(world).max(0.0001);
            let logical_half = half / dpi;
            let dot_size = 12.0_f32;
            let theta = hue_normalized * std::f32::consts::TAU - std::f32::consts::PI;
            let local_x = logical_half + saturation * logical_half * theta.cos() - dot_size * 0.5;
            let local_y = logical_half + saturation * logical_half * theta.sin() - dot_size * 0.5;
            if let Some(node) = world.ui.get_ui_layout_node_mut(wheel_data.dot_entity)
                && let Some(crate::ecs::ui::layout_types::UiLayoutType::Window(window)) =
                    node.base_layout.as_mut()
            {
                window.position = crate::ecs::ui::units::Ab(Vec2::new(local_x, local_y)).into();
            }
        }
    }
    if let Some(widget_data) = world.ui.get_ui_color_wheel_mut(wheel_entity) {
        widget_data.color = color;
        widget_data.hue = hue_normalized;
        widget_data.saturation = saturation;
        widget_data.value = value;
        widget_data.alpha = alpha;
    }
}
