//! # XYZ: Shared Storage for Cartesian Types
//!
//! This module provides the internal `XYZ<T>` type, a thin `#[repr(transparent)]`
//! wrapper around a `[T; 3]` array.
//!
//! ## Purpose
//!
//! `XYZ<T>` centralises all component-wise mathematical operations in one place:
//! - Addition, subtraction, scaling
//! - Dot product, magnitude, normalization
//!
//! The semantic types (`Position`, `Vector`, `Direction`) are thin wrappers over
//! `XYZ<T>` with `PhantomData` markers for center, frame, and unit information.
//! This eliminates code duplication while maintaining zero-cost abstractions.
//!
//! ## Design Notes
//!
//! - Uses `#[repr(transparent)]` for zero-cost ABI compatibility with `[T; 3]`
//! - All operations are `#[inline]` for optimal performance
//! - Generic over the component type `T` (typically `f64` or `Quantity<U>`)
//! - No external linear-algebra dependencies; all arithmetic is hand-coded

use std::ops::{Add, Mul, Neg, Sub};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Internal shared storage for 3D Cartesian coordinates.
///
/// This is a thin wrapper around `[T; 3]` that provides centralised
/// implementations of component-wise operations.
///
/// # Type Parameter
/// - `T`: The component type (e.g., `f64`, `Quantity<U>`)
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct XYZ<T>(pub(crate) [T; 3]);

// =============================================================================
// Constructors
// =============================================================================

impl<T> XYZ<T> {
    /// Creates a new XYZ from individual components.
    #[inline]
    pub const fn new(x: T, y: T, z: T) -> Self {
        Self([x, y, z])
    }

    /// Creates an XYZ from a `[T; 3]` array.
    #[inline]
    pub const fn from_array(arr: [T; 3]) -> Self {
        Self(arr)
    }

    /// Returns a reference to the underlying `[T; 3]` array.
    #[inline]
    pub const fn as_array(&self) -> &[T; 3] {
        &self.0
    }

    /// Consumes self and returns the underlying `[T; 3]` array.
    #[inline]
    pub fn into_array(self) -> [T; 3] {
        self.0
    }
}

// =============================================================================
// Component Access
// =============================================================================

impl<T: Copy> XYZ<T> {
    /// Returns the x-component.
    #[inline]
    pub fn x(&self) -> T {
        self.0[0]
    }

    /// Returns the y-component.
    #[inline]
    pub fn y(&self) -> T {
        self.0[1]
    }

    /// Returns the z-component.
    #[inline]
    pub fn z(&self) -> T {
        self.0[2]
    }

    /// Returns components as a tuple.
    #[inline]
    pub fn components(&self) -> (T, T, T) {
        (self.x(), self.y(), self.z())
    }
}

// =============================================================================
// Arithmetic Operations for f64 components
// =============================================================================

impl XYZ<f64> {
    /// The zero vector.
    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);

    /// Computes the Euclidean magnitude (length) of the vector.
    #[inline]
    pub fn magnitude(&self) -> f64 {
        let [x, y, z] = self.0;
        (x * x + y * y + z * z).sqrt()
    }

    /// Computes the squared magnitude (avoids sqrt).
    #[inline]
    pub fn magnitude_squared(&self) -> f64 {
        let [x, y, z] = self.0;
        x * x + y * y + z * z
    }

    /// Computes the dot product with another vector.
    #[inline]
    pub fn dot(&self, other: &Self) -> f64 {
        self.0[0] * other.0[0] + self.0[1] * other.0[1] + self.0[2] * other.0[2]
    }

    /// Computes the cross product with another vector.
    #[inline]
    pub fn cross(&self, other: &Self) -> Self {
        let [ax, ay, az] = self.0;
        let [bx, by, bz] = other.0;
        Self([ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx])
    }

    /// Returns a normalized (unit length) version, or None if magnitude is zero.
    #[inline]
    pub fn try_normalize(&self) -> Option<Self> {
        let mag = self.magnitude();
        if mag.abs() < f64::EPSILON {
            None
        } else {
            Some(Self([self.0[0] / mag, self.0[1] / mag, self.0[2] / mag]))
        }
    }

    /// Returns a normalized version, assuming non-zero magnitude.
    ///
    /// # Safety
    /// Caller must ensure the vector is non-zero.
    #[inline]
    pub fn normalize_unchecked(&self) -> Self {
        let mag = self.magnitude();
        Self([self.0[0] / mag, self.0[1] / mag, self.0[2] / mag])
    }

    /// Scales the vector by a scalar factor.
    #[inline]
    pub fn scale(&self, factor: f64) -> Self {
        Self([self.0[0] * factor, self.0[1] * factor, self.0[2] * factor])
    }

    /// Component-wise addition.
    #[inline]
    pub fn add(&self, other: &Self) -> Self {
        Self([
            self.0[0] + other.0[0],
            self.0[1] + other.0[1],
            self.0[2] + other.0[2],
        ])
    }

    /// Component-wise subtraction.
    #[inline]
    pub fn sub(&self, other: &Self) -> Self {
        Self([
            self.0[0] - other.0[0],
            self.0[1] - other.0[1],
            self.0[2] - other.0[2],
        ])
    }

    /// Negates all components.
    #[inline]
    pub fn neg(&self) -> Self {
        Self([-self.0[0], -self.0[1], -self.0[2]])
    }
}

// =============================================================================
// Operator Implementations for XYZ<f64>
// =============================================================================

impl Add for XYZ<f64> {
    type Output = Self;

    #[inline]
    fn add(self, other: Self) -> Self::Output {
        Self([
            self.0[0] + other.0[0],
            self.0[1] + other.0[1],
            self.0[2] + other.0[2],
        ])
    }
}

forward_ref_binop! { impl Add, add for XYZ<f64>, XYZ<f64> }

impl Sub for XYZ<f64> {
    type Output = Self;

    #[inline]
    fn sub(self, other: Self) -> Self::Output {
        Self([
            self.0[0] - other.0[0],
            self.0[1] - other.0[1],
            self.0[2] - other.0[2],
        ])
    }
}

forward_ref_binop! { impl Sub, sub for XYZ<f64>, XYZ<f64> }

impl Neg for XYZ<f64> {
    type Output = Self;

    #[inline]
    fn neg(self) -> Self::Output {
        Self([-self.0[0], -self.0[1], -self.0[2]])
    }
}

forward_ref_unop! { impl Neg, neg for XYZ<f64> }

impl Mul<f64> for XYZ<f64> {
    type Output = Self;

    #[inline]
    fn mul(self, scalar: f64) -> Self::Output {
        Self([self.0[0] * scalar, self.0[1] * scalar, self.0[2] * scalar])
    }
}

forward_ref_binop! { impl Mul, mul for XYZ<f64>, f64 }

impl Mul<XYZ<f64>> for f64 {
    type Output = XYZ<f64>;

    #[inline]
    fn mul(self, xyz: XYZ<f64>) -> Self::Output {
        XYZ([xyz.0[0] * self, xyz.0[1] * self, xyz.0[2] * self])
    }
}

forward_ref_binop! { impl Mul, mul for f64, XYZ<f64> }

// =============================================================================
// Quantity-aware operations
// =============================================================================

use qtty::{Quantity, Unit, UnitMul};

impl<U: Unit> XYZ<Quantity<U>> {
    /// The zero vector for this unit type.
    pub const ZERO: Self = Self::new(
        Quantity::<U>::new(0.0),
        Quantity::<U>::new(0.0),
        Quantity::<U>::new(0.0),
    );

    /// Computes the Euclidean magnitude in the same unit.
    #[inline]
    pub fn magnitude(&self) -> Quantity<U> {
        let x = self.0[0].value();
        let y = self.0[1].value();
        let z = self.0[2].value();
        Quantity::new((x * x + y * y + z * z).sqrt())
    }

    /// Extracts raw f64 values as an XYZ<f64>.
    #[inline]
    pub fn to_raw(&self) -> XYZ<f64> {
        XYZ::new(self.0[0].value(), self.0[1].value(), self.0[2].value())
    }

    /// Creates from raw f64 values.
    #[inline]
    pub fn from_raw(raw: XYZ<f64>) -> Self {
        Self::new(
            Quantity::new(raw.x()),
            Quantity::new(raw.y()),
            Quantity::new(raw.z()),
        )
    }

    /// Component-wise addition.
    #[inline]
    pub fn add(&self, other: &Self) -> Self {
        Self([
            self.0[0] + other.0[0],
            self.0[1] + other.0[1],
            self.0[2] + other.0[2],
        ])
    }

    /// Component-wise subtraction.
    #[inline]
    pub fn sub(&self, other: &Self) -> Self {
        Self([
            self.0[0] - other.0[0],
            self.0[1] - other.0[1],
            self.0[2] - other.0[2],
        ])
    }
}

impl<U: Unit + UnitMul<U>> XYZ<Quantity<U>> {
    /// Computes the squared magnitude as a typed quantity in unit `U·U`.
    ///
    /// This relies on `qtty`'s squared-unit support: for any `U: UnitMul<U>`
    /// the output unit is `<U as UnitMul<U>>::Output` (e.g. `Prod<Meter, Meter>`
    /// for length). If you only need a raw `f64` for ordering or comparison,
    /// use the deprecated [`magnitude_squared_raw`](Self::magnitude_squared_raw).
    #[inline]
    pub fn magnitude_squared(&self) -> Quantity<<U as UnitMul<U>>::Output> {
        let x = self.0[0].value();
        let y = self.0[1].value();
        let z = self.0[2].value();
        Quantity::new(x * x + y * y + z * z)
    }
}

impl<U: Unit> Add for XYZ<Quantity<U>> {
    type Output = Self;

    #[inline]
    fn add(self, other: Self) -> Self::Output {
        Self([
            self.0[0] + other.0[0],
            self.0[1] + other.0[1],
            self.0[2] + other.0[2],
        ])
    }
}

forward_ref_binop! { impl[U: Unit] Add, add for XYZ<Quantity<U>>, XYZ<Quantity<U>> }

impl<U: Unit> Sub for XYZ<Quantity<U>> {
    type Output = Self;

    #[inline]
    fn sub(self, other: Self) -> Self::Output {
        Self([
            self.0[0] - other.0[0],
            self.0[1] - other.0[1],
            self.0[2] - other.0[2],
        ])
    }
}

forward_ref_binop! { impl[U: Unit] Sub, sub for XYZ<Quantity<U>>, XYZ<Quantity<U>> }

impl<U: Unit> Neg for XYZ<Quantity<U>> {
    type Output = Self;

    #[inline]
    fn neg(self) -> Self::Output {
        Self::new(-self.0[0], -self.0[1], -self.0[2])
    }
}

forward_ref_unop! { impl[U: Unit] Neg, neg for XYZ<Quantity<U>> }

// =============================================================================
// Default Implementation
// =============================================================================

impl<T: Default> Default for XYZ<T> {
    fn default() -> Self {
        Self([T::default(), T::default(), T::default()])
    }
}

// =============================================================================
// Display
// =============================================================================

impl_quantity_fmt_triplet! {
    impl[T] for XYZ<T>
    where { T: Copy, },
    fmt_each: { T, },
    |this, f, FmtOne| {
        write!(f, "(")?;
        FmtOne::fmt(&this.x(), f)?;
        write!(f, ", ")?;
        FmtOne::fmt(&this.y(), f)?;
        write!(f, ", ")?;
        FmtOne::fmt(&this.z(), f)?;
        write!(f, ")")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use qtty::units::Meter;
    use qtty::{Quantity, M};

    #[test]
    fn test_xyz_basic_ops() {
        let a = XYZ::new(1.0, 2.0, 3.0);
        let b = XYZ::new(4.0, 5.0, 6.0);

        // Addition
        let sum = a + b;
        assert!((sum.x() - 5.0).abs() < f64::EPSILON);
        assert!((sum.y() - 7.0).abs() < f64::EPSILON);
        assert!((sum.z() - 9.0).abs() < f64::EPSILON);

        // Subtraction
        let diff = b - a;
        assert!((diff.x() - 3.0).abs() < f64::EPSILON);
        assert!((diff.y() - 3.0).abs() < f64::EPSILON);
        assert!((diff.z() - 3.0).abs() < f64::EPSILON);

        // Scaling
        let scaled = a.scale(2.0);
        assert!((scaled.x() - 2.0).abs() < f64::EPSILON);
        assert!((scaled.y() - 4.0).abs() < f64::EPSILON);
        assert!((scaled.z() - 6.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_magnitude_and_normalize() {
        let v = XYZ::new(3.0, 4.0, 0.0);
        assert!((v.magnitude() - 5.0).abs() < f64::EPSILON);

        let n = v.try_normalize().unwrap();
        assert!((n.magnitude() - 1.0).abs() < f64::EPSILON);
        assert!((n.x() - 0.6).abs() < f64::EPSILON);
        assert!((n.y() - 0.8).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_dot_and_cross() {
        let a = XYZ::new(1.0, 0.0, 0.0);
        let b = XYZ::new(0.0, 1.0, 0.0);

        // Dot product of perpendicular vectors
        assert!(a.dot(&b).abs() < f64::EPSILON);

        // Cross product
        let c = a.cross(&b);
        assert!((c.x()).abs() < f64::EPSILON);
        assert!((c.y()).abs() < f64::EPSILON);
        assert!((c.z() - 1.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_zero_normalize() {
        let zero = XYZ::<f64>::ZERO;
        assert!(zero.try_normalize().is_none());
    }

    #[test]
    fn test_xyz_quantity_ops() {
        let a = XYZ::new(3.0 * M, 4.0 * M, 0.0 * M);
        let b = XYZ::new(1.0 * M, 2.0 * M, 3.0 * M);

        let sum = a + b;
        assert!((sum.x().value() - 4.0).abs() < f64::EPSILON);
        assert!((sum.y().value() - 6.0).abs() < f64::EPSILON);

        let diff = a - b;
        assert!((diff.x().value() - 2.0).abs() < f64::EPSILON);
        assert!((diff.y().value() - 2.0).abs() < f64::EPSILON);

        let mag = a.magnitude();
        assert!((mag.value() - 5.0).abs() < f64::EPSILON);
        let mag_sq = a.magnitude_squared();
        assert!((mag_sq.value() - 25.0).abs() < f64::EPSILON);

        // Typed magnitude_squared yields Quantity<Prod<Meter, Meter>>
        let mag_sq_typed: qtty::Quantity<qtty::Prod<Meter, Meter>> = a.magnitude_squared();
        assert!((mag_sq_typed.value() - 25.0).abs() < f64::EPSILON);

        let raw = a.to_raw();
        assert!((raw.x() - 3.0).abs() < f64::EPSILON);
        let back = XYZ::<Quantity<Meter>>::from_raw(raw);
        assert!((back.y().value() - 4.0).abs() < f64::EPSILON);

        let neg = -b;
        assert!((neg.z().value() + 3.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_array_roundtrip() {
        let arr: [f64; 3] = [1.0, 2.0, 3.0];
        let xyz: XYZ<f64> = XYZ::from_array(arr);
        assert!((xyz.x() - 1.0).abs() < f64::EPSILON);

        let as_arr = xyz.as_array();
        assert!((as_arr[2] - 3.0).abs() < f64::EPSILON);

        let into_arr = xyz.into_array();
        assert!((into_arr[1] - 2.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_f64_helpers_and_traits() {
        let a = XYZ::new(1.0, -2.0, 3.5);
        let b = XYZ::new(-4.0, 5.0, 6.0);

        let mag_sq = a.magnitude_squared();
        assert!((mag_sq - (1.0 + 4.0 + 12.25)).abs() < f64::EPSILON);

        let sum = XYZ::<f64>::add(&a, &b);
        assert!((sum.x() + 3.0).abs() < f64::EPSILON);
        assert!((sum.y() - 3.0).abs() < f64::EPSILON);
        assert!((sum.z() - 9.5).abs() < f64::EPSILON);

        let diff = XYZ::<f64>::sub(&a, &b);
        assert!((diff.x() - 5.0).abs() < f64::EPSILON);
        assert!((diff.y() + 7.0).abs() < f64::EPSILON);
        assert!((diff.z() - -2.5).abs() < f64::EPSILON);

        let (x, y, z) = a.components();
        assert!((x - 1.0).abs() < f64::EPSILON);
        assert!((y + 2.0).abs() < f64::EPSILON);
        assert!((z - 3.5).abs() < f64::EPSILON);

        let default_xyz: XYZ<f64> = Default::default();
        assert!((default_xyz.x()).abs() < f64::EPSILON);
        assert!((default_xyz.y()).abs() < f64::EPSILON);
        assert!((default_xyz.z()).abs() < f64::EPSILON);

        let display = a.to_string();
        assert_eq!(display, "(1, -2, 3.5)");

        let display_prec = format!("{:.2}", a);
        assert_eq!(display_prec, "(1.00, -2.00, 3.50)");

        let display_exp = format!("{:.2e}", a);
        assert_eq!(display_exp, "(1.00e0, -2.00e0, 3.50e0)");

        let neg = -a;
        assert!((neg.x() + 1.0).abs() < f64::EPSILON);
        assert!((neg.y() - 2.0).abs() < f64::EPSILON);
        assert!((neg.z() + 3.5).abs() < f64::EPSILON);

        let scaled = a * 2.0;
        assert!((scaled.x() - 2.0).abs() < f64::EPSILON);
        let scaled2 = 2.0 * a;
        assert!((scaled2.y() + 4.0).abs() < f64::EPSILON);
    }

    #[test]
    fn test_xyz_quantity_helpers() {
        let a = XYZ::new(1.0 * M, 2.0 * M, 3.0 * M);
        let b = XYZ::new(-1.0 * M, 0.5 * M, 4.0 * M);

        let sum = XYZ::<Quantity<Meter>>::add(&a, &b);
        assert!((sum.x().value()).abs() < f64::EPSILON);
        assert!((sum.y().value() - 2.5).abs() < f64::EPSILON);

        let diff = XYZ::<Quantity<Meter>>::sub(&a, &b);
        assert!((diff.x().value() - 2.0).abs() < f64::EPSILON);
        assert!((diff.z().value() - -1.0).abs() < f64::EPSILON);
    }
}
