///|
/// Rank of a JSON value's type in jq's ordering:
/// null < false < true < numbers < strings < arrays < objects.
fn json_type_rank(v : Json) -> Int {
  match v {
    Null => 0
    False => 1
    True => 2
    Number(_) => 3
    String(_) => 4
    Array(_) => 5
    Object(_) => 6
  }
}

///|
/// Compare JSON values using jq's total order (used by comparisons, `==`,
/// sorting, `unique`, `min`/`max`, ...).
fn compare_json(a : Json, b : Json) -> Int {
  let rank_a = json_type_rank(a)
  let rank_b = json_type_rank(b)
  if rank_a != rank_b {
    return rank_a.compare(rank_b)
  }
  match (a, b) {
    (Number(x, ..), Number(y, ..)) => x.compare(y)
    (String(x), String(y)) => compare_code_points(x, y)
    (Array(x), Array(y)) => compare_array_view(x, y)
    (Object(x), Object(y)) => compare_objects(x, y)
    // Same rank and not a container, number or string: null/false/true.
    _ => 0
  }
}

///|
/// Compare strings lexicographically by Unicode code point, as jq does.
/// (MoonBit's `String::compare` orders by length first.)
fn compare_code_points(a : String, b : String) -> Int {
  let iter_a = a.iter()
  let iter_b = b.iter()
  while true {
    match (iter_a.next(), iter_b.next()) {
      (None, None) => return 0
      (None, Some(_)) => return -1
      (Some(_), None) => return 1
      (Some(x), Some(y)) => if x != y { return x.compare(y) }
    }
  }
  0
}

///|
fn sorted_object_keys(obj : Map[String, Json]) -> Array[String] {
  let keys = obj.keys().collect()
  keys.sort_by(compare_code_points)
  keys
}

///|
/// Objects compare first by their sorted key arrays, then value by value in
/// sorted key order.
fn compare_objects(x : Map[String, Json], y : Map[String, Json]) -> Int {
  let keys_x = sorted_object_keys(x)
  let keys_y = sorted_object_keys(y)
  let common = if keys_x.length() < keys_y.length() {
    keys_x.length()
  } else {
    keys_y.length()
  }
  for i in 0.. Int {
  match (a, b) {
    ([], []) => 0
    ([], _) => -1
    (_, []) => 1
    ([head_a, .. tail_a], [head_b, .. tail_b]) => {
      let cmp = compare_json(head_a, head_b)
      if cmp != 0 {
        cmp
      } else {
        compare_array_view(tail_a, tail_b)
      }
    }
  }
}