//! SIMD boolean bit-packing for q → Arrow projection.
//!
//! q stores boolean vectors as one byte per element (`0` = false, `1` = true,
//! `2` = null on the wire — see [`crate::nulls::Q_NULL_BOOLEAN_WIRE`]).
//! Arrow `BooleanArray` uses a compact bitmap: one bit per element, LSB-first
//! within each byte.
//!
//! [`pack_bool_bytes`] converts a q boolean byte slice into an Arrow-compatible
//! packed bitmap using SIMD comparisons, processing `N` bytes per iteration.

use std::simd::prelude::*;

/// Packs a slice of q boolean bytes into an Arrow-compatible LSB-first bitmap.
///
/// Each source byte is treated as non-zero → `1` bit, zero → `0` bit.
/// Null bytes (`2`) are treated as truthy here — callers that need a separate
/// null buffer should pass in a pre-filtered slice or handle nulls separately.
///
/// Returns `(bitmap_bytes, element_count)` where `bitmap_bytes` is the packed
/// bitmap (length `ceil(src.len() / 8)`) and `element_count == src.len()`.
///
/// The returned `Vec<u8>` is suitable for wrapping directly into an Arrow
/// `arrow_buffer::Buffer` → `arrow_array::types::BooleanBuffer`.
#[inline]
pub fn pack_bool_bytes(src: &[u8]) -> (Vec<u8>, usize) {
    let len = src.len();
    let out_len = len.div_ceil(8);
    let mut out = vec![0u8; out_len];

    const N: usize = 8;
    let zero_v = Simd::<u8, N>::splat(0u8);
    // Number of full 8-byte chunks we can process with SIMD.
    let n_aligned = (len / N) * N;

    for (i, chunk) in src[..n_aligned].chunks_exact(N).enumerate() {
        let v = Simd::<u8, N>::from_slice(chunk);
        // Compare each byte to zero: non-zero → true (1-bit), zero → false (0-bit).
        let mask: std::simd::Mask<i8, N> = v.simd_ne(zero_v);
        // `to_bitmask()` produces a u8 with one bit per lane, LSB = lane 0.
        out[i] = mask.to_bitmask() as u8;
    }

    // Scalar tail (fewer than N elements remain).
    if n_aligned < len {
        let mut tail_byte = 0u8;
        for (bit, &b) in src[n_aligned..].iter().enumerate() {
            if b != 0 {
                tail_byte |= 1u8 << bit;
            }
        }
        out[n_aligned / N] = tail_byte;
    }

    (out, len)
}

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

    #[test]
    fn pack_empty() {
        let (bm, n) = pack_bool_bytes(&[]);
        assert_eq!(n, 0);
        assert!(bm.is_empty());
    }

    #[test]
    fn pack_all_false() {
        let src = [0u8; 16];
        let (bm, n) = pack_bool_bytes(&src);
        assert_eq!(n, 16);
        assert_eq!(bm, [0u8, 0u8]);
    }

    #[test]
    fn pack_all_true() {
        let src = [1u8; 16];
        let (bm, n) = pack_bool_bytes(&src);
        assert_eq!(n, 16);
        assert_eq!(bm, [0xFF, 0xFF]);
    }

    #[test]
    fn pack_lsb_first() {
        // Only the first element is true → bit 0 of byte 0 should be set.
        let mut src = [0u8; 8];
        src[0] = 1;
        let (bm, _) = pack_bool_bytes(&src);
        assert_eq!(bm[0], 0b00000001);
    }

    #[test]
    fn pack_last_element_in_first_chunk() {
        // Only the 8th element (index 7) is true → bit 7 of byte 0.
        let mut src = [0u8; 8];
        src[7] = 1;
        let (bm, _) = pack_bool_bytes(&src);
        assert_eq!(bm[0], 0b10000000);
    }

    #[test]
    fn pack_tail_single() {
        // 9 elements: first 8 all false, 9th is true → bit 0 of byte 1.
        let mut src = [0u8; 9];
        src[8] = 1;
        let (bm, n) = pack_bool_bytes(&src);
        assert_eq!(n, 9);
        assert_eq!(bm.len(), 2);
        assert_eq!(bm[0], 0x00);
        assert_eq!(bm[1], 0b00000001);
    }

    #[test]
    fn pack_non_zero_is_true() {
        // Any non-zero value should count as true.
        let src = [0u8, 2u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8];
        let (bm, _) = pack_bool_bytes(&src);
        assert_eq!(bm[0], 0b00000010);
    }
}
