///|
/// Calendar utilities: leap year detection, days-in-month,
/// and date arithmetic for cron calculations.

///|
/// Check if a year is a leap year in the Gregorian calendar.
pub fn is_leap_year(year : Int) -> Bool {
  if year % 400 == 0 {
    true
  } else if year % 100 == 0 {
    false
  } else {
    year % 4 == 0
  }
}

///|
/// Return the number of days in a given month (1-indexed).
pub fn days_in_month(year : Int, month : Int) -> Int {
  match month {
    1 => 31
    2 => if is_leap_year(year) { 29 } else { 28 }
    3 => 31
    4 => 30
    5 => 31
    6 => 30
    7 => 31
    8 => 31
    9 => 30
    10 => 31
    11 => 30
    12 => 31
    _ => 0
  }
}

///|
/// Add one minute to a DateTime, handling all overflow cases
/// including cross-day, cross-month, and cross-year.
pub fn add_minute(dt : DateTime) -> DateTime {
  let minute = dt.minute + 1
  if minute < 60 {
    return { ..dt, minute, second: 0 }
  }
  // Minute overflow: advance hour
  let hour = dt.hour + 1
  if hour < 24 {
    return { ..dt, hour, minute: 0, second: 0 }
  }
  // Hour overflow: advance day
  let day = dt.day + 1
  let dim = days_in_month(dt.year, dt.month)
  if day <= dim {
    return { ..dt, day, hour: 0, minute: 0, second: 0 }
  }
  // Day overflow: advance month
  let month = dt.month + 1
  if month <= 12 {
    return { ..dt, month, day: 1, hour: 0, minute: 0, second: 0 }
  }
  // Month overflow: advance year
  { year: dt.year + 1, month: 1, day: 1, hour: 0, minute: 0, second: 0 }
}

///|
/// Add one second to a DateTime, handling all overflow cases.
pub fn add_second(dt : DateTime) -> DateTime {
  if dt.second + 1 < 60 {
    return { ..dt, second: dt.second + 1 }
  }
  if dt.minute + 1 < 60 {
    return { ..dt, minute: dt.minute + 1, second: 0 }
  }
  if dt.hour + 1 < 24 {
    return { ..dt, hour: dt.hour + 1, minute: 0, second: 0 }
  }
  let dim = days_in_month(dt.year, dt.month)
  if dt.day + 1 <= dim {
    return { ..dt, day: dt.day + 1, hour: 0, minute: 0, second: 0 }
  }
  if dt.month + 1 <= 12 {
    return { ..dt, month: dt.month + 1, day: 1, hour: 0, minute: 0, second: 0 }
  }
  { year: dt.year + 1, month: 1, day: 1, hour: 0, minute: 0, second: 0 }
}

///|
/// Get the day of week for a given date.
/// Returns 0=Sunday, 1=Monday, ..., 6=Saturday
/// Uses Zeller's congruence.
pub fn day_of_week(year : Int, month : Int, day : Int) -> Int {
  let m = if month < 3 { month + 12 } else { month }
  let y = if month < 3 { year - 1 } else { year }
  let k = y % 100
  let j = y / 100
  // Zeller's congruence for Gregorian calendar
  let h = day + 13 * (m + 1) / 5 + k + k / 4 + j / 4 + 5 * j
  let result = h % 7
  // Convert Zeller result (0=Saturday) to our format (0=Sunday)
  match result {
    0 => 6 // Saturday
    1 => 0 // Sunday
    2 => 1 // Monday
    3 => 2 // Tuesday
    4 => 3 // Wednesday
    5 => 4 // Thursday
    6 => 5 // Friday
    _ => 0 // should not happen
  }
}

///|
/// Validate that a date actually exists in the calendar.
pub fn is_valid_date(year : Int, month : Int, day : Int) -> Bool {
  if month < 1 || month > 12 {
    return false
  }
  if day < 1 || day > days_in_month(year, month) {
    return false
  }
  true
}

///|
/// Validate a full DateTime.
pub fn is_valid_datetime(dt : DateTime) -> Bool {
  if dt.hour < 0 || dt.hour > 23 {
    return false
  }
  if dt.minute < 0 || dt.minute > 59 {
    return false
  }
  if dt.second < 0 || dt.second > 59 {
    return false
  }
  is_valid_date(dt.year, dt.month, dt.day)
}