///|
fn pdf_metadata_pad2(value : Int) -> String {
  let absolute = if value < 0 { -value } else { value }
  if absolute < 10 {
    "0" + absolute.to_string()
  } else {
    absolute.to_string()
  }
}

///|
fn pdf_metadata_pad4(value : Int) -> 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 pdf_metadata_xmp_date(date : String) -> String raise @core.PdfError {
  if !date.has_prefix("D:") {
    "0000-01-01T00:00:00Z"
  } else {
    let parsed = @date.pdf_date_of_string(date)
    let tzd = if parsed.hour_offset == 0 && parsed.minute_offset == 0 {
      "Z"
    } else {
      let sign = if parsed.hour_offset < 0 || parsed.minute_offset < 0 {
        "-"
      } else {
        "+"
      }
      sign +
      pdf_metadata_pad2(parsed.hour_offset) +
      ":" +
      pdf_metadata_pad2(parsed.minute_offset)
    }
    pdf_metadata_pad4(parsed.year) +
    "-" +
    pdf_metadata_pad2(parsed.month) +
    "-" +
    pdf_metadata_pad2(parsed.day) +
    "T" +
    pdf_metadata_pad2(parsed.hour) +
    ":" +
    pdf_metadata_pad2(parsed.minute) +
    ":" +
    pdf_metadata_pad2(parsed.second) +
    tzd
  }
}

///|
fn pdf_metadata_is_leap_year(year : Int) -> Bool {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

///|
fn pdf_metadata_days_in_month(year : Int, month : Int) -> Int {
  match month {
    1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
    4 | 6 | 9 | 11 => 30
    2 => if pdf_metadata_is_leap_year(year) { 29 } else { 28 }
    _ => 30
  }
}

///|
fn pdf_metadata_pdf_date_from_unix_millis(milliseconds : UInt64) -> String {
  let total_seconds = (milliseconds / (1000 : UInt64)).reinterpret_as_int64()
  let mut days = (total_seconds / 86400L).to_int()
  let seconds_of_day = (total_seconds % 86400L).to_int()
  let hour = seconds_of_day / 3600
  let minute = seconds_of_day % 3600 / 60
  let second = seconds_of_day % 60
  let mut year = 1970
  let mut year_days = if pdf_metadata_is_leap_year(year) { 366 } else { 365 }
  while days >= year_days {
    days -= year_days
    year += 1
    year_days = if pdf_metadata_is_leap_year(year) { 366 } else { 365 }
  }
  let mut month = 1
  while days >= pdf_metadata_days_in_month(year, month) {
    days -= pdf_metadata_days_in_month(year, month)
    month += 1
  }
  let day = days + 1
  "D:" +
  pdf_metadata_pad4(year) +
  pdf_metadata_pad2(month) +
  pdf_metadata_pad2(day) +
  pdf_metadata_pad2(hour) +
  pdf_metadata_pad2(minute) +
  pdf_metadata_pad2(second)
}

///|
fn pdf_metadata_expand_date(date : String) -> String {
  if date == "now" {
    match @env.get_env_var("CPDF_REPRODUCIBLE_DATES") {
      Some("true") =>
        @date.pdf_strftime_with_time(
          @date.pdf_strftime_dummy_time(),
          "D:%Y%m%d%H%M%S",
        )
      _ => pdf_metadata_pdf_date_from_unix_millis(@env.now())
    }
  } else {
    date
  }
}

///|
/// Expand cpdf's metadata date alias.
///
/// This mirrors `Cpdfmetadata.expand_date`: `"now"` becomes a PDF date string
/// and all other inputs are returned unchanged. Setting
/// `CPDF_REPRODUCIBLE_DATES=true` uses the deterministic dummy timestamp.
pub fn pdf_expand_date(date : String) -> String {
  pdf_metadata_expand_date(date)
}