// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// A MoonBit value that binds as a parameter: Go's `driver.Valuer`, Diesel's
/// `ToSql`. Implemented for the type behind each [`Value`] case and for `T?`,
/// where `None` binds `NULL`. A type of the program's own implements it to be
/// stored as one of those cases.
pub(open) trait ToValue {
  fn to_value(Self) -> Value
}

///|
/// A MoonBit value read back from a cell: Go's `sql.Scanner`, Diesel's `FromSql`.
/// The conversions are the ones [`Row`]'s typed accessors make. A refusal is a
/// `TypeError` whose message reads after "column N is", such as `"Text, not Int"`.
pub(open) trait FromValue {
  fn from_value(Value) -> Self raise DbError
}

///|
fn refuse(got : Value, want : String) -> DbError {
  TypeError(got.kind() + ", not " + want)
}

///|
pub impl ToValue for Value with fn to_value(self) {
  self
}

///|
pub impl FromValue for Value with fn from_value(v) {
  v
}

///|
pub extend Value with ToValue::{to_value}

///|
pub extend Value with FromValue::{from_value}

///|
pub impl ToValue for Bool with fn to_value(self) {
  Bool(self)
}

///|
pub impl FromValue for Bool with fn from_value(v) {
  match v {
    Bool(b) => b
    other => raise refuse(other, "Bool")
  }
}

///|
pub impl ToValue for Int32 with fn to_value(self) {
  Int(self)
}

///|
/// Narrows an `Int64` to its low 32 bits, as [`Row::int`] does.
pub impl FromValue for Int32 with fn from_value(v) {
  match v {
    Int(n) => n
    Int64(n) => n.to_int()
    other => raise refuse(other, "Int")
  }
}

///|
pub impl ToValue for Int64 with fn to_value(self) {
  Int64(self)
}

///|
pub impl FromValue for Int64 with fn from_value(v) {
  match v {
    Int64(n) => n
    Int(n) => n.to_int64()
    other => raise refuse(other, "Int64")
  }
}

///|
pub impl ToValue for Double with fn to_value(self) {
  Double(self)
}

///|
pub impl FromValue for Double with fn from_value(v) {
  match v {
    Double(d) => d
    Int(n) => n.to_double()
    Int64(n) => n.to_double()
    other => raise refuse(other, "Double")
  }
}

///|
pub impl ToValue for String with fn to_value(self) {
  Text(self)
}

///|
pub impl FromValue for String with fn from_value(v) {
  match v {
    Text(s) => s
    other => raise refuse(other, "Text")
  }
}

///|
pub impl ToValue for Bytes with fn to_value(self) {
  Blob(self)
}

///|
pub impl FromValue for Bytes with fn from_value(v) {
  match v {
    Blob(b) => b
    other => raise refuse(other, "Blob")
  }
}

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

///|
pub impl[T : FromValue] FromValue for T? with fn from_value(v) {
  match v {
    Null => None
    v => Some(T::from_value(v))
  }
}