///|
fn decode_json(text : String) -> Json raise FcaError {
  check_text_size(text)
  let mut depth = 0
  let mut quoted = false
  let mut escaped = false
  for c in text.to_array() {
    if quoted {
      if escaped {
        escaped = false
      } else if c == '\\' {
        escaped = true
      } else if c == '"' {
        quoted = false
      }
    } else if c == '"' {
      quoted = true
    } else if c == '[' || c == '{' {
      depth = depth + 1
      if depth > 16 {
        raise Budget("JSON nesting exceeds 16")
      }
    } else if c == ']' || c == '}' {
      depth = depth - 1
    }
  }
  @json.parse(text[:]) catch {
    _ => raise Invalid("invalid JSON syntax or excessive nesting")
  }
}

///|
fn json_fields(
  value : Json,
  keys : Array[String],
) -> Map[String, Json] raise FcaError {
  match value {
    Object(fields) => {
      if fields.length() != keys.length() {
        raise Invalid("JSON schema has missing or unknown fields")
      }
      for key in keys {
        if !fields.contains(key) {
          raise Invalid("JSON schema is missing a required field")
        }
      }
      fields
    }
    _ => raise Invalid("JSON schema requires an object")
  }
}

///|
fn json_required(
  fields : Map[String, Json],
  key : String,
) -> Json raise FcaError {
  match fields.get(key) {
    Some(value) => value
    None => raise Invalid("JSON required field is missing")
  }
}

///|
fn json_array(value : Json, maximum : Int32) -> Array[Json] raise FcaError {
  match value {
    Array(values) => {
      if values.length() > maximum {
        raise Budget("JSON array exceeds its schema limit")
      }
      values
    }
    _ => raise Invalid("JSON schema requires an array")
  }
}

///|
fn json_integer(value : Json, maximum : Int32) -> Int32 raise FcaError {
  match value {
    Number(n, ..) => {
      if n.is_nan() || n.is_inf() || n < 0.0 || n > maximum.to_double() {
        raise Invalid("JSON integer is outside its supported range")
      }
      let integer = n.to_int()
      if integer.to_double() != n {
        raise Invalid("JSON index or dimension is not an integer")
      }
      integer
    }
    _ => raise Invalid("JSON schema requires an integer")
  }
}

///|
fn json_labels(value : Json, maximum : Int32) -> Array[String] raise FcaError {
  json_array(value, maximum).map(v => {
    match v {
      String(label) => label
      _ => raise Invalid("JSON labels must be strings")
    }
  })
}

///|
fn json_indices(value : Json, dimension : Int32) -> Array[Int32] raise FcaError {
  let indices = json_array(value, 128).map(v => json_integer(v, 127))
  checked_indices(indices, dimension)
}

///|
fn json_schema(
  fields : Map[String, Json],
  expected : String,
) -> Unit raise FcaError {
  match json_required(fields, "schema") {
    String(actual) if actual == expected => ()
    _ => raise Invalid("unsupported JSON schema version")
  }
}

///|
/// Strict typed schema after core JSON parsing. Duplicate object keys follow core's last-wins policy.
/// This data interchange is not a signed/security-policy canonicalization format.
pub fn parse_context_json(text : String) -> Context raise FcaError {
  let fields = json_fields(decode_json(text), [
    "schema", "objects", "attributes", "rows",
  ])
  json_schema(fields, "moonconcept-context/v1")
  let objects = json_labels(json_required(fields, "objects"), 4096)
  let attributes = json_labels(json_required(fields, "attributes"), 128)
  let rows = json_array(json_required(fields, "rows"), 4096).map(row => {
    json_array(row, 128).map(cell => {
      match cell {
        True => true
        False => false
        _ => raise Invalid("JSON incidence cells must be booleans")
      }
    })
  })
  context(objects, attributes, rows)
}

///|
pub fn Context::to_json(self : Context) -> Json {
  Json::object({
    "schema": Json::string("moonconcept-context/v1"),
    "objects": Json::array(self.object_names.map(fn(s) { Json::string(s) })),
    "attributes": Json::array(
      self.attribute_names.map(fn(s) { Json::string(s) }),
    ),
    "rows": Json::array(
      self.incidence.map(fn(row) {
        Json::array(row.map(fn(cell) { Json::boolean(cell) }))
      }),
    ),
  })
}

///|
pub fn Context::to_json_string(self : Context) -> String {
  self.to_json().stringify()
}

///|
pub fn parse_rules_json(text : String) -> RuleSystem raise FcaError {
  let fields = json_fields(decode_json(text), ["schema", "dimension", "rules"])
  json_schema(fields, "moonconcept-rules/v1")
  let dimension = json_integer(json_required(fields, "dimension"), 128)
  let rules = json_array(json_required(fields, "rules"), 4096).map(value => {
    let fields = json_fields(value, ["premise", "conclusion"])
    implication(
      json_indices(json_required(fields, "premise"), dimension),
      json_indices(json_required(fields, "conclusion"), dimension),
      dimension,
    )
  })
  rule_system(dimension, rules)
}

///|
fn implication_json(rule : Implication) -> Json {
  Json::object({
    "premise": Json::array(
      rule.premise.map(fn(i) { Json::number(i.to_double()) }),
    ),
    "conclusion": Json::array(
      rule.conclusion.map(fn(i) { Json::number(i.to_double()) }),
    ),
  })
}

///|
pub fn RuleSystem::to_json(self : RuleSystem) -> Json {
  Json::object({
    "schema": Json::string("moonconcept-rules/v1"),
    "dimension": Json::number(self.attribute_count().to_double()),
    "rules": Json::array(
      self.implications().map(fn(rule) { implication_json(rule) }),
    ),
  })
}

///|
pub fn RuleSystem::to_json_string(self : RuleSystem) -> String {
  self.to_json().stringify()
}