///|
/// Core JSON syntax validation plus duplicate-key rejection. A duplicate object
/// key must not silently overwrite a review choice, even when escaped differently.
fn checked_json(source : String) -> Json raise MergeError {
  if source.length() > max_chars {
    raise LimitExceeded("JSON exceeds 16000000 UTF-16 code units")
  }
  validate_unicode(source)
  let mut depth = 0
  let mut quoted = false
  let mut escaped = false
  for c in source {
    if quoted {
      if !escaped && c == '"' {
        quoted = false
      }
      if !escaped && c == '\\' {
        escaped = true
      } else {
        escaped = false
      }
    } else if c == '"' {
      quoted = true
    } else if c == '{' || c == '[' {
      depth += 1
      if depth > 64 {
        raise InvalidDecision(
          "JSON nesting exceeds 64; simplify the review file",
        )
      }
    } else if c == '}' || c == ']' {
      depth -= 1
    }
  }
  let value = @json.parse(source) catch {
    _ =>
      raise InvalidDecision(
        "invalid JSON or nesting exceeds 64; correct the review file",
      )
  }
  let scanner : JsonScanner = { chars: source.to_array(), offset: 0, }
  scanner.scan_value()
  value
}

///|
priv struct JsonScanner {
  chars : Array[Char]
  mut offset : Int
}

///|
fn JsonScanner::space(self : JsonScanner) -> Unit {
  while self.offset < self.chars.length() &&
        (
          self.chars[self.offset] == ' ' ||
          self.chars[self.offset] == '\n' ||
          self.chars[self.offset] == '\r' ||
          self.chars[self.offset] == '\t'
        ) {
    self.offset += 1
  }
}

///|
fn JsonScanner::string(self : JsonScanner) -> String raise MergeError {
  let token = StringBuilder()
  token.write_char('"')
  self.offset += 1
  let mut escaped = false
  while self.offset < self.chars.length() {
    let c = self.chars[self.offset]
    token.write_char(c)
    self.offset += 1
    if !escaped && c == '"' {
      break
    }
    if !escaped && c == '\\' {
      escaped = true
    } else {
      escaped = false
    }
  }
  let decoded = @json.parse(token.to_string()) catch {
    _ => raise InvalidDecision("invalid JSON string")
  }
  guard decoded is String(value) else {
    raise InvalidDecision("expected JSON string")
  }
  validate_unicode(value)
  value
}

///|
fn JsonScanner::scan_value(self : JsonScanner) -> Unit raise MergeError {
  self.space()
  match self.chars.get(self.offset) {
    Some('{') => {
      self.offset += 1
      self.space()
      let seen : Map[String, Bool] = Map([])
      while self.chars.get(self.offset) != Some('}') {
        let name = self.string()
        if seen.contains(name) {
          raise InvalidDecision(
            "duplicate JSON object key '\{name}'; keep each field once",
          )
        }
        seen[name] = true
        self.space()
        self.offset += 1 // Colon; syntax was checked before scanning.
        self.scan_value()
        self.space()
        if self.chars.get(self.offset) != Some(',') {
          break
        }
        self.offset += 1
        self.space()
      }
      self.offset += 1
    }
    Some('[') => {
      self.offset += 1
      self.space()
      while self.chars.get(self.offset) != Some(']') {
        self.scan_value()
        self.space()
        if self.chars.get(self.offset) != Some(',') {
          break
        }
        self.offset += 1
      }
      self.offset += 1
    }
    Some('"') => ignore(self.string())
    _ =>
      while self.offset < self.chars.length() &&
            self.chars[self.offset] != ',' &&
            self.chars[self.offset] != '}' &&
            self.chars[self.offset] != ']' &&
            self.chars[self.offset] != ' ' &&
            self.chars[self.offset] != '\n' &&
            self.chars[self.offset] != '\r' &&
            self.chars[self.offset] != '\t' {
        self.offset += 1
      }
  }
}