use crate::{Drift, Dt, Scale};

/// A fully self-describing custom relativistic time scale.
///
/// Bundles a base `Scale` (`Custom`) with the quadratic
/// polynomial and reference epoch needed for exact conversion to any other scale
/// (typically TT or TDB).
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
pub struct ClockModel {
    /// Base scale (usually `Custom`)
    pub base: Scale,
    /// Epoch at which the polynomial was defined (e.g. last ground contact)
    pub reference: Dt,
    /// Offset
    pub drift: Drift,
}

impl ClockModel {
    /// Creates a new self-describing scale (most common for Proper time).
    #[inline]
    pub const fn new(base: Scale, reference: Dt, drift: Drift) -> Self {
        Self {
            base,
            reference,
            drift,
        }
    }
}

#[cfg(feature = "wire")]
impl ClockModel {
    /// Current wire format version.
    pub const WIRE_VERSION: u8 = 1;

    /// Size of the canonical wire representation in bytes.
    pub const WIRE_SIZE: usize = 1 + Scale::WIRE_SIZE + Dt::WIRE_SIZE + Drift::WIRE_SIZE;

    /// Serializes this self-describing `ClockModel` into a fixed buffer.
    ///
    /// # Wire Format
    ///
    /// - Byte `0`: Version (`WIRE_VERSION`)
    /// - Byte `1`: `base` (`Scale`)
    /// - Bytes `2..20`: `reference` (`Dt`)
    /// - Bytes `20..71`: `drift` (`Drift`)
    pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
        let mut buf = [0u8; Self::WIRE_SIZE];
        buf[0] = Self::WIRE_VERSION;
        buf[1] = self.base as u8;

        let tp = self.reference.to_wire_bytes();
        buf[2..2 + Dt::WIRE_SIZE].copy_from_slice(&tp);

        let cd = self.drift.to_wire_bytes();
        buf[2 + Dt::WIRE_SIZE..].copy_from_slice(&cd);

        buf
    }

    /// Deserializes a `ClockModel` from exactly `WIRE_SIZE` bytes of wire data.
    ///
    /// Returns `None` if the version byte is unknown or any nested component
    /// fails validation.
    ///
    /// ## Security
    ///
    /// This function is safe to call with arbitrary untrusted data because:
    /// - Fixed total size eliminates length-prefix vulnerabilities
    /// - Validation is performed at every layer
    /// - No allocation, no `unsafe`, no possibility of code execution
    /// - Returns `None` on any invalid or malicious input
    pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
        if bytes.len() != Self::WIRE_SIZE {
            return None;
        }

        if bytes[0] != Self::WIRE_VERSION {
            return None;
        }

        let base = Scale::from_u8(bytes[1]);
        let reference = Dt::from_wire_bytes(&bytes[2..2 + Dt::WIRE_SIZE])?;
        let drift = Drift::from_wire_bytes(&bytes[2 + Dt::WIRE_SIZE..])?;

        Some(Self {
            base,
            reference,
            drift,
        })
    }
}

impl Dt {
    /// Creates a new custom time model using this exact instant as the reference epoch.
    /// - The supplied [`Drift`] defines the relativistic model for the timescale.
    /// - The returned [`ClockModel`] can be used to convert to or from the custom timescale.
    #[inline]
    pub const fn to_model_as_epoch(self, drift: Drift) -> ClockModel {
        ClockModel::new(Scale::Custom, self, drift)
    }
}
