// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0

///|
priv struct JsonBinaryReader {
  data : BytesView
  file_offset : UInt64
  column_index : Int32
  max_depth : Int32
  max_nodes : Int32
  mut nodes : Int32
}

///|
fn JsonBinaryReader::invalid(
  self : JsonBinaryReader,
  position : Int32,
  reason : String,
) -> DecodeError {
  InvalidJsonValue(
    file_offset=self.file_offset,
    column_index=self.column_index,
    value_offset=position,
    reason~,
  )
}

///|
fn JsonBinaryReader::need(
  self : JsonBinaryReader,
  pos : Int32,
  size : Int32,
  limit : Int32,
) -> Unit raise DecodeError {
  guard pos >= 0 && pos <= limit && size >= 0 && size <= limit - pos else {
    raise self.invalid(pos, "JSON value exceeds its containing span")
  }
}

///|
fn JsonBinaryReader::uint(
  self : JsonBinaryReader,
  pos : Int32,
  width : Int32,
  limit : Int32,
) -> UInt64 raise DecodeError {
  self.need(pos, width, limit)
  let mut bits = 0UL
  for i in 0.. (Int32, Int32) raise DecodeError {
  let mut length = 0UL
  for i in 0..<5 {
    let byte = self.uint(pos + i, 1, limit)
    length = length | ((byte & 0x7f) << (7 * i))
    if (byte & 0x80) == 0 {
      guard length <= 0xffffffffUL &&
        length <= (limit - pos - i - 1).reinterpret_as_uint().to_uint64() else {
        raise self.invalid(pos, "invalid JSON string length")
      }
      return (i + 1, length.to_int())
    }
  }
  raise self.invalid(pos, "unterminated JSON string length")
}

///|
// Measure before decoding/allocation so sibling aliasing cannot amplify strings
// or nested containers. Limits are checked before narrowing sizes to Int.
fn JsonBinaryReader::extent(
  self : JsonBinaryReader,
  kind : Byte,
  pos : Int32,
  limit : Int32,
) -> Int32 raise DecodeError {
  let size = match kind {
    0..<=3 => {
      let width = if kind == 0 || kind == 2 { 2 } else { 4 }
      let size = self.uint(pos + width, width, limit)
      guard size >= (2 * width).reinterpret_as_uint().to_uint64() &&
        size <= (limit - pos).reinterpret_as_uint().to_uint64() else {
        raise self.invalid(pos, "invalid JSON container size")
      }
      size.to_int()
    }
    4 => 1
    5 | 6 => 2
    7 | 8 => 4
    9..<=11 => 8
    12 => {
      let (prefix, length) = self.string_span(pos, limit)
      prefix + length
    }
    binary_type =>
      raise UnsupportedJsonType(
        file_offset=self.file_offset,
        column_index=self.column_index,
        value_offset=pos,
        binary_type~,
      )
  }
  self.need(pos, size, limit)
  size
}

///|
fn JsonBinaryReader::text(
  self : JsonBinaryReader,
  pos : Int32,
  size : Int32,
) -> String raise DecodeError {
  @utf8.decode(self.data[pos:pos + size]) catch {
    _ => raise self.invalid(pos, "invalid UTF-8 in JSON text")
  }
}

///|
fn json_inline(kind : Byte, width : Int32) -> Bool {
  (kind >= 4 && kind <= 6) || (width == 4 && (kind == 7 || kind == 8))
}

///|
fn JsonBinaryReader::value(
  self : JsonBinaryReader,
  kind : Byte,
  pos : Int32,
  limit : Int32,
  depth : Int32,
) -> BinaryJsonValue raise DecodeError {
  guard depth <= self.max_depth && self.nodes < self.max_nodes else {
    raise JsonLimitExceeded(
      file_offset=self.file_offset,
      column_index=self.column_index,
      reason="JSON depth/node budget exceeded",
    )
  }
  self.nodes += 1
  match kind {
    0..<=3 => self.container(kind, pos, limit, depth)
    4 =>
      match self.uint(pos, 1, limit) {
        0 => Null
        1 => Boolean(true)
        2 => Boolean(false)
        _ => raise self.invalid(pos, "invalid JSON literal")
      }
    5..<=10 => {
      let width = if kind <= 6 { 2 } else if kind <= 8 { 4 } else { 8 }
      let bits = self.uint(pos, width, limit)
      let integer = if kind == 5 || kind == 7 || kind == 9 {
        let extended = if width < 8 && (bits & (1UL << (width * 8 - 1))) != 0 {
          bits | (0xffffffffffffffffUL << (width * 8))
        } else {
          bits
        }
        Signed(extended.reinterpret_as_int64())
      } else {
        Unsigned(bits)
      }
      Integer(integer)
    }
    11 => {
      let bits = self.uint(pos, 8, limit)
      guard (bits & 0x7ff0000000000000UL) != 0x7ff0000000000000UL else {
        raise self.invalid(pos, "non-finite JSON double")
      }
      DoubleNumber(value=bits.reinterpret_as_double(), bits~)
    }
    12 => {
      let (prefix, length) = self.string_span(pos, limit)
      Text(self.text(pos + prefix, length))
    }
    binary_type =>
      raise UnsupportedJsonType(
        file_offset=self.file_offset,
        column_index=self.column_index,
        value_offset=pos,
        binary_type~,
      )
  }
}

///|
fn JsonBinaryReader::container(
  self : JsonBinaryReader,
  kind : Byte,
  pos : Int32,
  limit : Int32,
  depth : Int32,
) -> BinaryJsonValue raise DecodeError {
  let width = if kind == 0 || kind == 2 { 2 } else { 4 }
  let object = kind <= 1
  let size = self.extent(kind, pos, limit)
  let end = pos + size
  let count = self.uint(pos, width, end)
  let stride = 1 + width + (if object { width + 2 } else { 0 })
  guard count <= ((size - 2 * width) / stride).reinterpret_as_uint().to_uint64() else {
    raise self.invalid(pos, "JSON entry table exceeds container")
  }
  guard count <= (self.max_nodes - self.nodes).reinterpret_as_uint().to_uint64() else {
    raise JsonLimitExceeded(
      file_offset=self.file_offset,
      column_index=self.column_index,
      reason="JSON node budget exceeded",
    )
  }
  let count = count.to_int()
  let header = 2 * width + count * stride
  let values_start = pos +
    2 * width +
    (if object { count * (width + 2) } else { 0 })
  let spans : Array[(Int32, Int32)] = []
  let keys : Array[(Int32, Int32)] = []
  let values : Array[(Byte, Int32, Int32)] = []
  if object {
    for i in 0..= header.reinterpret_as_uint().to_uint64() &&
        offset <= size.reinterpret_as_uint().to_uint64() &&
        length <= size.reinterpret_as_uint().to_uint64() - offset else {
        raise self.invalid(entry, "JSON key points outside data area")
      }
      let start = pos + offset.to_int()
      keys.push((start, length.to_int()))
      if length > 0 {
        spans.push((start, start + length.to_int()))
      }
    }
  }
  for i in 0..= header.reinterpret_as_uint().to_uint64() &&
        offset <= size.reinterpret_as_uint().to_uint64() else {
        raise self.invalid(entry, "JSON value points outside data area")
      }
      pos + offset.to_int()
    }
    let value_end = if json_inline(tag, width) {
      entry + 1 + width
    } else {
      end
    }
    let consumed = self.extent(tag, start, value_end)
    if !json_inline(tag, width) {
      spans.push((start, start + consumed))
    }
    values.push((tag, start, value_end))
  }
  spans.sort_by((a, b) => a.0 - b.0)
  let mut previous_end = pos + header
  for (start, finish) in spans {
    guard start >= previous_end else {
      raise self.invalid(start, "overlapping JSON key/value spans")
    }
    previous_end = finish
  }
  let names = []
  for i, (start, length) in keys {
    if i > 0 {
      let (prev, prev_length) = keys[i - 1]
      let mut less = prev_length < length
      if prev_length == length {
        for j in 0..