// 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.

///|
const INT_MIN = 0x80000000

///|
const INT_MAX = 0x7fffffff

///|
const INT64_MIN = -0x8000000000000000L

///|
const INT64_MAX = 0x7fffffffffffffffL

///|
/// This function check whether the prefix of the string is consistent with the given base,
/// and consume the prefix.
/// The boolean flag `allow_underscore` is used to check validity of underscores.
fn check_and_consume_base(
  view : StringView,
  base : Int,
) -> (Int, StringView, Bool) raise StrConvError {
  // if the base is not given, we need to determine it from the prefix
  if base == 0 {
    match view {
      ['0', 'x' | 'X', .. rest] => (16, rest, true)
      ['0', 'o' | 'O', .. rest] => (8, rest, true)
      ['0', 'b' | 'B', .. rest] => (2, rest, true)
      _ => (10, view, false)
    }
  } else {
    // if the base is given, we need to check whether the prefix is consistent with it
    match view {
      ['0', 'x' | 'X', .. rest] if base == 16 => (16, rest, true)
      ['0', 'o' | 'O', .. rest] if base == 8 => (8, rest, true)
      ['0', 'b' | 'B', .. rest] if base == 2 => (2, rest, true)
      _ => if base is (2..=36) { (base, view, false) } else { base_err() }
    }
  }
}

///|
test {
  inspect(try? parse_int64("0b01", base=3), content="Err(invalid syntax)")
  inspect(try? parse_int64("0x01", base=3), content="Err(invalid syntax)")
  inspect(try? parse_int64("0o01", base=3), content="Err(invalid syntax)")
}

///|
/// Parses a string into an Int64 number using the specified base, or returns an error.
/// The base must be 0 or between 2 and 36 (inclusive). If base is 0, it will be 
/// inferred from the string prefix:
///   - "0x" or "0X" for base 16 (hex)
///   - "0o" or "0O" for base 8 (octal) 
///   - "0b" or "0B" for base 2 (binary)
///   - Default is base 10 (decimal)
/// For readability, underscores may appear after base prefixes or between digits.
/// These underscores do not affect the value.
/// Examples:
/// ```mbt check
/// #warnings("-deprecated")
/// test {
///   inspect(@strconv.parse_int64("123"), content="123")
///   inspect(@strconv.parse_int64("0xff", base=0), content="255")
///   inspect(@strconv.parse_int64("0o10"), content="8")
///   inspect(@strconv.parse_int64("0b1010"), content="10")
///   inspect(@strconv.parse_int64("1_234"), content="1234")
///   inspect(@strconv.parse_int64("-123"), content="-123")
///   inspect(@strconv.parse_int64("ff", base=16), content="255")
///   inspect(@strconv.parse_int64("zz", base=36), content="1295")
/// }
/// ```
/// 
#deprecated("use `@string.parse_int64` instead", skip_current_package=true)
pub fn parse_int64(
  str : StringView,
  base? : Int = 0,
) -> Int64 raise StrConvError {
  guard str != "" else { syntax_err() }
  let (neg, rest) = match str.view() {
    ['+', .. rest] => (false, rest)
    ['-', .. rest] => (true, rest)
    rest => (false, rest)
  }

  // `allow_underscore` is used to check validity of underscores
  let (num_base, rest, allow_underscore) = check_and_consume_base(rest, base)

  // calculate overflow threshold
  let overflow_threshold = overflow_threshold(num_base, neg)
  let has_digit = rest
    is (['0'..='9' | 'a'..='z' | 'A'..='Z', ..]
    | ['_', '0'..='9' | 'a'..='z' | 'A'..='Z', ..])
  guard has_digit else { syntax_err() }
  // convert
  for s = rest, acc = 0L, au = allow_underscore {
    match (s, acc, au) {
      (['_'], _, _) =>
        // the last character cannot be underscore
        syntax_err()
      (['_', ..], _, false) => syntax_err()
      (['_', .. rest], acc, true) => continue rest, acc, false
      ([c, .. rest], acc, _) => {
        let c = c.to_int()
        let d = match c {
          '0'..='9' => c - '0'
          'a'..='z' => c + (10 - 'a')
          'A'..='Z' => c + (10 - 'A')
          _ => syntax_err()
        }
        guard d < num_base else { syntax_err() }
        if neg {
          guard acc >= overflow_threshold else { range_err() }
          let next_acc = acc * num_base.to_int64() - d.to_int64()
          guard next_acc <= acc else { range_err() }
          continue rest, next_acc, true
        } else {
          guard acc < overflow_threshold else { range_err() }
          let next_acc = acc * num_base.to_int64() + d.to_int64()
          guard next_acc >= acc else { range_err() }
          continue rest, next_acc, true
        }
      }
      ([], acc, _) => break acc
    }
  }
}

///|
/// Parse a string in the given base (0, 2 to 36), return a Int number or an error.
/// If the `~base` argument is 0, the base will be inferred by the prefix.
#deprecated("use `@string.parse_int` instead", skip_current_package=true)
pub fn parse_int(str : StringView, base? : Int = 0) -> Int raise StrConvError {
  let n = parse_int64(str, base~)
  if n < INT_MIN.to_int64() || n > INT_MAX.to_int64() {
    range_err()
  }
  n.to_int()
}

// Check whether the underscores are correct.
// Underscores must appear only between digits or between a base prefix and a digit.

///|
fn check_underscore(str : StringView) -> Bool {
  // skip the sign
  let rest = match str {
    ['+' | '-', .. rest] => rest
    rest => rest
    // CR: the type maybe a bit confusing?
  }

  // base prefix
  let (rest, allow_underscore, hex) = lexscan rest with longest {
    (re"^0[xX]", after=rest) => (rest, true, true)
    (re"^0[oO]", after=rest) => (rest, true, false)
    (re"^0[bB]", after=rest) => (rest, true, false)
    _ => (rest, false, false)
  }

  // 'e' and 'E' are valid hex digits
  // but are not treated as digits in decimal strings since they're used for scientific notation
  fn is_digit(c : Char) -> Bool {
    c is ('0'..='9') || (hex && c is ('a'..='f' | 'A'..='F'))
  }

  // Track whether the previous character was an underscore
  let follow_underscore = false
  for s = rest, au = allow_underscore, fu = follow_underscore {
    match (s, au, fu) {
      // Empty string is valid
      ([], _, _) => break true
      // String ending with underscore is invalid
      (['_'], _, _) => break false
      // Underscore not allowed in current position (e.g., between non-digits)
      (['_', ..], false, _) => break false
      // Valid underscore - continue but mark that next char must be a digit
      (['_', .. rest], true, _) => continue rest, false, true
      // Handle non-underscore character
      ([c, .. rest], _, fu) =>
        if is_digit(c) {
          // Digit found - allow underscore in next position
          continue rest, true, false
        } else if fu {
          // Non-digit found after underscore - invalid
          break false
        } else {
          // Non-digit found (not after underscore) - continue but don't allow underscores
          continue rest, false, false
        }
    }
  }
}

///|
fn overflow_threshold(base : Int, neg : Bool) -> Int64 {
  if !neg {
    if base == 10 {
      INT64_MAX / 10L + 1L
    } else if base == 16 {
      INT64_MAX / 16L + 1L
    } else {
      INT64_MAX / base.to_int64() + 1L
    }
  } else if base == 10 {
    INT64_MIN / 10L
  } else if base == 16 {
    INT64_MIN / 16L
  } else {
    INT64_MIN / base.to_int64()
  }
}