// SPDX-FileCopyrightText: 2025 Helge Eichhorn <git@helgeeichhorn.de>
//
// SPDX-License-Identifier: MPL-2.0

pub mod builders;
mod cartesian;
pub mod ensemble;
mod keplerian;
pub mod sso;
mod trajectory;

pub use cartesian::StateToDynGroundError;
pub use ensemble::{DynEnsemble, Ensemble};
pub use trajectory::{DynTrajectory, Trajectory, TrajectoryError, TrajectoryTransformationError};

use lox_bodies::{DynOrigin, Origin, PointMass, TryPointMass, UndefinedOriginPropertyError};
use lox_core::{
    coords::Cartesian,
    elements::{GravitationalParameter, Keplerian},
};
use lox_frames::{DynFrame, ReferenceFrame};
use lox_time::{
    Time,
    time_scales::{DynTimeScale, TimeScale},
};
use thiserror::Error;

pub enum OrbitType {
    Cartesian(Cartesian),
    Keplerian(Keplerian),
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Orbit<S, T: TimeScale, O: Origin, R: ReferenceFrame> {
    state: S,
    time: Time<T>,
    origin: O,
    frame: R,
}

pub type DynOrbit = Orbit<OrbitType, DynTimeScale, DynOrigin, DynFrame>;

impl<S, T, O, R> Orbit<S, T, O, R>
where
    T: TimeScale,
    O: Origin,
    R: ReferenceFrame,
{
    #[inline]
    pub const fn from_state(state: S, time: Time<T>, origin: O, frame: R) -> Self {
        Self {
            state,
            time,
            origin,
            frame,
        }
    }

    #[inline]
    pub fn state(&self) -> S
    where
        S: Copy,
    {
        self.state
    }

    #[inline]
    pub fn time(&self) -> Time<T>
    where
        T: Copy,
    {
        self.time
    }

    #[inline]
    pub fn origin(&self) -> O
    where
        O: Copy,
    {
        self.origin
    }

    #[inline]
    pub fn reference_frame(&self) -> R
    where
        R: Copy,
    {
        self.frame
    }

    pub fn try_gravitational_parameter(
        &self,
    ) -> Result<GravitationalParameter, UndefinedOriginPropertyError>
    where
        O: TryPointMass,
    {
        self.origin.try_gravitational_parameter()
    }

    pub fn gravitational_parameter(&self) -> GravitationalParameter
    where
        O: PointMass,
    {
        self.origin.gravitational_parameter()
    }
}

impl<S, T, O, R> Orbit<S, T, O, R>
where
    T: TimeScale + Copy + Into<DynTimeScale>,
    O: Origin + Copy + Into<DynOrigin>,
    R: ReferenceFrame + Copy + Into<DynFrame>,
{
    pub fn into_dyn(self) -> Orbit<S, DynTimeScale, DynOrigin, DynFrame> {
        Orbit::from_state(
            self.state,
            self.time.into_dyn(),
            self.origin.into(),
            self.frame.into(),
        )
    }
}

pub type CartesianOrbit<T, O, R> = Orbit<Cartesian, T, O, R>;
pub type DynCartesianOrbit = Orbit<Cartesian, DynTimeScale, DynOrigin, DynFrame>;

pub type KeplerianOrbit<T, O, R> = Orbit<Keplerian, T, O, R>;
pub type DynKeplerianOrbit = Orbit<Keplerian, DynTimeScale, DynOrigin, DynFrame>;

#[derive(Debug, Clone, PartialEq, Error)]
pub enum TrajectorError {
    #[error("at least 2 states are required but only {0} were provided")]
    InsufficientStates(usize),
}
