use serde::{Deserialize, Serialize};

mod track_rules;
pub use track_rules::*;

mod album_rules;
pub use album_rules::*;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum GroupOp {
    Any,
    All,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum EqOp {
    EqAny,
    EqAll,
    NeqAny,
    NeqAll,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum OrdOp {
    Eq,
    Neq,
    Greater,
    GreaterEq,
    Lesser,
    LesserEq,
}

pub trait Rule {
    type Item;
    fn matches(&self, item: &Self::Item) -> bool;
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RuleGroup<R: Rule> {
    pub rules: Vec<R>,
    pub op: GroupOp,
}

impl<R: Rule> Default for RuleGroup<R> {
    fn default() -> Self {
        Self::new()
    }
}

impl<R: Rule> RuleGroup<R> {
    #[must_use]
    pub const fn new() -> Self {
        Self {
            rules: Vec::new(),
            op: GroupOp::Any,
        }
    }

    pub fn filter<'a>(&self, candidates: &'a [R::Item]) -> impl Iterator<Item = &'a R::Item> {
        candidates.iter().filter(|c| {
            if self.rules.is_empty() {
                return true;
            }

            match self.op {
                GroupOp::Any => self.rules.iter().any(|rule| rule.matches(c)),
                GroupOp::All => self.rules.iter().all(|rule| rule.matches(c)),
            }
        })
    }
}

pub(crate) fn eq_single<T: PartialEq>(item: &T, cmp: &[T], op: EqOp) -> bool {
    match op {
        EqOp::EqAny | EqOp::EqAll => cmp.contains(item),
        EqOp::NeqAny => !cmp.contains(item),
        EqOp::NeqAll => cmp.iter().any(|c| c != item),
    }
}

pub(crate) fn eq_many<T: PartialEq>(values: &[T], cmp: &[T], op: EqOp) -> bool {
    match op {
        EqOp::EqAny => cmp.iter().any(|c| values.contains(c)),
        EqOp::EqAll => cmp.iter().all(|c| values.contains(c)),
        EqOp::NeqAny => cmp.iter().any(|c| !values.contains(c)),
        EqOp::NeqAll => cmp.iter().all(|c| !values.contains(c)),
    }
}

pub(crate) fn ord_single<T: PartialOrd>(value: T, cmp: T, op: OrdOp) -> bool {
    match op {
        OrdOp::Eq => value == cmp,
        OrdOp::Neq => value != cmp,
        OrdOp::Greater => value > cmp,
        OrdOp::GreaterEq => value >= cmp,
        OrdOp::Lesser => value < cmp,
        OrdOp::LesserEq => value <= cmp,
    }
}
