use super::*;

fn sample_text(content: &str, cx: f32) -> Text {
    Text {
        cx,
        cy: 0.0,
        size: 0.1,
        color: Color::WHITE,
        content: content.to_string(),
        origin: Anchor::Center,
    }
}

#[test]
fn lerp_text_same_content_lerps_fields() {
    let a = sample_text("hi", 0.0);
    let b = sample_text("hi", 1.0);
    let m = lerp_text(&a, &b, 0.5);
    assert_eq!(m.content, "hi");
    assert!((m.cx - 0.5).abs() < 1e-6);
}

#[test]
fn lerp_text_cross_content_snaps_at_half() {
    let a = sample_text("hi", 0.0);
    let b = sample_text("bye", 1.0);
    assert_eq!(lerp_text(&a, &b, 0.3).content, "hi");
    assert_eq!(lerp_text(&a, &b, 0.5).content, "bye");
    assert_eq!(lerp_text(&a, &b, 0.7).content, "bye");
}

#[test]
fn shape_text_set_alpha_scales_color() {
    let mut s = Shape::Text(sample_text("hi", 0.0));
    s.set_alpha(0.25);
    match &s {
        Shape::Text(t) => assert!((t.color.a - 0.25).abs() < 1e-6),
        _ => panic!(),
    }
}

#[test]
fn lerp_line_midpoint_averages_endpoints() {
    let a = Line {
        x1: 0.0,
        y1: 0.0,
        x2: 1.0,
        y2: 0.0,
        thickness: 0.01,
        color: Color::RED,
    };
    let b = Line {
        x1: 1.0,
        y1: 1.0,
        x2: 0.0,
        y2: 1.0,
        thickness: 0.05,
        color: Color::WHITE,
    };
    let m = lerp_line(a, b, 0.5);
    assert!((m.x1 - 0.5).abs() < 1e-6);
    assert!((m.y1 - 0.5).abs() < 1e-6);
    assert!((m.x2 - 0.5).abs() < 1e-6);
    assert!((m.y2 - 0.5).abs() < 1e-6);
    assert!((m.thickness - 0.03).abs() < 1e-6);
}

#[test]
fn fadeout_inverts_fade_in_alpha() {
    let target = scene_shape(1, Shape::Circle(red_circle()));
    let anim = Animation::FadeOut { target };
    let at0 = anim.apply(&[], 0.0);
    let at1 = anim.apply(&[], 1.0);
    match &at0[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 1.0).abs() < 1e-6),
        _ => panic!(),
    }
    match &at1[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 0.0).abs() < 1e-6),
        _ => panic!(),
    }
}

#[test]
fn ratefunc_there_and_back_peaks_at_midpoint() {
    assert!((RateFunc::ThereAndBack.apply(0.0) - 0.0).abs() < 1e-6);
    assert!((RateFunc::ThereAndBack.apply(0.25) - 0.5).abs() < 1e-6);
    assert!((RateFunc::ThereAndBack.apply(0.5) - 1.0).abs() < 1e-6);
    assert!((RateFunc::ThereAndBack.apply(0.75) - 0.5).abs() < 1e-6);
    assert!((RateFunc::ThereAndBack.apply(1.0) - 0.0).abs() < 1e-6);
}

#[test]
fn timeline_fadeout_linear_midway_is_half_alpha() {
    let target = scene_shape(1, Shape::Circle(red_circle()));
    let tl = Timeline {
        segments: vec![Segment {
            start_frame: 0,
            end_frame: 10,
            base: vec![],
            animation: Animation::FadeOut { target },
            rate: RateFunc::Linear,
        }],
        fps: 30,
        background: Color::BLACK,
        width: 64,
        height: 64,
    };
    let mid = tl.shapes_at(5);
    assert_eq!(mid.len(), 1);
    match &mid[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 0.5).abs() < 1e-6),
        _ => panic!(),
    }
}

fn tri(color: Color) -> Polygon {
    Polygon {
        points: vec![(-0.5, -0.5), (0.5, -0.5), (0.0, 0.5)],
        color,
    }
}

#[test]
fn set_alpha_polygon_scales_color_alpha() {
    let mut s = Shape::Polygon(tri(Color::rgba(1.0, 0.0, 0.0, 1.0)));
    s.set_alpha(0.25);
    match s {
        Shape::Polygon(p) => assert!((p.color.a - 0.25).abs() < 1e-6),
        _ => panic!(),
    }
}

#[test]
fn lerp_same_count_polygons_averages_points_and_color() {
    let a = Polygon {
        points: vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)],
        color: Color::rgba(1.0, 0.0, 0.0, 1.0),
    };
    let b = Polygon {
        points: vec![(2.0, 0.0), (3.0, 0.0), (2.0, 1.0)],
        color: Color::rgba(0.0, 1.0, 0.0, 1.0),
    };
    let mid = lerp_shape(&Shape::Polygon(a), &Shape::Polygon(b), 0.5);
    match mid {
        Shape::Polygon(p) => {
            assert_eq!(p.points.len(), 3);
            assert!((p.points[0].0 - 1.0).abs() < 1e-6);
            assert!((p.points[1].0 - 2.0).abs() < 1e-6);
            assert!((p.color.r - 0.5).abs() < 1e-6);
            assert!((p.color.g - 0.5).abs() < 1e-6);
        }
        _ => panic!(),
    }
}

#[test]
fn lerp_mismatched_polygons_duplicate_vertices_instead_of_snapping() {
    let a = Shape::Polygon(Polygon {
        points: vec![(-0.3, -0.2), (0.3, -0.2), (0.0, 0.3)],
        color: Color::WHITE,
    });
    let b = Shape::Polygon(Polygon {
        points: vec![(-0.3, -0.2), (0.3, -0.2), (0.3, 0.3), (-0.3, 0.3)],
        color: Color::WHITE,
    });
    let start = lerp_shape(&a, &b, 0.0);
    let middle = lerp_shape(&a, &b, 0.50);
    let late = lerp_shape(&a, &b, 0.51);
    match (&start, &middle, &late) {
        (Shape::Polygon(start), Shape::Polygon(middle), Shape::Polygon(late)) => {
            assert_eq!(
                start.points,
                vec![(-0.3, -0.2), (0.3, -0.2), (0.0, 0.3), (0.0, 0.3)]
            );
            assert_eq!(middle.points.len(), 4);
            assert_eq!(middle.points[0], (-0.3, -0.2));
            assert_eq!(middle.points[1], (0.3, -0.2));
            assert!((middle.points[2].0 - 0.15).abs() < 1e-6);
            assert!((middle.points[2].1 - 0.3).abs() < 1e-6);
            assert!((middle.points[3].0 + 0.15).abs() < 1e-6);
            assert!((middle.points[3].1 - 0.3).abs() < 1e-6);
            assert_eq!(late.points.len(), 4);
            assert_ne!(start.points, middle.points);
            assert_ne!(middle.points, late.points);
        }
        _ => panic!(),
    }
}

#[test]
fn timeline_with_smooth_rate_applies_easing() {
    let target = scene_shape(1, Shape::Circle(red_circle()));
    let tl = Timeline {
        segments: vec![Segment {
            start_frame: 0,
            end_frame: 100,
            base: vec![],
            animation: Animation::FadeIn { target },
            rate: RateFunc::Smooth,
        }],
        fps: 30,
        background: Color::BLACK,
        width: 64,
        height: 64,
    };
    let quarter = tl.shapes_at(25);
    match &quarter[0].shape {
        Shape::Circle(c) => {
            assert!(
                (c.color.a - 0.15625).abs() < 1e-5,
                "smooth at t=0.25 expected 0.15625, got {}",
                c.color.a
            );
        }
        _ => panic!(),
    }
}

#[test]
fn transform_preserves_untargeted_siblings() {
    let from = scene_shape(1, Shape::Circle(red_circle()));
    let sibling = scene_shape(
        2,
        Shape::Rect(Rectangle {
            cx: -0.5,
            cy: 0.0,
            width: 0.2,
            height: 0.2,
            color: Color::WHITE,
        }),
    );
    let base = vec![from.clone(), sibling.clone()];
    let anim = Animation::Transform {
        from: from.clone(),
        to: scene_shape(99, Shape::Circle(white_circle())),
    };

    let final_state = anim.final_state(&base, RateFunc::Linear);
    assert_eq!(final_state.len(), 2);
    assert_eq!(
        final_state[0],
        scene_shape(1, Shape::Circle(white_circle()))
    );
    assert_eq!(final_state[1], sibling);
}

#[test]
fn fadeout_final_state_removes_target_and_preserves_siblings() {
    let target = scene_shape(1, Shape::Circle(red_circle()));
    let sibling = scene_shape(
        2,
        Shape::Text(Text {
            cx: 0.0,
            cy: 0.0,
            size: 0.1,
            color: Color::WHITE,
            content: "still here".to_string(),
            origin: Anchor::Center,
        }),
    );
    let base = vec![target.clone(), sibling.clone()];
    let anim = Animation::FadeOut {
        target: scene_shape(1, Shape::Circle(white_circle())),
    };

    let mid = anim.apply(&base, 0.5);
    assert_eq!(mid.len(), 2);
    assert_eq!(mid[1], sibling);
    match &mid[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 0.5).abs() < 1e-6),
        _ => panic!("expected circle"),
    }

    let final_state = anim.final_state(&base, RateFunc::Linear);
    assert_eq!(final_state, vec![sibling]);
}

#[test]
fn transform_final_state_respects_there_and_back_rate() {
    let from = scene_shape(1, Shape::Circle(red_circle()));
    let to = scene_shape(2, Shape::Circle(white_circle()));
    let base = vec![from.clone()];
    let anim = Animation::Transform { from, to };

    assert_eq!(anim.final_state(&base, RateFunc::ThereAndBack), base);
}

#[test]
fn transform_preserves_existing_layer() {
    let from = SceneShape::new_with_layer(1, 7, Shape::Circle(red_circle()));
    let to = SceneShape::new_with_layer(2, 2, Shape::Circle(white_circle()));
    let base = vec![from.clone()];
    let anim = Animation::Transform { from, to };

    let mid = anim.apply(&base, 0.5);
    assert_eq!(mid[0].layer, 7);
    let final_state = anim.final_state(&base, RateFunc::Linear);
    assert_eq!(final_state[0].layer, 7);
}

fn sample_path(color: Color) -> VectorPath {
    VectorPath {
        commands: vec![
            PathCommand::MoveTo([-0.5, -0.5]),
            PathCommand::LineTo([0.5, -0.5]),
            PathCommand::LineTo([0.0, 0.5]),
            PathCommand::Close,
        ],
        color,
    }
}

#[test]
fn path_shape_set_alpha_scales_color() {
    let mut shape = Shape::Path(sample_path(Color::WHITE));
    shape.set_alpha(0.25);
    match shape {
        Shape::Path(path) => assert!((path.color.a - 0.25).abs() < 1e-6),
        _ => panic!("expected path"),
    }
}

#[test]
fn lerp_path_same_shape_interpolates_and_preserves_commands() {
    let a = sample_path(Color::rgba(1.0, 0.0, 0.0, 1.0));
    let mut b = sample_path(Color::rgba(0.0, 1.0, 0.0, 1.0));
    b.commands[1] = PathCommand::LineTo([1.5, -0.5]);

    let mid = lerp_path(&a, &b, 0.5);
    match mid.commands[1] {
        PathCommand::LineTo([x, y]) => {
            assert!((x - 1.0).abs() < 1e-6);
            assert!((y + 0.5).abs() < 1e-6);
        }
        _ => panic!("expected line command"),
    }
    assert!((mid.color.r - 0.5).abs() < 1e-6);
    assert!((mid.color.g - 0.5).abs() < 1e-6);
}
