///|
/// A month and a day with no year, such as a recurring anniversary.
///
/// A reference year is kept internally so the pair names a concrete date;
/// 1972 is used because it is a leap year, so February 29 survives the round
/// trip.
///
/// Reference: 
pub struct PlainMonthDay {
  iso : IsoDate
  calendar : Calendar
} derive(Eq)

///|
pub impl Show for PlainMonthDay with fn output(self, logger) {
  logger.write_string(self.to_string_with_options(DisplayCalendar::default()))
}

///|
pub impl Debug for PlainMonthDay with fn to_repr(self) {
  Repr::Repr(self.to_string())
}

///|
/// The reference year the specification uses for a bare month-day.
pub const MONTH_DAY_REFERENCE_YEAR : Int = 1972

///|
/// Creates a month-day.
///
/// ```mbt check
/// test {
///   inspect(@temporal.PlainMonthDay::try_new(2, 29), content="02-29")
/// }
/// ```
pub fn PlainMonthDay::try_new(
  month : Int,
  day : Int,
  reference_year? : Int = MONTH_DAY_REFERENCE_YEAR,
  overflow? : Overflow = Reject,
  calendar? : Calendar = Calendar::ISO,
) -> PlainMonthDay raise TemporalError {
  let iso = IsoDate::new_with_overflow(reference_year, month, day, overflow)
  { iso, calendar }
}

///|
/// Returns the month, 1-based.
pub fn PlainMonthDay::month(self : PlainMonthDay) -> Int {
  self.iso.month
}

///|
/// Returns the calendar-independent month code.
pub fn PlainMonthDay::month_code(self : PlainMonthDay) -> String {
  month_code_of(self.iso.month)
}

///|
/// Returns the day of the month.
pub fn PlainMonthDay::day(self : PlainMonthDay) -> Int {
  self.iso.day
}

///|
/// Returns the calendar.
pub fn PlainMonthDay::calendar(self : PlainMonthDay) -> Calendar {
  self.calendar
}

///|
/// Returns the internal reference year.
pub fn PlainMonthDay::reference_year(self : PlainMonthDay) -> Int {
  self.iso.year
}

///|
/// Returns whether both name the same month and day in the same calendar.
pub fn PlainMonthDay::equals(
  self : PlainMonthDay,
  other : PlainMonthDay,
) -> Bool {
  self.iso == other.iso && self.calendar == other.calendar
}

///|
/// Returns a copy with the month or day replaced.
pub fn PlainMonthDay::with_fields(
  self : PlainMonthDay,
  month? : Int,
  day? : Int,
  overflow? : Overflow = Constrain,
) -> PlainMonthDay raise TemporalError {
  PlainMonthDay::try_new(
    month.unwrap_or(self.iso.month),
    day.unwrap_or(self.iso.day),
    reference_year=self.iso.year,
    overflow~,
    calendar=self.calendar,
  )
}

///|
/// Combines this month-day with a year to produce a `PlainDate`.
///
/// ```mbt check
/// test {
///   let leap_day = @temporal.PlainMonthDay::try_new(2, 29)
///   // 2023 is not a leap year, so the day is constrained to the 28th.
///   inspect(leap_day.to_plain_date(2023), content="2023-02-28")
/// }
/// ```
pub fn PlainMonthDay::to_plain_date(
  self : PlainMonthDay,
  year : Int,
  overflow? : Overflow = Constrain,
) -> PlainDate raise TemporalError {
  PlainDate::new_with_overflow(
    year,
    self.iso.month,
    self.iso.day,
    overflow,
    self.calendar,
  )
}

///|
/// Parses a month-day from an RFC 9557 string.
///
/// ```mbt check
/// test {
///   inspect(@temporal.PlainMonthDay::of_string("--02-29"), content="02-29")
///   inspect(@temporal.PlainMonthDay::of_string("2024-02-29"), content="02-29")
/// }
/// ```
pub fn PlainMonthDay::of_string(
  source : String,
) -> PlainMonthDay raise TemporalError {
  let record = parse_month_day_string(source)
  let date = temporal_unwrap(record.date, "date component")
  // The reference year is always 1972, whether or not the string named one:
  // a month-day has no year of its own, and 1972 is a leap year so that
  // February 29 survives.
  PlainMonthDay::try_new(
    date.month,
    date.day,
    reference_year=MONTH_DAY_REFERENCE_YEAR,
    calendar=calendar_of(record),
  )
}

///|
/// Renders the month-day as `MM-DD`.
///
/// The reference year is included when a calendar annotation is present,
/// since a non-ISO calendar needs it to reconstruct the same day.
pub fn PlainMonthDay::to_string_with_options(
  self : PlainMonthDay,
  display_calendar : DisplayCalendar,
) -> String {
  let buf = StringBuilder::new()
  if display_calendar is (Always | Critical) || self.calendar != Calendar::ISO {
    write_year(buf, self.iso.year)
    buf.write_string("-")
  }
  write_padded_2(buf, self.iso.month)
  buf.write_string("-")
  write_padded_2(buf, self.iso.day)
  write_calendar_annotation(buf, self.calendar, display_calendar)
  buf.to_string()
}

///|
/// Renders the month-day as `MM-DD`.
pub fn PlainMonthDay::to_string(self : PlainMonthDay) -> String {
  self.to_string_with_options(DisplayCalendar::default())
}