/// Trait for kmer row filters.
///
/// `row` contains raw per-genome counts (or 0/1 for presence/absence data).
/// `n_genomes` equals `row.len()`.
pub trait KmerFilter: Send + Sync {
    fn passes(&self, row: &[u32], n_genomes: usize) -> bool;
}

/// True when `row` passes every filter in `filters`.
/// Returns `true` if `filters` is empty.
pub fn passes_all(filters: &[Box<dyn KmerFilter>], row: &[u32], n_genomes: usize) -> bool {
    filters.iter().all(|f| f.passes(row, n_genomes))
}

// ── Quorum filters ─────────────────────────────────────────────────────────────

fn present_count(row: &[u32], threshold: u32) -> usize {
    row.iter().filter(|&&v| v > threshold).count()
}

/// At least `frac` fraction of genomes contain this kmer (count > `threshold`).
pub struct MinGenomeFraction {
    pub frac: f64,
    pub threshold: u32,
}

impl KmerFilter for MinGenomeFraction {
    fn passes(&self, row: &[u32], n_genomes: usize) -> bool {
        let p = present_count(row, self.threshold);
        p as f64 / n_genomes as f64 >= self.frac
    }
}

/// At most `frac` fraction of genomes contain this kmer (count > `threshold`).
pub struct MaxGenomeFraction {
    pub frac: f64,
    pub threshold: u32,
}

impl KmerFilter for MaxGenomeFraction {
    fn passes(&self, row: &[u32], n_genomes: usize) -> bool {
        let p = present_count(row, self.threshold);
        p as f64 / n_genomes as f64 <= self.frac
    }
}

/// At least `count` genomes contain this kmer (count > `threshold`).
pub struct MinGenomeCount {
    pub count: usize,
    pub threshold: u32,
}

impl KmerFilter for MinGenomeCount {
    fn passes(&self, row: &[u32], _n_genomes: usize) -> bool {
        present_count(row, self.threshold) >= self.count
    }
}

/// At most `count` genomes contain this kmer (count > `threshold`).
pub struct MaxGenomeCount {
    pub count: usize,
    pub threshold: u32,
}

impl KmerFilter for MaxGenomeCount {
    fn passes(&self, row: &[u32], _n_genomes: usize) -> bool {
        present_count(row, self.threshold) <= self.count
    }
}

// ── Total-count filters (count indexes only) ───────────────────────────────────

/// Sum of counts across all genomes >= `total`.
pub struct MinTotalCount {
    pub total: u32,
}

impl KmerFilter for MinTotalCount {
    fn passes(&self, row: &[u32], _n_genomes: usize) -> bool {
        row.iter().sum::<u32>() >= self.total
    }
}

/// Sum of counts across all genomes <= `total`.
pub struct MaxTotalCount {
    pub total: u32,
}

impl KmerFilter for MaxTotalCount {
    fn passes(&self, row: &[u32], _n_genomes: usize) -> bool {
        row.iter().sum::<u32>() <= self.total
    }
}

// ── Group-based quorum filter ─────────────────────────────────────────────────

/// Quorum filter operating on pre-classified genome groups.
///
/// `ingroup_idx` / `outgroup_idx` are column indices into the per-genome row.
/// When `ingroup_idx` is empty, no ingroup quorum is checked.
/// When `outgroup_idx` is empty, no outgroup quorum is checked.
pub struct GroupQuorumFilter {
    pub ingroup_idx:        Vec<usize>,
    pub outgroup_idx:       Vec<usize>,
    pub threshold:          u32,
    pub min_count:          usize,
    pub max_count:          usize,
    pub min_frac:           f64,
    pub max_frac:           f64,
    pub min_outgroup_count: usize,
    pub max_outgroup_count: usize,
    pub min_outgroup_frac:  f64,
    pub max_outgroup_frac:  f64,
}

impl KmerFilter for GroupQuorumFilter {
    fn passes(&self, row: &[u32], _n_genomes: usize) -> bool {
        if !self.ingroup_idx.is_empty() {
            let n = self.ingroup_idx.iter()
                .filter(|&&i| row.get(i).copied().unwrap_or(0) > self.threshold)
                .count();
            let denom = self.ingroup_idx.len();
            if n < self.min_count { return false; }
            if n > self.max_count { return false; }
            let frac = n as f64 / denom as f64;
            if frac < self.min_frac { return false; }
            if frac > self.max_frac { return false; }
        }
        if !self.outgroup_idx.is_empty() {
            let n = self.outgroup_idx.iter()
                .filter(|&&i| row.get(i).copied().unwrap_or(0) > self.threshold)
                .count();
            let denom = self.outgroup_idx.len();
            if n < self.min_outgroup_count { return false; }
            if n > self.max_outgroup_count { return false; }
            let frac = n as f64 / denom as f64;
            if frac < self.min_outgroup_frac { return false; }
            if frac > self.max_outgroup_frac { return false; }
        }
        true
    }
}
