//! Extraction of a 64-bit window at an arbitrary bit offset.
use super::U256;

impl U256 {
    /// Extracts 64 bits starting at the given bit offset from the least
    /// significant end.
    ///
    /// Equivalent to `(self >> shift) as u64` but reads at most two
    /// adjacent limbs instead of constructing a full shifted `U256`.
    /// This makes it ideal for extracting windows from large values
    /// without touching all four limbs.
    ///
    /// # Examples
    ///
    /// ```
    /// use cnfy_uint::u256::U256;
    ///
    /// let v = U256::from_be_limbs([0, 0, 0, 0xFF]);
    /// assert_eq!(v.extract_u64(0), 0xFF);
    /// assert_eq!(v.extract_u64(4), 0x0F);
    /// ```
    #[inline]
    pub const fn extract_u64(&self, shift: u32) -> u64 {
        let limb = (shift / 64) as usize;
        let bit = shift % 64;

        // little-endian: self.0[0] is the LSB limb, self.0[3] is the MSB limb
        let lo = self.0[limb];
        let hi = if limb < 3 { self.0[limb + 1] } else { 0 };

        if bit == 0 {
            lo
        } else {
            (lo >> bit) | (hi << (64 - bit))
        }
    }
}

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

    /// Zero shift returns the least significant limb.
    #[test]
    fn shift_zero() {
        let v = U256::from_be_limbs([0xAA, 0xBB, 0xCC, 0xDD]);
        assert_eq!(v.extract_u64(0), 0xDD);
    }

    /// Shift by 64 returns the second limb.
    #[test]
    fn shift_one_limb() {
        let v = U256::from_be_limbs([0xAA, 0xBB, 0xCC, 0xDD]);
        assert_eq!(v.extract_u64(64), 0xCC);
    }

    /// Shift by 192 returns the most significant limb.
    #[test]
    fn shift_three_limbs() {
        let v = U256::from_be_limbs([0xAA, 0xBB, 0xCC, 0xDD]);
        assert_eq!(v.extract_u64(192), 0xAA);
    }

    /// Sub-limb shift straddles two limbs.
    #[test]
    fn cross_limb_shift() {
        let v = U256::from_be_limbs([0, 0, 1, 0]);
        // bit 64 is set → shift by 32 gives bit 32 set in result
        assert_eq!(v.extract_u64(32), 1u64 << 32);
    }

    /// Matches shr_bits for various shift amounts.
    #[test]
    fn matches_shr_bits() {
        let val = U256::from_be_limbs([
            0x123456789ABCDEF0,
            0xFEDCBA9876543210,
            0x1111222233334444,
            0x5555666677778888,
        ]);
        for shift in [1, 32, 63, 64, 65, 100, 127, 128, 129, 190, 192, 193] {
            assert_eq!(
                val.extract_u64(shift),
                val.shr_bits(shift).0[0],
                "mismatch at shift={shift}",
            );
        }
    }
}
