use super::mutable_null_index::MutableNullIndex;

mod lifecycle;
mod read_ops;

pub use self::lifecycle::ImmutableNullIndexBuilder;

pub struct ImmutableNullIndex(pub(super) MutableNullIndex);

#[cfg(test)]
mod tests {
    use common::bitvec::BitVec;
    use common::counter::hardware_counter::HardwareCounterCell;
    use itertools::Itertools as _;
    use serde_json::{Value, json};
    use tempfile::TempDir;

    use super::super::read_ops::NullIndexRead;
    use super::*;
    use crate::index::field_index::{FieldIndexBuilderTrait, PayloadFieldIndexRead};
    use crate::json_path::JsonPath;
    use crate::types::FieldCondition;

    #[test]
    fn test_remove_idempotent() {
        let dir = TempDir::with_prefix("test_immutable_null_index").unwrap();
        let mut builder = ImmutableNullIndex::builder(dir.path(), 0).unwrap();
        let hw_counter = HardwareCounterCell::new();

        let null_value = Value::Null;
        let null_value_in_array =
            Value::Array(vec![Value::String("test".to_string()), Value::Null]);

        builder.add_point(0, &[&null_value], &hw_counter).unwrap();
        builder
            .add_point(1, &[&null_value_in_array], &hw_counter)
            .unwrap();
        builder.add_point(2, &[], &hw_counter).unwrap();
        builder
            .add_point(3, &[&Value::Bool(true)], &hw_counter)
            .unwrap();

        let mut index = builder.finalize().unwrap();

        let key = JsonPath::new("test");

        let filter_is_null = FieldCondition::new_is_null(key.clone(), true);

        let filter_is_not_empty = FieldCondition {
            key: key.clone(),
            r#match: None,
            range: None,
            geo_bounding_box: None,
            geo_radius: None,
            geo_polygon: None,
            values_count: None,
            is_empty: Some(false),
            is_null: None,
        };

        assert_eq!(
            index
                .filter(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0, 1],
        );
        assert_eq!(
            index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![1, 3],
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            2,
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            2,
        );

        index.remove_point(1).unwrap();
        assert_eq!(
            index
                .filter(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0],
        );
        assert_eq!(
            index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![3],
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );

        index.remove_point(1).unwrap();
        assert_eq!(
            index
                .filter(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0],
        );
        assert_eq!(
            index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![3],
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );

        index.remove_point(3).unwrap();
        assert_eq!(
            index
                .filter(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0],
        );
        assert!(
            index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec()
                .is_empty(),
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            0,
        );

        index.remove_point(3).unwrap();
        assert_eq!(
            index
                .filter(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0],
        );
        assert!(
            index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec()
                .is_empty(),
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_null, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            1,
        );
        assert_eq!(
            index
                .estimate_cardinality(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .exp,
            0,
        );
    }

    #[test]
    fn test_remove_reopen() {
        let dir = TempDir::with_prefix("test_immutable_null_index").unwrap();
        let mut builder = ImmutableNullIndex::builder(dir.path(), 0).unwrap();
        let hw_counter = HardwareCounterCell::new();
        builder.add_point(0, &[&json!(true)], &hw_counter).unwrap();
        builder.add_point(1, &[&json!(true)], &hw_counter).unwrap();
        builder.add_point(2, &[&json!(false)], &hw_counter).unwrap();

        let index = builder.finalize().unwrap();

        let mut deleted = BitVec::repeat(false, 3);
        deleted.set(1, true);
        drop(index);

        let reopened_index = ImmutableNullIndex::open(dir.path(), 3, &deleted)
            .unwrap()
            .unwrap();

        let filter_is_not_empty = FieldCondition {
            key: JsonPath::new("test"),
            r#match: None,
            range: None,
            geo_bounding_box: None,
            geo_radius: None,
            geo_polygon: None,
            values_count: None,
            is_empty: Some(false),
            is_null: None,
        };

        assert_eq!(
            reopened_index
                .filter(&filter_is_not_empty, &hw_counter)
                .unwrap()
                .unwrap()
                .collect_vec(),
            vec![0, 2],
        );
        assert_eq!(reopened_index.count_indexed_points().unwrap(), 3);

        // Direct API parity: deleted point reads as empty; live points don't.
        assert!(reopened_index.values_is_empty(1).unwrap());
        assert!(!reopened_index.values_is_empty(0).unwrap());
        assert!(!reopened_index.values_is_empty(2).unwrap());
    }
}
