///|
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()
}

///|
/// Preserve numeric tokens in a shadow tree. Core still owns JSON grammar,
/// escaped keys and duplicate-key resolution, so overwritten values are ignored.
fn rule_json_lexemes(text : String) -> Json raise FcaError {
  let chars = text.to_array()
  let out = StringBuilder()
  let mut quoted = false
  let mut escaped = false
  let mut i = 0
  while i < chars.length() {
    let c = chars[i]
    if quoted {
      out.write_char(c)
      if escaped {
        escaped = false
      } else if c == '\\' {
        escaped = true
      } else if c == '"' {
        quoted = false
      }
      i = i + 1
    } else if c == '"' {
      quoted = true
      out.write_char(c)
      i = i + 1
    } else if c == '-' || (c >= '0' && c <= '9') {
      out.write_char('"')
      while i < chars.length() {
        let d = chars[i]
        if (d >= '0' && d <= '9') ||
          d == '-' ||
          d == '+' ||
          d == '.' ||
          d == 'e' ||
          d == 'E' {
          out.write_char(d)
          i = i + 1
        } else {
          break
        }
      }
      out.write_char('"')
    } else {
      out.write_char(c)
      i = i + 1
    }
  }
  // The original input has already passed syntax, size and depth validation.
  @json.parse(out.to_string()[:]) catch {
    _ => raise Invalid("invalid numeric JSON representation")
  }
}

///|
/// The original tree has already checked the field's numeric type. Validate its
/// exact decimal value, not the Double rounded by the standard JSON parser.
fn check_rule_json_number(value : Json) -> Unit raise FcaError {
  let token = match value {
    String(token) => token
    _ => raise Invalid("JSON numeric field has no decimal token")
  }
  let chars = token.to_array()
  let negative = chars[0] == '-'
  let mut i = if negative { 1 } else { 0 }
  let start = i
  let mut digit_count = 0
  let mut integer_digits = 0
  let mut first_nonzero = -1
  let mut last_nonzero = -1
  let mut fraction = false
  while i < chars.length() {
    let digit = chars[i]
    if digit >= '0' && digit <= '9' {
      if digit != '0' {
        if first_nonzero < 0 {
          first_nonzero = digit_count
        }
        last_nonzero = digit_count
      }
      digit_count = digit_count + 1
      if !fraction {
        integer_digits = integer_digits + 1
      }
    } else if digit == '.' {
      fraction = true
    } else {
      break
    }
    i = i + 1
  }
  let end = i
  let mut exponent = 0
  if i < chars.length() {
    i = i + 1
    let mut exponent_negative = false
    if chars[i] == '+' || chars[i] == '-' {
      exponent_negative = chars[i] == '-'
      i = i + 1
    }
    // Exponents beyond the token length cannot change acceptance. Saturation
    // avoids integer overflow even for adversarial exponent strings.
    let cap = chars.length() + 1
    while i < chars.length() {
      if exponent < cap {
        exponent = exponent * 10 + chars[i].to_int() - '0'.to_int()
        if exponent > cap {
          exponent = cap
        }
      }
      i = i + 1
    }
    if exponent_negative {
      exponent = -exponent
    }
  }
  if first_nonzero >= 0 {
    let point = integer_digits + exponent
    if negative || last_nonzero >= point || point - first_nonzero > 3 {
      raise Invalid("JSON numeric field must be an exact integer in 0..128")
    }
    let mut position = 0
    let mut exact = 0
    for j = start; j < end; j = j + 1 {
      if chars[j] != '.' {
        if position >= first_nonzero && position < point {
          exact = exact * 10 + chars[j].to_int() - '0'.to_int()
        }
        position = position + 1
      }
    }
    while position < point {
      exact = exact * 10
      position = position + 1
    }
    if exact > 128 {
      raise Invalid("JSON numeric field must be an exact integer in 0..128")
    }
  }
}

///|
fn check_rule_json_numbers(text : String) -> Unit raise FcaError {
  let fields = json_fields(rule_json_lexemes(text), [
    "schema", "dimension", "rules",
  ])
  check_rule_json_number(json_required(fields, "dimension"))
  for rule in json_array(json_required(fields, "rules"), 4096) {
    let fields = json_fields(rule, ["premise", "conclusion"])
    for key in ["premise", "conclusion"] {
      for index in json_array(json_required(fields, key), 128) {
        check_rule_json_number(index)
      }
    }
  }
}

///|
pub fn parse_rules_json(text : String) -> RuleSystem raise FcaError {
  let value = decode_json(text)
  let fields = json_fields(value, ["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,
    )
  })
  let result = rule_system(dimension, rules)
  check_rule_json_numbers(text)
  result
}

///|
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()
}