///|
/// Human-readable JSON kind used in diagnostics.
fn json_kind(value : Json) -> String {
  match value {
    Null => "null"
    True | False => "boolean"
    Number(_, ..) => "number"
    String(_) => "string"
    Array(_) => "array"
    Object(_) => "object"
  }
}

///|
fn json_object(value : Json) -> Map[String, Json]? {
  match value {
    Object(members) => Some(members)
    _ => None
  }
}

///|
fn json_string(value : Json) -> String? {
  match value {
    String(text) => Some(text)
    _ => None
  }
}

///|
/// Status is an integer JSON number representable by MoonBit Int.
fn json_int(value : Json) -> Int? {
  match value {
    Number(number, ..) =>
      if number.is_nan() ||
        number.is_inf() ||
        number.trunc() != number ||
        number < -2_147_483_648.0 ||
        number > 2_147_483_647.0 {
        None
      } else {
        Some(number.to_int())
      }
    _ => None
  }
}

///|
fn map_keys(members : Map[String, Json]) -> Array[String] {
  let keys : Array[String] = []
  for key in members.keys() {
    keys.push(key)
  }
  keys
}

///|
fn is_reserved_member(name : String) -> Bool {
  name == "type" ||
  name == "title" ||
  name == "status" ||
  name == "detail" ||
  name == "instance"
}

///|
/// UTF-8 byte length, not code point length.
fn utf8_length(value : String) -> Int {
  @utf8.encode(value).length()
}

///|
/// Code-unit lexical ordering, independent of MoonBit's shortlex String Compare.
fn lexical_compare(left : String, right : String) -> Int {
  let a = left.to_array()
  let b = right.to_array()
  let length = if a.length() < b.length() { a.length() } else { b.length() }
  for i = 0; i < length; i = i + 1 {
    if a[i] < b[i] {
      return -1
    }
    if a[i] > b[i] {
      return 1
    }
  }
  if a.length() < b.length() {
    -1
  } else if a.length() > b.length() {
    1
  } else {
    0
  }
}