use std::path::PathBuf;

use clap::Args;
use obiread::NucPage;
use obikseq::RoutableSuperKmer;
use obipipeline::Throttled;

// ── Shared arguments ──────────────────────────────────────────────────────────

#[derive(Args)]
pub struct CommonArgs {
    /// Input files or directories (FASTA/FASTQ, optionally gzip-compressed).
    /// If omitted, reads from stdin.
    #[arg(num_args = 0..)]
    pub inputs: Vec<String>,

    /// k-mer size
    #[arg(short, long, default_value_t = 31)]
    pub kmer_size: usize,

    /// Minimizer size
    #[arg(short, long, default_value_t = 11)]
    pub minimizer_size: usize,

    /// Entropy threshold (k-mers with score ≤ theta are rejected)
    #[arg(long, default_value_t = 0.7)]
    pub theta: f64,

    /// Maximum sub-word size for entropy computation
    #[arg(long, default_value_t = 6)]
    pub level_max: usize,

    /// Number of partitions (rounded up to the next power of 2)
    #[arg(short, long, default_value_t = 256)]
    pub partitions: usize,

    /// Number of worker threads
    #[arg(
        short = 'T',
        long,
        default_value_t = obisys::effective_parallelism()
    )]
    pub threads: usize,

    /// Maximum number of input files open simultaneously.
    /// Defaults to threads/4 (minimum 1). Keep below the number of workers
    /// to ensure CPU workers are always available for the transform stage.
    #[arg(long)]
    pub max_open_files: Option<usize>,
}

/// Smallest `b` such that `2^b >= n` (i.e. `n.next_power_of_two().ilog2()`).
/// Minimum 1 (degenerate n=0 or n=1 → 1 partition).
pub fn partitions_to_bits(n: usize) -> usize {
    n.max(1).next_power_of_two().trailing_zeros() as usize
}

/// Convert a block size (number of unitigs per block) to its `block_bits` exponent.
/// `block_size=1` → `block_bits=0` (one entry per unitig, O(1) random access).
pub fn block_size_to_bits(n: usize) -> u8 {
    n.max(1).next_power_of_two().trailing_zeros() as u8
}

impl CommonArgs {
    /// Validate k and m constraints. Exits on error.
    pub fn validate(&self) {
        let k = self.kmer_size;
        let m = self.minimizer_size;

        if k < 11 || k > 31 {
            eprintln!("error: --kmer-size must be in [11, 31] (got {k})");
            std::process::exit(1);
        }
        if k % 2 == 0 {
            eprintln!("error: --kmer-size must be odd (got {k}); even k allows palindromic k-mers");
            std::process::exit(1);
        }
        if m < 3 || m >= k {
            eprintln!("error: --minimizer-size must be in [3, k−1] = [3, {}] (got {m})", k - 1);
            std::process::exit(1);
        }
        if m % 2 == 0 {
            eprintln!("error: --minimizer-size must be odd (got {m})");
            std::process::exit(1);
        }
    }

    pub fn effective_max_open(&self) -> usize {
        self.max_open_files
            .unwrap_or_else(|| (self.threads / 4).max(1))
            .max(1)
    }

    pub fn seqfile_paths(&self) -> obiread::PathIter {
        let paths: Vec<PathBuf> = if self.inputs.is_empty() {
            vec![PathBuf::from("-")]
        } else {
            self.inputs.iter().map(PathBuf::from).collect()
        };
        obiread::PathIter::new(paths)
    }
}

// ── Pipeline data carrier ─────────────────────────────────────────────────────

pub enum PipelineData {
    Path(Throttled<PathBuf>),
    NucPage(NucPage),
    Batch(Vec<RoutableSuperKmer>),
}

unsafe impl Send for PipelineData {}
unsafe impl Sync for PipelineData {}

