///|
// ============================================================================
// Type Parsing
// ============================================================================

///|
/// Map DuckDB type id to ColumnType.
pub fn column_type_from_id(id : Int) -> ColumnType {
  match id {
    0 => ColumnType::Invalid
    1 => ColumnType::Boolean
    2 => ColumnType::TinyInt
    3 => ColumnType::SmallInt
    4 => ColumnType::Integer
    5 => ColumnType::BigInt
    6 => ColumnType::UTinyInt
    7 => ColumnType::USmallInt
    8 => ColumnType::UInteger
    9 => ColumnType::UBigInt
    10 => ColumnType::Float
    11 => ColumnType::Double
    12 => ColumnType::Timestamp
    13 => ColumnType::Date
    14 => ColumnType::Time
    15 => ColumnType::Interval
    16 => ColumnType::HugeInt
    17 => ColumnType::Varchar
    18 => ColumnType::Blob
    19 => ColumnType::Decimal
    20 => ColumnType::TimestampS
    21 => ColumnType::TimestampMs
    22 => ColumnType::TimestampNs
    23 => ColumnType::Enum
    24 => ColumnType::List
    25 => ColumnType::Struct
    26 => ColumnType::Map
    27 => ColumnType::Uuid
    28 => ColumnType::Union
    29 => ColumnType::Bit
    30 => ColumnType::TimeTz
    31 => ColumnType::TimestampTz
    32 => ColumnType::UHugeInt
    33 => ColumnType::Array
    34 => ColumnType::Any
    35 => ColumnType::Bignum
    36 => ColumnType::SqlNull
    37 => ColumnType::StringLiteral
    38 => ColumnType::IntegerLiteral
    39 => ColumnType::TimeNs
    _ => ColumnType::Unknown(id)
  }
}

///|
/// Infer and parse a string value into an appropriate Value type.
pub fn parse_value(s : String) -> Value {
  if s == "true" {
    Value::Bool(true)
  } else if s == "false" {
    Value::Bool(false)
  } else if is_integer(s) {
    Value::Int(parse_int(s))
  } else if is_double(s) {
    Value::Double(parse_double(s))
  } else if is_date(s) {
    match parse_date(s) {
      Ok(days) => Value::Date(days)
      Err(_) => Value::String(s)
    }
  } else if is_timestamp(s) {
    match parse_timestamp(s) {
      Ok(micros) => Value::Timestamp(micros)
      Err(_) => Value::String(s)
    }
  } else {
    Value::String(s)
  }
}

///|
/// Parse a string value using the declared column type when available.
fn parse_value_with_type(s : String, column_type : ColumnType) -> Value {
  match column_type {
    ColumnType::Boolean =>
      if s == "true" {
        Value::Bool(true)
      } else if s == "false" {
        Value::Bool(false)
      } else {
        Value::String(s)
      }
    ColumnType::TinyInt
    | ColumnType::SmallInt
    | ColumnType::Integer
    | ColumnType::BigInt
    | ColumnType::UTinyInt
    | ColumnType::USmallInt
    | ColumnType::UInteger
    | ColumnType::UBigInt => Value::Int(parse_int(s))
    ColumnType::Float | ColumnType::Double =>
      if is_special_float_string(s) {
        Value::String(s)
      } else {
        Value::Double(parse_double(s))
      }
    ColumnType::Date =>
      match parse_date(s) {
        Ok(days) => Value::Date(days)
        Err(_) => Value::String(s)
      }
    ColumnType::Timestamp
    | ColumnType::TimestampS
    | ColumnType::TimestampMs
    | ColumnType::TimestampNs
    | ColumnType::TimestampTz =>
      match parse_timestamp(s) {
        Ok(micros) => Value::Timestamp(micros)
        Err(_) => Value::String(s)
      }
    ColumnType::Varchar
    | ColumnType::Enum
    | ColumnType::Uuid
    | ColumnType::StringLiteral => Value::String(s)
    ColumnType::Decimal
    | ColumnType::HugeInt
    | ColumnType::UHugeInt
    | ColumnType::Interval
    | ColumnType::List
    | ColumnType::Struct
    | ColumnType::Map
    | ColumnType::Array
    | ColumnType::Union
    | ColumnType::Bit
    | ColumnType::Time
    | ColumnType::TimeTz
    | ColumnType::TimeNs
    | ColumnType::Any
    | ColumnType::Bignum
    | ColumnType::Blob
    | ColumnType::SqlNull
    | ColumnType::IntegerLiteral => Value::String(s)
    ColumnType::Invalid | ColumnType::Unknown(_) => parse_value(s)
  }
}

///|
pub fn is_special_float_string(s : String) -> Bool {
  s == "nan" ||
  s == "NaN" ||
  s == "inf" ||
  s == "Infinity" ||
  s == "-inf" ||
  s == "-Infinity"
}

///|
/// Check if string represents an integer.
pub fn is_integer(s : String) -> Bool {
  if s.length() == 0 {
    false
  } else {
    let mut has_digit = false
    let start = if s[0] == '-' || s[0] == '+' { 1 } else { 0 }
    for i in start.. '9' {
        return false
      }
      has_digit = true
    }
    has_digit
  }
}

///|
/// Check if string represents a double.
pub fn is_double(s : String) -> Bool {
  if s.length() == 0 {
    false
  } else {
    let mut has_dot = false
    let mut has_digit = false
    let start = if s[0] == '-' || s[0] == '+' { 1 } else { 0 }
    for i in start.. '9' {
        return false
      } else {
        has_digit = true
      }
    }
    has_digit && has_dot
  }
}

///|
/// Parse string to Int.
pub fn parse_int(s : String) -> Int {
  let mut result = 0
  let mut negative = false
  let mut start = 0
  if s.length() > 0 {
    if s[0] == '-' {
      negative = true
      start = 1
    } else if s[0] == '+' {
      start = 1
    }
  }
  let limit = if negative { @int.MIN_VALUE } else { -@int.MAX_VALUE }
  let multmin = limit / 10
  for i in start..= '0' && c <= '9' {
      let digit = c.to_int() - '0'.to_int()
      if result < multmin {
        return if negative { @int.MIN_VALUE } else { @int.MAX_VALUE }
      }
      result = result * 10
      let next = result - digit
      if next < limit {
        return if negative { @int.MIN_VALUE } else { @int.MAX_VALUE }
      }
      result = next
    }
  }
  if negative {
    result
  } else {
    -result
  }
}

///|
/// Parse string to Double.
pub fn parse_double(s : String) -> Double {
  // For simplicity, use Int parsing and add fractional part
  let dot_index = find_dot(s)
  match dot_index {
    Some(idx) => {
      let int_part = s.view(start_offset=0, end_offset=idx).to_string()
      let frac_part = s
        .view(start_offset=idx + 1, end_offset=s.length())
        .to_string()
      let int_val = parse_int(int_part)
      let frac_val = parse_fractional(frac_part)
      let sign = if s.length() > 0 && s[0] == '-' { -1.0 } else { 1.0 }
      sign * (int_val.abs().to_double() + frac_val)
    }
    None => parse_int(s).to_double()
  }
}

///|
/// Find dot position in string.
fn find_dot(s : String) -> Int? {
  for i in 0.. Double {
  let mut result = 0.0
  let mut divisor = 1.0
  for i in 0..= '0' && c <= '9' {
      divisor = divisor * 10.0
      result = result + (c.to_int() - '0'.to_int()).to_double() / divisor
    }
  }
  result
}

///|
/// Check if string matches ISO date format (YYYY-MM-DD).
fn is_date(s : String) -> Bool {
  s.length() == 10 && s[4] == '-' && s[7] == '-'
}

///|
/// Parse ISO date string to days since epoch.
pub fn parse_date(s : String) -> Result[Int, String] {
  if !is_date(s) {
    Err("invalid date format")
  } else {
    let year_str = s.view(start_offset=0, end_offset=4).to_string()
    let month_str = s.view(start_offset=5, end_offset=7).to_string()
    let day_str = s.view(start_offset=8, end_offset=10).to_string()
    let year = parse_int(year_str)
    let month = parse_int(month_str)
    let day = parse_int(day_str)

    // Validate month range
    if month < 1 || month > 12 {
      Err("invalid month")
    } else if day < 1 || day > typed_days_in_month(year, month) {
      // Validate day range for the specific month/year
      Err("invalid day")
    } else {
      Ok(date_to_days(year, month, day))
    }
  }
}

///|
/// Convert year, month, day to days since epoch (1970-01-01).
pub fn date_to_days(year : Int, month : Int, day : Int) -> Int {
  // Simplified calculation - days from 1970-01-01
  let y = year - 1970
  let mut days = y * 365

  // Add leap days
  let mut leap_years = 0
  for ly in 1970.. Bool {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

///|
/// Get the number of days in a month.
fn typed_days_in_month(year : Int, month : Int) -> Int {
  if month == 2 {
    if typed_is_leap_year(year) {
      29
    } else {
      28
    }
  } else if month == 4 || month == 6 || month == 9 || month == 11 {
    30
  } else {
    31
  }
}

///|
/// Check if string matches ISO timestamp format.
fn is_timestamp(s : String) -> Bool {
  s.length() >= 19 &&
  s[4] == '-' &&
  s[7] == '-' &&
  s[10] == ' ' &&
  s[13] == ':' &&
  s[16] == ':'
}

///|
/// Parse ISO timestamp string to microseconds since epoch.
pub fn parse_timestamp(s : String) -> Result[Int64, String] {
  if !is_timestamp(s) {
    Err("invalid timestamp format")
  } else {
    let date_part = s.view(start_offset=0, end_offset=10).to_string()
    match parse_date(date_part) {
      Ok(days) => {
        let time_part = s
          .view(start_offset=11, end_offset=s.length())
          .to_string()
        match parse_time_to_micros(time_part) {
          Ok(micros) => {
            // Use Int64 to avoid overflow
            let days_64 = days.to_int64()
            let days_micros = days_64 * 86400L * 1000000L
            Ok(days_micros + micros)
          }
          Err(e) => Err(e)
        }
      }
      Err(e) => Err(e)
    }
  }
}

///|
/// Parse fractional seconds (up to 6 digits) into microseconds.
pub fn parse_fraction_to_micros(s : String) -> Int {
  let mut micros = 0
  let mut factor = 100000
  for i in 0.. '9' {
      break
    }
    let digit = c.to_int() - '0'.to_int()
    if factor >= 1 {
      micros = micros + digit * factor
      factor = factor / 10
    }
  }
  micros
}

///|
/// Parse time string (HH:MM:SS[.sss...]) to microseconds since midnight.
pub fn parse_time_to_micros(s : String) -> Result[Int64, String] {
  // Manual parsing since split() returns Iter
  let mut colon_count = 0
  let mut colon1 = -1
  let mut colon2 = -1
  for i in 0.. {
        let second = parse_int(
          sec_part.view(start_offset=0, end_offset=idx).to_string(),
        )
        let frac = sec_part
          .view(start_offset=idx + 1, end_offset=sec_part.length())
          .to_string()
        (second, parse_fraction_to_micros(frac))
      }
      None => (parse_int(sec_part), 0)
    }
    let hour = parse_int(hour_str)
    let minute = parse_int(minute_str)
    // Use Int64 to avoid overflow
    let base = (
        hour.to_int64() * 3600L + minute.to_int64() * 60L + second.to_int64()
      ) *
      1000000L
    Ok(base + frac_micros.to_int64())
  }
}