//! Integer fixed-point arithmetic — the *only* place magnitude math rounds.
//!
//! Determinism (spec §10) requires bit-exact results across compilers, opt
//! levels, and machines. IEEE-754 `f64` multiply *chains* do **not** give that,
//! so every balance-relevant magnitude is an integer and every formula routes
//! through the helpers here.
//!
//! ## Units
//! - **Magnitudes** (vigor, cap, fatigue, damage, regen) are **milli-units**:
//!   one displayed "unit" of Vigor = [`UNIT`] (`1000`) internal points. The ×1000
//!   headroom means per-tick regen and small percentages don't round to zero.
//! - **Ratios / percentages / multipliers** are **basis points**: [`BP_ONE`]
//!   (`10_000`) == `1.0`, so `2_500` == `25%`, `12_457` == `1.2457×`.
//!
//! ## Rounding
//! Every conversion rounds **half to even** (banker's rounding). Always-floor
//! or always-up accumulates a systematic skew over a long fight — e.g.
//! always-floor on mitigation quietly favors the defender (spec §15). Half-to-
//! even is symmetric around zero and unbiased on a stream of ties, so it is the
//! single documented rule for *every* pipeline stage.

/// A magnitude in milli-units (vigor, fatigue, damage, …). Signed so that
/// recovery (negative fatigue cost) and over-spend are representable.
pub type Milli = i64;

/// One displayed Vigor unit, in milli-units.
pub const UNIT: Milli = 1_000;

/// `1.0` expressed in basis points.
pub const BP_ONE: i64 = 10_000;

/// Multiply a magnitude by a basis-point ratio, rounding half-to-even.
///
/// `apply_bp(20_000, 5_000)` = `20_000 × 0.5` = `10_000`.
/// `apply_bp(1_000, 12_457)` = `1_000 × 1.2457` ≈ `1_246`.
#[inline]
pub fn apply_bp(amount: Milli, ratio_bp: i64) -> Milli {
    mul_div_round(amount, ratio_bp, BP_ONE)
}

/// Convert displayed units to internal milli-units.
#[inline]
pub fn units(n: i64) -> Milli {
    n * UNIT
}

/// `a * b / den`, computed in `i128` to avoid overflow, rounded half-to-even.
/// `den` must be positive.
#[inline]
pub fn mul_div_round(a: i64, b: i64, den: i64) -> i64 {
    debug_assert!(den > 0, "mul_div_round denominator must be positive");
    let num = (a as i128) * (b as i128);
    div_round_even(num, den as i128) as i64
}

/// Integer division `num / den` rounding half **to even**. `den > 0`.
///
/// Built on Euclidean division so the floor base and non-negative remainder are
/// well-defined for negative `num` (recovery / over-spend produce negatives).
#[inline]
fn div_round_even(num: i128, den: i128) -> i128 {
    let q = num.div_euclid(den); // floor(num/den)
    let r = num.rem_euclid(den); // 0 <= r < den
    let twice = r * 2;
    if twice < den {
        q
    } else if twice > den {
        q + 1
    } else {
        // Exactly halfway: round toward the even neighbor.
        if q % 2 == 0 {
            q
        } else {
            q + 1
        }
    }
}

/// Clamp `x` into `[lo, hi]`. Panics in debug if `lo > hi`.
#[inline]
pub fn clamp(x: Milli, lo: Milli, hi: Milli) -> Milli {
    debug_assert!(lo <= hi);
    x.max(lo).min(hi)
}

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

    #[test]
    fn apply_bp_basic() {
        assert_eq!(apply_bp(20_000, 5_000), 10_000); // 50%
        assert_eq!(apply_bp(20_000, 10_000), 20_000); // 100%
        assert_eq!(apply_bp(20_000, 0), 0);
        assert_eq!(apply_bp(1_000, 12_457), 1_246); // 1.2457×, rounds 1245.7 -> 1246
    }

    #[test]
    fn rounds_half_to_even() {
        // 2.5 -> 2 (even), 3.5 -> 4 (even), 1.5 -> 2, 0.5 -> 0.
        assert_eq!(mul_div_round(5, 1, 2), 2);
        assert_eq!(mul_div_round(7, 1, 2), 4);
        assert_eq!(mul_div_round(3, 1, 2), 2);
        assert_eq!(mul_div_round(1, 1, 2), 0);
    }

    #[test]
    fn rounds_negatives_symmetrically() {
        // -2.5 -> -2 (even), -3.5 -> -4, -0.5 -> 0, -1.5 -> -2.
        assert_eq!(mul_div_round(-5, 1, 2), -2);
        assert_eq!(mul_div_round(-7, 1, 2), -4);
        assert_eq!(mul_div_round(-1, 1, 2), 0);
        assert_eq!(mul_div_round(-3, 1, 2), -2);
    }

    /// Over a symmetric stream of half-ties the even rule introduces no net
    /// drift — the property that protects long-fight balance (spec §15).
    #[test]
    fn no_systematic_drift_on_ties() {
        // Sum of round(k + 0.5) for k in -100..100 should equal the sum of the
        // exact values (the +0.5 ties cancel under round-half-to-even).
        let mut rounded = 0i128;
        let mut exact = 0i128;
        for k in -100..100 {
            // value = (2k+1)/2  -> numerator 2k+1, den 2
            rounded += div_round_even((2 * k + 1) as i128, 2);
            exact += (2 * k + 1) as i128; // times 2; compare scaled below
        }
        // exact average is sum/2; rounded should equal that with no bias.
        assert_eq!(rounded * 2, exact);
    }

    #[test]
    fn mul_div_no_overflow_near_limits() {
        // Large but realistic: 10^9 milli * 30000 bp / 10000 = 3 * 10^9.
        assert_eq!(mul_div_round(1_000_000_000, 30_000, 10_000), 3_000_000_000);
    }
}
