//! The format described in ISO 8601.

mod adt_hack;

use core::num::NonZeroU8;

pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig};

/// A configuration for [`Iso8601`] that only parses values.
const PARSING_ONLY: EncodedConfig = Config {
    formatted_components: FormattedComponents::None,
    use_separators: false,
    year_is_six_digits: false,
    date_kind: DateKind::Calendar,
    time_precision: TimePrecision::Hour {
        decimal_digits: None,
    },
    offset_precision: OffsetPrecision::Hour,
}
.encode();

/// The default configuration for [`Iso8601`].
const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode();

/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html).
///
/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions.
///
/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`].
/// Passing any other value is **unspecified behavior**.
///
/// Example: 1997-11-21T09:55:06.000000000-06:00
///
/// # Examples
///
/// ```rust
/// # use time::format_description::well_known::Iso8601;
/// # use time::macros::datetime;
/// assert_eq!(
///     datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?,
///     "1997-11-12T09:55:06.000000000-06:00"
/// );
/// # Ok::<_, time::Error>(())
/// ```
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Iso8601<const CONFIG: EncodedConfig>;

impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Iso8601")
            .field("config", &Config::decode(CONFIG))
            .finish()
    }
}

impl Iso8601<DEFAULT_CONFIG> {
    /// An [`Iso8601`] with the default configuration.
    ///
    /// The following is the default behavior:
    ///
    /// - The configuration can be used for both formatting and parsing.
    /// - The date, time, and UTC offset are all formatted.
    /// - Separators (such as `-` and `:`) are included.
    /// - The year contains four digits, such that the year must be between 0 and 9999.
    /// - The date uses the calendar format.
    /// - The time has precision to the second and nine decimal digits.
    /// - The UTC offset has precision to the minute.
    ///
    /// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create
    /// a custom configuration.
    pub const DEFAULT: Self = Self;
}

impl Iso8601<PARSING_ONLY> {
    /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is
    /// unspecified behavior.
    pub const PARSING: Self = Self;
}

/// Which components to format.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormattedComponents {
    /// The configuration can only be used for parsing. Using this to format a value is
    /// unspecified behavior.
    None,
    /// Format only the date.
    Date,
    /// Format only the time.
    Time,
    /// Format only the UTC offset.
    Offset,
    /// Format the date and time.
    DateTime,
    /// Format the date, time, and UTC offset.
    DateTimeOffset,
    /// Format the time and UTC offset.
    TimeOffset,
}

/// Which format to use for the date.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DateKind {
    /// Use the year-month-day format.
    Calendar,
    /// Use the year-week-weekday format.
    Week,
    /// Use the week-ordinal format.
    Ordinal,
}

/// The precision and number of decimal digits present for the time.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimePrecision {
    /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the
    /// specified number of decimal digits, if any.
    Hour {
        #[allow(clippy::missing_docs_in_private_items)]
        decimal_digits: Option<NonZeroU8>,
    },
    /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified
    /// number of decimal digits, if any.
    Minute {
        #[allow(clippy::missing_docs_in_private_items)]
        decimal_digits: Option<NonZeroU8>,
    },
    /// Format the hour, minute, and second. Nanoseconds will be represented with the specified
    /// number of decimal digits, if any.
    Second {
        #[allow(clippy::missing_docs_in_private_items)]
        decimal_digits: Option<NonZeroU8>,
    },
}

/// The precision for the UTC offset.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OffsetPrecision {
    /// Format only the offset hour. Requires the offset minute to be zero.
    Hour,
    /// Format both the offset hour and minute.
    Minute,
}

/// Configuration for [`Iso8601`].
// This is only used as a const generic, so there's no need to have a number of implementations on
// it.
#[allow(missing_copy_implementations)]
#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this.
#[derive(Debug)]
pub struct Config {
    /// Which components, if any, will be formatted.
    pub(crate) formatted_components: FormattedComponents,
    /// Whether the format contains separators (such as `-` or `:`).
    pub(crate) use_separators: bool,
    /// Whether the year is six digits.
    pub(crate) year_is_six_digits: bool,
    /// The format used for the date.
    pub(crate) date_kind: DateKind,
    /// The precision and number of decimal digits present for the time.
    pub(crate) time_precision: TimePrecision,
    /// The precision for the UTC offset.
    pub(crate) offset_precision: OffsetPrecision,
}

impl Config {
    /// A configuration for the [`Iso8601`] format.
    ///
    /// The following is the default behavior:
    ///
    /// - The configuration can be used for both formatting and parsing.
    /// - The date, time, and UTC offset are all formatted.
    /// - Separators (such as `-` and `:`) are included.
    /// - The year contains four digits, such that the year must be between 0 and 9999.
    /// - The date uses the calendar format.
    /// - The time has precision to the second and nine decimal digits.
    /// - The UTC offset has precision to the minute.
    ///
    /// If you need different behavior, use the setter methods on this struct.
    pub const DEFAULT: Self = Self {
        formatted_components: FormattedComponents::DateTimeOffset,
        use_separators: true,
        year_is_six_digits: false,
        date_kind: DateKind::Calendar,
        time_precision: TimePrecision::Second {
            decimal_digits: NonZeroU8::new(9),
        },
        offset_precision: OffsetPrecision::Minute,
    };

    /// Set whether the format the date, time, and/or UTC offset.
    pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self {
        Self {
            formatted_components,
            ..self
        }
    }

    /// Set whether the format contains separators (such as `-` or `:`).
    pub const fn set_use_separators(self, use_separators: bool) -> Self {
        Self {
            use_separators,
            ..self
        }
    }

    /// Set whether the year is six digits.
    pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self {
        Self {
            year_is_six_digits,
            ..self
        }
    }

    /// Set the format used for the date.
    pub const fn set_date_kind(self, date_kind: DateKind) -> Self {
        Self { date_kind, ..self }
    }

    /// Set the precision and number of decimal digits present for the time.
    pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self {
        Self {
            time_precision,
            ..self
        }
    }

    /// Set the precision for the UTC offset.
    pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self {
        Self {
            offset_precision,
            ..self
        }
    }
}
