//! 384-bit addition with overflow detection.
use super::U384;

impl U384 {
    /// Computes `self + other` by packing limb pairs into `u128` for
    /// native carry chaining, returning the 384-bit result and a boolean
    /// indicating whether the addition overflowed (i.e., the true result
    /// exceeds `2^384 - 1`).
    ///
    /// The six limbs are grouped into three `u128` pairs: `[0]+[1]`,
    /// `[2]+[3]`, `[4]+[5]`, with carries propagated between groups.
    ///
    /// # Examples
    ///
    /// ```
    /// use cnfy_uint::u384::U384;
    ///
    /// let a = U384::from_be_limbs([0, 0, 0, 0, 0, 10]);
    /// let b = U384::from_be_limbs([0, 0, 0, 0, 0, 20]);
    /// let (result, overflow) = a.overflowing_add(&b);
    /// assert_eq!(result, U384::from_be_limbs([0, 0, 0, 0, 0, 30]));
    /// assert!(!overflow);
    /// ```
    #[inline]
    pub const fn overflowing_add(&self, other: &U384) -> (U384, bool) {
        // Pack limb pairs into u128 for native carry chaining
        let a_lo = ((self.0[1] as u128) << 64) | (self.0[0] as u128);
        let a_mid = ((self.0[3] as u128) << 64) | (self.0[2] as u128);
        let a_hi = ((self.0[5] as u128) << 64) | (self.0[4] as u128);

        let b_lo = ((other.0[1] as u128) << 64) | (other.0[0] as u128);
        let b_mid = ((other.0[3] as u128) << 64) | (other.0[2] as u128);
        let b_hi = ((other.0[5] as u128) << 64) | (other.0[4] as u128);

        let (sum_lo, carry0) = a_lo.overflowing_add(b_lo);
        let (sum_mid, c1) = a_mid.overflowing_add(b_mid);
        let (sum_mid, c2) = sum_mid.overflowing_add(carry0 as u128);
        let carry1 = c1 | c2;
        let (sum_hi, c3) = a_hi.overflowing_add(b_hi);
        let (sum_hi, c4) = sum_hi.overflowing_add(carry1 as u128);

        (
            U384([
                sum_lo as u64,
                (sum_lo >> 64) as u64,
                sum_mid as u64,
                (sum_mid >> 64) as u64,
                sum_hi as u64,
                (sum_hi >> 64) as u64,
            ]),
            c3 | c4,
        )
    }
}

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

    /// Adding two small values produces no overflow.
    #[test]
    fn small_no_overflow() {
        let a = U384::from_be_limbs([0, 0, 0, 0, 0, 100]);
        let b = U384::from_be_limbs([0, 0, 0, 0, 0, 200]);
        let (result, overflow) = a.overflowing_add(&b);
        assert_eq!(result, U384::from_be_limbs([0, 0, 0, 0, 0, 300]));
        assert!(!overflow);
    }

    /// Adding MAX to one overflows, wrapping to zero.
    #[test]
    fn max_plus_one_overflows() {
        let (result, overflow) = U384::MAX.overflowing_add(&U384::ONE);
        assert_eq!(result, U384::ZERO);
        assert!(overflow);
    }

    /// Carry propagates across all six limbs.
    #[test]
    fn carry_propagation() {
        let a = U384::from_be_limbs([0, 0, 0, 0, 0, u64::MAX]);
        let (result, overflow) = a.overflowing_add(&U384::ONE);
        assert_eq!(result, U384::from_be_limbs([0, 0, 0, 0, 1, 0]));
        assert!(!overflow);
    }

    /// Carry crosses the lo-mid boundary (limb [4] to [3]).
    #[test]
    fn carry_lo_to_mid() {
        let a = U384::from_be_limbs([0, 0, 0, 0, u64::MAX, u64::MAX]);
        let (result, overflow) = a.overflowing_add(&U384::ONE);
        assert_eq!(result, U384::from_be_limbs([0, 0, 0, 1, 0, 0]));
        assert!(!overflow);
    }

    /// Carry crosses the mid-hi boundary (limb [2] to [1]).
    #[test]
    fn carry_mid_to_hi() {
        let a = U384::from_be_limbs([0, 0, u64::MAX, u64::MAX, u64::MAX, u64::MAX]);
        let (result, overflow) = a.overflowing_add(&U384::ONE);
        assert_eq!(result, U384::from_be_limbs([0, 1, 0, 0, 0, 0]));
        assert!(!overflow);
    }

    /// Zero is the additive identity.
    #[test]
    fn additive_identity() {
        let a = U384::from_be_limbs([0x1234, 0x5678, 0x9ABC, 0xDEF0, 0x1111, 0x2222]);
        let (result, overflow) = a.overflowing_add(&U384::ZERO);
        assert_eq!(result, a);
        assert!(!overflow);
    }

    /// Commutativity holds.
    #[test]
    fn commutative() {
        let a = U384::from_be_limbs([1, 2, 3, 4, 5, 6]);
        let b = U384::from_be_limbs([7, 8, 9, 10, 11, 12]);
        assert_eq!(a.overflowing_add(&b), b.overflowing_add(&a));
    }
}
