//! Error detail implementation for [`Timestamp`] parsing.
//!
//! This type wraps parsing errors generated by the `time` crate.
//!
//! [`Timestamp`]: super::Timestamp

use std::{
    error::Error,
    fmt::{Display, Formatter, Result as FmtResult},
};
use time::error::{ComponentRange as ComponentRangeError, Parse as ParseError};

/// Reason that an ISO 8601 format couldn't be parsed.
#[derive(Debug)]
pub struct TimestampParseError {
    /// Type of error that occurred.
    kind: TimestampParseErrorType,
    /// Source of the error, if there is any.
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl TimestampParseError {
    /// Error that was caused by the datetime being of an improper format.
    pub(super) const FORMAT: TimestampParseError = TimestampParseError {
        kind: TimestampParseErrorType::Format,
        source: None,
    };

    /// Immutable reference to the type of error that occurred.
    #[must_use = "retrieving the type has no effect if left unused"]
    pub const fn kind(&self) -> &TimestampParseErrorType {
        &self.kind
    }

    /// Consume the error, returning the source error if there is any.
    #[allow(clippy::unused_self)]
    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
        self.source
    }

    /// Consume the error, returning the owned error type and the source error.
    #[must_use = "consuming the error into its parts has no effect if left unused"]
    pub fn into_parts(
        self,
    ) -> (
        TimestampParseErrorType,
        Option<Box<dyn Error + Send + Sync>>,
    ) {
        (self.kind, self.source)
    }

    /// Create a new error from a [`ComponentRangeError`].
    pub(super) fn from_component_range(source: ComponentRangeError) -> Self {
        Self {
            kind: TimestampParseErrorType::Range,
            source: Some(Box::new(source)),
        }
    }

    /// Create a new error from a [`ParseError`].
    pub(super) fn from_parse(source: ParseError) -> Self {
        Self {
            kind: TimestampParseErrorType::Parsing,
            source: Some(Box::new(source)),
        }
    }
}

impl Display for TimestampParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match &self.kind {
            TimestampParseErrorType::Format => {
                f.write_str("provided value is not in an iso 8601 format")
            }
            TimestampParseErrorType::Parsing => f.write_str("timestamp parsing failed"),
            TimestampParseErrorType::Range => {
                f.write_str("value of a field is not in an acceptable range")
            }
        }
    }
}

impl Error for TimestampParseError {}

/// Type of [`TimestampParseError`] that occurred.
#[derive(Debug)]
pub enum TimestampParseErrorType {
    /// Format of the input datetime is invalid.
    ///
    /// A datetime can take two forms: with microseconds and without
    /// microseconds.
    Format,
    /// Timestamp parsing failed.
    Parsing,
    /// Value of a field is not in an acceptable range.
    Range,
}

#[cfg(test)]
mod tests {
    use super::{TimestampParseError, TimestampParseErrorType};
    use static_assertions::assert_impl_all;
    use std::{error::Error, fmt::Debug};

    assert_impl_all!(TimestampParseErrorType: Debug, Send, Sync);
    assert_impl_all!(TimestampParseError: Error, Send, Sync);
}
