///|
/// Convert a MoonBit value into the BSON data model.
pub(open) trait ToBson {
  fn to_bson(Self) -> Bson raise BsonError
}

///|
/// Convert a BSON value into a MoonBit value.
pub(open) trait FromBson {
  fn from_bson(Bson) -> Self raise BsonError
}

///|
pub fn[T : ToBson] to_bson(value : T) -> Bson raise BsonError {
  ToBson::to_bson(value)
}

///|
pub fn[T : FromBson] from_bson(value : Bson) -> T raise BsonError {
  FromBson::from_bson(value)
}

///|
pub fn[T : ToBson] serialize_to_bytes(value : T) -> Bytes raise BsonError {
  match value.to_bson() {
    Document(document) => encode(document)
    value => serialization_type_error("document", value)
  }
}

///|
pub fn[T : FromBson] deserialize_from_bytes(value : Bytes) -> T raise BsonError {
  T::from_bson(Document(decode(value)))
}

///|
pub impl ToBson for Bson with fn to_bson(self) {
  self
}

///|
pub impl FromBson for Bson with fn from_bson(value) {
  value
}

///|
pub impl ToBson for Document with fn to_bson(self) {
  Document(self)
}

///|
pub impl FromBson for Document with fn from_bson(value) {
  match value {
    Document(document) => document
    value => serialization_type_error("document", value)
  }
}

///|
pub impl ToBson for String with fn to_bson(self) {
  String(self)
}

///|
pub impl FromBson for String with fn from_bson(value) {
  match value {
    String(value) => value
    value => serialization_type_error("string", value)
  }
}

///|
pub impl ToBson for Bool with fn to_bson(self) {
  Boolean(self)
}

///|
pub impl FromBson for Bool with fn from_bson(value) {
  match value {
    Boolean(value) => value
    value => serialization_type_error("boolean", value)
  }
}

///|
pub impl ToBson for Int with fn to_bson(self) {
  Int32(self)
}

///|
pub impl FromBson for Int with fn from_bson(value) {
  match value {
    Int32(value) => value
    value => serialization_type_error("int32", value)
  }
}

///|
pub impl ToBson for Int64 with fn to_bson(self) {
  Int64(self)
}

///|
pub impl FromBson for Int64 with fn from_bson(value) {
  match value {
    Int64(value) => value
    Int32(value) => value.to_int64()
    value => serialization_type_error("int64", value)
  }
}

///|
pub impl ToBson for Double with fn to_bson(self) {
  Double(self)
}

///|
pub impl FromBson for Double with fn from_bson(value) {
  match value {
    Double(value) => value
    value => serialization_type_error("double", value)
  }
}

///|
pub impl ToBson for Bytes with fn to_bson(self) {
  Binary(Binary::new(Generic, self))
}

///|
pub impl FromBson for Bytes with fn from_bson(value) {
  match value {
    Binary(value) if value.subtype() == Generic => value.bytes()
    value => serialization_type_error("generic binary", value)
  }
}

///|
pub impl ToBson for DateTime with fn to_bson(self) {
  DateTime(self)
}

///|
pub impl FromBson for DateTime with fn from_bson(value) {
  match value {
    DateTime(value) => value
    value => serialization_type_error("datetime", value)
  }
}

///|
pub impl ToBson for ObjectId with fn to_bson(self) {
  ObjectId(self)
}

///|
pub impl FromBson for ObjectId with fn from_bson(value) {
  match value {
    ObjectId(value) => value
    value => serialization_type_error("objectId", value)
  }
}

///|
pub impl ToBson for Decimal128 with fn to_bson(self) {
  Decimal128(self)
}

///|
pub impl FromBson for Decimal128 with fn from_bson(value) {
  match value {
    Decimal128(value) => value
    value => serialization_type_error("decimal128", value)
  }
}

///|
pub impl ToBson for Uuid with fn to_bson(self) {
  Binary(self.to_binary())
}

///|
pub impl FromBson for Uuid with fn from_bson(value) {
  match value {
    Binary(value) => Uuid::from_binary(value)
    value => serialization_type_error("UUID binary", value)
  }
}

///|
pub impl[T : ToBson] ToBson for Array[T] with fn to_bson(self) {
  Array(self.map(value => value.to_bson()))
}

///|
pub impl[T : FromBson] FromBson for Array[T] with fn from_bson(value) {
  match value {
    Array(values) => values.map(value => T::from_bson(value))
    value => serialization_type_error("array", value)
  }
}

///|
pub impl[T : ToBson] ToBson for T? with fn to_bson(self) {
  match self {
    Some(value) => value.to_bson()
    None => Null
  }
}

///|
pub impl[T : FromBson] FromBson for T? with fn from_bson(value) {
  match value {
    Null => None
    value => Some(T::from_bson(value))
  }
}

///|
pub impl[T : ToBson] ToBson for Map[String, T] with fn to_bson(self) {
  let document = Document::new()
  for key, value in self {
    document.set(key, value.to_bson()) |> ignore
  }
  Document(document)
}

///|
pub impl[T : FromBson] FromBson for Map[String, T] with fn from_bson(value) {
  match value {
    Document(document) => {
      let values : Map[String, T] = Map([])
      for entry in document.entries() {
        let (key, value) = entry
        values.set(key, T::from_bson(value))
      }
      values
    }
    value => serialization_type_error("document", value)
  }
}

///|
fn[T] serialization_type_error(
  expected : String,
  actual : Bson,
) -> T raise BsonError {
  raise bson_error(
    TypeMismatch,
    -1,
    "$",
    "expected " + expected + ", found " + actual.type_name(),
  )
}