// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
let min_19digit_int : UInt64 = 100_0000_0000_0000_0000UL

///|
priv struct Number {
  exponent : Int64
  mantissa : UInt64
  negative : Bool
  many_digits : Bool
}

///|
/// Returns the remaining slice, the parsed number, and the number of digits parsed.
fn parse_digits(s : StringView, x : UInt64) -> (StringView, UInt64, Int) {
  s.fold_digits(x, (digit, acc : UInt64) => {
    acc * 10UL + UInt64::extend_uint(digit.reinterpret_as_uint())
  })
}

///|
fn try_parse_19digits(s : StringView, x : UInt64) -> (StringView, UInt64, Int) {
  let mut x = x
  let mut len = 0
  for s = s {
    match s {
      ['0'..='9' as ch, .. rest] if x < min_19digit_int => {
        len += 1
        x = x * 10UL +
          UInt64::extend_uint((ch.to_int() - '0').reinterpret_as_uint()) // no overflows here
        continue rest
      }
      ['_', .. rest] => continue rest
      s => return (s, x, len)
    }
  }
}

///|
fn parse_scientific(s : StringView) -> (StringView, Int64)? {
  let mut s = s
  let exp_num = 0L
  let mut neg_exp = false
  if s is ['+' | '-' as ch, .. rest] {
    neg_exp = ch == '-'
    s = rest
  }
  if s is ['0'..='9', ..] {
    let (s, exp_num, _) = s.fold_digits(exp_num, (digit, exp_num : Int64) => {
      if exp_num < 0x10000L {
        10L * exp_num + digit.to_int64() // no overflows here
      } else {
        exp_num
      }
    })
    if neg_exp {
      Some((s, -exp_num))
    } else {
      Some((s, exp_num))
    }
  } else {
    None
  }
}

///|
/// Parse the number from the string, raising StrConvError if invalid.
fn parse_number(s : StringView) -> Number? raise StrConvError {
  let start = s

  // handle optional +/- sign
  let (s, negative) = match s {
    ['-', .. rest] => (rest, true)
    ['+', .. rest] | rest => (rest, false)
  }
  if s.is_empty() {
    return None
  }

  // parse initial digits before dot
  let (s, mantissa, consumed) = parse_digits(s, 0UL)
  let mut mantissa = mantissa
  let mut s = s
  let mut n_digits = consumed

  // handle dot with the following digits
  let mut n_after_dot = 0
  let mut exponent = 0L
  if s is ['.', .. rest] {
    s = rest
    // TODO: optimization chance. In the original Rust implementation,
    // the digits are stored as consecutive bytes in the string.
    // It directly reads 8 bytes to `u64`.
    let (new_s, new_mantissa, consumed_digit) = parse_digits(s, mantissa)
    s = new_s
    mantissa = new_mantissa
    n_after_dot = consumed_digit
    exponent = -n_after_dot.to_int64()
  }
  n_digits += n_after_dot
  if n_digits == 0 {
    return None
  }

  // handle scientific format
  let exp_number = 0L
  if s is ['e' | 'E', .. rest] {
    let (new_s, exp_number) = match parse_scientific(rest) {
      Some(res) => res
      None => return None
    }
    s = new_s
    exponent += exp_number
  }
  guard s is "" else { syntax_err() }

  // handle uncommon case with many digits
  if n_digits <= 19 {
    return Some({ exponent, mantissa, negative, many_digits: false })
  }
  n_digits -= 19
  let mut many_digits = false
  for s = start {
    match s {
      ['0' | '.' as ch, .. rest] => {
        n_digits -= (ch.to_int() - 46) / 2 // '0' = b'.' + 2
        continue rest
      }
      _ => break
    }
  }
  let mut mantissa = mantissa
  if n_digits > 0 {
    // at this point we have more than 19 significant digits, let's try again
    many_digits = true
    mantissa = 0UL
    let s = start
    let (s, new_mantissa, consumed_digit) = try_parse_19digits(s, mantissa)
    mantissa = new_mantissa
    exponent = (if mantissa >= min_19digit_int {
      consumed_digit // big int
    } else {
      // fractional component, skip the '.'
      guard s is [_, .. s] else { return None }
      let (_, new_mantissa, consumed_digit) = try_parse_19digits(s, mantissa)
      mantissa = new_mantissa
      consumed_digit
    }).to_int64()
    exponent += exp_number
  } // add back the explicit part
  Some({ exponent, mantissa, negative, many_digits })
}

///|
/// Parse the number from the string, raising `StrConvError` if invalid.
fn parse_inf_nan(rest : StringView) -> Double raise StrConvError {
  let (pos, rest) = match rest {
    ['-', .. rest] => (false, rest)
    ['+', .. rest] | rest => (true, rest)
  }
  lexscan rest with longest {
    re"^(?i:nan)$" => @double.not_a_number
    re"^(?i:inf(inity)?)$" =>
      if pos {
        @double.infinity
      } else {
        @double.neg_infinity
      }
    _ => syntax_err()
  }
}

///|
/// Returns None if the multiplication might overflow (there are some false-negative corner cases).
/// Otherwise, returns Some(m), where m = self * b.
/// WARNING: Note this function is only used internally in the strconv module, 
/// the current implementation is not completely safe against overflows.
fn checked_mul(a : UInt64, b : UInt64) -> UInt64? {
  if a == 0UL || b == 0UL {
    return Some(0UL)
  }
  if a == 1UL {
    return Some(b)
  }
  if b == 1UL {
    return Some(a)
  }
  // Can only multiply by 1 or 0, which is handled above.
  if b.clz() == 0 || a.clz() == 0 {
    return None
  }
  let quotient : UInt64 = @uint64.MAX_VALUE / b
  if a > quotient {
    return None
  }
  Some(a * b)
}