//! Affine [`Transform`]: translation, rotation, and (non-uniform) scale.
//!
//! A `Transform` is the canonical way to place an object in space. It composes
//! as `parent * child`, matching the convention used by the scene graph in
//! later stages. Internally it is stored in decomposed (TRS) form so that
//! individual components stay editable without matrix round-trips.

use glam::{Affine3A, Mat4, Quat, Vec3};
use serde::{Deserialize, Serialize};

/// A 3D affine transform stored as translation, rotation, and scale.
///
/// The effective matrix is `T * R * S` (scale applied first, then rotation,
/// then translation), which is the standard convention for scene hierarchies.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, crate::reflect::Reflect)]
pub struct Transform {
    /// World/local-space position.
    pub translation: Vec3,
    /// Orientation as a unit quaternion.
    pub rotation: Quat,
    /// Per-axis scale. May be non-uniform; zero or negative components are
    /// permitted but make the transform non-invertible / mirror-inducing.
    pub scale: Vec3,
}

impl Default for Transform {
    /// The identity transform: no translation, no rotation, unit scale.
    fn default() -> Self {
        Self::IDENTITY
    }
}

impl Transform {
    /// The identity transform.
    pub const IDENTITY: Self = Self {
        translation: Vec3::ZERO,
        rotation: Quat::IDENTITY,
        scale: Vec3::ONE,
    };

    /// Creates a transform from a translation only (identity rotation, unit scale).
    #[inline]
    pub const fn from_translation(translation: Vec3) -> Self {
        Self {
            translation,
            rotation: Quat::IDENTITY,
            scale: Vec3::ONE,
        }
    }

    /// Creates a transform from a rotation only.
    #[inline]
    pub const fn from_rotation(rotation: Quat) -> Self {
        Self {
            translation: Vec3::ZERO,
            rotation,
            scale: Vec3::ONE,
        }
    }

    /// Creates a transform from a uniform scale.
    #[inline]
    pub const fn from_scale(scale: Vec3) -> Self {
        Self {
            translation: Vec3::ZERO,
            rotation: Quat::IDENTITY,
            scale,
        }
    }

    /// Creates a transform from all three components.
    #[inline]
    pub const fn from_trs(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
        Self {
            translation,
            rotation,
            scale,
        }
    }

    /// Decomposes a 4x4 matrix back into a TRS transform.
    ///
    /// Negative determinants (mirrored matrices) are handled by `glam`'s
    /// decomposition, which folds the sign into the scale.
    #[inline]
    pub fn from_matrix(matrix: Mat4) -> Self {
        let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
        Self {
            translation,
            rotation,
            scale,
        }
    }

    /// Returns the equivalent 4x4 homogeneous matrix.
    #[inline]
    pub fn to_matrix(&self) -> Mat4 {
        Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
    }

    /// Returns the equivalent [`Affine3A`], which is cheaper to compose than a
    /// full `Mat4` and is what the renderer/scene graph use internally.
    #[inline]
    pub fn to_affine(&self) -> Affine3A {
        Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
    }

    /// Composes two transforms: `self * rhs` applies `rhs` first, then `self`.
    ///
    /// This is exact for the translation and rotation channels. When either
    /// operand carries non-uniform scale combined with rotation, the true
    /// product is no longer a pure TRS transform; in that case the result is
    /// re-decomposed from the composed matrix so the returned `Transform`
    /// remains the closest TRS approximation. For uniform scale (the common
    /// scene-graph case) the composition is exact.
    #[inline]
    pub fn mul_transform(&self, rhs: &Transform) -> Transform {
        // Fast path: uniform scale composes exactly in TRS form.
        if is_uniform(self.scale) {
            let scale = self.scale * rhs.scale;
            let rotation = self.rotation * rhs.rotation;
            let translation = self.translation + self.rotation * (self.scale * rhs.translation);
            Transform {
                translation,
                rotation,
                scale,
            }
        } else {
            Transform::from_matrix(self.to_matrix() * rhs.to_matrix())
        }
    }

    /// Transforms a point (affected by translation, rotation, and scale).
    #[inline]
    pub fn transform_point(&self, point: Vec3) -> Vec3 {
        self.translation + self.rotation * (self.scale * point)
    }

    /// Transforms a direction vector (rotation and scale only, no translation).
    #[inline]
    pub fn transform_vector(&self, vector: Vec3) -> Vec3 {
        self.rotation * (self.scale * vector)
    }

    /// Returns the inverse transform, such that
    /// `t.mul_transform(&t.inverse())` is approximately the identity.
    ///
    /// # Panics
    /// Does not panic, but if any scale component is zero the inverse scale
    /// will contain infinities — the transform is not invertible in that case.
    #[inline]
    pub fn inverse(&self) -> Transform {
        let inv_scale = Vec3::ONE / self.scale;
        let inv_rotation = self.rotation.inverse();
        let inv_translation = inv_rotation * (inv_scale * -self.translation);
        Transform {
            translation: inv_translation,
            rotation: inv_rotation,
            scale: inv_scale,
        }
    }

    /// The local forward direction (`-Z`) rotated into this transform's space.
    #[inline]
    pub fn forward(&self) -> Vec3 {
        self.rotation * Vec3::NEG_Z
    }

    /// The local up direction (`+Y`) rotated into this transform's space.
    #[inline]
    pub fn up(&self) -> Vec3 {
        self.rotation * Vec3::Y
    }

    /// The local right direction (`+X`) rotated into this transform's space.
    #[inline]
    pub fn right(&self) -> Vec3 {
        self.rotation * Vec3::X
    }

    /// Builds a transform positioned at `eye` looking toward `target`.
    ///
    /// `up` is the reference up vector. Returns the identity rotation if `eye`
    /// and `target` coincide.
    pub fn looking_at(eye: Vec3, target: Vec3, up: Vec3) -> Transform {
        let forward = target - eye;
        let rotation = if forward.length_squared() <= f32::EPSILON {
            Quat::IDENTITY
        } else {
            // glam's look_to is right-handed with -Z forward; invert the view
            // rotation to get an object-space orientation.
            Quat::from_mat4(&Mat4::look_to_rh(eye, forward.normalize(), up)).inverse()
        };
        Transform {
            translation: eye,
            rotation,
            scale: Vec3::ONE,
        }
    }

    /// Returns `true` if every component is finite (no NaN/inf).
    #[inline]
    pub fn is_finite(&self) -> bool {
        self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite()
    }
}

/// Returns `true` if all three components of `scale` are equal.
#[inline]
fn is_uniform(scale: Vec3) -> bool {
    (scale.x - scale.y).abs() <= f32::EPSILON && (scale.y - scale.z).abs() <= f32::EPSILON
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::f32::consts::{FRAC_PI_2, PI};

    const EPS: f32 = 1e-4;

    fn approx_vec(a: Vec3, b: Vec3) -> bool {
        (a - b).length() <= EPS
    }

    #[test]
    fn identity_is_default() {
        assert_eq!(Transform::default(), Transform::IDENTITY);
        let p = Vec3::new(1.0, 2.0, 3.0);
        assert_eq!(Transform::IDENTITY.transform_point(p), p);
    }

    #[test]
    fn translation_moves_points() {
        let t = Transform::from_translation(Vec3::new(1.0, 2.0, 3.0));
        assert!(approx_vec(
            t.transform_point(Vec3::ZERO),
            Vec3::new(1.0, 2.0, 3.0)
        ));
        // Vectors ignore translation.
        assert!(approx_vec(t.transform_vector(Vec3::X), Vec3::X));
    }

    #[test]
    fn rotation_rotates_points() {
        let t = Transform::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
        assert!(approx_vec(t.transform_point(Vec3::X), Vec3::Y));
    }

    #[test]
    fn scale_scales_points() {
        let t = Transform::from_scale(Vec3::new(2.0, 3.0, 4.0));
        assert!(approx_vec(
            t.transform_point(Vec3::ONE),
            Vec3::new(2.0, 3.0, 4.0)
        ));
    }

    #[test]
    fn matrix_round_trip() {
        let t = Transform::from_trs(
            Vec3::new(5.0, -2.0, 1.0),
            Quat::from_euler(glam::EulerRot::XYZ, 0.3, -0.7, 1.1),
            Vec3::new(2.0, 2.0, 2.0),
        );
        let back = Transform::from_matrix(t.to_matrix());
        assert!(approx_vec(t.translation, back.translation));
        assert!(approx_vec(t.scale, back.scale));
        // Quaternions q and -q represent the same rotation.
        let dot = t.rotation.dot(back.rotation).abs();
        assert!((dot - 1.0).abs() <= EPS, "rotation mismatch: dot={dot}");
    }

    #[test]
    fn inverse_cancels() {
        let t = Transform::from_trs(
            Vec3::new(3.0, 4.0, 5.0),
            Quat::from_rotation_y(0.9),
            Vec3::splat(2.0),
        );
        let id = t.mul_transform(&t.inverse());
        assert!(approx_vec(id.translation, Vec3::ZERO));
        assert!(approx_vec(id.scale, Vec3::ONE));
        assert!(approx_vec(
            id.transform_point(Vec3::new(7.0, 8.0, 9.0)),
            Vec3::new(7.0, 8.0, 9.0)
        ));
    }

    #[test]
    fn composition_matches_matrix() {
        let a = Transform::from_trs(
            Vec3::new(1.0, 0.0, -2.0),
            Quat::from_rotation_x(0.4),
            Vec3::splat(1.5),
        );
        let b = Transform::from_trs(
            Vec3::new(-3.0, 2.0, 1.0),
            Quat::from_rotation_z(-0.8),
            Vec3::splat(0.5),
        );
        let composed = a.mul_transform(&b);
        let p = Vec3::new(2.0, -1.0, 3.0);
        let via_transform = composed.transform_point(p);
        let via_matrix = (a.to_matrix() * b.to_matrix()).transform_point3(p);
        assert!(approx_vec(via_transform, via_matrix));
    }

    #[test]
    fn nonuniform_composition_falls_back_to_matrix() {
        let a = Transform::from_trs(
            Vec3::new(0.0, 1.0, 0.0),
            Quat::from_rotation_z(FRAC_PI_2),
            Vec3::new(2.0, 1.0, 1.0),
        );
        let b = Transform::from_trs(
            Vec3::new(1.0, 0.0, 0.0),
            Quat::IDENTITY,
            Vec3::new(1.0, 3.0, 1.0),
        );
        let composed = a.mul_transform(&b);
        let p = Vec3::new(1.0, 2.0, -1.0);
        let via_transform = composed.transform_point(p);
        let via_matrix = (a.to_matrix() * b.to_matrix()).transform_point3(p);
        // Re-decomposition keeps this close even with non-uniform scale.
        assert!(
            approx_vec(via_transform, via_matrix),
            "{via_transform} vs {via_matrix}"
        );
    }

    #[test]
    fn zero_scale_is_non_invertible() {
        let t = Transform::from_scale(Vec3::new(0.0, 1.0, 1.0));
        let inv = t.inverse();
        assert!(!inv.scale.x.is_finite());
        assert!(t.is_finite()); // the forward transform itself is still finite
    }

    #[test]
    fn gimbal_lock_path_stays_stable() {
        // Pitch to +90° (a classic gimbal-lock orientation) and confirm the
        // basis vectors remain orthonormal after round-tripping through a matrix.
        let t =
            Transform::from_rotation(Quat::from_euler(glam::EulerRot::YXZ, 0.0, FRAC_PI_2, 0.0));
        let back = Transform::from_matrix(t.to_matrix());
        assert!(approx_vec(back.forward(), t.forward()));
        assert!(approx_vec(back.up(), t.up()));
        // Orthonormality.
        assert!(t.forward().dot(t.up()).abs() <= EPS);
        assert!(t.right().dot(t.up()).abs() <= EPS);
    }

    #[test]
    fn looking_at_faces_target() {
        let t = Transform::looking_at(Vec3::new(0.0, 0.0, 5.0), Vec3::ZERO, Vec3::Y);
        // Forward should point toward the target (-Z world direction).
        assert!(approx_vec(t.forward(), Vec3::NEG_Z));
    }

    #[test]
    fn looking_at_degenerate_is_identity_rotation() {
        let t = Transform::looking_at(Vec3::ONE, Vec3::ONE, Vec3::Y);
        assert_eq!(t.rotation, Quat::IDENTITY);
    }

    #[test]
    fn basis_vectors_for_half_turn() {
        let t = Transform::from_rotation(Quat::from_rotation_y(PI));
        assert!(approx_vec(t.forward(), Vec3::Z));
        assert!(approx_vec(t.right(), Vec3::NEG_X));
    }
}
