///|
/// Incremental V8 reader: large numeric arrays never become JSON syntax trees.
struct Decoder {
  limits : Limits
  mut state : Int32
  mut key : String
  seen : Map[String, Bool]
  metadata : Map[String, Json]
  nodes : Array[UInt64]
  edges : Array[UInt64]
  strings : Array[String]
  token : StringBuilder
  mut token_length : Int32
  mut token_kind : Int32
  mut quoted : Bool
  mut escaped : Bool
  mut depth : Int32
  mut number : UInt64
  mut digits : Int32
  mut chars : Int32
  mut failed : Bool
  mut finished : Bool
}

///|
pub fn Decoder::new(
  limits~ : Limits = Limits::default(),
) -> Decoder raise HeapError {
  validate_limits(limits)
  {
    limits,
    state: 0,
    key: "",
    seen: Map([]),
    metadata: Map([]),
    nodes: [],
    edges: [],
    strings: [],
    token: StringBuilder(),
    token_length: 0,
    token_kind: 0,
    quoted: false,
    escaped: false,
    depth: 0,
    number: 0UL,
    digits: 0,
    chars: 0,
    failed: false,
    finished: false,
  }
}

///|
fn whitespace(c : Char) -> Bool {
  c == ' ' || c == '\n' || c == '\r' || c == '\t'
}

///|
fn Decoder::write(self : Decoder, c : Char) -> Unit raise HeapError {
  guard self.token_length < 16777216 else {
    raise Limit("single metadata value or string exceeds 16 Mi characters")
  }
  self.token.write_char(c)
  self.token_length += 1
}

///|
fn Decoder::start_token(
  self : Decoder,
  kind : Int32,
  c : Char,
) -> Unit raise HeapError {
  self.token.reset()
  self.token_length = 0
  self.token_kind = kind
  self.quoted = c == '"'
  self.escaped = false
  self.depth = if c == '{' || c == '[' { 1 } else { 0 }
  self.write(c)
  self.state = 9
}

///|
fn Decoder::complete_token(self : Decoder) -> Unit raise HeapError {
  let value = @json.parse(
    self.token.to_string(),
    reject_duplicate_keys=true,
    preserve_number_repr=true,
    max_nesting_depth=64,
  ) catch {
    _ => raise Invalid("invalid JSON field or string")
  }
  if self.token_kind == 1 {
    let key = text(value, "top-level key")
    guard !self.seen.contains(key) else {
      raise Invalid("duplicate JSON key: \{key}")
    }
    self.seen[key] = true
    self.key = key
    self.state = 3
  } else if self.token_kind == 2 {
    guard self.strings.length() < self.limits.max_strings else {
      raise Limit("string table budget exceeded")
    }
    self.strings.push(text(value, "string table"))
    self.state = 7
  } else {
    if self.key == "snapshot" ||
      self.key == "locations" ||
      self.key == "trace_function_infos" ||
      self.key == "trace_tree" {
      self.metadata[self.key] = value
    }
    self.state = 8
  }
  self.token.reset()
}

///|
fn Decoder::complete_number(self : Decoder) -> Unit raise HeapError {
  let target = if self.key == "nodes" { self.nodes } else { self.edges }
  let count = if self.key == "nodes" {
    self.limits.max_nodes
  } else {
    self.limits.max_edges
  }
  guard target.length().to_int64() < count.to_int64() * 32L else {
    raise Limit("numeric record budget exceeded")
  }
  target.push(self.number)
  self.digits = 0
  self.number = 0UL
  self.state = 7
}

///|
fn Decoder::consume(self : Decoder, c : Char) -> Unit raise HeapError {
  let mut again = true
  while again {
    again = false
    match self.state {
      0 => {
        if whitespace(c) {
          continue
        }
        guard c == '{' else { raise Invalid("snapshot must be a JSON object") }
        self.state = 1
      }
      1 | 2 => {
        if whitespace(c) {
          continue
        }
        if c == '}' && self.state == 1 {
          self.state = 10
        } else {
          guard c == '"' else { raise Invalid("expected JSON field name") }
          self.start_token(1, c)
        }
      }
      3 => {
        if whitespace(c) {
          continue
        }
        guard c == ':' else { raise Invalid("expected colon") }
        self.state = 4
      }
      4 => {
        if whitespace(c) {
          continue
        }
        if self.key == "nodes" || self.key == "edges" || self.key == "strings" {
          guard c == '[' else { raise Invalid("\{self.key}: expected array") }
          self.state = 5
        } else {
          guard c != ',' && c != '}' else {
            raise Invalid("missing JSON value")
          }
          self.start_token(3, c)
        }
      }
      5 | 6 => {
        if whitespace(c) {
          continue
        }
        if c == ']' && self.state == 5 {
          self.state = 8
        } else if self.key == "strings" {
          guard c == '"' else { raise Invalid("string table: expected string") }
          self.start_token(2, c)
        } else {
          guard c >= '0' && c <= '9' else {
            raise Invalid("\{self.key}: expected nonnegative decimal integer")
          }
          self.digits = 0
          self.number = 0UL
          self.state = 11
          again = true
        }
      }
      7 => {
        if whitespace(c) {
          continue
        }
        if c == ',' {
          self.state = 6
        } else if c == ']' {
          self.state = 8
        } else {
          raise Invalid("expected array comma or closing bracket")
        }
      }
      8 => {
        if whitespace(c) {
          continue
        }
        if c == ',' {
          self.state = 2
        } else if c == '}' {
          self.state = 10
        } else {
          raise Invalid("expected object comma or closing brace")
        }
      }
      9 => {
        if !self.quoted &&
          self.depth == 0 &&
          (whitespace(c) || c == ',' || c == '}') {
          self.complete_token()
          again = true
          continue
        }
        self.write(c)
        if self.quoted {
          if self.escaped {
            self.escaped = false
          } else if c == '\\' {
            self.escaped = true
          } else if c == '"' {
            self.quoted = false
            if self.depth == 0 {
              self.complete_token()
            }
          }
        } else if c == '"' {
          self.quoted = true
        } else if c == '{' || c == '[' {
          self.depth += 1
          guard self.depth <= 64 else {
            raise Limit("metadata nesting exceeds 64")
          }
        } else if c == '}' || c == ']' {
          self.depth -= 1
          guard self.depth >= 0 else { raise Invalid("unbalanced metadata") }
          if self.depth == 0 {
            self.complete_token()
          }
        }
      }
      10 => if !whitespace(c) { raise Invalid("data after top-level object") }
      11 =>
        if c >= '0' && c <= '9' {
          let digit = (c.to_int() - 48).to_uint64()
          guard !(self.digits > 0 && self.number == 0UL) else {
            raise Invalid("leading zero in integer")
          }
          guard self.number <= (18446744073709551615UL - digit) / 10UL else {
            raise Invalid("integer overflow")
          }
          self.number = self.number * 10UL + digit
          self.digits += 1
        } else {
          self.complete_number()
          again = true
        }
      _ => raise Invalid("invalid decoder state")
    }
  }
}

///|
/// Feed any text chunk. UTF-8 byte decoding belongs to the host.
pub fn Decoder::feed(self : Decoder, chunk : String) -> Unit raise HeapError {
  guard !self.failed && !self.finished else {
    raise Invalid("decoder is closed or failed")
  }
  errdefer {
    self.failed = true
  }
  guard chunk.length() <= self.limits.max_chars - self.chars else {
    raise Limit("input exceeds max_chars")
  }
  self.chars += chunk.length()
  for c in chunk.iter() {
    self.consume(c)
  }
}

///|
pub fn Decoder::finish(self : Decoder) -> Snapshot raise HeapError {
  guard !self.failed && !self.finished else {
    raise Invalid("decoder is closed or failed")
  }
  self.finished = true
  guard self.state == 10 else { raise Invalid("truncated JSON snapshot") }
  for key in ["snapshot", "nodes", "edges", "strings"] {
    guard self.seen.contains(key) else {
      raise Invalid("missing field: \{key}")
    }
  }
  parse_parts(self.metadata, self.nodes, self.edges, self.strings, self.limits)
}

///|
pub fn Decoder::characters_read(self : Decoder) -> Int32 {
  self.chars
}

///|
/// Configure an incremental reader using the shared versioned request validator.
pub fn decoder_with_options(input : String) -> Decoder raise HeapError {
  let req = request(input)
  let mib = int_option(req, "max_mib", 256)
  guard mib >= 1 && mib <= 1024 else {
    raise Invalid("max_mib must be in 1..1024")
  }
  Decoder::new(limits={
    max_chars: mib * 1048576,
    max_nodes: int_option(req, "max_nodes", 2000000),
    max_edges: int_option(req, "max_edges", 16000000),
    max_strings: int_option(req, "max_strings", 4000000),
    max_work: int_option(req, "max_work", 200000000),
  })
}