//! Validates that the [`Decimal`] trait exposes a uniform width-generic
//! surface: a single generic function works on every width without
//! reaching for inherent methods.

use decimal_scaled::{D9, D18, D38, Decimal, RoundingMode};

/// A width-generic helper that touches each major surface area: ops,
/// sign, overflow variants, integer methods, pow, and float bridge.
/// Returning a tuple lets the caller assert per-width.
fn surface_check<D: Decimal>(seed_i32: i32) -> (D, D, D, D) {
    let v = D::from_i32(seed_i32);
    // Operators via supertrait bounds.
    let doubled = v + v;
    let zero_via_sub = v - v;
    assert!(zero_via_sub.is_zero());
    // Sign.
    assert!(v.is_positive() ^ v.is_negative() ^ v.is_zero() == (!v.is_zero()));
    // Pow + checked.
    let squared = v.pow(2);
    assert_eq!(v.checked_pow(0), Some(D::ONE));
    // Overflow variants — saturating mul by ONE is identity.
    assert_eq!(v.saturating_mul(D::ONE), v);
    // Integer methods.
    assert_eq!(v.abs_diff(D::ZERO), v.abs());
    // Float bridge (gated to keep the function callable in any feature
    // configuration; tests assert when std is present).
    #[cfg(feature = "std")]
    {
        let f = v.to_f64();
        let back = D::from_f64(f);
        assert_eq!(back, v);
    }
    (v, doubled, squared, v.signum())
}

#[test]
fn surface_check_d9() {
    type D9_2 = D9<2>;
    let (v, doubled, squared, signum) = surface_check::<D9_2>(5);
    assert_eq!(v.to_bits(), 500);
    assert_eq!(doubled.to_bits(), 1000);
    assert_eq!(squared.to_bits(), 2500);
    assert_eq!(signum, D9_2::ONE);
}

#[test]
fn surface_check_d18() {
    type D18_4 = D18<4>;
    let (v, doubled, squared, _) = surface_check::<D18_4>(5);
    assert_eq!(v.to_bits(), 50_000);
    assert_eq!(doubled.to_bits(), 100_000);
    assert_eq!(squared.to_bits(), 250_000);
}

#[test]
fn surface_check_d38() {
    type D38_6 = D38<6>;
    let (v, doubled, squared, _) = surface_check::<D38_6>(5);
    assert_eq!(v.to_bits(), 5_000_000);
    assert_eq!(doubled.to_bits(), 10_000_000);
    assert_eq!(squared.to_bits(), 25_000_000);
}

#[cfg(feature = "wide")]
#[test]
fn surface_check_d76() {
    use decimal_scaled::D76;
    type D76_6 = D76<6>;
    let (v, _, squared, _) = surface_check::<D76_6>(5);
    let expected_v: D76_6 = D38::<6>::from_int(5).into();
    let expected_squared: D76_6 = D38::<6>::from_int(25).into();
    assert_eq!(v, expected_v);
    assert_eq!(squared, expected_squared);
}

/// Width-generic `sum` and `product` over an iterator.
fn fold_sum_product<D: Decimal>() -> (D, D) {
    let vs: [D; 4] = [D::from_i32(1), D::from_i32(2), D::from_i32(3), D::from_i32(4)];
    (D::sum(vs.iter().copied()), D::product(vs.iter().copied()))
}

#[test]
fn sum_product_d9() {
    type D9_0 = D9<0>;
    let (s, p) = fold_sum_product::<D9_0>();
    assert_eq!(s, D9_0::from_i32(10));
    assert_eq!(p, D9_0::from_i32(24));
}

#[test]
fn sum_product_d38() {
    type D38_2 = D38<2>;
    let (s, p) = fold_sum_product::<D38_2>();
    assert_eq!(s, D38_2::from_i32(10));
    assert_eq!(p, D38_2::from_i32(24));
}

/// `to_int_with` exercised via trait dispatch under each rounding mode.
#[test]
fn trait_to_int_with_modes() {
    fn cast<D: Decimal>(d: D, mode: RoundingMode) -> i64 {
        Decimal::to_int_with(d, mode)
    }
    let v = D38::<2>::from_bits(151);
    assert_eq!(cast(v, RoundingMode::Floor), 1);
    assert_eq!(cast(v, RoundingMode::Ceiling), 2);
    assert_eq!(cast(v, RoundingMode::Trunc), 1);
}

/// The trait supertrait bounds let a generic fn use the bitwise
/// operators on the raw storage.
#[test]
fn trait_bitwise_supertraits() {
    fn high_bits_off<D: Decimal>(v: D, mask: D) -> D {
        v & !mask
    }
    let v = D38::<0>::from_bits(0b1111);
    let mask = D38::<0>::from_bits(0b1100);
    let r = high_bits_off(v, mask);
    assert_eq!(r.to_bits(), 0b0011);
}

/// Default `is_zero` / `is_one` / `is_normal` reachable via trait
/// dispatch (verified via fully-qualified syntax).
#[test]
fn trait_default_predicates_per_width() {
    fn check<D: Decimal>() {
        assert!(<D as Decimal>::is_zero(D::ZERO));
        assert!(<D as Decimal>::is_one(D::ONE));
        assert!(!<D as Decimal>::is_normal(D::ZERO));
        assert!(<D as Decimal>::is_normal(D::ONE));
    }
    check::<D9<0>>();
    check::<D18<0>>();
    check::<D38<0>>();
    #[cfg(feature = "wide")]
    check::<decimal_scaled::D76<0>>();
}
