use jcore::civil::ISOWeekDate as JISOWeekDate;

use crate::{
    civil::{Date, DateTime, Weekday},
    error::Error,
    fmt::temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER},
    Zoned,
};

/// A type representing an [ISO 8601 week date].
///
/// The ISO 8601 week date scheme devises a calendar where days are identified
/// by their year, week number and weekday. All years have either precisely
/// 52 or 53 weeks.
///
/// The first week of an ISO 8601 year corresponds to the week containing the
/// first Thursday of the year. For this reason, an ISO 8601 week year can be
/// mismatched with the day's corresponding Gregorian year. For example, the
/// ISO 8601 week date for `1995-01-01` is `1994-W52-7` (with `7` corresponding
/// to Sunday).
///
/// ISO 8601 also considers Monday to be the start of the week, and uses
/// a 1-based numbering system. That is, Monday corresponds to `1` while
/// Sunday corresponds to `7` and is the last day of the week. Weekdays are
/// encapsulated by the [`Weekday`] type, which provides routines for easily
/// converting between different schemes (such as weeks where Sunday is the
/// beginning).
///
/// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date
///
/// # Use case
///
/// Some domains use this method of timekeeping. Otherwise, unless you
/// specifically want a week oriented calendar, it's likely that you'll never
/// need to care about this type.
///
/// # Parsing and printing
///
/// The `ISOWeekDate` type provides convenient trait implementations of
/// [`std::str::FromStr`] and [`std::fmt::Display`]. These use the format
/// specified by ISO 8601 for week dates:
///
/// ```
/// use jiff::civil::ISOWeekDate;
///
/// let week_date: ISOWeekDate = "2024-W24-7".parse()?;
/// assert_eq!(week_date.to_string(), "2024-W24-7");
/// assert_eq!(week_date.date().to_string(), "2024-06-16");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// ISO 8601 allows the `-` separator to be absent:
///
/// ```
/// use jiff::civil::ISOWeekDate;
///
/// let week_date: ISOWeekDate = "2024W241".parse()?;
/// assert_eq!(week_date.to_string(), "2024-W24-1");
/// assert_eq!(week_date.date().to_string(), "2024-06-10");
///
/// // But you cannot mix and match. Either `-` separates
/// // both the year and week, or neither.
/// assert!("2024W24-1".parse::<ISOWeekDate>().is_err());
/// assert!("2024-W241".parse::<ISOWeekDate>().is_err());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// And the `W` may also be lowercase:
///
/// ```
/// use jiff::civil::ISOWeekDate;
///
/// let week_date: ISOWeekDate = "2024-w24-2".parse()?;
/// assert_eq!(week_date.to_string(), "2024-W24-2");
/// assert_eq!(week_date.date().to_string(), "2024-06-11");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Default value
///
/// For convenience, this type implements the `Default` trait. Its default
/// value is the first day of the zeroth year. i.e., `0000-W1-1`.
///
/// # Example: sample dates
///
/// This example shows a couple ISO 8601 week dates and their corresponding
/// Gregorian equivalents:
///
/// ```
/// use jiff::civil::{ISOWeekDate, Weekday, date};
///
/// let d = date(2019, 12, 30);
/// let weekdate = ISOWeekDate::new(2020, 1, Weekday::Monday).unwrap();
/// assert_eq!(d.iso_week_date(), weekdate);
///
/// let d = date(2024, 3, 9);
/// let weekdate = ISOWeekDate::new(2024, 10, Weekday::Saturday).unwrap();
/// assert_eq!(d.iso_week_date(), weekdate);
/// ```
///
/// # Example: overlapping leap and long years
///
/// A "long" ISO 8601 week year is a year with 53 weeks. That is, it is a year
/// that includes a leap week. This example shows all years in the 20th
/// century that are both Gregorian leap years and long years.
///
/// ```
/// use jiff::civil::date;
///
/// let mut overlapping = vec![];
/// for year in 1900..=1999 {
///     let date = date(year, 1, 1);
///     if date.in_leap_year() && date.iso_week_date().in_long_year() {
///         overlapping.push(year);
///     }
/// }
/// assert_eq!(overlapping, vec![
///     1904, 1908, 1920, 1932, 1936, 1948, 1960, 1964, 1976, 1988, 1992,
/// ]);
/// ```
///
/// # Example: printing all weeks in a year
///
/// The ISO 8601 week calendar can be useful when you want to categorize
/// things into buckets of weeks where all weeks are exactly 7 days, _and_
/// you don't care as much about the precise Gregorian year. Here's an example
/// that prints all of the ISO 8601 weeks in one ISO 8601 week year:
///
/// ```
/// use jiff::{civil::{ISOWeekDate, Weekday}, ToSpan};
///
/// let target_year = 2024;
/// let iso_week_date = ISOWeekDate::new(target_year, 1, Weekday::Monday)?;
/// // Create a series of dates via the Gregorian calendar. But since a
/// // Gregorian week and an ISO 8601 week calendar week are both 7 days,
/// // this works fine.
/// let weeks = iso_week_date
///     .date()
///     .series(1.week())
///     .map(|d| d.iso_week_date())
///     .take_while(|wd| wd.year() == target_year);
/// for start_of_week in weeks {
///     let end_of_week = start_of_week.last_of_week()?;
///     println!(
///         "ISO week {}: {} - {}",
///         start_of_week.week(),
///         start_of_week.date(),
///         end_of_week.date()
///     );
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct ISOWeekDate {
    pub(crate) inner: JISOWeekDate,
}

impl ISOWeekDate {
    /// The maximum representable ISO week date.
    ///
    /// The maximum corresponds to the ISO week date of the maximum [`Date`]
    /// value. That is, `-9999-01-01`.
    pub const MIN: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MIN };

    /// The minimum representable ISO week date.
    ///
    /// The minimum corresponds to the ISO week date of the minimum [`Date`]
    /// value. That is, `9999-12-31`.
    pub const MAX: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MAX };

    /// The first day of the zeroth year.
    ///
    /// This is guaranteed to be equivalent to `ISOWeekDate::default()`. Note
    /// that this is not equivalent to `Date::default()`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, date};
    ///
    /// assert_eq!(ISOWeekDate::ZERO, ISOWeekDate::default());
    /// // The first day of the 0th year in the ISO week calendar is actually
    /// // the third day of the 0th year in the proleptic Gregorian calendar!
    /// assert_eq!(ISOWeekDate::default().date(), date(0, 1, 3));
    /// ```
    pub const ZERO: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::ZERO };

    /// Create a new ISO week date from it constituent parts.
    ///
    /// If the given values are out of range (based on what is representable
    /// as a [`Date`]), then this returns an error. This will also return an
    /// error if a leap week is given (week number `53`) for a year that does
    /// not contain a leap week.
    ///
    /// # Example
    ///
    /// This example shows some the boundary conditions involving minimum
    /// and maximum dates:
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// // The year 1949 does not contain a leap week.
    /// assert!(ISOWeekDate::new(1949, 53, Weekday::Monday).is_err());
    ///
    /// // Examples of dates at or exceeding the maximum.
    /// let max = ISOWeekDate::new(9999, 52, Weekday::Friday).unwrap();
    /// assert_eq!(max, ISOWeekDate::MAX);
    /// assert_eq!(max.date(), date(9999, 12, 31));
    /// assert!(ISOWeekDate::new(9999, 52, Weekday::Saturday).is_err());
    /// assert!(ISOWeekDate::new(9999, 53, Weekday::Monday).is_err());
    ///
    /// // Examples of dates at or exceeding the minimum.
    /// let min = ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap();
    /// assert_eq!(min, ISOWeekDate::MIN);
    /// assert_eq!(min.date(), date(-9999, 1, 1));
    /// assert!(ISOWeekDate::new(-10000, 52, Weekday::Sunday).is_err());
    /// ```
    #[inline]
    pub fn new(
        year: i16,
        week: i8,
        weekday: Weekday,
    ) -> Result<ISOWeekDate, Error> {
        JISOWeekDate::new(year, week, weekday.to_jcore())
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Converts a Gregorian date to an ISO week date.
    ///
    /// The minimum and maximum allowed values of an ISO week date are
    /// set based on the minimum and maximum values of a `Date`. Therefore,
    /// converting to and from `Date` values is non-lossy and infallible.
    ///
    /// This routine is equivalent to [`Date::iso_week_date`]. This routine
    /// is also available via a `From<Date>` trait implementation for
    /// `ISOWeekDate`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let weekdate = ISOWeekDate::from_date(date(1948, 2, 10));
    /// assert_eq!(
    ///     weekdate,
    ///     ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap(),
    /// );
    /// ```
    #[inline]
    pub fn from_date(date: Date) -> ISOWeekDate {
        date.iso_week_date()
    }

    // N.B. I tried defining a `ISOWeekDate::constant` for defining ISO week
    // dates as constants, but it was too annoying to do. We could do it if
    // there was a compelling reason for it though.

    /// Returns the year component of this ISO 8601 week date.
    ///
    /// The value returned is guaranteed to be in the range `-9999..=9999`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::date;
    ///
    /// let weekdate = date(2019, 12, 30).iso_week_date();
    /// assert_eq!(weekdate.year(), 2020);
    /// ```
    #[inline]
    pub fn year(self) -> i16 {
        self.inner.year()
    }

    /// Returns the week component of this ISO 8601 week date.
    ///
    /// The value returned is guaranteed to be in the range `1..=53`. A
    /// value of `53` can only occur for "long" years. That is, years
    /// with a leap week. This occurs precisely in cases for which
    /// [`ISOWeekDate::in_long_year`] returns `true`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::date;
    ///
    /// let weekdate = date(2019, 12, 30).iso_week_date();
    /// assert_eq!(weekdate.year(), 2020);
    /// assert_eq!(weekdate.week(), 1);
    ///
    /// let weekdate = date(1948, 12, 31).iso_week_date();
    /// assert_eq!(weekdate.year(), 1948);
    /// assert_eq!(weekdate.week(), 53);
    /// ```
    #[inline]
    pub fn week(self) -> i8 {
        self.inner.week()
    }

    /// Returns the day component of this ISO 8601 week date.
    ///
    /// One can use methods on `Weekday` such as
    /// [`Weekday::to_monday_one_offset`]
    /// and
    /// [`Weekday::to_sunday_zero_offset`]
    /// to convert the weekday to a number.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{date, Weekday};
    ///
    /// let weekdate = date(1948, 12, 31).iso_week_date();
    /// assert_eq!(weekdate.year(), 1948);
    /// assert_eq!(weekdate.week(), 53);
    /// assert_eq!(weekdate.weekday(), Weekday::Friday);
    /// assert_eq!(weekdate.weekday().to_monday_zero_offset(), 4);
    /// assert_eq!(weekdate.weekday().to_monday_one_offset(), 5);
    /// assert_eq!(weekdate.weekday().to_sunday_zero_offset(), 5);
    /// assert_eq!(weekdate.weekday().to_sunday_one_offset(), 6);
    /// ```
    #[inline]
    pub fn weekday(self) -> Weekday {
        Weekday::from_jcore(self.inner.weekday())
    }

    /// Returns the ISO 8601 week date corresponding to the first day in the
    /// week of this week date. The date returned is guaranteed to have a
    /// weekday of [`Weekday::Monday`].
    ///
    /// # Errors
    ///
    /// Since `-9999-01-01` falls on a Monday, it follows that the minimum
    /// supported Gregorian date is exactly equivalent to the minimum supported
    /// ISO 8601 week date. This means that this routine can never actually
    /// fail, but only insomuch as the minimums line up. For that reason, and
    /// for consistency with [`ISOWeekDate::last_of_week`], the API is
    /// fallible.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(wd.date(), date(2025, 1, 29));
    /// assert_eq!(
    ///     wd.first_of_week()?,
    ///     ISOWeekDate::new(2025, 5, Weekday::Monday).unwrap(),
    /// );
    ///
    /// // Works even for the minimum date.
    /// assert_eq!(
    ///     ISOWeekDate::MIN.first_of_week()?,
    ///     ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn first_of_week(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .first_of_week()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Returns the ISO 8601 week date corresponding to the last day in the
    /// week of this week date. The date returned is guaranteed to have a
    /// weekday of [`Weekday::Sunday`].
    ///
    /// # Errors
    ///
    /// This can return an error if the last day of the week exceeds Jiff's
    /// maximum Gregorian date of `9999-12-31`. It turns out this can happen
    /// since `9999-12-31` falls on a Friday.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(wd.date(), date(2025, 1, 29));
    /// assert_eq!(
    ///     wd.last_of_week()?,
    ///     ISOWeekDate::new(2025, 5, Weekday::Sunday).unwrap(),
    /// );
    ///
    /// // Unlike `first_of_week`, this routine can actually fail on real
    /// // values, although, only when close to the maximum supported date.
    /// assert_eq!(
    ///     ISOWeekDate::MAX.last_of_week().unwrap_err().to_string(),
    ///     "parameter 'weekday (Monday 1-indexed)' \
    ///      is not in the required range of 1..=7",
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn last_of_week(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .last_of_week()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Returns the ISO 8601 week date corresponding to the first day in the
    /// year of this week date. The date returned is guaranteed to have a
    /// weekday of [`Weekday::Monday`].
    ///
    /// # Errors
    ///
    /// Since `-9999-01-01` falls on a Monday, it follows that the minimum
    /// support Gregorian date is exactly equivalent to the minimum supported
    /// ISO 8601 week date. This means that this routine can never actually
    /// fail, but only insomuch as the minimums line up. For that reason, and
    /// for consistency with [`ISOWeekDate::last_of_year`], the API is
    /// fallible.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(wd.date(), date(2025, 1, 29));
    /// assert_eq!(
    ///     wd.first_of_year()?,
    ///     ISOWeekDate::new(2025, 1, Weekday::Monday).unwrap(),
    /// );
    ///
    /// // Works even for the minimum date.
    /// assert_eq!(
    ///     ISOWeekDate::MIN.first_of_year()?,
    ///     ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(),
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn first_of_year(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .first_of_year()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Returns the ISO 8601 week date corresponding to the last day in the
    /// year of this week date. The date returned is guaranteed to have a
    /// weekday of [`Weekday::Sunday`].
    ///
    /// # Errors
    ///
    /// This can return an error if the last day of the year exceeds Jiff's
    /// maximum Gregorian date of `9999-12-31`. It turns out this can happen
    /// since `9999-12-31` falls on a Friday.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(wd.date(), date(2025, 1, 29));
    /// assert_eq!(
    ///     wd.last_of_year()?,
    ///     ISOWeekDate::new(2025, 52, Weekday::Sunday).unwrap(),
    /// );
    ///
    /// // Works correctly for "long" years.
    /// let wd = ISOWeekDate::new(2026, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(wd.date(), date(2026, 1, 28));
    /// assert_eq!(
    ///     wd.last_of_year()?,
    ///     ISOWeekDate::new(2026, 53, Weekday::Sunday).unwrap(),
    /// );
    ///
    /// // Unlike `first_of_year`, this routine can actually fail on real
    /// // values, although, only when close to the maximum supported date.
    /// assert_eq!(
    ///     ISOWeekDate::MAX.last_of_year().unwrap_err().to_string(),
    ///     "parameter 'weekday (Monday 1-indexed)' \
    ///      is not in the required range of 1..=7",
    /// );
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn last_of_year(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .last_of_year()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Returns the total number of days in the year of this ISO 8601 week
    /// date.
    ///
    /// It is guaranteed that the value returned is either 364 or 371. The
    /// latter case occurs precisely when [`ISOWeekDate::in_long_year`]
    /// returns `true`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday};
    ///
    /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap();
    /// assert_eq!(weekdate.days_in_year(), 364);
    /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap();
    /// assert_eq!(weekdate.days_in_year(), 371);
    /// ```
    #[inline]
    pub fn days_in_year(self) -> i16 {
        self.inner.days_in_year()
    }

    /// Returns the total number of weeks in the year of this ISO 8601 week
    /// date.
    ///
    /// It is guaranteed that the value returned is either 52 or 53. The
    /// latter case occurs precisely when [`ISOWeekDate::in_long_year`]
    /// returns `true`.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday};
    ///
    /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap();
    /// assert_eq!(weekdate.weeks_in_year(), 52);
    /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap();
    /// assert_eq!(weekdate.weeks_in_year(), 53);
    /// ```
    #[inline]
    pub fn weeks_in_year(self) -> i8 {
        self.inner.weeks_in_year()
    }

    /// Returns true if and only if the year of this week date is a "long"
    /// year.
    ///
    /// A long year is one that contains precisely 53 weeks. All other years
    /// contain precisely 52 weeks.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday};
    ///
    /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Monday).unwrap();
    /// assert!(weekdate.in_long_year());
    /// let weekdate = ISOWeekDate::new(1949, 7, Weekday::Monday).unwrap();
    /// assert!(!weekdate.in_long_year());
    /// ```
    #[inline]
    pub fn in_long_year(self) -> bool {
        self.inner.in_long_year()
    }

    /// Returns the ISO 8601 date immediately following this one.
    ///
    /// # Errors
    ///
    /// This returns an error when this date is the maximum value.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(
    ///     wd.tomorrow()?,
    ///     ISOWeekDate::new(2025, 5, Weekday::Thursday).unwrap(),
    /// );
    ///
    /// // The max doesn't have a tomorrow.
    /// assert!(ISOWeekDate::MAX.tomorrow().is_err());
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn tomorrow(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .tomorrow()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Returns the ISO 8601 week date immediately preceding this one.
    ///
    /// # Errors
    ///
    /// This returns an error when this date is the minimum value.
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday};
    ///
    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
    /// assert_eq!(
    ///     wd.yesterday()?,
    ///     ISOWeekDate::new(2025, 5, Weekday::Tuesday).unwrap(),
    /// );
    ///
    /// // The min doesn't have a yesterday.
    /// assert!(ISOWeekDate::MIN.yesterday().is_err());
    ///
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn yesterday(self) -> Result<ISOWeekDate, Error> {
        self.inner
            .yesterday()
            .map(ISOWeekDate::from_jcore)
            .map_err(Error::jcore_range)
    }

    /// Converts this ISO week date to a Gregorian [`Date`].
    ///
    /// The minimum and maximum allowed values of an ISO week date are
    /// set based on the minimum and maximum values of a `Date`. Therefore,
    /// converting to and from `Date` values is non-lossy and infallible.
    ///
    /// This routine is equivalent to [`Date::from_iso_week_date`].
    ///
    /// # Example
    ///
    /// ```
    /// use jiff::civil::{ISOWeekDate, Weekday, date};
    ///
    /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap();
    /// assert_eq!(weekdate.date(), date(1948, 2, 10));
    /// ```
    #[inline]
    pub fn date(self) -> Date {
        Date::from_iso_week_date(self)
    }

    #[inline]
    pub(crate) const fn from_jcore(week_date: JISOWeekDate) -> ISOWeekDate {
        ISOWeekDate { inner: week_date }
    }

    #[inline]
    pub(crate) const fn to_jcore(self) -> JISOWeekDate {
        self.inner
    }
}

impl Default for ISOWeekDate {
    fn default() -> ISOWeekDate {
        ISOWeekDate::ZERO
    }
}

impl core::fmt::Debug for ISOWeekDate {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        core::fmt::Display::fmt(self, f)
    }
}

impl core::fmt::Display for ISOWeekDate {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        use crate::fmt::StdFmtWrite;

        DEFAULT_DATETIME_PRINTER
            .print_iso_week_date(self, StdFmtWrite(f))
            .map_err(|_| core::fmt::Error)
    }
}

impl core::str::FromStr for ISOWeekDate {
    type Err = Error;

    fn from_str(string: &str) -> Result<ISOWeekDate, Error> {
        DEFAULT_DATETIME_PARSER.parse_iso_week_date(string)
    }
}

impl Ord for ISOWeekDate {
    #[inline]
    fn cmp(&self, other: &ISOWeekDate) -> core::cmp::Ordering {
        (self.year(), self.week(), self.weekday().to_monday_one_offset()).cmp(
            &(
                other.year(),
                other.week(),
                other.weekday().to_monday_one_offset(),
            ),
        )
    }
}

impl PartialOrd for ISOWeekDate {
    #[inline]
    fn partial_cmp(&self, other: &ISOWeekDate) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl From<Date> for ISOWeekDate {
    #[inline]
    fn from(date: Date) -> ISOWeekDate {
        ISOWeekDate::from_date(date)
    }
}

impl From<DateTime> for ISOWeekDate {
    #[inline]
    fn from(dt: DateTime) -> ISOWeekDate {
        ISOWeekDate::from(dt.date())
    }
}

impl From<Zoned> for ISOWeekDate {
    #[inline]
    fn from(zdt: Zoned) -> ISOWeekDate {
        ISOWeekDate::from(zdt.date())
    }
}

impl<'a> From<&'a Zoned> for ISOWeekDate {
    #[inline]
    fn from(zdt: &'a Zoned) -> ISOWeekDate {
        ISOWeekDate::from(zdt.date())
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for ISOWeekDate {
    fn format(&self, f: defmt::Formatter) {
        use crate::fmt::DefmtWrite;

        defmt::unwrap!(
            DEFAULT_DATETIME_PRINTER.print_iso_week_date(self, DefmtWrite(f))
        );
    }
}

#[cfg(feature = "serde")]
impl serde_core::Serialize for ISOWeekDate {
    #[inline]
    fn serialize<S: serde_core::Serializer>(
        &self,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        serializer.collect_str(self)
    }
}

#[cfg(feature = "serde")]
impl<'de> serde_core::Deserialize<'de> for ISOWeekDate {
    #[inline]
    fn deserialize<D: serde_core::Deserializer<'de>>(
        deserializer: D,
    ) -> Result<ISOWeekDate, D::Error> {
        use serde_core::de;

        struct ISOWeekDateVisitor;

        impl<'de> de::Visitor<'de> for ISOWeekDateVisitor {
            type Value = ISOWeekDate;

            fn expecting(
                &self,
                f: &mut core::fmt::Formatter,
            ) -> core::fmt::Result {
                f.write_str("an ISO 8601 week date string")
            }

            #[inline]
            fn visit_bytes<E: de::Error>(
                self,
                value: &[u8],
            ) -> Result<ISOWeekDate, E> {
                DEFAULT_DATETIME_PARSER
                    .parse_iso_week_date(value)
                    .map_err(de::Error::custom)
            }

            #[inline]
            fn visit_str<E: de::Error>(
                self,
                value: &str,
            ) -> Result<ISOWeekDate, E> {
                self.visit_bytes(value.as_bytes())
            }
        }

        deserializer.deserialize_str(ISOWeekDateVisitor)
    }
}
