///|
/// Whether `value` is an exact TUF timestamp: `YYYY-MM-DDTHH:MM:SSZ`.
///
/// TUF metadata carries second-resolution UTC timestamps. Calendar validity is
/// checked too, including leap years, so `2026-02-31T00:00:00Z` is rejected.
/// Fractional seconds and numeric offsets are intentionally not accepted: the
/// specification requires `Z`.
fn valid_utc_time(value : String) -> Bool {
  let chars = value.iter().to_array()
  if chars.length() != 20 ||
    chars[4] != '-' ||
    chars[7] != '-' ||
    chars[10] != 'T' ||
    chars[13] != ':' ||
    chars[16] != ':' ||
    chars[19] != 'Z' {
    return false
  }
  for index in [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18] {
    if chars[index] < '0' || chars[index] > '9' {
      return false
    }
  }
  let month = digit_pair(chars[5], chars[6])
  let day = digit_pair(chars[8], chars[9])
  let year = digit_pair(chars[0], chars[1]) * 100 +
    digit_pair(chars[2], chars[3])
  let hour = digit_pair(chars[11], chars[12])
  let minute = digit_pair(chars[14], chars[15])
  let second = digit_pair(chars[17], chars[18])
  let max_day = match month {
    2 =>
      if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) {
        29
      } else {
        28
      }
    4 | 6 | 9 | 11 => 30
    _ => 31
  }
  month >= 1 &&
  month <= 12 &&
  day >= 1 &&
  day <= max_day &&
  hour <= 23 &&
  minute <= 59 &&
  second <= 59
}

///|
/// The integer value of two decimal ASCII digits.
fn digit_pair(a : Char, b : Char) -> Int {
  (a.to_int() - 48) * 10 + b.to_int() - 48
}

///|
/// Whether `value` is a dotted numeric version such as `1`, `1.0` or `1.0.31`.
///
/// TUF metadata carries the specification version that the file was written
/// against. This accepts the numeric forms repositories use in practice and
/// rejects anything else, so a typo like `v1.0` or `1.0.31-beta` is surfaced
/// rather than silently ignored. It intentionally does not enforce a fixed
/// number of components: clients are required to be tolerant of extra ones.
fn valid_spec_version(value : String) -> Bool {
  if value == "" {
    return false
  }
  let mut digits = 0
  for char in value.iter() {
    if char == '.' {
      if digits == 0 {
        // Leading dot or two dots in a row.
        return false
      }
      digits = 0
    } else if char >= '0' && char <= '9' {
      digits += 1
    } else {
      return false
    }
  }
  digits > 0
}

///|
/// Lexicographic ordering of two `YYYY-MM-DDTHH:MM:SSZ` strings.
///
/// Because the format is fixed-width and big-endian, string comparison is the
/// same as chronological comparison. Both inputs must already have passed
/// [`valid_utc_time`]: they are known to be 20 characters long, so indexing is
/// safe. Returns `true` when `a` is at or before `b`.
fn utc_not_after(a : String, b : String) -> Bool {
  let left = a.iter().to_array()
  let right = b.iter().to_array()
  for index in 0..<20 {
    if left[index] < right[index] {
      return true
    }
    if left[index] > right[index] {
      return false
    }
  }
  true
}

///|
/// The `signed` object of a metadata envelope, or `None` when `input` is not
/// JSON, is not an object, or has no object-valued `signed` field.
fn signed_object(input : String) -> Map[String, Json]? {
  let document = @json.parse(input) catch { _ => return None }
  guard document is Object(envelope) else { return None }
  match envelope.get("signed") {
    Some(Object(value)) => Some(value)
    _ => None
  }
}

///|
/// Narrow a JSON value to a TUF `version` integer.
fn positive_version(value : Json?) -> Int? {
  match value {
    Some(Number(number, ..)) if in_int_range(number) => Some(number.to_int())
    _ => None
  }
}

///|
/// The `signed.version` of a metadata document, or `None` when it is absent or
/// not a valid version integer.
fn metadata_version(input : String) -> Int? {
  match signed_object(input) {
    Some(signed) => positive_version(signed.get("version"))
    None => None
  }
}

///|
/// The version that a `timestamp` or `snapshot` document references for the
/// meta file `name`, or `None` when the reference or its version is missing.
fn referenced_version(input : String, name : String) -> Int? {
  match signed_object(input) {
    Some(signed) =>
      match signed.get("meta") {
        Some(Object(meta)) =>
          match meta.get(name) {
            Some(Object(entry)) => positive_version(entry.get("version"))
            _ => None
          }
        _ => None
      }
    None => None
  }
}