use std::path::PathBuf;

use clap::Args;
use obikindex::{KmerIndex, MergeMode};
use obikpartitionner::filter::{MaxTotalCount, MinComplexity, MinTotalCount};
use obisys::Reporter;
use tracing::info;

use super::predicate::FilterArgs as KmerFilterArgs;

#[derive(Args)]
pub struct FilterCmdArgs {
    /// Source index directory
    pub source: PathBuf,

    /// Output index directory
    #[arg(short, long)]
    pub output: PathBuf,

    #[command(flatten)]
    pub filter: KmerFilterArgs,

    /// Minimum total count across all genomes (count index only)
    #[arg(long)]
    pub min_total_count: Option<u32>,

    /// Maximum total count across all genomes (count index only)
    #[arg(long)]
    pub max_total_count: Option<u32>,

    /// Minimum normalized entropy (complexity) to keep a k-mer — same metric
    /// as `obikmer index`'s --theta, applied here to k-mers already committed
    /// to the source index (reconstructed from unitigs.bin). K-mers scoring
    /// below this are removed.
    #[arg(long)]
    pub min_complexity: Option<f64>,

    /// Maximum sub-word size for the complexity computation (see `obikmer
    /// index`'s --level-max). Only used when --min-complexity is set.
    #[arg(long, default_value_t = 6)]
    pub complexity_level_max: usize,

    /// Output as presence/absence instead of counts
    #[arg(long)]
    pub presence: bool,

    /// Overwrite existing output directory
    #[arg(short, long)]
    pub force: bool,
}

pub fn run(args: FilterCmdArgs) {
    let src = KmerIndex::open(&args.source).unwrap_or_else(|e| {
        eprintln!("error opening source index: {e}");
        std::process::exit(1);
    });

    let mode = if args.presence || !src.meta().config.with_counts {
        MergeMode::Presence
    } else {
        MergeMode::Count
    };

    info!(
        "filter: {} genome(s), mode={:?}, source={}",
        &src.meta().genomes.len(), mode, args.source.display()
    );

    let mut filters = args.filter.build_filters(&src.meta().genomes);

    if let Some(v) = args.min_total_count {
        filters.push(Box::new(MinTotalCount { total: v }));
    }
    if let Some(v) = args.max_total_count {
        filters.push(Box::new(MaxTotalCount { total: v }));
    }
    if let Some(theta) = args.min_complexity {
        filters.push(Box::new(MinComplexity { level_max: args.complexity_level_max, theta }));
    }

    // Source is opened read-only above and needs no lock; only the
    // destination is written.
    let _lock = obisys::DirLock::acquire(&args.output).unwrap_or_else(|e| {
        eprintln!("error locking output directory {}: {e}", args.output.display());
        std::process::exit(1);
    });

    let mut rep = Reporter::new();
    KmerIndex::rebuild(&args.output, &src, &filters, mode, args.force, &mut rep)
        .unwrap_or_else(|e| {
            eprintln!("error filtering index: {e}");
            std::process::exit(1);
        });

    rep.print();
    info!("filtered index → {}", args.output.display());
}
