///|
/// A validated BSON element backed by its owning RawDocument.
pub struct RawElement {
  document : RawDocument
  key : String
  type_code : Byte
  value_start : Int
  value_end : Int
} derive(Eq, Debug)

///|
pub fn RawElement::key(self : RawElement) -> String {
  self.key
}

///|
pub fn RawElement::type_code(self : RawElement) -> Byte {
  self.type_code
}

///|
pub fn RawElement::raw_bytes(self : RawElement) -> BytesView {
  self.document.bytes()[self.value_start:self.value_end]
}

///|
pub fn RawElement::to_bson(self : RawElement) -> Bson raise BsonError {
  let decoder = Decoder::new(self.document.bytes(), DecodeOptions::new())
  decoder.position = self.value_start
  decoder.read_element(
    self.type_code,
    self.value_end,
    0,
    field_path("$", self.key),
  )
}

///|
/// Return the top-level elements without decoding their values.
pub fn RawDocument::elements(
  self : RawDocument,
) -> Array[RawElement] raise BsonError {
  raw_scan_document(self, self.bytes(), 0, "$")
}

///|
pub fn RawDocument::get_element(
  self : RawDocument,
  key : String,
) -> RawElement? raise BsonError {
  for element in self.elements() {
    if element.key() == key {
      return Some(element)
    }
  }
  None
}

///|
fn raw_scan_document(
  document : RawDocument,
  bytes : Bytes,
  start : Int,
  path : String,
) -> Array[RawElement] raise BsonError {
  let length = raw_i32(bytes, start, path)
  if length < 5 || start + length > bytes.length() {
    raise bson_error(InvalidLength, start, path, "invalid raw document length")
  }
  let end = start + length
  if bytes[end - 1] != 0 {
    raise bson_error(
      InvalidLength,
      end - 1,
      path,
      "missing raw document terminator",
    )
  }
  let elements : Array[RawElement] = []
  let mut position = start + 4
  while position < end - 1 {
    let type_offset = position
    let type_code = bytes[position]
    position += 1
    let key_start = position
    let key_end = raw_cstring_end(bytes, position, end - 1, path)
    let key = @utf8.decode(bytes[key_start:key_end].to_owned()) catch {
      _ =>
        raise bson_error(
          InvalidUtf8,
          key_start,
          path,
          "invalid raw element key",
        )
    }
    position = key_end + 1
    let value_start = position
    let value_end = raw_skip_value(bytes, type_code, position, end - 1, path)
    if value_end < value_start {
      raise bson_error(
        InvalidLength,
        type_offset,
        path,
        "invalid raw element length",
      )
    }
    elements.push({ document, key, type_code, value_start, value_end })
    position = value_end
  }
  elements
}

///|
fn raw_skip_value(
  bytes : Bytes,
  type_code : Byte,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  match type_code {
    0x01 => raw_require(start + 8, limit, path)
    0x02 | 0x0D | 0x0E => raw_skip_string(bytes, start, limit, path)
    0x03 | 0x04 => raw_skip_container(bytes, start, limit, path)
    0x05 => raw_skip_binary(bytes, start, limit, path)
    0x06 | 0x0A | 0x7F | 0xFF => start
    0x07 => raw_require(start + 12, limit, path)
    0x08 | 0x09 =>
      raw_require(start + (if type_code == 0x08 { 1 } else { 8 }), limit, path)
    0x0B => {
      let first = raw_cstring_end(bytes, start, limit, path)
      raw_cstring_end(bytes, first + 1, limit, path) + 1
    }
    0x0C => {
      let collection_end = raw_skip_string(bytes, start, limit, path)
      raw_require(collection_end + 12, limit, path)
    }
    0x0F => {
      let total = raw_i32(bytes, start, path)
      if total < 14 || start + total > limit {
        raise bson_error(
          InvalidLength,
          start,
          path,
          "invalid raw code-with-scope length",
        )
      }
      let code_end = raw_skip_string(bytes, start + 4, start + total, path)
      raw_skip_container(bytes, code_end, start + total, path)
    }
    0x10 => raw_require(start + 4, limit, path)
    0x11 => raw_require(start + 8, limit, path)
    0x12 => raw_require(start + 8, limit, path)
    0x13 => raw_require(start + 16, limit, path)
    _ =>
      raise bson_error(
        UnsupportedType,
        start,
        path,
        "unsupported raw BSON type",
      )
  }
}

///|
fn raw_skip_container(
  bytes : Bytes,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let length = raw_i32(bytes, start, path)
  if length < 5 || start + length > limit {
    raise bson_error(
      InvalidLength,
      start,
      path,
      "invalid raw nested document length",
    )
  }
  let end = start + length
  if bytes[end - 1] != 0 {
    raise bson_error(
      InvalidLength,
      end - 1,
      path,
      "missing raw nested terminator",
    )
  }
  let mut position = start + 4
  while position < end - 1 {
    let type_code = bytes[position]
    position += 1
    let key_end = raw_cstring_end(bytes, position, end - 1, path)
    position = key_end + 1
    position = raw_skip_value(bytes, type_code, position, end - 1, path)
  }
  end
}

///|
fn raw_skip_string(
  bytes : Bytes,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let length = raw_i32(bytes, start, path)
  if length < 1 || start + 4 + length > limit {
    raise bson_error(InvalidLength, start, path, "invalid raw string length")
  }
  if bytes[start + 4 + length - 1] != 0 {
    raise bson_error(
      InvalidLength,
      start + 4 + length - 1,
      path,
      "raw string is not terminated",
    )
  }
  start + 4 + length
}

///|
fn raw_skip_binary(
  bytes : Bytes,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let length = raw_i32(bytes, start, path)
  if length < 0 || start + 5 + length > limit {
    raise bson_error(InvalidBinary, start, path, "invalid raw binary length")
  }
  let payload_start = start + 5
  if bytes[start + 4] == 2 {
    if length < 4 || raw_i32(bytes, payload_start, path) != length - 4 {
      raise bson_error(
        InvalidBinary,
        start,
        path,
        "invalid raw old binary length",
      )
    }
  }
  start + 5 + length
}

///|
fn raw_cstring_end(
  bytes : Bytes,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let mut position = start
  while position < limit {
    if bytes[position] == 0 {
      return position
    }
    position += 1
  }
  raise bson_error(UnexpectedEnd, position, path, "unterminated raw cstring")
}

///|
fn raw_require(end : Int, limit : Int, path : String) -> Int raise BsonError {
  if end > limit {
    raise bson_error(
      UnexpectedEnd,
      limit,
      path,
      "raw value exceeds its container",
    )
  }
  end
}

///|
fn raw_i32(bytes : Bytes, start : Int, path : String) -> Int raise BsonError {
  if start < 0 || start + 4 > bytes.length() {
    raise bson_error(UnexpectedEnd, start, path, "raw integer exceeds input")
  }
  let value : UInt = bytes[start].to_uint() |
    (bytes[start + 1].to_uint() << 8) |
    (bytes[start + 2].to_uint() << 16) |
    (bytes[start + 3].to_uint() << 24)
  value.reinterpret_as_int()
}