//! Motion paths and tween-driven path animation.

use crate::bezier::{CubicBezierCurve, PathEvaluate, QuadBezier};
use crate::math;
use crate::poly::{CompoundPath, EllipticalArc, LineSegment, PathCommand, PathSegment};
use animato_core::{Easing, Playable, Update};
use animato_tween::{Loop, Tween};

/// A unified motion path built from one or more drawable path segments.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MotionPath {
    inner: CompoundPath,
}

impl MotionPath {
    /// Create an empty motion path.
    pub fn new() -> Self {
        Self::default()
    }

    /// Build a motion path from canonical path commands.
    pub fn from_commands(commands: &[PathCommand]) -> Self {
        Self {
            inner: CompoundPath::from_commands(commands),
        }
    }

    /// Parse an SVG `d` attribute into a motion path.
    pub fn from_svg(d: &str) -> Self {
        Self {
            inner: CompoundPath::from_svg(d),
        }
    }

    /// Parse an SVG `d` attribute into a motion path with error reporting.
    pub fn try_from_svg(d: &str) -> Result<Self, crate::svg::SvgPathError> {
        Ok(Self {
            inner: CompoundPath::try_from_svg(d)?,
        })
    }

    /// Append a path segment and return the path.
    pub fn push_segment(mut self, segment: PathSegment) -> Self {
        self.inner = self.inner.push_segment(segment);
        self
    }

    /// Segments in drawing order.
    pub fn segments(&self) -> &[PathSegment] {
        self.inner.segments()
    }

    /// Number of drawable segments.
    pub fn len(&self) -> usize {
        self.inner.len()
    }

    /// `true` when the path has no drawable segments.
    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}

impl From<CompoundPath> for MotionPath {
    fn from(inner: CompoundPath) -> Self {
        Self { inner }
    }
}

impl From<LineSegment> for MotionPath {
    fn from(segment: LineSegment) -> Self {
        Self {
            inner: CompoundPath::new().push_segment(PathSegment::Line(segment)),
        }
    }
}

impl From<QuadBezier> for MotionPath {
    fn from(segment: QuadBezier) -> Self {
        Self {
            inner: CompoundPath::new().push_segment(PathSegment::Quad(segment)),
        }
    }
}

impl From<CubicBezierCurve> for MotionPath {
    fn from(segment: CubicBezierCurve) -> Self {
        Self {
            inner: CompoundPath::new().push_segment(PathSegment::Cubic(segment)),
        }
    }
}

impl From<EllipticalArc> for MotionPath {
    fn from(segment: EllipticalArc) -> Self {
        Self {
            inner: CompoundPath::new().push_segment(PathSegment::Arc(segment)),
        }
    }
}

impl From<PathSegment> for MotionPath {
    fn from(segment: PathSegment) -> Self {
        Self {
            inner: CompoundPath::new().push_segment(segment),
        }
    }
}

impl PathEvaluate for MotionPath {
    fn position(&self, t: f32) -> [f32; 2] {
        self.inner.position(t)
    }

    fn tangent(&self, t: f32) -> [f32; 2] {
        self.inner.tangent(t)
    }

    fn arc_length(&self) -> f32 {
        self.inner.arc_length()
    }
}

/// Builder for [`MotionPathTween`].
#[derive(Clone, Debug)]
pub struct MotionPathTweenBuilder {
    path: MotionPath,
    duration: f32,
    easing: Easing,
    delay: f32,
    time_scale: f32,
    looping: Loop,
    auto_rotate: bool,
    start_offset: f32,
    end_offset: f32,
}

impl MotionPathTweenBuilder {
    /// Create a builder from a motion path.
    pub fn new(path: impl Into<MotionPath>) -> Self {
        Self {
            path: path.into(),
            duration: 1.0,
            easing: Easing::Linear,
            delay: 0.0,
            time_scale: 1.0,
            looping: Loop::Once,
            auto_rotate: false,
            start_offset: 0.0,
            end_offset: 1.0,
        }
    }

    /// Set the animation duration in seconds.
    pub fn duration(mut self, secs: f32) -> Self {
        self.duration = secs.max(0.0);
        self
    }

    /// Set the easing curve for progress along the path.
    pub fn easing(mut self, easing: Easing) -> Self {
        self.easing = easing;
        self
    }

    /// Set delay before motion begins.
    pub fn delay(mut self, secs: f32) -> Self {
        self.delay = secs.max(0.0);
        self
    }

    /// Set the time-scale multiplier.
    pub fn time_scale(mut self, scale: f32) -> Self {
        self.time_scale = scale.max(0.0);
        self
    }

    /// Set looping behavior.
    pub fn looping(mut self, mode: Loop) -> Self {
        self.looping = mode;
        self
    }

    /// Enable or disable auto-rotation.
    pub fn auto_rotate(mut self, yes: bool) -> Self {
        self.auto_rotate = yes;
        self
    }

    /// Set the normalized start offset along the path.
    pub fn start_offset(mut self, offset: f32) -> Self {
        self.start_offset = math::clamp01(offset);
        self
    }

    /// Set the normalized end offset along the path.
    pub fn end_offset(mut self, offset: f32) -> Self {
        self.end_offset = math::clamp01(offset);
        self
    }

    /// Build the configured motion path tween.
    pub fn build(self) -> MotionPathTween {
        let tween = Tween::new(0.0_f32, 1.0)
            .duration(self.duration)
            .easing(self.easing)
            .delay(self.delay)
            .time_scale(self.time_scale)
            .looping(self.looping)
            .build();
        MotionPathTween {
            path: self.path,
            tween,
            auto_rotate: self.auto_rotate,
            start_offset: self.start_offset,
            end_offset: self.end_offset,
        }
    }
}

/// Tween-driven animation along a [`MotionPath`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MotionPathTween {
    path: MotionPath,
    tween: Tween<f32>,
    auto_rotate: bool,
    start_offset: f32,
    end_offset: f32,
}

impl MotionPathTween {
    /// Start building a motion tween for `path`.
    #[allow(clippy::new_ret_no_self)]
    pub fn new(path: impl Into<MotionPath>) -> MotionPathTweenBuilder {
        MotionPathTweenBuilder::new(path)
    }

    /// Create a motion tween from an existing progress tween.
    pub fn from_tween(path: impl Into<MotionPath>, tween: Tween<f32>) -> Self {
        Self {
            path: path.into(),
            tween,
            auto_rotate: false,
            start_offset: 0.0,
            end_offset: 1.0,
        }
    }

    /// The underlying motion path.
    pub fn path(&self) -> &MotionPath {
        &self.path
    }

    /// The internal progress tween.
    pub fn tween(&self) -> &Tween<f32> {
        &self.tween
    }

    /// Mutable access to the internal progress tween.
    pub fn tween_mut(&mut self) -> &mut Tween<f32> {
        &mut self.tween
    }

    /// Current position on the path.
    pub fn value(&self) -> [f32; 2] {
        self.path.position(self.path_t())
    }

    /// Current heading in degrees when auto-rotation is enabled.
    ///
    /// Returns `0.0` when auto-rotation is disabled.
    pub fn rotation_deg(&self) -> f32 {
        if self.auto_rotate {
            self.path.rotation_deg(self.path_t())
        } else {
            0.0
        }
    }

    /// Current normalized path progress after offsets are applied.
    pub fn path_progress(&self) -> f32 {
        self.path_t()
    }

    /// `true` when auto-rotation is enabled.
    pub fn is_auto_rotate(&self) -> bool {
        self.auto_rotate
    }

    /// Enable or disable auto-rotation.
    pub fn set_auto_rotate(&mut self, yes: bool) {
        self.auto_rotate = yes;
    }

    /// Set normalized start and end offsets along the path.
    pub fn set_offsets(&mut self, start: f32, end: f32) {
        self.start_offset = math::clamp01(start);
        self.end_offset = math::clamp01(end);
    }

    /// `true` when the internal tween is complete.
    pub fn is_complete(&self) -> bool {
        self.tween.is_complete()
    }

    /// Reset the internal tween to the beginning.
    pub fn reset(&mut self) {
        self.tween.reset();
    }

    /// Seek the internal tween to normalized progress.
    pub fn seek(&mut self, t: f32) {
        self.tween.seek(t);
    }

    fn path_t(&self) -> f32 {
        let progress = math::clamp01(self.tween.value());
        math::clamp01(self.start_offset + (self.end_offset - self.start_offset) * progress)
    }
}

impl Update for MotionPathTween {
    fn update(&mut self, dt: f32) -> bool {
        self.tween.update(dt)
    }
}

impl Playable for MotionPathTween {
    fn duration(&self) -> f32 {
        Playable::duration(&self.tween)
    }

    fn reset(&mut self) {
        MotionPathTween::reset(self);
    }

    fn seek_to(&mut self, progress: f32) {
        Playable::seek_to(&mut self.tween, progress);
    }

    fn is_complete(&self) -> bool {
        MotionPathTween::is_complete(self)
    }

    fn as_any(&self) -> &dyn core::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn motion_path_from_cubic_evaluates() {
        let curve = CubicBezierCurve::new([0.0, 0.0], [25.0, 50.0], [75.0, -50.0], [100.0, 0.0]);
        let path = MotionPath::from(curve);
        assert_eq!(path.position(0.0), [0.0, 0.0]);
        assert_eq!(path.position(1.0), [100.0, 0.0]);
    }

    #[test]
    fn motion_tween_updates_position() {
        let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
        let mut tween = MotionPathTween::new(line).duration(1.0).build();
        tween.update(0.5);
        assert_eq!(tween.value(), [50.0, 0.0]);
    }

    #[test]
    fn offsets_trim_path() {
        let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
        let mut tween = MotionPathTween::new(line)
            .duration(1.0)
            .start_offset(0.25)
            .end_offset(0.75)
            .build();
        assert_eq!(tween.value(), [25.0, 0.0]);
        tween.update(1.0);
        assert_eq!(tween.value(), [75.0, 0.0]);
    }

    #[test]
    fn auto_rotate_uses_path_heading() {
        let line = LineSegment::new([0.0, 0.0], [0.0, 100.0]);
        let tween = MotionPathTween::new(line).auto_rotate(true).build();
        assert!((tween.rotation_deg() - 90.0).abs() < 0.001);
    }

    #[test]
    fn motion_path_constructors_and_accessors_work() {
        let mut path = MotionPath::new();
        assert!(path.is_empty());
        assert_eq!(path.len(), 0);
        assert_eq!(path.position(0.5), [0.0, 0.0]);

        path = path.push_segment(PathSegment::Line(LineSegment::new([0.0, 0.0], [10.0, 0.0])));
        assert_eq!(path.len(), 1);
        assert_eq!(path.segments().len(), 1);
        assert_eq!(path.position(0.5), [5.0, 0.0]);

        let from_commands = MotionPath::from_commands(&[
            PathCommand::MoveTo([0.0, 0.0]),
            PathCommand::LineTo([10.0, 0.0]),
        ]);
        assert_eq!(from_commands.len(), 1);

        let from_svg = MotionPath::from_svg("M0 0 L10 0");
        assert_eq!(from_svg.len(), 1);
        assert!(MotionPath::try_from_svg("M0 0 L10 0").is_ok());
        assert!(MotionPath::try_from_svg("M0 0 C").is_err());
    }

    #[test]
    fn motion_path_from_each_segment_type() {
        let paths = [
            MotionPath::from(LineSegment::new([0.0, 0.0], [10.0, 0.0])),
            MotionPath::from(QuadBezier::new([0.0, 0.0], [5.0, 5.0], [10.0, 0.0])),
            MotionPath::from(CubicBezierCurve::new(
                [0.0, 0.0],
                [3.0, 5.0],
                [7.0, -5.0],
                [10.0, 0.0],
            )),
            MotionPath::from(EllipticalArc::from_svg(
                [0.0, 0.0],
                [10.0, 10.0],
                0.0,
                false,
                true,
                [10.0, 0.0],
            )),
            MotionPath::from(PathSegment::Line(LineSegment::new([0.0, 0.0], [10.0, 0.0]))),
        ];

        for path in paths {
            assert_eq!(path.len(), 1);
            assert!(path.position(0.5)[0].is_finite());
            assert!(path.tangent(0.5)[0].is_finite());
            assert!(path.arc_length() >= 0.0);
        }
    }

    #[test]
    fn builder_clamps_values_and_exposes_state() {
        let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
        let mut tween = MotionPathTween::new(line)
            .duration(-1.0)
            .delay(-1.0)
            .time_scale(-1.0)
            .looping(Loop::Forever)
            .easing(Easing::EaseInQuad)
            .start_offset(-1.0)
            .end_offset(2.0)
            .build();

        assert!(!tween.is_auto_rotate());
        assert_eq!(tween.rotation_deg(), 0.0);
        assert_eq!(tween.path_progress(), 1.0);
        assert_eq!(tween.value(), [100.0, 0.0]);
        assert_eq!(Playable::duration(&tween), f32::INFINITY);
        assert_eq!(tween.path().len(), 1);
        assert!(!tween.tween().is_complete());
        assert!(!tween.update(0.0));
        assert!(tween.is_complete());

        tween.tween_mut().reset();
        tween.seek(0.25);
        assert_eq!(tween.path_progress(), 1.0);
        tween.reset();
        assert!(!tween.is_complete());
        assert!(!tween.update(0.0));
        assert!(tween.is_complete());
    }

    #[test]
    fn from_tween_and_playable_methods_work() {
        let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
        let base = Tween::new(0.0_f32, 1.0).duration(1.0).build();
        let mut tween = MotionPathTween::from_tween(line, base);

        assert_eq!(Playable::duration(&tween), 1.0);
        Playable::seek_to(&mut tween, 0.5);
        assert_eq!(tween.value(), [50.0, 0.0]);
        assert!(!Playable::is_complete(&tween));
        assert!(Playable::as_any(&tween).is::<MotionPathTween>());
        assert!(Playable::as_any_mut(&mut tween).is::<MotionPathTween>());
        Playable::reset(&mut tween);
        assert_eq!(tween.value(), [0.0, 0.0]);
    }
}
