// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Vallés Puig, Ramon

//! # VSOP87E Barycentric Rectangular Coordinates (Generated Data)
//!
//! ## Scientific scope
//!
//! This file contains the machine-generated coefficient tables for the
//! VSOP87E solution: barycentric ecliptic rectangular coordinates
//! (X, Y, Z) for all eight major planets plus the Sun, referred to
//! the mean ecliptic and equinox of J2000.0.
//!
//! Time argument `T` is Julian millennia from J2000.0 (TDB).
//! Positions are in **astronomical units** (AU).
//!
//! ## Technical scope
//!
//! - One `impl <Planet>` block per planet, each providing methods
//!   `vsop87e_x`, `vsop87e_y`, `vsop87e_z`.
//! - Each method delegates to [`super::vsop87_impl::position`] with
//!   the planet-specific coefficient slices.
//! - The top-level `vsop87e` method on each planet is generated by the
//!   `impl_vsop87_for_planet!` macro in `vsop87_trait.rs`.
//! - Coefficient source: original VSOP87E data file from the IMCCE archive.
//!
//! ## References
//!
//! - Bretagnon, P., & Francou, G. (1988). "Planetary theories in rectangular
//!   and spherical variables: VSOP87 solutions".
//!   *Astronomy and Astrophysics* 202, 309–315.
//! - IMCCE VSOP87 coefficient archive:
//!   <https://www.imcce.fr/inpop/ephemerides/vsop87/>

use super::*;
use crate::archive::vsop::vsop87e_data::*;
use crate::bodies::solar_system::*;
use crate::coordinates::{
    cartesian::{Position, Velocity},
    centers::Barycentric,
    frames::EclipticMeanJ2000,
};
use crate::qtty::*;
use crate::time::JulianDate;
type AuPerDay = crate::qtty::Per<AstronomicalUnit, Day>;

macro_rules! impl_vsop87e {
    (
        $Planet:ident,
        $prefix:ident,
        x: [$($x:ident),+ $(,)?],
        y: [$($y:ident),+ $(,)?],
        z: [$($z:ident),+ $(,)?]
    ) => {
        paste::paste! {
            /// Computes the VSOP87E barycentric ecliptic Cartesian position (mean J2000.0 equinox, AU).
            #[allow(unreachable_pub)]
            pub fn [<$prefix _vsop87e>](jd: JulianDate) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
                let (x, y, z) = position(
                    jd,
                    &[$( &$x ),+],
                    &[$( &$y ),+],
                    &[$( &$z ),+]
                );
                Position::new(
                    AstronomicalUnits::new(x),
                    AstronomicalUnits::new(y),
                    AstronomicalUnits::new(z),
                )
            }

            /// Computes the VSOP87E barycentric ecliptic Cartesian velocity (mean J2000.0 equinox, AU/day).
            #[allow(unreachable_pub)]
            pub fn [<$prefix _vsop87e_vel>](jd: JulianDate) -> Velocity<EclipticMeanJ2000, AuPerDay> {
                let (vx, vy, vz) = velocity(
                    jd,
                    &[$( &$x ),+],
                    &[$( &$y ),+],
                    &[$( &$z ),+]
                );
                Velocity::new(
                    crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vx),
                    crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vy),
                    crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vz)
                )
            }

            /// Computes both VSOP87E barycentric position and velocity in one pass (more efficient than calling both separately).
            #[allow(unreachable_pub)]
            pub fn [<$prefix _vsop87e_pos_vel>](jd: JulianDate)
                -> (Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, Velocity<EclipticMeanJ2000, AuPerDay>) {
                let ((x, y, z), (vx, vy, vz)) = position_velocity(
                    jd,
                    &[$( &$x ),+],
                    &[$( &$y ),+],
                    &[$( &$z ),+]
                );
                (
                    Position::new(
                        AstronomicalUnits::new(x),
                        AstronomicalUnits::new(y),
                        AstronomicalUnits::new(z),
                    ),
                    Velocity::new(
                        crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vx),
                        crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vy),
                        crate::qtty::velocity::Velocity::<AstronomicalUnit, Day>::new(vz))
                )
            }

            impl $Planet {
                /// Computes the VSOP87E barycentric ecliptic Cartesian position (mean J2000.0 equinox, AU).
                pub fn vsop87e(jd: JulianDate) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
                    [<$prefix _vsop87e>](jd)
                }

                /// Computes the VSOP87E barycentric ecliptic Cartesian velocity (mean J2000.0 equinox, AU/day).
                pub fn vsop87e_vel(jd: JulianDate) -> Velocity<EclipticMeanJ2000, AuPerDay> {
                    [<$prefix _vsop87e_vel>](jd)
                }

                /// Computes both VSOP87E barycentric position and velocity in one pass (more efficient than calling both separately).
                pub fn vsop87e_pos_vel(jd: JulianDate)
                    -> (Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit>, Velocity<EclipticMeanJ2000, AuPerDay>) {
                    [<$prefix _vsop87e_pos_vel>](jd)
                }
            }
        }
    };
}

/// Computes the VSOP87E barycentric position of the Sun (mean J2000.0 ecliptic equinox, AU).
#[allow(unreachable_pub)]
pub fn sun_vsop87e(jd: JulianDate) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
    let (x, y, z) = position(
        jd,
        &[&SUN_X0, &SUN_X1, &SUN_X2, &SUN_X3, &SUN_X4, &SUN_X5],
        &[&SUN_Y0, &SUN_Y1, &SUN_Y2, &SUN_Y3, &SUN_Y4, &SUN_Y5],
        &[&SUN_Z0, &SUN_Z1, &SUN_Z2, &SUN_Z3, &SUN_Z4, &SUN_Z5],
    );
    Position::new(
        AstronomicalUnits::new(x),
        AstronomicalUnits::new(y),
        AstronomicalUnits::new(z),
    )
}

impl Sun {
    /// Computes the VSOP87E barycentric position of the Sun (mean J2000.0 ecliptic equinox, AU).
    pub fn vsop87e(jd: JulianDate) -> Position<Barycentric, EclipticMeanJ2000, AstronomicalUnit> {
        sun_vsop87e(jd)
    }
}

impl_vsop87e!(
    Mercury,
    mercury,
    x: [MERCURY_X0, MERCURY_X1, MERCURY_X2, MERCURY_X3, MERCURY_X4, MERCURY_X5],
    y: [MERCURY_Y0, MERCURY_Y1, MERCURY_Y2, MERCURY_Y3, MERCURY_Y4, MERCURY_Y5],
    z: [MERCURY_Z0, MERCURY_Z1, MERCURY_Z2, MERCURY_Z3, MERCURY_Z4, MERCURY_Z5]
);

impl_vsop87e!(
    Venus,
    venus,
    x: [VENUS_X0, VENUS_X1, VENUS_X2, VENUS_X3, VENUS_X4, VENUS_X5],
    y: [VENUS_Y0, VENUS_Y1, VENUS_Y2, VENUS_Y3, VENUS_Y4, VENUS_Y5],
    z: [VENUS_Z0, VENUS_Z1, VENUS_Z2, VENUS_Z3, VENUS_Z4, VENUS_Z5]
);

impl_vsop87e!(
    Earth,
    earth,
    x: [EARTH_X0, EARTH_X1, EARTH_X2, EARTH_X3, EARTH_X4, EARTH_X5],
    y: [EARTH_Y0, EARTH_Y1, EARTH_Y2, EARTH_Y3, EARTH_Y4, EARTH_Y5],
    z: [EARTH_Z0, EARTH_Z1, EARTH_Z2, EARTH_Z3, EARTH_Z4, EARTH_Z5]
);

impl_vsop87e!(
    Mars,
    mars,
    x: [MARS_X0, MARS_X1, MARS_X2, MARS_X3, MARS_X4, MARS_X5],
    y: [MARS_Y0, MARS_Y1, MARS_Y2, MARS_Y3, MARS_Y4, MARS_Y5],
    z: [MARS_Z0, MARS_Z1, MARS_Z2, MARS_Z3, MARS_Z4, MARS_Z5]
);

impl_vsop87e!(
    Jupiter,
    jupiter,
    x: [JUPITER_X0, JUPITER_X1, JUPITER_X2, JUPITER_X3, JUPITER_X4, JUPITER_X5],
    y: [JUPITER_Y0, JUPITER_Y1, JUPITER_Y2, JUPITER_Y3, JUPITER_Y4, JUPITER_Y5],
    z: [JUPITER_Z0, JUPITER_Z1, JUPITER_Z2, JUPITER_Z3, JUPITER_Z4, JUPITER_Z5]
);

impl_vsop87e!(
    Saturn,
    saturn,
    x: [SATURN_X0, SATURN_X1, SATURN_X2, SATURN_X3, SATURN_X4, SATURN_X5],
    y: [SATURN_Y0, SATURN_Y1, SATURN_Y2, SATURN_Y3, SATURN_Y4, SATURN_Y5],
    z: [SATURN_Z0, SATURN_Z1, SATURN_Z2, SATURN_Z3, SATURN_Z4, SATURN_Z5]
);

impl_vsop87e!(
    Uranus,
    uranus,
    x: [URANUS_X0, URANUS_X1, URANUS_X2, URANUS_X3, URANUS_X4],
    y: [URANUS_Y0, URANUS_Y1, URANUS_Y2, URANUS_Y3, URANUS_Y4],
    z: [URANUS_Z0, URANUS_Z1, URANUS_Z2, URANUS_Z3]
);

impl_vsop87e!(
    Neptune,
    neptune,
    x: [NEPTUNE_X0, NEPTUNE_X1, NEPTUNE_X2, NEPTUNE_X3, NEPTUNE_X4],
    y: [NEPTUNE_Y0, NEPTUNE_Y1, NEPTUNE_Y2, NEPTUNE_Y3, NEPTUNE_Y4],
    z: [NEPTUNE_Z0, NEPTUNE_Z1, NEPTUNE_Z2, NEPTUNE_Z3]
);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::coordinates::cartesian::Position;
    use crate::macros::assert_cartesian_eq;
    use crate::qtty::AU;

    const PRECISION: f64 = 1.0e-6;

    #[test]
    fn test_mercury_at_epoch() {
        let coord = mercury_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(-0.1372349394 * AU, -0.4500758422 * AU, -0.0243922379 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_venus_at_epoch() {
        let coord = venus_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(-0.7254438061 * AU, -0.0354427729 * AU, 0.0412204390 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_earth_barycentric_at_epoch() {
        let coord = earth_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(-0.1842769894 * AU, 0.9644534522 * AU, 0.0002021000 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_mars_at_epoch() {
        let coord = mars_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(1.3835744053 * AU, -0.0162038666 * AU, -0.0342616574 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_jupiter_at_epoch() {
        let coord = jupiter_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(3.9940325025 * AU, 2.9357928287 * AU, -0.1015776146 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_saturn_at_epoch() {
        let coord = saturn_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(6.3992653459 * AU, 6.5672047374 * AU, -0.3688706482 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_uranus_at_epoch() {
        let coord = uranus_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(14.4247519568 * AU, -13.7371045087 * AU, -0.2379360887 * AU),
            PRECISION
        );
    }

    #[test]
    fn test_neptune_at_epoch() {
        let coord = neptune_vsop87e(crate::J2000);
        assert_cartesian_eq!(
            coord,
            Position::new(16.8049701269 * AU, -24.9944513569 * AU, 0.1274251215 * AU),
            PRECISION
        );
    }
}
