///|
fn two_digits(value : Int32) -> String {
  if value < 10 {
    "0" + value.to_string()
  } else {
    value.to_string()
  }
}

///|
fn four_digits(value : Int32) -> String {
  if value < 10 {
    "000" + value.to_string()
  } else if value < 100 {
    "00" + value.to_string()
  } else if value < 1000 {
    "0" + value.to_string()
  } else {
    value.to_string()
  }
}

///|
fn six_digits(value : Int32) -> String {
  if value < 10 {
    "00000" + value.to_string()
  } else if value < 100 {
    "0000" + value.to_string()
  } else if value < 1000 {
    "000" + value.to_string()
  } else if value < 10000 {
    "00" + value.to_string()
  } else if value < 100000 {
    "0" + value.to_string()
  } else {
    value.to_string()
  }
}

///|
let month_short : Array[String] = [
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
]

///|
let month_long : Array[String] = [
  "January", "February", "March", "April", "May", "June", "July", "August", "September",
  "October", "November", "December",
]

///|
let weekday_short : Array[String] = [
  "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
]

///|
let weekday_long : Array[String] = [
  "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday",
]

///|
/// Format standard strftime date/time directives in the deterministic C locale.
/// Week and day fields refer to this value's local calendar. Unknown directives
/// are retained literally; platform flags and arbitrary locales are unsupported.
///
/// ```mbt check
/// test {
///   let date = @date.datetime(2021, 1, 1, hour=13)
///   assert_eq(
///     @date.format_date(date, "%G-W%V-%u %j %I %p"),
///     "2020-W53-5 001 01 PM",
///   )
/// }
/// ```
pub fn format_date(value : DateTime, format : String) -> String {
  let result = StringBuilder()
  let chars = format.to_array()
  let mut index = 0
  while index < chars.length() {
    if chars[index] != '%' || index + 1 >= chars.length() {
      result.write_char(chars[index])
      index += 1
      continue
    }
    index += 1
    match chars[index] {
      'Y' => result.write_string(four_digits(value.year))
      'y' => result.write_string(two_digits(value.year % 100))
      'm' => result.write_string(two_digits(value.month))
      'd' => result.write_string(two_digits(value.day))
      'H' => result.write_string(two_digits(value.hour))
      'I' =>
        result.write_string(
          two_digits(if value.hour % 12 == 0 { 12 } else { value.hour % 12 }),
        )
      'p' => result.write_string(if value.hour < 12 { "AM" } else { "PM" })
      'M' => result.write_string(two_digits(value.minute))
      'S' => result.write_string(two_digits(value.second))
      'f' => result.write_string(six_digits(value.microsecond))
      'z' => result.write_string(format_timezone_offset(value.offset_seconds))
      'Z' =>
        if value.abbreviation is Some(name) {
          result.write_string(name)
        } else if value.offset_seconds == 0 {
          result.write_string("UTC")
        } else {
          result.write_string("UTC")
          result.write_string(format_timezone_name_offset(value.offset_seconds))
        }
      'b' => result.write_string(month_short[value.month - 1])
      'B' => result.write_string(month_long[value.month - 1])
      'a' => result.write_string(weekday_short[value.weekday()])
      'A' => result.write_string(weekday_long[value.weekday()])
      'w' => result.write_string(((value.weekday() + 1) % 7).to_string())
      'j' => result.write_string(three_digits(value.day_of_year()))
      'U' => result.write_string(two_digits(value.calendar_week(false)))
      'W' => result.write_string(two_digits(value.calendar_week(true)))
      'u' => result.write_string((value.weekday() + 1).to_string())
      'G' => result.write_string(four_digits(value.iso_week().0))
      'V' => result.write_string(two_digits(value.iso_week().1))
      'c' => {
        result.write_string(weekday_short[value.weekday()])
        result.write_char(' ')
        result.write_string(month_short[value.month - 1])
        result.write_char(' ')
        if value.day < 10 {
          result.write_char(' ')
        }
        result.write_string(value.day.to_string())
        result.write_char(' ')
        result.write_string(
          two_digits(value.hour) +
          ":" +
          two_digits(value.minute) +
          ":" +
          two_digits(value.second),
        )
        result.write_char(' ')
        result.write_string(four_digits(value.year))
      }
      'x' =>
        result.write_string(
          two_digits(value.month) +
          "/" +
          two_digits(value.day) +
          "/" +
          two_digits(value.year % 100),
        )
      'X' =>
        result.write_string(
          two_digits(value.hour) +
          ":" +
          two_digits(value.minute) +
          ":" +
          two_digits(value.second),
        )
      '%' => result.write_char('%')
      _ => {
        result.write_char('%')
        result.write_char(chars[index])
      }
    }
    index += 1
  }
  result.to_string()
}

///|
fn format_timezone_offset(offset_seconds : Int32) -> String {
  let sign = if offset_seconds < 0 { "-" } else { "+" }
  let absolute = Int32::abs(offset_seconds)
  sign +
  two_digits(absolute / 3600) +
  two_digits(absolute / 60 % 60) +
  (if absolute % 60 == 0 { "" } else { two_digits(absolute % 60) })
}

///|
fn format_timezone_name_offset(offset_seconds : Int32) -> String {
  let sign = if offset_seconds < 0 { "-" } else { "+" }
  let absolute = Int32::abs(offset_seconds)
  sign +
  two_digits(absolute / 3600) +
  ":" +
  two_digits(absolute / 60 % 60) +
  (if absolute % 60 == 0 { "" } else { ":" + two_digits(absolute % 60) })
}