mod cli;
mod cmd;
mod steps;

use clap::{Parser, Subcommand};
use tracing_subscriber::{EnvFilter, fmt};

#[derive(Parser)]
#[command(name = "obikmer", about = "DNA k-mer tools", version)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Extract super-kmers from a sequence file and write to stdout
    Superkmer(cmd::superkmer::SuperkmerArgs),
    /// Build the complete genome index (scatter → dereplicate → count → layered MPHF)
    Index(cmd::index::IndexArgs),
    /// Merge multiple built indexes into one
    Merge(cmd::merge::MergeArgs),
    /// Apply row-level selection (σ) to an index: retain only k-mers matching the predicates
    Filter(cmd::filter::FilterCmdArgs),
    /// Project and/or aggregate genome columns into a new or in-place index
    Select(cmd::select::SelectArgs),
    /// Query an index with sequences and annotate matches
    Query(cmd::query::QueryArgs),
    /// Dump all indexed kmers as CSV (kmer + per-genome counts or presence)
    Dump(cmd::dump::DumpArgs),
    /// Add or update genome metadata from a CSV file; or dump metadata as CSV
    Annotate(cmd::annotate::AnnotateArgs),
    /// Compute pairwise distance matrix between genomes; optionally build NJ/UPGMA trees
    Distance(cmd::distance::DistanceArgs),
    /// Dump unitigs from a built index to stdout (debug)
    Unitig(cmd::unitig::UnitigArgs),
    /// Estimate approximate-index parameters (z, evidence bits, FP rates) before indexing
    Estimate(cmd::estimate::EstimateArgs),
    /// Convert an index's evidence in-place: exact ↔ approx
    Reindex(cmd::reindex::ReindexArgs),
    /// Miscellaneous index utilities (--rename, …)
    Utils(cmd::utils::UtilsArgs),
    /// Pack matrix column files into single-file format to reduce query I/O
    Pack(cmd::pack::PackArgs),
}

fn main() {
    fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
        )
        .with_writer(std::io::stderr)
        .init();

    #[cfg(feature = "profiling")]
    let _guard = {
        let guard = pprof::ProfilerGuardBuilder::default()
            .frequency(1000)
            .build()
            .expect("failed to start pprof profiler");
        guard
    };

    let cli = Cli::parse();
    match cli.command {
        Commands::Superkmer(args) => cmd::superkmer::run(args),
        Commands::Index(args)     => cmd::index::run(args),
        Commands::Merge(args)     => cmd::merge::run(args),
        Commands::Dump(args)      => cmd::dump::run(args),
        Commands::Filter(args)    => cmd::filter::run(args),
        Commands::Select(args)    => cmd::select::run(args),
        Commands::Query(args)     => cmd::query::run(args),
        Commands::Annotate(args)  => cmd::annotate::run(args),
        Commands::Distance(args)  => cmd::distance::run(args),
        Commands::Unitig(args)    => cmd::unitig::run(args),
        Commands::Estimate(args)  => cmd::estimate::run(args),
        Commands::Reindex(args)   => cmd::reindex::run(args),
        Commands::Utils(args)     => cmd::utils::run(args),
        Commands::Pack(args)      => cmd::pack::run(args),
    }

    #[cfg(feature = "profiling")]
    {
        use pprof::protos::Message;
        if let Ok(report) = _guard.report().build() {
            let mut bytes = Vec::new();
            report
                .pprof()
                .expect("pprof encode failed")
                .encode(&mut bytes)
                .expect("pprof encode failed");
            std::fs::write("profile.pb", &bytes).expect("cannot write profile.pb");
            eprintln!("profile written to profile.pb");
        }
    }
}
