///|
/// A BSON string whose UTF-8 payload remains borrowed.
pub struct RawStringRef {
  bytes : BytesView
} derive(Eq, Debug)

///|
pub fn RawStringRef::bytes(self : RawStringRef) -> BytesView {
  self.bytes
}

///|
pub fn RawStringRef::to_string(self : RawStringRef) -> String raise BsonError {
  @utf8.decode(self.bytes) catch {
    _ =>
      raise bson_error(
        InvalidUtf8,
        self.bytes.start_offset(),
        "$.string",
        "invalid raw UTF-8 string",
      )
  }
}

///|
/// Borrowed BSON binary payload.
pub struct RawBinaryRef {
  subtype : BinarySubtype
  bytes : BytesView
} derive(Eq, Debug)

///|
pub fn RawBinaryRef::subtype(self : RawBinaryRef) -> BinarySubtype {
  self.subtype
}

///|
pub fn RawBinaryRef::bytes(self : RawBinaryRef) -> BytesView {
  self.bytes
}

///|
/// Borrowed BSON regular expression.
pub struct RawRegexRef {
  pattern : BytesView
  options : BytesView
} derive(Eq, Debug)

///|
pub fn RawRegexRef::pattern(self : RawRegexRef) -> BytesView {
  self.pattern
}

///|
pub fn RawRegexRef::options(self : RawRegexRef) -> BytesView {
  self.options
}

///|
pub fn RawRegexRef::to_regex(self : RawRegexRef) -> Regex raise BsonError {
  let pattern = @utf8.decode(self.pattern) catch {
    _ =>
      raise bson_error(
        InvalidUtf8,
        self.pattern.start_offset(),
        "$.regex.pattern",
        "raw regex pattern is not valid UTF-8",
      )
  }
  let options = @utf8.decode(self.options) catch {
    _ =>
      raise bson_error(
        InvalidUtf8,
        self.options.start_offset(),
        "$.regex.options",
        "raw regex options are not valid UTF-8",
      )
  }
  Regex::new(pattern, options)
}

///|
/// Borrowed BSON DBPointer.
pub struct RawDbPointerRef {
  namespace_value : RawStringRef
  id : BytesView
} derive(Eq, Debug)

///|
pub fn RawDbPointerRef::namespace_value(self : RawDbPointerRef) -> RawStringRef {
  self.namespace_value
}

///|
pub fn RawDbPointerRef::id_bytes(self : RawDbPointerRef) -> BytesView {
  self.id
}

///|
/// Borrowed JavaScript-with-scope value.
pub struct RawJavaScriptWithScopeRef {
  code : RawStringRef
  scope : RawDocumentView
} derive(Eq, Debug)

///|
pub fn RawJavaScriptWithScopeRef::code(
  self : RawJavaScriptWithScopeRef,
) -> RawStringRef {
  self.code
}

///|
pub fn RawJavaScriptWithScopeRef::scope(
  self : RawJavaScriptWithScopeRef,
) -> RawDocumentView {
  self.scope
}

///|
/// A BSON value that borrows all byte payloads from its source document.
pub(all) enum RawBsonRef {
  Double(Double)
  String(RawStringRef)
  Document(RawDocumentView)
  Array(RawArrayView)
  Binary(RawBinaryRef)
  Undefined
  ObjectId(BytesView)
  Boolean(Bool)
  DateTime(Int64)
  Null
  Regex(RawRegexRef)
  DbPointer(RawDbPointerRef)
  JavaScript(RawStringRef)
  Symbol(RawStringRef)
  JavaScriptWithScope(RawJavaScriptWithScopeRef)
  Int32(Int)
  Timestamp(Timestamp)
  Int64(Int64)
  Decimal128(BytesView)
  MinKey
  MaxKey
} derive(Eq, Debug)

///|
/// A borrowed BSON array, represented by its wire document view.
pub struct RawArrayView {
  document : RawDocumentView
} derive(Eq, Debug)

///|
pub fn RawArrayView::from_bytes(
  bytes : BytesView,
) -> RawArrayView raise BsonError {
  { document: RawDocumentView::from_bytes(bytes) }
}

///|
pub fn RawArrayView::document(self : RawArrayView) -> RawDocumentView {
  self.document
}

///|
pub fn RawArrayView::iter(self : RawArrayView) -> RawElementViewIter {
  self.document.iter()
}

///|
pub fn RawArrayView::get(
  self : RawArrayView,
  index : Int,
) -> RawBsonRef? raise BsonError {
  let expected = index.to_string()
  let iterator = self.iter()
  while iterator.next() is Some(element) {
    if element.key() == expected {
      return Some(element.value())
    }
  }
  None
}

///|
/// Convert a borrowed array to owned values explicitly.
pub fn RawArrayView::to_array(
  self : RawArrayView,
) -> Array[Bson] raise BsonError {
  let values : Array[Bson] = []
  let iterator = self.iter()
  while iterator.next() is Some(element) {
    values.push(element.value().to_bson())
  }
  values
}

///|
/// Return the borrowed value represented by this element.
pub fn RawElementView::value(
  self : RawElementView,
) -> RawBsonRef raise BsonError {
  let bytes = self.document.bytes
  let start = self.value_start
  let path = field_path("$", self.key())
  match self.type_code {
    0x01 => Double(raw_view_i64(bytes, start, path).reinterpret_as_double())
    0x02 =>
      String({
        bytes: raw_view_string_payload(bytes, start, self.value_end, path),
      })
    0x03 => Document(RawDocumentView::from_bytes(self.raw_bytes()))
    0x04 => Array(RawArrayView::from_bytes(self.raw_bytes()))
    0x05 => {
      let length = raw_view_i32(bytes, start, path)
      let subtype = BinarySubtype::from_byte(bytes[start + 4])
      let payload = start + 5
      if subtype == BinaryOld {
        if length < 4 || raw_view_i32(bytes, payload, path) != length - 4 {
          raise bson_error(
            InvalidBinary,
            start,
            path,
            "invalid raw old binary length",
          )
        }
        Binary({ subtype, bytes: bytes[payload + 4:payload + length] })
      } else {
        Binary({ subtype, bytes: bytes[payload:payload + length] })
      }
    }
    0x06 => Undefined
    0x07 => ObjectId(bytes[start:start + 12])
    0x08 =>
      match bytes[start] {
        0 => Boolean(false)
        1 => Boolean(true)
        _ =>
          raise bson_error(
            InvalidBoolean,
            start,
            path,
            "invalid raw boolean byte",
          )
      }
    0x09 => DateTime(raw_view_i64(bytes, start, path))
    0x0A => Null
    0x0B => {
      let pattern_end = raw_view_cstring_end(bytes, start, self.value_end, path)
      let options_start = pattern_end + 1
      let options_end = raw_view_cstring_end(
        bytes,
        options_start,
        self.value_end,
        path,
      )
      Regex({
        pattern: bytes[start:pattern_end],
        options: bytes[options_start:options_end],
      })
    }
    0x0C => {
      let namespace_end = raw_view_skip_string(
        bytes,
        start,
        self.value_end,
        path,
      )
      DbPointer({
        namespace_value: {
          bytes: raw_view_string_payload(bytes, start, namespace_end, path),
        },
        id: bytes[namespace_end:namespace_end + 12],
      })
    }
    0x0D =>
      JavaScript({
        bytes: raw_view_string_payload(bytes, start, self.value_end, path),
      })
    0x0E =>
      Symbol({
        bytes: raw_view_string_payload(bytes, start, self.value_end, path),
      })
    0x0F => {
      let code_end = raw_view_skip_string(
        bytes,
        start + 4,
        start + raw_view_i32(bytes, start, path),
        path,
      )
      let scope = RawDocumentView::from_bytes(bytes[code_end:self.value_end])
      JavaScriptWithScope({
        code: {
          bytes: raw_view_string_payload(bytes, start + 4, code_end, path),
        },
        scope,
      })
    }
    0x10 => Int32(raw_view_i32(bytes, start, path))
    0x11 => {
      let increment = raw_view_i32(bytes, start, path).reinterpret_as_uint()
      let time = raw_view_i32(bytes, start + 4, path).reinterpret_as_uint()
      Timestamp(Timestamp::new(time, increment))
    }
    0x12 => Int64(raw_view_i64(bytes, start, path))
    0x13 => Decimal128(bytes[start:start + 16])
    0x7F => MaxKey
    0xFF => MinKey
    _ =>
      raise bson_error(
        UnsupportedType,
        start,
        path,
        "unsupported raw BSON type",
      )
  }
}

///|
pub fn RawDocumentView::get(
  self : RawDocumentView,
  key : String,
) -> RawBsonRef? raise BsonError {
  let iterator = self.iter()
  while iterator.next() is Some(element) {
    if element.key() == key {
      return Some(element.value())
    }
  }
  None
}

///|
pub fn RawDocumentView::to_document(
  self : RawDocumentView,
) -> Document raise BsonError {
  decode(self.bytes.to_owned())
}

///|
/// Explicitly materialize a borrowed value into the owned BSON model.
pub fn RawBsonRef::to_bson(self : RawBsonRef) -> Bson raise BsonError {
  match self {
    Double(value) => Double(value)
    String(value) => String(value.to_string())
    Document(value) => Document(value.to_document())
    Array(value) => Array(value.to_array())
    Binary(value) => Binary(Binary::new(value.subtype, value.bytes.to_owned()))
    Undefined => Undefined
    ObjectId(value) => ObjectId(ObjectId::from_bytes(value.to_owned()))
    Boolean(value) => Boolean(value)
    DateTime(value) => DateTime(DateTime::from_millis(value))
    Null => Null
    Regex(value) => Regex(value.to_regex())
    DbPointer(value) =>
      DbPointer(
        DbPointer::new(
          value.namespace_value.to_string(),
          ObjectId::from_bytes(value.id.to_owned()),
        ),
      )
    JavaScript(value) => JavaScript(value.to_string())
    Symbol(value) => Symbol(value.to_string())
    JavaScriptWithScope(value) =>
      JavaScriptWithScope(
        JavaScriptWithScope::new(
          value.code.to_string(),
          value.scope.to_document(),
        ),
      )
    Int32(value) => Int32(value)
    Timestamp(value) => Timestamp(value)
    Int64(value) => Int64(value)
    Decimal128(value) => Decimal128(Decimal128::from_bytes(value.to_owned()))
    MinKey => MinKey
    MaxKey => MaxKey
  }
}

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