//! Filter evaluation result type.
//!
//! `FilterResult` wraps the output of evaluating a `FilterExpr` -- either a
//! `RoaringBitmap` of matching entity IDs or a predicate closure for
//! per-candidate evaluation.

use roaring::RoaringBitmap;

/// The result of evaluating a filter expression.
pub enum FilterResult {
    /// A bitmap of matching entity IDs.
    Bitmap(RoaringBitmap),
    /// A predicate closure for per-candidate evaluation.
    Predicate(Box<dyn Fn(u64) -> bool + Send + Sync>),
}

impl FilterResult {
    /// Extract the bitmap representation.
    ///
    /// # Panics
    ///
    /// Panics in debug and release if called on a `FilterResult::Predicate`.
    /// `Predicate` is reserved for M3+ and is never constructed in M2.
    /// All M2 filter evaluation paths produce `Bitmap`.
    #[must_use]
    pub fn into_bitmap(self) -> RoaringBitmap {
        match self {
            Self::Bitmap(bm) => bm,
            Self::Predicate(_) => {
                unreachable!(
                    "FilterResult::Predicate is M3+; into_bitmap() called on impossible variant"
                )
            }
        }
    }

    /// Convert to a predicate closure checking bitmap containment.
    #[must_use]
    pub fn into_predicate(self) -> Box<dyn Fn(u64) -> bool + Send + Sync> {
        match self {
            Self::Bitmap(bitmap) => Box::new(move |id: u64| {
                debug_assert!(u32::try_from(id).is_ok(), "EntityId out of u32 range");
                bitmap.contains(id as u32)
            }),
            Self::Predicate(f) => f,
        }
    }

    /// Number of entities matching the filter.
    #[must_use]
    pub fn cardinality(&self) -> u64 {
        match self {
            Self::Bitmap(bm) => bm.len(),
            // M3+: cardinality unknown for predicate. 0 is a safe default but incorrect.
            Self::Predicate(_) => 0,
        }
    }

    /// Whether no entities match.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        match self {
            Self::Bitmap(bm) => bm.is_empty(),
            Self::Predicate(_) => false,
        }
    }
}

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

    #[test]
    fn into_predicate_checks_bitmap_containment() {
        let mut bitmap = RoaringBitmap::new();
        bitmap.insert(1);
        bitmap.insert(42);
        bitmap.insert(999);

        let result = FilterResult::Bitmap(bitmap);
        let predicate = result.into_predicate();

        assert!(predicate(1));
        assert!(predicate(42));
        assert!(predicate(999));
        assert!(!predicate(0));
        assert!(!predicate(2));
        assert!(!predicate(1000));
    }
}
