use super::*;

#[test]
fn black_frame_16x16_has_opaque_alpha_and_zero_color() {
    let f = RawFrame::black(16, 16);
    assert_eq!(f.rgba.len(), 1024);
    for (i, b) in f.rgba.iter().enumerate() {
        if i % 4 == 3 {
            assert_eq!(*b, 0xff, "alpha byte at {i} must be 0xff");
        } else {
            assert_eq!(*b, 0x00, "color byte at {i} must be 0");
        }
    }
}

#[test]
fn video_config_frame_byte_len_matches_wh4() {
    let c = VideoConfig {
        width: 1280,
        height: 720,
        fps: 30,
        total_frames: 60,
    };
    assert_eq!(c.frame_byte_len(), 1280 * 720 * 4);
}

#[test]
fn vpoint_is_tightly_packed_24_bytes() {
    assert_eq!(std::mem::size_of::<VPoint>(), 24);
}

#[test]
fn color_is_16_bytes_and_red_round_trips_to_array() {
    // allow: tuple-literal — these are the oracle values the API must serialize to.
    assert_eq!(Color::RED.as_array(), [1.0, 0.0, 0.0, 1.0]);
    assert_eq!(Color::BLACK.as_array(), [0.0, 0.0, 0.0, 1.0]);
}

struct SolidRed;

impl Scene for SolidRed {
    fn config(&self) -> VideoConfig {
        VideoConfig {
            width: 16,
            height: 16,
            fps: 30,
            total_frames: 10,
        }
    }
    fn clear_color(&self, _frame_idx: u32) -> Color {
        Color::RED
    }
}

#[test]
fn scene_trait_reports_config_and_clear_color() {
    let s = SolidRed;
    assert_eq!(s.config().total_frames, 10);
    assert_eq!(s.clear_color(5), Color::RED);
    assert!(s.mobjects(0).is_empty());
}

fn red_circle() -> Circle {
    Circle {
        x: 0.0,
        y: 0.0,
        radius: 0.1,
        color: Color::RED,
    }
}

fn white_circle() -> Circle {
    Circle {
        x: 1.0,
        y: 1.0,
        radius: 0.3,
        color: Color::WHITE,
    }
}

fn scene_shape(id: u64, shape: Shape) -> SceneShape {
    SceneShape::new(id, shape)
}

#[test]
fn lerp_circle_midpoint_yields_expected_values() {
    let m = lerp_circle(red_circle(), white_circle(), 0.5);
    assert_eq!(m.x, 0.5);
    assert_eq!(m.y, 0.5);
    assert!((m.radius - 0.2).abs() < 1e-6);
    assert!((m.color.r - 1.0).abs() < 1e-6);
    assert!((m.color.g - 0.5).abs() < 1e-6);
    assert!((m.color.b - 0.5).abs() < 1e-6);
}

#[test]
fn fadein_applies_alpha_scaling_on_circle_shape() {
    let target = scene_shape(1, Shape::Circle(red_circle()));
    let anim = Animation::FadeIn { target };
    let out = anim.apply(&[], 0.5);
    assert_eq!(out.len(), 1);
    assert_eq!(out[0].id, 1);
    match &out[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 0.5).abs() < 1e-6),
        _ => panic!("expected circle"),
    }
}

#[test]
fn transform_same_variant_lerps() {
    let from = scene_shape(1, Shape::Circle(red_circle()));
    let to = scene_shape(2, Shape::Circle(white_circle()));
    let anim = Animation::Transform {
        from: from.clone(),
        to: to.clone(),
    };
    let at0 = anim.apply(&[], 0.0);
    assert_eq!(at0, vec![from]);
    let at1 = anim.apply(&[], 1.0);
    assert_eq!(at1, vec![scene_shape(1, Shape::Circle(white_circle()))]);
    let final_state = anim.final_state(&[], RateFunc::Linear);
    assert_eq!(
        final_state,
        vec![scene_shape(1, Shape::Circle(white_circle()))]
    );
}

#[test]
fn fadeout_handles_duplicate_shape_ids_consistently() {
    let first = SceneShape::new_with_layer(7, 1, Shape::Circle(red_circle()));
    let second = SceneShape::new_with_layer(7, 2, Shape::Circle(white_circle()));
    let sibling = scene_shape(8, Shape::Circle(white_circle()));
    let base = vec![first.clone(), sibling.clone(), second.clone()];
    let anim = Animation::FadeOut {
        target: first.clone(),
    };

    let mid = anim.apply(&base, 0.5);
    assert_eq!(mid.len(), 3);
    for shape in mid.iter().filter(|shape| shape.id == 7) {
        match &shape.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_collapses_duplicate_shape_ids_to_single_target() {
    let first = SceneShape::new_with_layer(7, 3, Shape::Circle(red_circle()));
    let duplicate = SceneShape::new_with_layer(7, 4, Shape::Circle(red_circle()));
    let sibling = scene_shape(8, Shape::Circle(white_circle()));
    let target = scene_shape(99, Shape::Circle(white_circle()));
    let base = vec![first.clone(), sibling.clone(), duplicate];
    let anim = Animation::Transform {
        from: first,
        to: target,
    };

    let final_state = anim.final_state(&base, RateFunc::Linear);
    assert_eq!(final_state.len(), 2);
    assert_eq!(final_state[0].id, 7);
    assert_eq!(final_state[0].layer, 3);
    assert_eq!(final_state[1], sibling);
    match &final_state[0].shape {
        Shape::Circle(c) => assert_eq!(*c, white_circle()),
        _ => panic!("expected circle"),
    }
}

#[test]
fn lerp_shape_cross_variant_snaps_at_half() {
    let c = Shape::Circle(red_circle());
    let r = Shape::Rect(Rectangle {
        cx: 0.0,
        cy: 0.0,
        width: 0.2,
        height: 0.2,
        color: Color::WHITE,
    });
    assert_eq!(lerp_shape(&c, &r, 0.3), c);
    assert_eq!(lerp_shape(&c, &r, 0.5), r);
    assert_eq!(lerp_shape(&c, &r, 0.7), r);
}

#[test]
fn shape_set_alpha_scales_inner_color() {
    let mut s = Shape::Circle(red_circle());
    s.set_alpha(0.25);
    match s {
        Shape::Circle(c) => assert!((c.color.a - 0.25).abs() < 1e-6),
        _ => panic!("expected circle"),
    }

    let mut sr = Shape::Rect(Rectangle {
        cx: 0.0,
        cy: 0.0,
        width: 0.2,
        height: 0.2,
        color: Color::WHITE,
    });
    sr.set_alpha(0.5);
    match sr {
        Shape::Rect(r) => assert!((r.color.a - 0.5).abs() < 1e-6),
        _ => panic!("expected rect"),
    }
}

#[test]
fn ratefunc_smooth_midpoint_is_half_endpoints_are_0_and_1() {
    assert!((RateFunc::Smooth.apply(0.0) - 0.0).abs() < 1e-6);
    assert!((RateFunc::Smooth.apply(0.5) - 0.5).abs() < 1e-6);
    assert!((RateFunc::Smooth.apply(1.0) - 1.0).abs() < 1e-6);
    let at_q = RateFunc::Smooth.apply(0.25);
    assert!((at_q - 0.15625).abs() < 1e-6, "smooth(0.25) was {at_q}");
}

#[test]
fn extended_ratefuncs_have_expected_endpoints() {
    let terminal_one = [
        RateFunc::RushInto,
        RateFunc::RushFrom,
        RateFunc::SlowInto,
        RateFunc::ExponentialDecay,
        RateFunc::QuadIn,
        RateFunc::QuadOut,
        RateFunc::QuadInOut,
        RateFunc::CubicIn,
        RateFunc::CubicOut,
        RateFunc::CubicInOut,
        RateFunc::QuartIn,
        RateFunc::QuartOut,
        RateFunc::QuartInOut,
        RateFunc::QuintIn,
        RateFunc::QuintOut,
        RateFunc::QuintInOut,
        RateFunc::SineIn,
        RateFunc::SineOut,
        RateFunc::SineInOut,
        RateFunc::ExpoIn,
        RateFunc::ExpoOut,
        RateFunc::ExpoInOut,
        RateFunc::CircIn,
        RateFunc::CircOut,
        RateFunc::CircInOut,
        RateFunc::BackIn,
        RateFunc::BackOut,
        RateFunc::BackInOut,
        RateFunc::BounceIn,
        RateFunc::BounceOut,
        RateFunc::BounceInOut,
        RateFunc::ElasticIn,
        RateFunc::ElasticOut,
        RateFunc::ElasticInOut,
        RateFunc::Lingering,
    ];
    for rate in terminal_one {
        assert!(
            (rate.apply(0.0) - 0.0).abs() < 1e-5,
            "{rate:?} should start at 0"
        );
        assert!(
            (rate.apply(1.0) - 1.0).abs() < 1e-5,
            "{rate:?} should end at 1"
        );
    }
    assert!((RateFunc::Wiggle.apply(0.0) - 0.0).abs() < 1e-6);
    assert!((RateFunc::Wiggle.apply(1.0) - 0.0).abs() < 1e-5);
    assert!((RateFunc::ThereAndBackWithPause.apply(0.5) - 1.0).abs() < 1e-6);
}

#[test]
fn timeline_shapes_at_mid_segment_computes_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::FadeIn { target },
            rate: RateFunc::Linear,
        }],
        fps: 30,
        background: Color::BLACK,
        width: 64,
        height: 64,
    };
    let mid = tl.shapes_at(5);
    assert_eq!(mid.len(), 1);
    assert_eq!(mid[0].id, 1);
    match &mid[0].shape {
        Shape::Circle(c) => assert!((c.color.a - 0.5).abs() < 1e-6),
        _ => panic!("expected circle"),
    }
    assert_eq!(tl.total_frames(), 10);
}

#[test]
fn animation_group_can_run_children_simultaneously() {
    let left = scene_shape(1, Shape::Circle(red_circle()));
    let right = scene_shape(2, Shape::Circle(white_circle()));
    let anim = Animation::Group {
        items: vec![
            GroupAnimation {
                animation: Animation::FadeIn { target: left },
                start: 0.0,
                end: 1.0,
            },
            GroupAnimation {
                animation: Animation::FadeIn { target: right },
                start: 0.0,
                end: 1.0,
            },
        ],
    };
    let mid = anim.apply(&[], 0.5);
    assert_eq!(mid.len(), 2);
    for shape in mid {
        match shape.shape {
            Shape::Circle(c) => assert!((c.color.a - 0.5).abs() < 1e-6),
            _ => panic!("expected circle"),
        }
    }
}

#[test]
fn animation_group_respects_lag_windows() {
    let first = scene_shape(1, Shape::Circle(red_circle()));
    let second = scene_shape(2, Shape::Circle(white_circle()));
    let anim = Animation::Group {
        items: vec![
            GroupAnimation {
                animation: Animation::FadeIn { target: first },
                start: 0.0,
                end: 0.5,
            },
            GroupAnimation {
                animation: Animation::FadeIn { target: second },
                start: 0.5,
                end: 1.0,
            },
        ],
    };
    let mid = anim.apply(&[], 0.5);
    assert_eq!(mid.len(), 1);
    assert_eq!(mid[0].id, 1);
    let final_state = anim.final_state(&[], RateFunc::Linear);
    assert_eq!(final_state.len(), 2);
}
