///|
// Constant-extra-space lexical preflight, not a second JSON parser. Count keys and
// values alike, including duplicate keys that the parser would later discard.
// Keep string escape state so punctuation inside strings cannot affect depth.
fn preflight_json_text(text : String) -> Unit raise JmesPathError {
  if text.length() > 16777216 {
    fail("limit_exceeded", 0, "JSON text exceeds 16 Mi UTF-16 units")
  }
  let mut quoted = false
  let mut escaped = false
  let mut atom = false
  let mut depth = 0
  let mut tokens = 0
  let mut units = 0
  for c in text.iter() {
    if quoted {
      if !escaped && c == '"' {
        quoted = false
        continue
      }
      units += if c.to_int() > 65535 { 2 } else { 1 }
      if units > 8388608 {
        fail("limit_exceeded", 0, "JSON string token exceeds 8 Mi source units")
      }
      if escaped {
        escaped = false
      } else if c == '\\' {
        escaped = true
      }
      continue
    }
    if is_space(c) || c == ',' || c == ':' {
      atom = false
      continue
    }
    if c == ']' || c == '}' {
      depth -= 1
      atom = false
      // Leave exact grammar diagnostics to the parser, but do not allow an
      // invalid closing prefix to offset excessive later nesting.
      if depth < 0 {
        fail("invalid_json", 0, "unmatched JSON closing delimiter")
      }
      continue
    }
    if c == '"' || c == '[' || c == '{' || !atom {
      tokens += 1
      if tokens > 1000000 {
        fail("limit_exceeded", 0, "JSON text exceeds 1000000 lexical tokens")
      }
    }
    if c == '"' {
      quoted = true
      escaped = false
      units = 0
      atom = false
    } else if c == '[' || c == '{' {
      depth += 1
      if depth > 1024 {
        fail("limit_exceeded", 0, "JSON text exceeds nesting 1024")
      }
      atom = false
    } else {
      atom = true
    }
  }
}

///|
/// Preflight text allocation budgets, then parse JSON. This does not impose
/// per-query JSON shape limits; batch callers retain independent item checks.
pub fn parse_json_bounded(text : String) -> Json raise JmesPathError {
  preflight_json_text(text)
  @json.parse(text) catch {
    @json.DepthLimitExceeded =>
      fail("limit_exceeded", 0, "JSON nesting exceeds parser limit")
    _ => fail("invalid_json", 0, "input is not valid JSON")
  }
}