//! Arbitrary-precision number type for CalcText.
//!
//! Uses [`dashu`](https://docs.rs/dashu) for exact integer and rational arithmetic,
//! which is pure Rust and WASM-compatible. Numbers are stored as either exact integers
//! (`IBig`) or exact rationals (`RBig`), avoiding floating-point representation errors.
//!
//! # Examples
//!
//! ```
//! use calcpad_engine::number::Number;
//!
//! // 0.1 + 0.2 == 0.3 exactly (no floating-point drift)
//! let a = Number::parse("0.1").unwrap();
//! let b = Number::parse("0.2").unwrap();
//! let sum = a + b;
//! assert_eq!(sum.to_string(), "0.3");
//! ```

use dashu::integer::IBig;
use dashu::rational::RBig;
use serde::{Deserialize, Serialize};
use std::fmt;

/// Default number of significant digits for formatting non-terminating decimals.
pub const DEFAULT_PRECISION: usize = 30;

/// Arbitrary-precision number.
///
/// Uses `Integer` for exact integer values and `Rational` for exact
/// fractional values. Decimal inputs like `"0.1"` are stored as rationals
/// (1/10) to avoid floating-point representation errors.
#[derive(Clone, Debug)]
pub enum Number {
    Integer(IBig),
    Rational(RBig),
}

// ---------------------------------------------------------------------------
// Construction & parsing
// ---------------------------------------------------------------------------

impl Number {
    /// Parse a decimal string into a `Number`, preserving exact representation.
    ///
    /// - `"42"` becomes `Integer(42)`
    /// - `"0.1"` becomes `Rational(1/10)`
    /// - `"-3.14"` becomes `Rational(-314/100)` (auto-reduced)
    pub fn parse(s: &str) -> Option<Number> {
        let s = s.trim();
        if s.is_empty() {
            return None;
        }

        // Handle optional leading sign
        let (is_negative, s) = if let Some(rest) = s.strip_prefix('-') {
            (true, rest)
        } else if let Some(rest) = s.strip_prefix('+') {
            (false, rest)
        } else {
            (false, s)
        };

        let result = if s.contains('.') {
            // Parse as exact rational: "3.14" -> 314/100, auto-reduced by RBig
            let parts: Vec<&str> = s.splitn(2, '.').collect();
            let integer_part = parts[0];
            let fractional_part = parts[1];
            let decimal_places = fractional_part.len() as u32;

            let numerator_str = format!("{}{}", integer_part, fractional_part);
            let numerator: IBig = numerator_str.parse().ok()?;
            let denominator: IBig = IBig::from(10).pow(decimal_places as usize);

            let rational = RBig::from_parts(numerator.into(), denominator.try_into().ok()?);
            Number::from_rational(rational)
        } else {
            let i: IBig = s.parse().ok()?;
            Number::Integer(i)
        };

        if is_negative {
            Some(-result)
        } else {
            Some(result)
        }
    }

    /// Construct from a rational, normalising to `Integer` if the denominator is 1.
    pub fn from_rational(r: RBig) -> Number {
        let (num, den) = r.clone().into_parts();
        if *den.as_ibig() == IBig::from(1) {
            Number::Integer(num)
        } else {
            Number::Rational(r)
        }
    }

    /// Promote to `RBig` regardless of variant.
    pub fn to_rational(&self) -> RBig {
        match self {
            Number::Integer(i) => RBig::from(i.clone()),
            Number::Rational(r) => r.clone(),
        }
    }

    /// Convert to `f64` (lossy — for serialisation / FFI only).
    pub fn to_f64(&self) -> f64 {
        match self {
            Number::Integer(i) => {
                // Try i64 first for exactness on small ints
                if let Ok(n) = i64::try_from(i.clone()) {
                    n as f64
                } else {
                    // Fall back to string parsing for huge numbers
                    i.to_string().parse::<f64>().unwrap_or(f64::INFINITY)
                }
            }
            Number::Rational(r) => {
                let (num, den) = r.clone().into_parts();
                let n = if let Ok(n) = i64::try_from(num.clone()) {
                    n as f64
                } else {
                    num.to_string().parse::<f64>().unwrap_or(f64::INFINITY)
                };
                let d = if let Ok(d) = i64::try_from(den.as_ibig().clone()) {
                    d as f64
                } else {
                    den.as_ibig().to_string().parse::<f64>().unwrap_or(f64::INFINITY)
                };
                n / d
            }
        }
    }

    /// Construct from an `f64`. Converts through string representation to
    /// capture the exact decimal shown by Rust's `Display` formatting.
    pub fn from_f64(v: f64) -> Number {
        if v.fract() == 0.0 && v.abs() < 1e18 {
            Number::Integer(IBig::from(v as i64))
        } else {
            // Use Display which gives a shortest-round-trip representation
            let s = format!("{}", v);
            Number::parse(&s).unwrap_or_else(|| Number::Integer(IBig::from(0)))
        }
    }

    // -----------------------------------------------------------------------
    // Arithmetic helpers
    // -----------------------------------------------------------------------

    /// Integer exponentiation: `self ^ exp`.
    ///
    /// Returns `None` for non-integer exponents (fractional powers are not yet
    /// supported in exact arithmetic).
    pub fn pow(&self, exp: &Number) -> Option<Number> {
        match exp {
            Number::Integer(e) => {
                if *e < IBig::from(0) {
                    // Negative exponent: compute base^|e| then invert
                    let abs_exp: usize = (-e.clone()).try_into().ok()?;
                    let base_r = self.to_rational();
                    let result = rational_pow(&base_r, abs_exp);
                    let (num, den) = result.into_parts();
                    let inverted = RBig::from_parts(
                        den.as_ibig().clone().into(),
                        num.try_into().ok()?,
                    );
                    Some(Number::from_rational(inverted))
                } else {
                    let exp_usize: usize = e.clone().try_into().ok()?;
                    match self {
                        Number::Integer(base) => {
                            Some(Number::Integer(base.clone().pow(exp_usize)))
                        }
                        Number::Rational(base) => {
                            Some(Number::from_rational(rational_pow(base, exp_usize)))
                        }
                    }
                }
            }
            Number::Rational(_) => None,
        }
    }

    /// Factorial. Only defined for non-negative integers.
    pub fn factorial(&self) -> Option<Number> {
        match self {
            Number::Integer(n) => {
                if *n < IBig::from(0) {
                    return None;
                }
                let n_usize: usize = n.clone().try_into().ok()?;
                let mut result = IBig::from(1);
                for i in 2..=n_usize {
                    result *= IBig::from(i);
                }
                Some(Number::Integer(result))
            }
            Number::Rational(_) => None,
        }
    }

    /// Returns `true` if the value is zero.
    pub fn is_zero(&self) -> bool {
        match self {
            Number::Integer(i) => *i == IBig::from(0),
            Number::Rational(r) => {
                let (num, _) = r.clone().into_parts();
                num == IBig::from(0)
            }
        }
    }

    /// Returns `true` if this is an exact integer value (denominator == 1).
    pub fn is_integer(&self) -> bool {
        matches!(self, Number::Integer(_))
    }

    /// Format with a given cap on significant digits for non-terminating results.
    pub fn format(&self, max_sig_digits: usize) -> String {
        match self {
            Number::Integer(i) => i.to_string(),
            Number::Rational(r) => format_rational(r, max_sig_digits),
        }
    }
}

// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------

/// Raise a rational to a non-negative integer power.
fn rational_pow(base: &RBig, exp: usize) -> RBig {
    if exp == 0 {
        return RBig::from(IBig::from(1));
    }
    let (num, den) = base.clone().into_parts();
    let num_pow = num.pow(exp);
    let den_pow = den.as_ibig().clone().pow(exp);
    RBig::from_parts(num_pow.into(), den_pow.try_into().unwrap())
}

/// Format a rational as a decimal string.
///
/// If the decimal terminates (denominator has only factors 2 and 5), the exact
/// value is printed. Otherwise, `max_sig_digits` significant digits are shown
/// with rounding.
fn format_rational(r: &RBig, max_sig_digits: usize) -> String {
    let (num, den) = r.clone().into_parts();
    let den_ibig = den.as_ibig().clone();

    let is_negative = num < IBig::from(0);
    let abs_num = if is_negative { -num.clone() } else { num.clone() };

    if is_terminating_decimal(&den_ibig) {
        format_terminating(&abs_num, &den_ibig, is_negative)
    } else {
        format_non_terminating(&abs_num, &den_ibig, max_sig_digits, is_negative)
    }
}

fn format_terminating(abs_num: &IBig, den: &IBig, is_negative: bool) -> String {
    let quotient = abs_num / den;
    let remainder = abs_num % den;

    if remainder == IBig::from(0) {
        let s = quotient.to_string();
        return if is_negative { format!("-{}", s) } else { s };
    }

    // Long division for the fractional part
    let mut frac_digits = String::new();
    let mut rem = remainder;
    loop {
        rem *= IBig::from(10);
        let digit = &rem / den;
        rem = &rem % den;
        frac_digits.push_str(&digit.to_string());
        if rem == IBig::from(0) {
            break;
        }
    }

    let s = format!("{}.{}", quotient, frac_digits);
    if is_negative { format!("-{}", s) } else { s }
}

fn format_non_terminating(
    abs_num: &IBig,
    den: &IBig,
    max_sig_digits: usize,
    is_negative: bool,
) -> String {
    let quotient = abs_num / den;
    let remainder = abs_num % den;
    let int_str = quotient.to_string();

    let int_sig_digits = if quotient == IBig::from(0) { 0 } else { int_str.len() };
    let frac_digits_needed = if int_sig_digits >= max_sig_digits {
        1 // always show at least 1 fractional digit
    } else {
        max_sig_digits - int_sig_digits
    };

    let mut frac_digits = String::new();
    let mut rem = remainder;
    let mut sig_count = int_sig_digits;

    for _ in 0..frac_digits_needed + 100 {
        // +100 headroom for leading zeros
        rem *= IBig::from(10);
        let digit = &rem / den;
        rem = &rem % den;
        let d = digit.to_string();
        frac_digits.push_str(&d);

        if sig_count > 0 || d != "0" {
            sig_count += 1;
        }

        if sig_count >= max_sig_digits {
            // Round based on next digit
            rem *= IBig::from(10);
            let next_digit = &rem / den;
            if next_digit >= IBig::from(5) {
                frac_digits = round_up_decimal_str(&frac_digits);
            }
            break;
        }
    }

    // Trim trailing zeros after rounding
    let frac_trimmed = frac_digits.trim_end_matches('0');
    let s = if frac_trimmed.is_empty() {
        int_str
    } else {
        format!("{}.{}", int_str, frac_trimmed)
    };
    if is_negative { format!("-{}", s) } else { s }
}

/// True when the denominator has only factors of 2 and 5 (decimal terminates).
fn is_terminating_decimal(den: &IBig) -> bool {
    let mut d = den.clone();
    while &d % IBig::from(2) == IBig::from(0) {
        d /= IBig::from(2);
    }
    while &d % IBig::from(5) == IBig::from(0) {
        d /= IBig::from(5);
    }
    d == IBig::from(1)
}

/// Increment the last digit of a decimal-fraction digit string, carrying as needed.
fn round_up_decimal_str(s: &str) -> String {
    let mut chars: Vec<u8> = s.bytes().collect();
    let mut carry = true;
    for i in (0..chars.len()).rev() {
        if carry {
            if chars[i] == b'9' {
                chars[i] = b'0';
            } else {
                chars[i] += 1;
                carry = false;
            }
        }
    }
    let result: String = chars.iter().map(|&b| b as char).collect();
    if carry {
        format!("1{}", result)
    } else {
        result
    }
}

// ===========================================================================
// Trait implementations
// ===========================================================================

impl PartialEq for Number {
    fn eq(&self, other: &Self) -> bool {
        self.to_rational() == other.to_rational()
    }
}

impl Eq for Number {}

impl PartialOrd for Number {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Number {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.to_rational().cmp(&other.to_rational())
    }
}

impl fmt::Display for Number {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.format(DEFAULT_PRECISION))
    }
}

// -- Conversions from primitives -------------------------------------------

impl From<i64> for Number {
    fn from(v: i64) -> Self {
        Number::Integer(IBig::from(v))
    }
}

impl From<i32> for Number {
    fn from(v: i32) -> Self {
        Number::Integer(IBig::from(v))
    }
}

impl From<u64> for Number {
    fn from(v: u64) -> Self {
        Number::Integer(IBig::from(v))
    }
}

impl From<f64> for Number {
    fn from(v: f64) -> Self {
        Number::from_f64(v)
    }
}

// -- Arithmetic operators ---------------------------------------------------

impl std::ops::Add for Number {
    type Output = Number;
    fn add(self, rhs: Self) -> Self::Output {
        Number::from_rational(self.to_rational() + rhs.to_rational())
    }
}

impl std::ops::Sub for Number {
    type Output = Number;
    fn sub(self, rhs: Self) -> Self::Output {
        Number::from_rational(self.to_rational() - rhs.to_rational())
    }
}

impl std::ops::Mul for Number {
    type Output = Number;
    fn mul(self, rhs: Self) -> Self::Output {
        Number::from_rational(self.to_rational() * rhs.to_rational())
    }
}

impl std::ops::Div for Number {
    type Output = Option<Number>;
    fn div(self, rhs: Self) -> Self::Output {
        if rhs.is_zero() {
            return None;
        }
        Some(Number::from_rational(self.to_rational() / rhs.to_rational()))
    }
}

impl std::ops::Neg for Number {
    type Output = Number;
    fn neg(self) -> Self::Output {
        match self {
            Number::Integer(i) => Number::Integer(-i),
            Number::Rational(r) => Number::Rational(-r),
        }
    }
}

impl std::ops::Rem for Number {
    type Output = Option<Number>;
    fn rem(self, rhs: Self) -> Self::Output {
        if rhs.is_zero() {
            return None;
        }
        match (&self, &rhs) {
            (Number::Integer(a), Number::Integer(b)) => {
                Some(Number::Integer(a % b))
            }
            _ => {
                // a % b = a - b * floor(a / b)
                let a = self.to_rational();
                let b = rhs.to_rational();
                let div = &a / &b;
                let (num, den) = div.into_parts();
                let floor_div = &num / den.as_ibig();
                let result = a - b * RBig::from(floor_div);
                Some(Number::from_rational(result))
            }
        }
    }
}

// -- Serde ------------------------------------------------------------------
//
// We serialize as a decimal string so that JSON consumers always get a
// human-readable (and exact) representation, regardless of magnitude.

impl Serialize for Number {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for Number {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let s = String::deserialize(deserializer)?;
        Number::parse(&s).ok_or_else(|| serde::de::Error::custom(format!("invalid number: {}", s)))
    }
}

// ===========================================================================
// Tests
// ===========================================================================

#[cfg(test)]
mod tests {
    use super::*;

    // -- Parsing ------------------------------------------------------------

    #[test]
    fn test_parse_integer() {
        let n = Number::parse("42").unwrap();
        assert_eq!(n, Number::from(42i64));
    }

    #[test]
    fn test_parse_negative_integer() {
        let n = Number::parse("-7").unwrap();
        assert_eq!(n, Number::from(-7i64));
    }

    #[test]
    fn test_parse_decimal() {
        let n = Number::parse("3.14").unwrap();
        assert_eq!(n.to_string(), "3.14");
    }

    #[test]
    fn test_parse_zero_point_one() {
        let n = Number::parse("0.1").unwrap();
        assert!(matches!(n, Number::Rational(_)));
        assert_eq!(n.to_string(), "0.1");
    }

    #[test]
    fn test_parse_empty_returns_none() {
        assert!(Number::parse("").is_none());
        assert!(Number::parse("  ").is_none());
    }

    // -- Exact decimal arithmetic -------------------------------------------

    #[test]
    fn test_exact_decimal_addition() {
        let a = Number::parse("0.1").unwrap();
        let b = Number::parse("0.2").unwrap();
        assert_eq!((a + b).to_string(), "0.3");
    }

    #[test]
    fn test_ten_tenths_equal_one() {
        let tenth = Number::parse("0.1").unwrap();
        let mut sum = Number::from(0i64);
        for _ in 0..10 {
            sum = sum + tenth.clone();
        }
        assert_eq!(sum, Number::from(1i64));
    }

    #[test]
    fn test_financial_multiplication() {
        let amount = Number::parse("1000000.01").unwrap();
        let days = Number::from(365i64);
        assert_eq!((amount * days).to_string(), "365000003.65");
    }

    // -- Large numbers ------------------------------------------------------

    #[test]
    fn test_factorial_small() {
        assert_eq!(Number::from(5i64).factorial().unwrap(), Number::from(120i64));
    }

    #[test]
    fn test_factorial_100() {
        let result = Number::from(100i64).factorial().unwrap();
        let s = result.to_string();
        assert_eq!(s.len(), 158); // 100! has 158 digits
        assert!(s.starts_with("933262154439441526816992388562667004"));
    }

    #[test]
    fn test_pow_2_to_1000() {
        let result = Number::from(2i64).pow(&Number::from(1000i64)).unwrap();
        let s = result.to_string();
        assert!(s.starts_with("1071508607186267320948425049060001"));
        assert_eq!(s.len(), 302);
    }

    #[test]
    fn test_negative_exponent() {
        let result = Number::from(2i64).pow(&Number::from(-3i64)).unwrap();
        assert_eq!(result.to_string(), "0.125");
    }

    // -- Division & formatting ----------------------------------------------

    #[test]
    fn test_division_precision() {
        let result = (Number::from(1i64) / Number::from(3i64)).unwrap();
        let s = result.format(DEFAULT_PRECISION);
        assert!(s.starts_with("0."));
        let after_dot = &s[2..];
        assert!(after_dot.len() >= 30);
        assert!(after_dot.chars().all(|c| c == '3'));
    }

    #[test]
    fn test_integer_division_exact() {
        let result = (Number::from(10i64) / Number::from(2i64)).unwrap();
        assert_eq!(result, Number::from(5i64));
        assert!(result.is_integer());
    }

    #[test]
    fn test_division_by_zero() {
        assert!((Number::from(1i64) / Number::from(0i64)).is_none());
    }

    // -- Modulo -------------------------------------------------------------

    #[test]
    fn test_modulo_integer() {
        let result = (Number::from(10i64) % Number::from(3i64)).unwrap();
        assert_eq!(result, Number::from(1i64));
    }

    #[test]
    fn test_modulo_by_zero() {
        assert!((Number::from(5i64) % Number::from(0i64)).is_none());
    }

    // -- Negation & ordering ------------------------------------------------

    #[test]
    fn test_negation() {
        assert_eq!(-Number::from(5i64), Number::from(-5i64));
    }

    #[test]
    fn test_ordering() {
        let a = Number::from(3i64);
        let b = Number::parse("3.5").unwrap();
        assert!(a < b);
    }

    // -- f64 round-trip -----------------------------------------------------

    #[test]
    fn test_to_f64() {
        assert_eq!(Number::from(42i64).to_f64(), 42.0);
        let half = (Number::from(1i64) / Number::from(2i64)).unwrap();
        assert_eq!(half.to_f64(), 0.5);
    }

    #[test]
    fn test_from_f64() {
        let n = Number::from_f64(3.14);
        assert_eq!(n.to_string(), "3.14");
    }

    // -- Serde round-trip ---------------------------------------------------

    #[test]
    fn test_serde_roundtrip() {
        let n = Number::parse("123.456").unwrap();
        let json = serde_json::to_string(&n).unwrap();
        assert_eq!(json, "\"123.456\"");
        let back: Number = serde_json::from_str(&json).unwrap();
        assert_eq!(back, n);
    }

    #[test]
    fn test_serde_integer_roundtrip() {
        let n = Number::from(42i64);
        let json = serde_json::to_string(&n).unwrap();
        assert_eq!(json, "\"42\"");
        let back: Number = serde_json::from_str(&json).unwrap();
        assert_eq!(back, n);
    }
}
