// Copyright (c) 2026 colmugx
// SPDX-License-Identifier: Apache-2.0

// Exact numeric semantics for JSON Schema numeric keywords (2020-12 §6.2).
//
// Every predicate here is exact. There is no epsilon: `0.3` IS a multiple of
// `0.1`, and `1.0000000001` is NOT a multiple of `1`, however convenient the
// opposite would be for floating-point arithmetic. This layer deliberately
// avoids tolerance checks and rounded quotients.

///|
/// A number taken from a JSON tree, kept exactly.
///
/// `Repr` numbers carry the literal as written (`"0.3"`, `"1e10"`); `Double`
/// numbers were constructed in code and only the IEEE value remains. The
/// representation stays private: literal validation is exact, while `Double`
/// validation uses the finite double's shortest decimal rendering. Original
/// digits discarded by another parser cannot be recovered.
pub enum ExactNumber {
  Repr(coeff~ : @bigint.BigInt, scale~ : Int32)
  Double(Double)
}

///|
/// A Json number node read as exactly as the source allows: parsed trees
/// carry only the double (`@json.parse` does not keep literal text on this
/// toolchain — verified by probe), constructed trees may carry a repr.
pub fn ExactNumber::of_json(v : Json) -> ExactNumber? {
  match v {
    Number(d, repr~) =>
      match repr {
        Some(text) => {
          let exact = ExactNumber::from_literal(text)
          guard exact is Some(_) else { return None }
          let parsed = literal_double(text)
          match parsed {
            Some(binary) => if binary != d || d.is_nan() { None } else { exact }
            None => None
          }
        }
        None => if d.is_inf() || d.is_nan() { None } else { Some(Double(d)) }
      }
    _ => None
  }
}

///|
/// Interpret a Json number node's literal exactly when available.
pub fn ExactNumber::from_double(v : Double) -> ExactNumber {
  Double(v)
}

///|
priv struct Parts {
  coeff : String
  scale : Int32
  negative : Bool
}

///|
/// Outcome of lexing a JSON number literal. `MalformedLiteral` violates the
/// JSON number grammar; `BudgetExceeded` is a well-formed literal whose
/// decimal scale leaves the ±100000 exact-arithmetic budget. The distinction
/// lets callers report a resource limit instead of an invalid number.
priv enum PartsResult {
  Parsed(Parts)
  MalformedLiteral
  BudgetExceeded
}

///|
/// Lex a JSON number literal into (coefficient digits, decimal exponent of
/// the last digit). Accepts exactly the JSON number grammar. The coefficient
/// keeps every written digit, so only the derived scale can leave the
/// arithmetic budget.
fn parse_parts(text : String) -> PartsResult {
  let s = text
  let n = s.length()
  if n == 0 {
    return MalformedLiteral
  }
  let mut i = 0
  let mut negative = false
  match s.get_char(0) {
    Some('-') => {
      negative = true
      i = 1
    }
    Some('+') => return MalformedLiteral
    _ => ()
  }
  let int_digits = StringBuilder()
  let frac_digits = StringBuilder()
  let mut seen_int = false
  while i < n {
    match s.get_char(i) {
      Some(c) if c >= '0' && c <= '9' => {
        int_digits.write_char(c)
        seen_int = true
        i += 1
      }
      _ => break
    }
  }
  if !seen_int {
    return MalformedLiteral
  }
  // JSON leading-zero rule: "0" alone or "0.x" — "01" is invalid
  let ints = int_digits.to_string()
  if ints.length() > 1 && ints.get_char(0) == Some('0') {
    return MalformedLiteral
  }
  if i < n && s.get_char(i) == Some('.') {
    i += 1
    let mut seen_frac = false
    while i < n {
      match s.get_char(i) {
        Some(c) if c >= '0' && c <= '9' => {
          frac_digits.write_char(c)
          seen_frac = true
          i += 1
        }
        _ => break
      }
    }
    if !seen_frac {
      return MalformedLiteral
    }
  }
  let mut exp : Int64 = 0
  if i < n {
    match s.get_char(i) {
      Some('e') | Some('E') => {
        i += 1
        let mut exp_negative = false
        match s.get_char(i) {
          Some('+') => i += 1
          Some('-') => {
            exp_negative = true
            i += 1
          }
          _ => ()
        }
        let digits = StringBuilder()
        while i < n {
          match s.get_char(i) {
            Some(c) if c >= '0' && c <= '9' => {
              digits.write_char(c)
              i += 1
            }
            _ => break
          }
        }
        if digits.to_string() == "" {
          return MalformedLiteral
        }
        // An exponent magnitude that cannot even be held in an Int64 is far
        // outside the arithmetic budget, not a grammar error.
        let mag = @string.parse_int64(digits.to_string()) catch {
          _ => return BudgetExceeded
        }
        exp = if exp_negative { -mag } else { mag }
      }
      _ => ()
    }
  }
  if i != n {
    return MalformedLiteral
  }
  let fracs = frac_digits.to_string()
  let all_digits = ints + fracs
  // The budget constrains the derived decimal scale, not the written
  // exponent: `0.1e100001` normalizes to scale -100000 and stays exactly
  // representable, while `1e100001` leaves the budget. Overflowing Int64
  // arithmetic here can only wrap far outside ±100000, which the check below
  // still classifies as a budget limit.
  let scale64 : Int64 = fracs.length().to_int64() - exp
  if scale64 > 100000L || scale64 < -100000L {
    return BudgetExceeded
  }
  Parsed({ coeff: all_digits, scale: scale64.to_int(), negative, })
}

///|
// The core parser signals binary64 overflow as Failure. A valid decimal
// lexeme remains a JSON number; its repr, not the infinity placeholder, is
// authoritative for every mathematical predicate.
fn literal_double(text : String) -> Double? {
  guard parse_parts(text) is Parsed(parts) else { return None }
  Some(@string.parse_double(text)) catch {
    _ => {
      let mut leading = 0
      while leading < parts.coeff.length() &&
            parts.coeff.get_char(leading) == Some('0') {
        leading += 1
      }
      if leading == parts.coeff.length() {
        return Some(if parts.negative { -0.0 } else { 0.0 })
      }
      let order = parts.coeff.length() - leading - 1 - parts.scale
      if order < 308 {
        return None
      }
      let infinity = 1.0 / 0.0
      Some(if parts.negative { -infinity } else { infinity })
    }
  }
}

///|
/// Interpret a decimal literal (JSON number grammar) exactly.
pub fn ExactNumber::from_literal(text : String) -> ExactNumber? {
  match parse_parts(text) {
    Parsed(parts) => {
      let signed = if parts.negative && parts.coeff != "0" {
        "-" + parts.coeff
      } else {
        parts.coeff
      }
      match @bigint.BigInt::from_string(signed) {
        coeff => Some(Repr(coeff~, scale=parts.scale))
      }
    }
    MalformedLiteral | BudgetExceeded => None
  }
}

///|
pub(all) suberror NumericError {
  InvalidNumber
} derive(Debug)

///|
pub extend NumericError with @debug.Debug::{to_repr}

///|
fn ExactNumber::parts(
  self : ExactNumber,
) -> (@bigint.BigInt, Int32) raise NumericError {
  match self {
    Repr(coeff~, scale~) => {
      if scale < -100000 || scale > 100000 {
        raise InvalidNumber
      }
      (coeff, scale)
    }
    Double(value) => {
      if value.is_inf() || value.is_nan() {
        raise InvalidNumber
      }
      match ExactNumber::from_literal(value.to_string()) {
        Some(Repr(coeff~, scale~)) => (coeff, scale)
        _ => raise InvalidNumber
      }
    }
  }
}

///|
fn decimal_power(exponent : Int32) -> @bigint.BigInt {
  @bigint.BigInt::from_int(10).pow(@bigint.BigInt::from_int(exponent))
}

///|
fn aligned(
  left : ExactNumber,
  right : ExactNumber,
) -> (@bigint.BigInt, @bigint.BigInt) raise NumericError {
  let (a, sa) = left.parts()
  let (b, sb) = right.parts()
  if sa < sb {
    (a * decimal_power(sb - sa), b)
  } else if sa > sb {
    (a, b * decimal_power(sa - sb))
  } else {
    (a, b)
  }
}

///|
/// Decimal integer predicate; no rounding or floating-point tolerance.
pub fn ExactNumber::is_integer(self : ExactNumber) -> Bool raise NumericError {
  match self {
    Double(value) => {
      if value.is_inf() || value.is_nan() {
        raise InvalidNumber
      }
      value.floor() == value
    }
    Repr(..) => {
      let (coefficient, scale) = self.parts()
      scale <= 0 || coefficient.mod(decimal_power(scale)).is_zero()
    }
  }
}

///|
/// Mathematical equality, including differently spelled decimal numbers.
pub fn ExactNumber::equal(
  self : ExactNumber,
  other : ExactNumber,
) -> Bool raise NumericError {
  match (self, other) {
    (Double(a), Double(b)) => {
      if a.is_inf() || a.is_nan() || b.is_inf() || b.is_nan() {
        raise InvalidNumber
      }
      a == b
    }
    _ => {
      let (a, b) = aligned(self, other)
      a == b
    }
  }
}

///|
pub fn ExactNumber::compare(
  self : ExactNumber,
  other : ExactNumber,
) -> Int32 raise NumericError {
  match (self, other) {
    // Comparing a constructed binary64 number with an exact mathematical
    // zero does not need decimal rendering, exponent alignment or BigInt.
    // The coefficient is zero regardless of the Repr scale, including 0e400.
    (Double(a), Repr(coeff~, ..)) if coeff.is_zero() => {
      if a.is_inf() || a.is_nan() {
        raise InvalidNumber
      }
      if a < 0.0 {
        -1
      } else if a > 0.0 {
        1
      } else {
        0
      }
    }
    (Repr(coeff~, ..), Double(b)) if coeff.is_zero() => {
      if b.is_inf() || b.is_nan() {
        raise InvalidNumber
      }
      if b < 0.0 {
        1
      } else if b > 0.0 {
        -1
      } else {
        0
      }
    }
    (Double(a), Double(b)) => {
      if a.is_inf() || a.is_nan() || b.is_inf() || b.is_nan() {
        raise InvalidNumber
      }
      if a < b {
        -1
      } else if a > b {
        1
      } else {
        0
      }
    }
    _ => {
      let (a, b) = aligned(self, other)
      if a < b {
        -1
      } else if a > b {
        1
      } else {
        0
      }
    }
  }
}

///|
/// Exact divisibility of aligned integer coefficients, never rounded division.
pub fn ExactNumber::multiple_of(
  self : ExactNumber,
  divisor : ExactNumber,
) -> Bool raise NumericError {
  if divisor.sign() <= 0 {
    return false
  }
  let (a, b) = aligned(self, divisor)
  a.mod(b).is_zero()
}

///|
pub fn ExactNumber::sign(self : ExactNumber) -> Int32 raise NumericError {
  match self {
    Double(value) => {
      if value.is_inf() || value.is_nan() {
        raise InvalidNumber
      }
      if value < 0.0 {
        -1
      } else if value > 0.0 {
        1
      } else {
        0
      }
    }
    Repr(..) => {
      let (coefficient, _) = self.parts()
      let zero = @bigint.BigInt::from_int(0)
      if coefficient < zero {
        -1
      } else if coefficient > zero {
        1
      } else {
        0
      }
    }
  }
}

///|
/// A nonnegative integer count bound (`minItems`, `maxLength`,
/// `minContains`, ...) kept exactly as `coefficient × 10^exponent` with
/// `coefficient >= 0` and `exponent >= 0`. Bounds such as `1e100000` stay
/// compact: comparing one against an instance length decides by digit width
/// and never materializes `10^exponent`, so compiling a huge bound promises
/// nothing about the resources an instance would need to satisfy it.
priv struct CountBound {
  coefficient : @bigint.BigInt
  exponent : Int32
}

///|
/// The largest count an `Int` instance length can report; bounds at or above
/// the next decimal power are already decided by width.
let count_int_max : @bigint.BigInt = @bigint.BigInt::from_int(2147483647)

///|
/// Exact conversion of a verified nonnegative integer. The caller must have
/// checked `ExactNumber::is_integer` and `sign() >= 0` first; the defensive
/// divisibility re-check only runs for fractional scales and stays within
/// the ±100000 scale budget.
fn CountBound::of_exact(number : ExactNumber) -> CountBound raise NumericError {
  let (coeff, scale) = number.parts()
  if coeff.is_zero() {
    return { coefficient: coeff, exponent: 0, }
  }
  if scale <= 0 {
    return { coefficient: coeff, exponent: -scale, }
  }
  let divisor = decimal_power(scale)
  guard coeff.mod(divisor).is_zero() else { raise InvalidNumber }
  { coefficient: coeff.div(divisor), exponent: 0, }
}

///|
/// Exact bound for a small nonnegative count (the `minContains` default).
fn CountBound::of_int(count : Int32) -> CountBound {
  guard count >= 0 else { abort("count bound must be nonnegative") }
  { coefficient: @bigint.BigInt::from_int(count), exponent: 0, }
}

///|
/// Exact ordering between the bound and an instance count `n` (an `Int`
/// array/string/object length): `-1` when the bound is smaller, `1` when it
/// is larger, `0` when equal. Bounds wider than any `Int` length decide by
/// width alone; the remaining cases fit an `Int64` product exactly
/// (`coefficient <= 2147483647`, `exponent <= 9`), so no decimal expansion
/// of `10^exponent` is ever built and the comparison never raises.
fn CountBound::compare_int(self : CountBound, n : Int32) -> Int32 {
  // The bound is a nonnegative integer; decide zero bounds and nonpositive
  // instance counts first.
  if self.coefficient.is_zero() {
    return if n < 0 { 1 } else if n == 0 { 0 } else { -1 }
  }
  if n <= 0 {
    return 1
  }
  if self.exponent >= 10 || self.coefficient > count_int_max {
    // The value is >= 10^10 or >= 2147483648, above every Int length.
    return 1
  }
  let scaled = self.coefficient.to_int64() * pow10_int64(self.exponent)
  let length = n.to_int64()
  if scaled < length {
    -1
  } else if scaled > length {
    1
  } else {
    0
  }
}

///|
fn pow10_int64(exponent : Int32) -> Int64 {
  let mut result = 1L
  for _ in 0.. Bool {
  self.compare_int(n) == 1
}

///|
/// True when the bound is below the instance count `n`.
fn CountBound::below(self : CountBound, n : Int32) -> Bool {
  self.compare_int(n) == -1
}