use crate::ecs::ui::color::blend_state_colors;
use crate::ecs::ui::components::{
    UiAnimationPhase, UiAnimationType, UiNodeAnimation, UiNodeBlendedTransform, UiNodeShadow,
};
use crate::ecs::ui::state::STATE_COUNT;
use crate::ecs::ui::types::Rect;
use crate::ecs::world::World;
use nalgebra_glm::Vec2;

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

    let mut entities = std::mem::take(&mut world.resources.retained_ui.blend_scratch);
    entities.clear();
    entities.extend(world.ui.query_entities(crate::ecs::world::UI_NODE_COLOR));

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

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

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

        if let Some(color_comp) = world.ui.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.dirty.render_dirty = true;
            }
        }
    }

    entities.clear();
    entities.extend(
        world
            .ui
            .query_entities(crate::ecs::world::UI_NODE_SHADOW_STATES),
    );

    for entity in entities.iter().copied() {
        let shadows = match world.ui.get_ui_node_shadow_states(entity) {
            Some(states) => states.shadows,
            None => continue,
        };
        let weights = world
            .ui
            .get_ui_state_weights(entity)
            .map(|state_weights| state_weights.weights)
            .unwrap_or([0.0; STATE_COUNT]);

        let blended = dominant_state_shadow(&shadows, &weights);

        if let Some(slot) = world.ui.get_ui_node_shadow_mut(entity) {
            let old = *slot;
            *slot = blended;
            if !shadows_match(&old, &blended) {
                world.resources.retained_ui.dirty.render_dirty = true;
            }
        }
    }

    world.resources.retained_ui.blend_scratch = entities;
}

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

    let mut entities = std::mem::take(&mut world.resources.retained_ui.blend_scratch);
    entities.clear();
    entities.extend(
        world
            .ui
            .query_entities(crate::ecs::world::UI_NODE_TRANSFORM_STATES),
    );

    for entity in entities.iter().copied() {
        let states = match world.ui.get_ui_node_transform_states(entity) {
            Some(states) => *states,
            None => continue,
        };
        let weights = world
            .ui
            .get_ui_state_weights(entity)
            .map(|state_weights| state_weights.weights)
            .unwrap_or([0.0; STATE_COUNT]);

        let mut total_weight = 0.0_f32;
        let mut offset = Vec2::zeros();
        let mut scale = 0.0_f32;
        let mut have_explicit_scale = false;
        for (index, &weight) in weights.iter().enumerate() {
            if weight <= 0.0 {
                continue;
            }
            total_weight += weight;
            if let Some(state_offset) = states.offsets[index] {
                offset += state_offset * weight;
            }
            if let Some(state_scale) = states.scales[index] {
                scale += state_scale * weight;
                have_explicit_scale = true;
            } else {
                scale += weight;
            }
        }
        let blended_scale = if have_explicit_scale && total_weight > f32::EPSILON {
            scale / total_weight
        } else {
            1.0
        };
        let radius_override = dominant_state_radius(&states.radii, &weights);

        let blended = UiNodeBlendedTransform {
            offset,
            scale: blended_scale,
            radius_override,
        };

        world
            .ui
            .add_components(entity, crate::ecs::world::UI_NODE_BLENDED_TRANSFORM);
        if let Some(slot) = world.ui.get_ui_node_blended_transform_mut(entity) {
            let old = *slot;
            *slot = blended;
            if !blended_transforms_match(&old, &blended) {
                world.resources.retained_ui.dirty.render_dirty = true;
            }
        }
    }

    world.resources.retained_ui.blend_scratch = entities;
}

fn blended_transforms_match(a: &UiNodeBlendedTransform, b: &UiNodeBlendedTransform) -> bool {
    let eps = 0.001;
    (a.offset - b.offset).norm() < eps
        && (a.scale - b.scale).abs() < eps
        && match (a.radius_override, b.radius_override) {
            (Some(x), Some(y)) => (x - y).abs() < eps,
            (None, None) => true,
            _ => false,
        }
}

fn dominant_state_radius(
    radii: &[Option<f32>; STATE_COUNT],
    weights: &[f32; STATE_COUNT],
) -> Option<f32> {
    let mut best_index = None;
    let mut best_weight = -1.0;
    for index in 0..STATE_COUNT {
        if radii[index].is_none() {
            continue;
        }
        let weight = weights[index];
        if weight > best_weight {
            best_weight = weight;
            best_index = Some(index);
        }
    }
    best_index.and_then(|index| radii[index])
}

fn shadows_match(a: &UiNodeShadow, b: &UiNodeShadow) -> bool {
    let eps = 0.001;
    (a.color - b.color).norm() < eps
        && (a.offset - b.offset).norm() < eps
        && (a.blur - b.blur).abs() < eps
        && (a.spread - b.spread).abs() < eps
}

fn dominant_state_shadow(
    shadows: &[Option<UiNodeShadow>; STATE_COUNT],
    weights: &[f32; STATE_COUNT],
) -> UiNodeShadow {
    let mut best_index = 0;
    let mut best_weight = -1.0;
    for index in 0..STATE_COUNT {
        if shadows[index].is_none() {
            continue;
        }
        let weight = weights[index];
        if weight > best_weight {
            best_weight = weight;
            best_index = index;
        }
    }
    shadows[best_index].unwrap_or_default()
}

fn animation_alpha(animation: &UiNodeAnimation) -> f32 {
    let uses_fade = matches!(
        match animation.phase {
            UiAnimationPhase::IntroPlaying => animation.intro,
            UiAnimationPhase::OutroPlaying => animation.outro,
            _ => return 1.0,
        },
        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: &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
}

pub(super) fn apply_animation_to_rect(rect: Rect, animation: &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: UiAnimationType, t: f32) -> Rect {
    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: UiAnimationType, t: f32) -> Rect {
    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,
            )
        }
    }
}
