use super::*;

#[derive(Clone, Copy)]
pub(super) struct StackTransitionPose {
    pub(super) center: Vec2,
    pub(super) size: Vec2,
    pub(super) alpha: f32,
    pub(super) draw_order: i32,
}

pub(super) struct StackTransitionExtraInstance {
    pub(super) node_id: NodeId,
    pub(super) pose: StackTransitionPose,
}

pub(super) struct StackTransitionPlan {
    pub(super) poses: HashMap<NodeId, StackTransitionPose>,
    pub(super) extra_instances: Vec<StackTransitionExtraInstance>,
}

fn lerp_f32(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

fn lerp_vec2(a: Vec2, b: Vec2, t: f32) -> Vec2 {
    Vec2 {
        x: lerp_f32(a.x, b.x, t),
        y: lerp_f32(a.y, b.y, t),
    }
}

fn rect_center(rect: Rect) -> Vec2 {
    Vec2 {
        x: rect.x + rect.w * 0.5,
        y: rect.y + rect.h * 0.5,
    }
}

fn rect_size(rect: Rect) -> Vec2 {
    Vec2 {
        x: rect.w.max(1.0),
        y: rect.h.max(1.0),
    }
}

pub(super) fn stack_draw_order_map(front_to_back: &[NodeId]) -> HashMap<NodeId, i32> {
    let len = front_to_back.len() as i32;
    front_to_back
        .iter()
        .enumerate()
        .map(|(index, &node_id)| (node_id, len - index as i32 - 1))
        .collect()
}

pub(super) fn build_stack_transition_plan(
    st: &Halley,
    monitor: &str,
    transition: &crate::render::state::StackCycleTransitionSnapshot,
) -> Option<StackTransitionPlan> {
    let custom_source_rects = transition.source_rects.is_some();
    let old_rects = transition
        .source_rects
        .clone()
        .or_else(|| st.stack_layout_rects_for_members(monitor, &transition.old_visible))?;
    let new_rects = st.stack_layout_rects_for_members(monitor, &transition.new_visible)?;
    let t = ease_in_out_cubic(transition.progress);
    let draw_orders = stack_draw_order_map(&transition.new_visible);
    let topmost_order = transition.new_visible.len() as i32 + 1;
    let old_top = transition.old_visible.first().copied();
    let old_bottom = transition.old_visible.last().copied();
    let new_top = transition.new_visible.first().copied();
    let wrapped_same_set = !custom_source_rects
        && transition.old_visible.len() == transition.new_visible.len()
        && transition
            .old_visible
            .iter()
            .all(|id| transition.new_visible.contains(id));
    let wrapped_node = match transition.direction {
        ClusterCycleDirection::Next
            if wrapped_same_set && old_top == transition.new_visible.last().copied() =>
        {
            old_top
        }
        ClusterCycleDirection::Prev if wrapped_same_set && old_bottom == new_top => old_bottom,
        _ => None,
    };

    let mut ids = transition.old_visible.clone();
    for &node_id in &transition.new_visible {
        if !ids.contains(&node_id) {
            ids.push(node_id);
        }
    }

    let mut poses = HashMap::new();
    let mut extra_instances = Vec::new();
    for node_id in ids {
        if wrapped_node == Some(node_id)
            && let (Some(old_rect), Some(new_rect)) = (
                old_rects.get(&node_id).copied(),
                new_rects.get(&node_id).copied(),
            )
        {
            let canonical_pose = match transition.direction {
                ClusterCycleDirection::Next => StackTransitionPose {
                    center: rect_center(new_rect),
                    size: rect_size(new_rect),
                    alpha: t,
                    draw_order: draw_orders.get(&node_id).copied().unwrap_or_default(),
                },
                ClusterCycleDirection::Prev => {
                    let end_center = rect_center(new_rect);
                    let mut start_center = end_center;
                    start_center.x -= new_rect.w * 0.55;
                    StackTransitionPose {
                        center: lerp_vec2(start_center, end_center, t),
                        size: rect_size(new_rect),
                        alpha: t,
                        draw_order: topmost_order,
                    }
                }
            };
            poses.insert(node_id, canonical_pose);

            if matches!(transition.direction, ClusterCycleDirection::Next) {
                let mut end_center = rect_center(old_rect);
                end_center.x -= old_rect.w * 0.55;
                extra_instances.push(StackTransitionExtraInstance {
                    node_id,
                    pose: StackTransitionPose {
                        center: lerp_vec2(rect_center(old_rect), end_center, t),
                        size: rect_size(old_rect),
                        alpha: 1.0 - t,
                        draw_order: topmost_order,
                    },
                });
            }
            continue;
        }

        let pose = match (
            old_rects.get(&node_id).copied(),
            new_rects.get(&node_id).copied(),
        ) {
            (Some(old_rect), Some(new_rect)) => StackTransitionPose {
                center: lerp_vec2(rect_center(old_rect), rect_center(new_rect), t),
                size: lerp_vec2(rect_size(old_rect), rect_size(new_rect), t),
                alpha: 1.0,
                draw_order: draw_orders.get(&node_id).copied().unwrap_or_default(),
            },
            (Some(old_rect), None) => {
                let mut end_center = rect_center(old_rect);
                let draw_order = match transition.direction {
                    ClusterCycleDirection::Next if Some(node_id) == old_top => {
                        end_center.x -= old_rect.w * 0.55;
                        topmost_order
                    }
                    ClusterCycleDirection::Prev if Some(node_id) == old_bottom => {
                        end_center.x += old_rect.w * 0.08;
                        end_center.y += old_rect.h * 0.04;
                        0
                    }
                    _ => 0,
                };
                StackTransitionPose {
                    center: lerp_vec2(rect_center(old_rect), end_center, t),
                    size: rect_size(old_rect),
                    alpha: 1.0 - t,
                    draw_order,
                }
            }
            (None, Some(new_rect)) => {
                let end_center = rect_center(new_rect);
                let mut start_center = end_center;
                let draw_order = match transition.direction {
                    ClusterCycleDirection::Prev if Some(node_id) == new_top => {
                        start_center.x -= new_rect.w * 0.55;
                        topmost_order
                    }
                    _ => draw_orders.get(&node_id).copied().unwrap_or_default(),
                };
                StackTransitionPose {
                    center: lerp_vec2(start_center, end_center, t),
                    size: rect_size(new_rect),
                    alpha: t,
                    draw_order,
                }
            }
            (None, None) => continue,
        };
        poses.insert(node_id, pose);
    }

    Some(StackTransitionPlan {
        poses,
        extra_instances,
    })
}

fn transform_rect_about_center(
    x: i32,
    y: i32,
    w: i32,
    h: i32,
    from_center: (f32, f32),
    to_center: (f32, f32),
    scale_x: f32,
    scale_y: f32,
) -> (i32, i32, i32, i32) {
    let rect_center_x = x as f32 + w as f32 * 0.5;
    let rect_center_y = y as f32 + h as f32 * 0.5;
    let new_center_x = to_center.0 + (rect_center_x - from_center.0) * scale_x;
    let new_center_y = to_center.1 + (rect_center_y - from_center.1) * scale_y;
    let new_w = (w as f32 * scale_x).round().max(1.0) as i32;
    let new_h = (h as f32 * scale_y).round().max(1.0) as i32;
    (
        (new_center_x - new_w as f32 * 0.5).round() as i32,
        (new_center_y - new_h as f32 * 0.5).round() as i32,
        new_w,
        new_h,
    )
}

pub(super) fn clone_stack_window_unit_for_pose(
    st: &Halley,
    size: Size<i32, Physical>,
    unit: &StackWindowDrawUnit,
    from_pose: StackTransitionPose,
    to_pose: StackTransitionPose,
) -> Option<StackWindowDrawUnit> {
    let (from_cx, from_cy) =
        world_to_screen(st, size.w, size.h, from_pose.center.x, from_pose.center.y);
    let (to_cx, to_cy) = world_to_screen(st, size.w, size.h, to_pose.center.x, to_pose.center.y);
    let scale_x = (to_pose.size.x / from_pose.size.x.max(1.0)).max(0.01);
    let scale_y = (to_pose.size.y / from_pose.size.y.max(1.0)).max(0.01);

    let shadow_rects = unit
        .shadow_rects
        .iter()
        .cloned()
        .map(|mut rect| {
            let (x, y, w, h) = transform_rect_about_center(
                rect.x,
                rect.y,
                rect.w,
                rect.h,
                (from_cx as f32, from_cy as f32),
                (to_cx as f32, to_cy as f32),
                scale_x,
                scale_y,
            );
            rect.x = x;
            rect.y = y;
            rect.w = w;
            rect.h = h;
            rect.corner_radius *= scale_x.min(scale_y);
            rect.alpha *= to_pose.alpha.clamp(0.0, 1.0);
            rect
        })
        .collect::<Vec<_>>();

    let border_rects = unit
        .border_rects
        .iter()
        .cloned()
        .map(|mut rect| {
            let (x, y, w, h) = transform_rect_about_center(
                rect.x,
                rect.y,
                rect.w,
                rect.h,
                (from_cx as f32, from_cy as f32),
                (to_cx as f32, to_cy as f32),
                scale_x,
                scale_y,
            );
            rect.x = x;
            rect.y = y;
            rect.w = w;
            rect.h = h;
            rect.inner_w = w as f32;
            rect.inner_h = h as f32;
            rect.alpha *= to_pose.alpha.clamp(0.0, 1.0);
            rect
        })
        .collect::<Vec<_>>();

    let offscreen_textures = unit
        .offscreen_textures
        .iter()
        .cloned()
        .map(|mut tex| {
            let (dst_x, dst_y, dst_w, dst_h) = transform_rect_about_center(
                tex.dst_x,
                tex.dst_y,
                tex.dst_w,
                tex.dst_h,
                (from_cx as f32, from_cy as f32),
                (to_cx as f32, to_cy as f32),
                scale_x,
                scale_y,
            );
            let (clip_x, clip_y, clip_w, clip_h) = transform_rect_about_center(
                tex.clip_x,
                tex.clip_y,
                tex.clip_w,
                tex.clip_h,
                (from_cx as f32, from_cy as f32),
                (to_cx as f32, to_cy as f32),
                scale_x,
                scale_y,
            );
            tex.dst_x = dst_x;
            tex.dst_y = dst_y;
            tex.dst_w = dst_w;
            tex.dst_h = dst_h;
            tex.clip_x = clip_x;
            tex.clip_y = clip_y;
            tex.clip_w = clip_w;
            tex.clip_h = clip_h;
            tex.geo_offset_x *= scale_x;
            tex.geo_offset_y *= scale_y;
            tex.geo_w *= scale_x;
            tex.geo_h *= scale_y;
            tex.alpha *= to_pose.alpha.clamp(0.0, 1.0);
            tex
        })
        .collect::<Vec<_>>();

    if shadow_rects.is_empty() && border_rects.is_empty() && offscreen_textures.is_empty() {
        return None;
    }

    Some(StackWindowDrawUnit {
        node_id: unit.node_id,
        draw_order: to_pose.draw_order,
        shadow_rects,
        border_rects,
        active_elements: Vec::new(),
        offscreen_textures,
    })
}
