use super::*;

#[test]
fn value_timeline_linear_segment_interpolates() {
    let timeline = ValueTimeline {
        initial: -1.0,
        segments: vec![ValueSegment {
            start_frame: 0,
            end_frame: 10,
            from: -1.0,
            to: 1.0,
            rate: RateFunc::Linear,
        }],
    };

    assert!((timeline.value_at(0) + 1.0).abs() < 1e-6);
    assert!(timeline.value_at(5).abs() < 1e-6);
    assert!((timeline.value_at(10) - 1.0).abs() < 1e-6);
}

#[test]
fn value_timeline_last_in_range_frame_reaches_terminal_value() {
    let timeline = ValueTimeline {
        initial: -1.0,
        segments: vec![ValueSegment {
            start_frame: 0,
            end_frame: 10,
            from: -1.0,
            to: 1.0,
            rate: RateFunc::Linear,
        }],
    };

    assert!((timeline.value_at(9) - 1.0).abs() < 1e-6);
}

#[test]
fn value_timeline_there_and_back_reverts_to_initial_final_value() {
    let timeline = ValueTimeline {
        initial: 0.0,
        segments: vec![ValueSegment {
            start_frame: 0,
            end_frame: 10,
            from: 0.0,
            to: 2.0,
            rate: RateFunc::ThereAndBack,
        }],
    };

    assert!((timeline.value_at(5) - 2.0).abs() < 1e-6);
    assert!(timeline.final_value().abs() < 1e-6);
    assert!(timeline.value_at(10).abs() < 1e-6);
}

#[test]
fn camera_timeline_linear_segment_interpolates() {
    let initial = ThreeDCamera::default();
    let timeline = CameraTimeline {
        initial,
        segments: vec![CameraSegment {
            start_frame: 0,
            end_frame: 10,
            from: initial,
            to: ThreeDCamera {
                yaw: 1.1,
                pitch: 0.2,
                distance: 4.0,
                focal_length: 2.0,
                scale: 1.5,
            },
            rate: RateFunc::Linear,
        }],
    };

    let mid = timeline.camera_at(5);
    assert!((mid.yaw - 0.9).abs() < 1e-6);
    assert!((mid.distance - 3.5).abs() < 1e-6);
    let final_camera = timeline.final_camera();
    assert!((final_camera.scale - 1.5).abs() < 1e-6);
}

#[test]
fn camera_timeline_last_in_range_frame_reaches_terminal_camera() {
    let initial = ThreeDCamera::default();
    let target = ThreeDCamera {
        yaw: 1.1,
        pitch: 0.2,
        distance: 4.0,
        focal_length: 2.0,
        scale: 1.5,
    };
    let timeline = CameraTimeline {
        initial,
        segments: vec![CameraSegment {
            start_frame: 0,
            end_frame: 10,
            from: initial,
            to: target,
            rate: RateFunc::Linear,
        }],
    };

    let last = timeline.camera_at(9);
    assert!((last.yaw - target.yaw).abs() < 1e-6);
    assert!((last.distance - target.distance).abs() < 1e-6);
    assert!((last.scale - target.scale).abs() < 1e-6);
}

#[test]
fn updater_channel_runs_in_registration_order_and_concats() {
    let mut ch = UpdaterChannel::new();
    assert!(ch.is_empty());

    ch.push(Box::new(|t: f32, _dt: f32| {
        let cx = (t * 2.0).clamp(-1.0, 1.0);
        vec![SceneShape::new(
            100,
            Shape::Circle(Circle {
                x: cx,
                y: 0.0,
                radius: 0.05,
                color: Color::WHITE,
            }),
        )]
    }));
    ch.push(Box::new(|_t: f32, _dt: f32| Vec::new()));
    ch.push(Box::new(|_t: f32, dt: f32| {
        vec![SceneShape::new(
            200,
            Shape::Circle(Circle {
                x: 0.0,
                y: dt,
                radius: 0.05,
                color: Color::RED,
            }),
        )]
    }));

    assert_eq!(ch.len(), 3);
    let shapes = ch.shapes_at(0.25, 1.0 / 30.0);
    assert_eq!(shapes.len(), 2);
    assert_eq!(shapes[0].id, 100);
    match shapes[0].shape {
        Shape::Circle(c) => assert!((c.x - 0.5).abs() < 1e-6),
        _ => panic!("expected circle from first updater"),
    }
    assert_eq!(shapes[1].id, 200);
    match shapes[1].shape {
        Shape::Circle(c) => {
            assert!((c.y - 1.0 / 30.0).abs() < 1e-6);
            assert_eq!(c.color, Color::RED);
        }
        _ => panic!("expected red circle from third updater"),
    }
}

#[test]
fn updater_trait_is_implemented_for_closures_and_drives_state() {
    use std::cell::Cell;
    use std::rc::Rc;
    let counter = Rc::new(Cell::new(0u32));
    let counter_inner = Rc::clone(&counter);
    let updater = move |_t: f32, _dt: f32| {
        counter_inner.set(counter_inner.get() + 1);
        Vec::<SceneShape>::new()
    };
    // Drive trait method directly.
    assert!(updater.update(0.0, 1.0 / 60.0).is_empty());
    assert!(updater.update(1.0 / 60.0, 1.0 / 60.0).is_empty());
    assert_eq!(counter.get(), 2);
}

#[test]
fn frame_clock_matches_fps_cadence() {
    let (t0, dt0) = frame_clock(0, 30);
    assert_eq!(t0, 0.0);
    assert!((dt0 - 1.0 / 30.0).abs() < 1e-6);
    let (t30, dt30) = frame_clock(30, 30);
    assert!((t30 - 1.0).abs() < 1e-6);
    assert!((dt30 - 1.0 / 30.0).abs() < 1e-6);
    // fps=0 must clamp to 1 to avoid div-by-zero.
    let (t_zero, dt_zero) = frame_clock(0, 0);
    assert_eq!(t_zero, 0.0);
    assert_eq!(dt_zero, 1.0);
}

#[test]
fn camera_timeline_there_and_back_returns_to_initial_final_camera() {
    let initial = ThreeDCamera::default();
    let timeline = CameraTimeline {
        initial,
        segments: vec![CameraSegment {
            start_frame: 0,
            end_frame: 10,
            from: initial,
            to: ThreeDCamera {
                yaw: 1.4,
                pitch: 0.3,
                distance: 3.8,
                focal_length: 1.9,
                scale: 1.2,
            },
            rate: RateFunc::ThereAndBack,
        }],
    };

    let final_camera = timeline.final_camera();
    assert!((final_camera.yaw - initial.yaw).abs() < 1e-6);
    assert!((timeline.camera_at(10).distance - initial.distance).abs() < 1e-6);
}
