/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/
pub mod binning;

use crate::{
    filter::binning::{Binning, BinningComponent},
    query::Filter,
};

impl Filter {
    pub fn get_bins(&self) -> Vec<Binning> {
        fn recurse(f: &Filter) -> Vec<Vec<BinningComponent>> {
            match f {
                Filter::All(filters) => {
                    let mut o = vec![vec![]];
                    for filter in filters {
                        let mut new_o = Vec::new();
                        for par in recurse(filter) {
                            for mut prev in o.clone() {
                                prev.extend(par.clone());
                                new_o.push(prev);
                            }
                        }
                        o = new_o;
                    }
                    o
                }
                Filter::Any(filters) => filters.iter().flat_map(|f| recurse(f)).collect(),
                Filter::Match(path, _) => vec![vec![BinningComponent::Match(path.to_owned())]],
                Filter::Has(path) => vec![vec![BinningComponent::Has(path.to_owned())]],
            }
        }
        recurse(self).into_iter().map(Binning::new).collect()
    }
}

#[cfg(test)]
mod test {
    use crate::{
        filter::binning::{Binning, BinningComponent},
        query::Filter,
    };
    use jellycommon::jellyobject::{Tag, path::Path};

    #[test]
    fn all() {
        let f = Filter::All(vec![
            Filter::Has(Path(vec![Tag(0)])),
            Filter::Has(Path(vec![Tag(1)])),
        ]);
        let bins = vec![Binning::new(vec![
            BinningComponent::Has(Path(vec![Tag(0)])),
            BinningComponent::Has(Path(vec![Tag(1)])),
        ])];
        assert_eq!(f.get_bins(), bins)
    }

    #[test]
    fn any() {
        let f = Filter::Any(vec![
            Filter::Has(Path(vec![Tag(0)])),
            Filter::Has(Path(vec![Tag(1)])),
        ]);
        let bins = vec![
            Binning::new(vec![BinningComponent::Has(Path(vec![Tag(0)]))]),
            Binning::new(vec![BinningComponent::Has(Path(vec![Tag(1)]))]),
        ];
        assert_eq!(f.get_bins(), bins)
    }

    #[test]
    fn nested() {
        let f = Filter::All(vec![
            Filter::Any(vec![
                Filter::Has(Path(vec![Tag(0)])),
                Filter::Has(Path(vec![Tag(1)])),
            ]),
            Filter::Any(vec![
                Filter::Has(Path(vec![Tag(2)])),
                Filter::Has(Path(vec![Tag(3)])),
            ]),
        ]);
        let bins = vec![
            Binning::new(vec![
                BinningComponent::Has(Path(vec![Tag(0)])),
                BinningComponent::Has(Path(vec![Tag(2)])),
            ]),
            Binning::new(vec![
                BinningComponent::Has(Path(vec![Tag(1)])),
                BinningComponent::Has(Path(vec![Tag(2)])),
            ]),
            Binning::new(vec![
                BinningComponent::Has(Path(vec![Tag(0)])),
                BinningComponent::Has(Path(vec![Tag(3)])),
            ]),
            Binning::new(vec![
                BinningComponent::Has(Path(vec![Tag(1)])),
                BinningComponent::Has(Path(vec![Tag(3)])),
            ]),
        ];
        assert_eq!(f.get_bins(), bins)
    }
}
