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

// Type-value round-tripping in the wire's *text* format. This round binds and
// decodes everything as text (result format code 0); binary format is a later
// round (README roadmap). PostgreSQL type OIDs are stable, well-known integers.

///|
pub const OID_BOOL : Int = 16

///|
pub const OID_BYTEA : Int = 17

///|
pub const OID_INT8 : Int = 20

///|
pub const OID_INT2 : Int = 21

///|
pub const OID_INT4 : Int = 23

///|
pub const OID_FLOAT4 : Int = 700

///|
pub const OID_FLOAT8 : Int = 701

///|
/// Encode a bound parameter to its text-format bytes, or `None` for SQL `NULL`
/// (which the Bind message sends as a length of `-1`). Binding is out-of-band —
/// the value never touches the SQL string — so this is the injection-safe path.
///
/// * integers / doubles / bools render to their canonical PostgreSQL text
///   literals (`t`/`f` for booleans);
/// * `Text` passes through UTF-8;
/// * `Blob` uses the `bytea` hex format (`\x` + lowercase hex), which the server
///   accepts for a text-format `bytea` parameter.
pub fn encode_param(v : Value) -> Bytes? {
  match v {
    @moondb.Null => None
    @moondb.Bool(b) => Some(@utf8.encode(if b { "t" } else { "f" }))
    @moondb.Int(n) => Some(@utf8.encode(n.to_string()))
    @moondb.Int64(n) => Some(@utf8.encode(n.to_string()))
    @moondb.Double(d) => Some(@utf8.encode(d.to_string()))
    @moondb.Text(s) => Some(@utf8.encode(s))
    @moondb.Blob(b) => Some(@utf8.encode("\\x" + hex_lower(b)))
  }
}

///|
/// Decode a text-format result cell (`raw`, already UTF-8) tagged with its
/// column `oid` into a [`Value`]. `is_null` marks a wire `NULL` (length `-1`),
/// which decodes to `Null` regardless of type. Numeric and boolean OIDs decode
/// to their typed cases; everything else — including `numeric`, dates, and
/// unknown OIDs — rides back as `Text`, exactly the dialect-neutral contract
/// moondb documents (temporal/`numeric` typing is a roadmap item).
pub fn decode_value(oid : Int, is_null : Bool, raw : String) -> Value {
  if is_null {
    return @moondb.Null
  }
  if oid == OID_BOOL {
    @moondb.Bool(raw == "t")
  } else if oid == OID_INT2 || oid == OID_INT4 {
    match parse_int(raw) {
      Some(n) => @moondb.Int(n)
      None => @moondb.Text(raw)
    }
  } else if oid == OID_INT8 {
    match parse_int64(raw) {
      Some(n) => @moondb.Int64(n)
      None => @moondb.Text(raw)
    }
  } else if oid == OID_FLOAT4 || oid == OID_FLOAT8 {
    match parse_double(raw) {
      Some(d) => @moondb.Double(d)
      None => @moondb.Text(raw)
    }
  } else if oid == OID_BYTEA {
    @moondb.Blob(decode_bytea(raw))
  } else {
    @moondb.Text(raw)
  }
}

///|
/// Parse a base-10 `Int`, or `None` if `s` is not a well-formed integer. Used to
/// decode `int2`/`int4` result text; a parse failure falls back to `Text` so a
/// surprising server rendering never silently becomes a wrong number.
pub fn parse_int(s : String) -> Int? {
  match parse_int64(s) {
    Some(v) => Some(v.to_int())
    None => None
  }
}

///|
/// Parse a base-10 `Int64`, or `None` on any non-digit (after an optional sign).
pub fn parse_int64(s : String) -> Int64? {
  let n = s.length()
  if n == 0 {
    return None
  }
  let mut i = 0
  let mut neg = false
  if s[0] == '-' {
    neg = true
    i = 1
  } else if s[0] == '+' {
    i = 1
  }
  if i >= n {
    return None
  }
  let mut acc : Int64 = 0
  while i < n {
    let c = s[i].to_int()
    if c < 48 || c > 57 {
      return None
    }
    acc = acc * 10 + (c - 48).to_int64()
    i += 1
  }
  Some(if neg { -acc } else { acc })
}

///|
/// Parse a floating-point literal from PostgreSQL text (`123.45`, `-1e10`,
/// `Infinity`, `NaN`), or `None` if malformed. Handles sign, fraction, and a
/// base-10 exponent; the special IEEE tokens PostgreSQL emits are recognised.
pub fn parse_double(s : String) -> Double? {
  match s {
    "NaN" => return Some(@double.not_a_number)
    "Infinity" => return Some(@double.infinity)
    "-Infinity" => return Some(@double.neg_infinity)
    _ => ()
  }
  let n = s.length()
  if n == 0 {
    return None
  }
  let mut i = 0
  let mut neg = false
  if s[0] == '-' {
    neg = true
    i = 1
  } else if s[0] == '+' {
    i = 1
  }
  let mut int_part : Double = 0.0
  let mut saw_digit = false
  while i < n && s[i] >= '0' && s[i] <= '9' {
    int_part = int_part * 10.0 + (s[i].to_int() - 48).to_double()
    saw_digit = true
    i += 1
  }
  let mut frac : Double = 0.0
  let mut scale : Double = 1.0
  if i < n && s[i] == '.' {
    i += 1
    while i < n && s[i] >= '0' && s[i] <= '9' {
      frac = frac * 10.0 + (s[i].to_int() - 48).to_double()
      scale = scale * 10.0
      saw_digit = true
      i += 1
    }
  }
  if !saw_digit {
    return None
  }
  let mut mantissa = int_part + frac / scale
  if i < n && (s[i] == 'e' || s[i] == 'E') {
    i += 1
    let mut exp_neg = false
    if i < n && s[i] == '-' {
      exp_neg = true
      i += 1
    } else if i < n && s[i] == '+' {
      i += 1
    }
    if i >= n {
      return None
    }
    let mut exp = 0
    while i < n && s[i] >= '0' && s[i] <= '9' {
      exp = exp * 10 + (s[i].to_int() - 48)
      i += 1
    }
    let mut factor : Double = 1.0
    for _j in 0.. Bytes {
  if s.length() >= 2 && s[0] == '\\' && s[1] == 'x' {
    let hexn = (s.length() - 2) / 2
    let buf = Buffer()
    for i in 0.. Int {
  if c >= 48 && c <= 57 {
    c - 48
  } else if c >= 97 && c <= 102 {
    c - 97 + 10
  } else if c >= 65 && c <= 70 {
    c - 65 + 10
  } else {
    0
  }
}