///|
pub(all) enum Value {
  Null
  Bool(Bool)
  Number(Double)
  String(String)
  Array(Array[Value])
  Object(Map[String, Value])
} derive(Eq, @debug.Debug)

///|
pub fn from_json(value : Json) -> Value {
  match value {
    Json::Null => Null
    Json::True => Bool(true)
    Json::False => Bool(false)
    Json::Number(n, ..) => Number(n)
    Json::String(s) => String(s)
    Json::Array(items) => Array(items.map(from_json))
    Json::Object(fields) => Object(fields.map(fn(_, v) { from_json(v) }))
  }
}

///|
pub fn Value::to_json(self : Value) -> Json {
  match self {
    Null => Json::null()
    Bool(b) => Json::boolean(b)
    Number(n) => Json::number(n)
    String(s) => Json::string(s)
    Array(items) => Json::array(items.map(fn(v) { v.to_json() }))
    Object(fields) => Json::object(fields.map(fn(_, v) { v.to_json() }))
  }
}

///|
pub fn Value::canonical(self : Value) -> String {
  match self {
    Object(fields) => {
      let keys = fields.keys().collect()
      keys.sort_by(lexical_compare)
      "{" +
      keys
      .map(fn(k) {
        String(k).to_json().stringify() + ":" + fields[k].canonical()
      })
      .join(",") +
      "}"
    }
    Array(items) => "[" + items.map(fn(v) { v.canonical() }).join(",") + "]"
    _ => self.to_json().stringify()
  }
}

///|
fn lexical_compare(left : String, right : String) -> Int32 {
  let a = left.to_array()
  let b = right.to_array()
  let size = if a.length() < b.length() { a.length() } else { b.length() }
  for i in 0.. b[i] {
      return 1
    }
  }
  a.length() - b.length()
}

///|
pub(all) suberror InputError {
  InputError(String)
} derive(@debug.Debug)

///|
fn object(value : Value) -> Map[String, Value] raise InputError {
  match value {
    Object(v) => v
    _ => raise InputError("expected object")
  }
}

///|
fn array(value : Value) -> Array[Value] raise InputError {
  match value {
    Array(v) => v
    _ => raise InputError("expected array")
  }
}

///|
fn string(value : Value) -> String raise InputError {
  match value {
    String(v) => v
    _ => raise InputError("expected string")
  }
}

///|
fn field(value : Value, key : String) -> Value {
  match value {
    Object(v) => v.get(key).unwrap_or(Null)
    _ => Null
  }
}

///|
fn has(value : Value, key : String) -> Bool {
  match value {
    Object(v) => v.contains(key)
    _ => false
  }
}

///|
fn required(value : Value, key : String) -> Value raise InputError {
  if !has(value, key) {
    raise InputError("missing field: " + key)
  }
  field(value, key)
}

///|
fn fields_only(value : Value, allowed : Array[String]) -> Unit raise InputError {
  for key, _ in object(value) {
    if !allowed.contains(key) {
      raise InputError("unknown field: " + key)
    }
  }
}

///|
fn record(items : Array[(String, Value)]) -> Value {
  let result : Map[String, Value] = Map([])
  for (key, value) in items {
    result[key] = value
  }
  Object(result)
}

///|
fn strings(items : Array[String]) -> Value {
  Array(items.map(fn(s) { String(s) }))
}

///|
fn identifier(value : Value) -> String raise InputError {
  let result = string(value)
  if result.is_empty() || result.length() > 256 {
    raise InputError("identifier length must be 1..256")
  }
  result
}

///|
fn ordinal(value : Value) -> Double raise InputError {
  match value {
    Number(n) if !n.is_nan() &&
      !n.is_inf() &&
      n >= 0.0 &&
      n <= 9007199254740991.0 &&
      n.trunc() == n => n
    _ =>
      raise InputError(
        "event ordinal must be an exact nonnegative JSON integer",
      )
  }
}

///|
fn limit(
  value : Value,
  key : String,
  fallback : Int32,
  maximum : Int32,
) -> Int32 raise InputError {
  if !has(value, key) {
    return fallback
  }
  let n = ordinal(field(value, key))
  if n < 1.0 || n > maximum.to_double() {
    raise InputError("budget out of range: " + key)
  }
  n.to_int()
}

///|
fn validate_value(
  value : Value,
  depth : Int32,
  nodes : Ref[Int32],
) -> Unit raise InputError {
  nodes.val += 1
  if depth > 32 || nodes.val > 20000 {
    raise InputError("model data exceeds depth or node budget")
  }
  match value {
    Number(n) =>
      if n.is_nan() || n.is_inf() {
        raise InputError("non-finite model value")
      }
    Array(items) =>
      for item in items {
        validate_value(item, depth + 1, nodes)
      }
    Object(items) =>
      for _, item in items {
        validate_value(item, depth + 1, nodes)
      }
    String(s) =>
      if s.length() > 65536 {
        raise InputError("model string exceeds 65536 units")
      }
    _ => ()
  }
}