//! Celestial body identifiers: [`CelestialBodyClass`] and [`CelestialBody`].

use core::fmt;

use crate::custom_bodies::{CustomBodyId, CustomDefinitionValidationError};

/// A coarse classification for a celestial body.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum CelestialBodyClass {
    /// The Sun or Moon.
    Luminary,
    /// Mercury through Pluto.
    MajorPlanet,
    /// Lunar nodes and apsides.
    LunarPoint,
    /// Ceres, Pallas, Juno, and Vesta.
    BuiltInAsteroid,
    /// A structured custom body identifier.
    Custom,
}

impl CelestialBodyClass {
    /// Returns a stable human-readable label for the class.
    pub const fn label(self) -> &'static str {
        match self {
            Self::Luminary => "luminary",
            Self::MajorPlanet => "major planet",
            Self::LunarPoint => "lunar point",
            Self::BuiltInAsteroid => "built-in asteroid",
            Self::Custom => "custom body",
        }
    }
}

impl fmt::Display for CelestialBodyClass {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.label())
    }
}

/// The built-in and custom body identifiers recognized by the shared API.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CelestialBody {
    /// The Sun.
    Sun,
    /// The Moon.
    Moon,
    /// Mercury.
    Mercury,
    /// Venus.
    Venus,
    /// Mars.
    Mars,
    /// Jupiter.
    Jupiter,
    /// Saturn.
    Saturn,
    /// Uranus.
    Uranus,
    /// Neptune.
    Neptune,
    /// Pluto.
    Pluto,
    /// The mean lunar node.
    MeanNode,
    /// The true lunar node.
    TrueNode,
    /// The mean lunar apogee.
    MeanApogee,
    /// The true lunar apogee.
    TrueApogee,
    /// The mean lunar perigee.
    MeanPerigee,
    /// The true lunar perigee.
    TruePerigee,
    /// Ceres.
    Ceres,
    /// Pallas.
    Pallas,
    /// Juno.
    Juno,
    /// Vesta.
    Vesta,
    /// A body that is not yet one of the built-in identifiers.
    Custom(CustomBodyId),
}

impl CelestialBody {
    /// Returns the coarse class for this body.
    pub const fn class(&self) -> CelestialBodyClass {
        match self {
            Self::Sun | Self::Moon => CelestialBodyClass::Luminary,
            Self::Mercury
            | Self::Venus
            | Self::Mars
            | Self::Jupiter
            | Self::Saturn
            | Self::Uranus
            | Self::Neptune
            | Self::Pluto => CelestialBodyClass::MajorPlanet,
            Self::MeanNode
            | Self::TrueNode
            | Self::MeanApogee
            | Self::TrueApogee
            | Self::MeanPerigee
            | Self::TruePerigee => CelestialBodyClass::LunarPoint,
            Self::Ceres | Self::Pallas | Self::Juno | Self::Vesta => {
                CelestialBodyClass::BuiltInAsteroid
            }
            Self::Custom(_) => CelestialBodyClass::Custom,
        }
    }

    /// Returns a stable human-readable name for built-in bodies.
    pub const fn built_in_name(&self) -> Option<&'static str> {
        match self {
            Self::Sun => Some("Sun"),
            Self::Moon => Some("Moon"),
            Self::Mercury => Some("Mercury"),
            Self::Venus => Some("Venus"),
            Self::Mars => Some("Mars"),
            Self::Jupiter => Some("Jupiter"),
            Self::Saturn => Some("Saturn"),
            Self::Uranus => Some("Uranus"),
            Self::Neptune => Some("Neptune"),
            Self::Pluto => Some("Pluto"),
            Self::MeanNode => Some("Mean Node"),
            Self::TrueNode => Some("True Node"),
            Self::MeanApogee => Some("Mean Apogee"),
            Self::TrueApogee => Some("True Apogee"),
            Self::MeanPerigee => Some("Mean Perigee"),
            Self::TruePerigee => Some("True Perigee"),
            Self::Ceres => Some("Ceres"),
            Self::Pallas => Some("Pallas"),
            Self::Juno => Some("Juno"),
            Self::Vesta => Some("Vesta"),
            Self::Custom(_) => None,
        }
    }

    /// Validates the custom body identifier when this is a custom body.
    ///
    /// Built-in bodies are always valid; only the structured custom body
    /// identifier is checked. This keeps user-defined catalog entries from
    /// drifting into request or profile surfaces with blank or padded fields.
    pub fn validate(&self) -> Result<(), CustomDefinitionValidationError> {
        match self {
            Self::Custom(custom) => custom.validate(),
            _ => Ok(()),
        }
    }
}

impl fmt::Display for CelestialBody {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Sun => f.write_str("Sun"),
            Self::Moon => f.write_str("Moon"),
            Self::Mercury => f.write_str("Mercury"),
            Self::Venus => f.write_str("Venus"),
            Self::Mars => f.write_str("Mars"),
            Self::Jupiter => f.write_str("Jupiter"),
            Self::Saturn => f.write_str("Saturn"),
            Self::Uranus => f.write_str("Uranus"),
            Self::Neptune => f.write_str("Neptune"),
            Self::Pluto => f.write_str("Pluto"),
            Self::MeanNode => f.write_str("Mean Node"),
            Self::TrueNode => f.write_str("True Node"),
            Self::MeanApogee => f.write_str("Mean Apogee"),
            Self::TrueApogee => f.write_str("True Apogee"),
            Self::MeanPerigee => f.write_str("Mean Perigee"),
            Self::TruePerigee => f.write_str("True Perigee"),
            Self::Ceres => f.write_str("Ceres"),
            Self::Pallas => f.write_str("Pallas"),
            Self::Juno => f.write_str("Juno"),
            Self::Vesta => f.write_str("Vesta"),
            Self::Custom(custom) => fmt::Display::fmt(custom, f),
        }
    }
}
