// Manual number parsing shared by the CSV source and the SQL lexer.
// moonbitlang/core/internal/strconv is import-restricted, so these are
// hand-rolled; the double parser is accurate enough for the 1e-9
// relative tolerance used against duckdb goldens.

///|
/// Optional sign then digits, else None. Overflow is not checked (v1
/// values fit comfortably in 32 bits after inference).
pub fn parse_i32(s : String) -> Int? {
  let mut acc = 0
  let mut neg = false
  let mut seen = false
  for ch in s {
    if seen == false && (ch == '-' || ch == '+') {
      neg = ch == '-'
      seen = true
      continue
    }
    seen = true
    let v = ch.to_int()
    if v < 48 || v > 57 {
      return None
    }
    acc = acc * 10 + (v - 48)
  }
  if !seen {
    return None // empty
  }
  Some(if neg { -acc } else { acc })
}

///|
/// [sign] digits [ . digits ] [ (e|E) [sign] digits ], else None.
pub fn parse_f64(s : String) -> Double? {
  let chars : Array[Char] = []
  for ch in s {
    chars.push(ch)
  }
  let n = chars.length()
  if n == 0 {
    return None
  }
  let mut i = 0
  let mut neg = false
  if chars[0] == '-' || chars[0] == '+' {
    neg = chars[0] == '-'
    i = 1
  }
  let mut int_part = 0L
  let mut int_digits = 0
  while i < n && is_digit(chars[i]) {
    int_part = int_part * 10L + (chars[i].to_int() - 48).to_int64()
    int_digits += 1
    i += 1
  }
  let mut frac_part = 0L
  let mut frac_len = 0
  if i < n && chars[i] == '.' {
    i += 1
    while i < n && is_digit(chars[i]) {
      if frac_len < 18 {
        frac_part = frac_part * 10L + (chars[i].to_int() - 48).to_int64()
        frac_len += 1
      }
      i += 1
    }
  }
  if int_digits == 0 && frac_len == 0 {
    return None
  }
  let mut value = int_part.to_double() + frac_part.to_double() / pow10(frac_len)
  if i < n && (chars[i] == 'e' || chars[i] == 'E') {
    i += 1
    let mut exp_neg = false
    if i < n && (chars[i] == '-' || chars[i] == '+') {
      exp_neg = chars[i] == '-'
      i += 1
    }
    let mut exp = 0
    let mut exp_digits = 0
    while i < n && is_digit(chars[i]) {
      exp = exp * 10 + (chars[i].to_int() - 48)
      exp_digits += 1
      i += 1
      if exp > 300 {
        return None
      }
    }
    if exp_digits == 0 {
      return None
    }
    if exp_neg {
      value = value / pow10(exp)
    } else {
      value = value * pow10(exp)
    }
  }
  if i != n {
    return None // trailing junk
  }
  Some(if neg { -value } else { value })
}

///|
pub fn parse_bool(s : String) -> Bool? {
  if s == "true" {
    Some(true)
  } else if s == "false" {
    Some(false)
  } else {
    None
  }
}

///|
fn is_digit(ch : Char) -> Bool {
  let v = ch.to_int()
  v >= 48 && v <= 57
}

///|
fn pow10(e : Int) -> Double {
  let mut v = 1.0
  for _ in 0..