///|
/// Value conversion error.
pub(all) suberror ValueError {
  ValueError(String)
} derive(Debug)

// ===========================================================================
// Value
// ===========================================================================

///|
/// PostgreSQL value type used for encoding (client → server via `ToValue`)
/// and decoding (server → client via `FromValue`).
///
/// Variants map to PostgreSQL types as follows:
///
/// | Variant           | PG types                                  |
/// |-------------------|-------------------------------------------|
/// | `Null`            | SQL NULL                                  |
/// | `Bool`            | bool (16)                                 |
/// | `Int`             | int2 (21), int4 (23)                      |
/// | `Int64`           | int8 (20)                                 |
/// | `Float`           | float4 (700), float8 (701)                |
/// | `String`          | text, varchar, bpchar, name, uuid, …      |
/// | `Bytes`           | bytea (17)                                |
/// | `Timestamp(Int64)` | timestamp (1114), timestamptz (1184)      |
/// | `Json(Json)`      | json (114), jsonb (3802)                  |
pub(all) enum Value {
  Null
  Bool(Bool)
  Int(Int)
  Int64(Int64)
  Float(Double)
  String(String)
  Bytes(Bytes)
  Timestamp(Int64)
  Json(Json)
} derive(Eq, Debug)

// ===========================================================================
// Format
// ===========================================================================

///|
/// Data format for a PostgreSQL cell or parameter.
///
/// `Text` (format code 0) — human-readable representation.
/// `Binary` (format code 1) — compact wire-protocol representation.
pub(all) enum Format {
  Text
  Binary
} derive(Eq, Debug)

// ===========================================================================
// ToValue trait
// ===========================================================================

///|
/// Types that can be converted to a `Value` for use as SQL parameters.
///
/// ```moonbit nocheck
/// impl ToValue for MyType with fn to_value(self) -> Value {
///   ...
/// }
///
/// let val = my_val.to_value()
/// ```
pub(open) trait ToValue {
  fn to_value(Self) -> Value
}

// ===========================================================================
// FromValue trait
// ===========================================================================

///|
/// Types that can be decoded from a `Value`.
///
/// Implementations should be strict: raise `ValueError` when the `Value`
/// variant doesn't match the expected type.
///
/// ```moonbit nocheck
/// impl FromValue for MyType with fn from_value(v : Value) -> MyType raise ValueError {
///   match v {
///     Value::String(s) => parse_my_type(s)
///     _ => raise ValueError::ValueError("expected Value::String, got ...")
///   }
/// }
/// ```
pub(open) trait FromValue {
  fn from_value(Value) -> Self raise ValueError
}

// ===========================================================================
// Built-in type implementations
// ===========================================================================

// ---------------------------------------------------------------------------
// Int
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Int with fn from_value(v : Value) {
  match v {
    Value::Int(i) => i
    _ => raise ValueError::ValueError("expected Value::Int")
  }
}

// ---------------------------------------------------------------------------
// Int64
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Int64 with fn from_value(v : Value) {
  match v {
    Value::Int64(i) => i
    Value::Timestamp(i) => i
    _ =>
      raise ValueError::ValueError("expected Value::Int64 or Value::Timestamp")
  }
}

// ---------------------------------------------------------------------------
// Double
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Double with fn from_value(v : Value) {
  match v {
    Value::Float(f) => f
    _ => raise ValueError::ValueError("expected Value::Float")
  }
}

// ---------------------------------------------------------------------------
// Bool
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Bool with fn from_value(v : Value) {
  match v {
    Value::Bool(b) => b
    _ => raise ValueError::ValueError("expected Value::Bool")
  }
}

// ---------------------------------------------------------------------------
// String
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for String with fn from_value(v : Value) {
  match v {
    Value::String(s) => s
    _ => raise ValueError::ValueError("expected Value::String")
  }
}

// ---------------------------------------------------------------------------
// Bytes
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Bytes with fn from_value(v : Value) {
  match v {
    Value::Bytes(b) => b
    _ => raise ValueError::ValueError("expected Value::Bytes")
  }
}

// ---------------------------------------------------------------------------
// Option[T] — nullable
// ---------------------------------------------------------------------------

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

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

// ---------------------------------------------------------------------------
// Json
// ---------------------------------------------------------------------------

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

///|
pub impl FromValue for Json with fn from_value(v : Value) {
  match v {
    Value::Json(j) => j
    _ => raise ValueError::ValueError("expected Value::Json")
  }
}

// ---------------------------------------------------------------------------
// Decimal
// ---------------------------------------------------------------------------

///|
pub impl ToValue for @decimal.Decimal with fn to_value(self) {
  Value::String(self.to_string())
}

///|
pub impl FromValue for @decimal.Decimal with fn from_value(v : Value) {
  match v {
    Value::String(s) => {
      let d = @decimal.Decimal::from_string(s)
      match d {
        Some(d) => d
        None => raise ValueError::ValueError("cannot convert to Decimal")
      }
    }
    _ => raise ValueError::ValueError("expected Value::String for Decimal")
  }
}

// ---------------------------------------------------------------------------
// UUID
// ---------------------------------------------------------------------------

///|
pub impl ToValue for @uuid.UUID with fn to_value(self) {
  Value::String(self.to_string())
}

///|
pub impl FromValue for @uuid.UUID with fn from_value(v : Value) {
  match v {
    Value::String(s) =>
      @uuid.from_hex(s) catch {
        _ => raise ValueError::ValueError("cannot convert to UUID")
      }
    _ => raise ValueError::ValueError("expected Value::String for UUID")
  }
}

// ===========================================================================
// Tests
// ===========================================================================

///|
test "to_value_int" {
  let val = ToValue::to_value(42)
  assert_eq(val, Value::Int(42))
}

///|
test "from_value_int_roundtrip" {
  let v : Int = FromValue::from_value(Value::Int(42))
  assert_eq(v, 42)
}

///|
test "from_value_int_mismatch" {
  let mut got_error : Bool = false
  let _ = FromValue::from_value(Value::String("bad")) catch {
    _ => {
      got_error = true
      0
    }
  }
  assert_true(got_error)
}

///|
test "option_to_value_some" {
  let v : Int? = Some(42)
  let val = ToValue::to_value(v)
  match val {
    Value::Int(n) => assert_eq(n, 42)
    _ => fail("expected Value::Int")
  }
}

///|
test "option_to_value_none" {
  let v : Int? = None
  let val = ToValue::to_value(v)
  match val {
    Value::Null => ()
    _ => fail("expected Value::Null")
  }
}

///|
test "option_from_value_null" {
  let v : Int? = FromValue::from_value(Value::Null)
  match v {
    None => ()
    Some(_) => fail("expected None")
  }
}

///|
test "json_to_value" {
  let j = @json.parse("[1,2,3]")
  let val = ToValue::to_value(j)
  match val {
    Value::Json(v) => assert_eq(v.stringify(), "[1,2,3]")
    _ => fail("expected Value::Json")
  }
}

///|
test "bool_from_value" {
  assert_eq(FromValue::from_value(Value::Bool(true)), true)
  assert_eq(FromValue::from_value(Value::Bool(false)), false)
}

// ===========================================================================
// Timestamp
// ===========================================================================

///|
/// Microseconds since Unix epoch (1970-01-01 00:00:00 UTC).
pub(all) struct Timestamp(Int64) derive(Eq, Debug)

///|
pub impl ToValue for Timestamp with fn to_value(self) {
  Value::Timestamp(self.0)
}

///|
pub impl FromValue for Timestamp with fn from_value(v : Value) {
  match v {
    Value::Timestamp(u) => Timestamp(u)
    _ => raise ValueError::ValueError("expected Value::Timestamp")
  }
}

///|
test "timestamp_to_value" {
  let ts = Timestamp::Timestamp(1720528496000000L)
  let val = ToValue::to_value(ts)
  match val {
    Value::Timestamp(u) => assert_eq(u, 1720528496000000L)
    _ => fail("expected Value::Timestamp")
  }
}

///|
test "timestamp_from_value" {
  let val = Value::Timestamp(1720528496000000L)
  let ts : Timestamp = FromValue::from_value(val)
  assert_eq(ts.0, 1720528496000000L)
}

// ===========================================================================
// Edge case tests
// ===========================================================================

// --- String roundtrip ---

///|
test "string_to_value" {
  let val = ToValue::to_value("hello")
  assert_eq(val, Value::String("hello"))
}

///|
test "string_from_value" {
  let v : String = FromValue::from_value(Value::String("hello"))
  assert_eq(v, "hello")
}

///|
test "string_from_value_mismatch" {
  let mut ok = false
  let _ = FromValue::from_value(Value::Int(42)) catch {
    _ => {
      ok = true
      ""
    }
  }
  assert_true(ok)
}

// --- Bytes roundtrip ---

///|
test "bytes_to_value" {
  let data = b"\x00\x01\xFF"
  let val = ToValue::to_value(data)
  assert_eq(val, Value::Bytes(data))
}

///|
test "bytes_from_value" {
  let data = b"\x00\x01\xFF"
  let v : Bytes = FromValue::from_value(Value::Bytes(data))
  assert_eq(v, data)
}

// --- Double roundtrip ---

///|
test "double_to_value" {
  let val = ToValue::to_value(3.14)
  assert_eq(val, Value::Float(3.14))
}

///|
test "double_from_value" {
  let v : Double = FromValue::from_value(Value::Float(3.14))
  assert_eq(v, 3.14)
}

// --- Int64 roundtrip ---

///|
test "int64_to_value" {
  let val = ToValue::to_value(42L)
  assert_eq(val, Value::Int64(42L))
}

///|
test "int64_from_value" {
  let v : Int64 = FromValue::from_value(Value::Int64(42L))
  assert_eq(v, 42L)
}

// --- Int64 from Timestamp (compatibility) ---

///|
test "int64_from_timestamp" {
  // Int64::from_value accepts Value::Timestamp
  let v : Int64 = FromValue::from_value(Value::Timestamp(1720000000000000L))
  assert_eq(v, 1720000000000000L)
}

// --- NULL roundtrip via Option ---

///|
test "option_string_from_null" {
  let v : String? = FromValue::from_value(Value::Null)
  match v {
    None => ()
    Some(_) => fail("expected None")
  }
}

///|
test "option_int_from_some" {
  let v : Int? = FromValue::from_value(Value::Int(42))
  match v {
    Some(n) => assert_eq(n, 42)
    None => fail("expected Some(42)")
  }
}

// --- Decimal roundtrip ---

///|
test "decimal_to_value" {
  let d = @decimal.Decimal::from_string("3.14").unwrap()
  let val = ToValue::to_value(d)
  assert_eq(val, Value::String("3.14"))
}

///|
test "decimal_from_value" {
  let v : @decimal.Decimal = FromValue::from_value(Value::String("3.14"))
  assert_eq(v.to_string(), "3.14")
}

///|
test "decimal_from_value_invalid" {
  let mut ok = false
  let _ = FromValue::from_value(Value::String("not_a_number")) catch {
    _ => {
      ok = true
      @decimal.Decimal::from_string("0").unwrap()
    }
  }
  assert_true(ok)
}

// --- UUID roundtrip ---

///|
test "uuid_from_value" {
  let v : @uuid.UUID = FromValue::from_value(
    Value::String("550e8400-e29b-41d4-a716-446655440000"),
  )
  assert_eq(v.to_string(), "550e8400-e29b-41d4-a716-446655440000")
}

// --- Timestamp mismatch ---

///|
test "timestamp_from_value_mismatch" {
  let mut ok = false
  let _ = FromValue::from_value(Value::String("bad")) catch {
    _ => {
      ok = true
      Timestamp::Timestamp(0L)
    }
  }
  assert_true(ok)
}

// --- Json roundtrip ---

///|
test "json_from_value" {
  let j = @json.parse("{\"a\":1}")
  let val = Value::Json(j)
  let v : Json = FromValue::from_value(val)
  assert_eq(v.stringify(), "{\"a\":1}")
}

// --- Bool edge cases ---

///|
test "bool_from_value_all" {
  assert_eq(FromValue::from_value(Value::Bool(true)), true)
  assert_eq(FromValue::from_value(Value::Bool(false)), false)
}

///|
test "bool_from_value_mismatch" {
  let mut ok = false
  let _ = FromValue::from_value(Value::Int(1)) catch {
    _ => {
      ok = true
      false
    }
  }
  assert_true(ok)
}