//! Global k-mer and minimizer length parameters, set once at program startup.
//!
//! # Production vs. test behaviour
//!
//! In production (`#[cfg(not(test))]`) both `K` and `M` are stored in a
//! [`OnceLock`]: they can be initialised exactly once; any attempt to set a
//! different value panics.  This prevents silent divergence between the global
//! parameter and the values used to build data structures.
//!
//! In test builds (`#[cfg(test)]`) the same public API is backed by plain
//! process-wide atomics instead, freely overwritable (no write-once
//! constraint) so tests don't need a reset mechanism between runs.
//!
//! An earlier version of this module used `thread_local!` `Cell`s here,
//! reasoning that "each test thread gets its own copy" gives isolation
//! between tests using different `k`/`m` values. That assumption broke as
//! soon as any code under test fanned work out to *other* threads it
//! doesn't control — `PartitionRunner`'s pre-spawned workers, or a bare
//! `rayon::par_iter()` — since a freshly spawned thread never inherits the
//! calling test thread's thread-local state, silently reading back `k=0`
//! there instead (surfaced as a `bitvec`/slice-indexing panic deep inside
//! whatever used the bogus length). Process-wide atomics make `k()`/`m()`
//! correct on *any* thread without every call site having to know to
//! re-propagate them. Unset still silently reads back as `0` (same as the
//! old `Cell` default) rather than panicking: several existing tests read
//! `m()` without ever calling `set_m` themselves, relying on that default.
//! The trade-off: tests that genuinely need different `k`/`m` values from
//! other tests must not run concurrently with them in the same process (in
//! practice: every test file in this workspace already uses one fixed
//! `k`/`m` pair for all its own tests, so this doesn't currently cost
//! anything).

// ── Production implementation ─────────────────────────────────────────────────

#[cfg(not(any(test, feature = "test-utils")))]
mod state {
    use std::sync::OnceLock;

    static K: OnceLock<usize> = OnceLock::new();
    static M: OnceLock<usize> = OnceLock::new();

    pub fn set_k(k: usize) {
        K.get_or_init(|| k);
        assert_eq!(*K.get().unwrap(), k, "K already initialized to a different value");
    }

    pub fn k() -> usize {
        *K.get().expect("K not initialized — call params::set_k or params::init first")
    }

    pub fn set_m(m: usize) {
        M.get_or_init(|| m);
        assert_eq!(*M.get().unwrap(), m, "M already initialized to a different value");
    }

    pub fn m() -> usize {
        *M.get().expect("M not initialized — call params::set_m or params::init first")
    }
}

// ── Test implementation ───────────────────────────────────────────────────────
//
// Process-wide, freely overwritable (no write-once constraint), visible from
// any thread — including threads a test doesn't spawn itself (rayon workers,
// PartitionRunner workers, ...). `0` (never explicitly set) is returned as-is,
// same default as the old thread-local `Cell`.

#[cfg(any(test, feature = "test-utils"))]
mod state {
    use std::sync::atomic::{AtomicUsize, Ordering};

    static K: AtomicUsize = AtomicUsize::new(0);
    static M: AtomicUsize = AtomicUsize::new(0);

    pub fn set_k(k: usize) { K.store(k, Ordering::SeqCst); }
    pub fn k() -> usize { K.load(Ordering::SeqCst) }
    pub fn set_m(m: usize) { M.store(m, Ordering::SeqCst); }
    pub fn m() -> usize { M.load(Ordering::SeqCst) }
}

// ── Public API (identical signature in both configurations) ───────────────────

/// Initialise both K and M in one call.
///
/// In production, panics if either value has already been set to a different
/// value.  In tests, simply overwrites the thread-local.
pub fn init(k: usize, m: usize) {
    state::set_k(k);
    state::set_m(m);
}

/// Set the k-mer length.
///
/// In production: idempotent for the same value, panics on conflict.
/// In tests: unconditionally updates the calling thread's value.
pub fn set_k(k: usize) {
    state::set_k(k);
}

/// Returns the k-mer length. Panics if not yet initialized.
#[inline]
pub fn k() -> usize {
    state::k()
}

/// Set the minimizer length.
///
/// In production: idempotent for the same value, panics on conflict.
/// In tests: unconditionally updates the calling thread's value.
pub fn set_m(m: usize) {
    state::set_m(m);
}

/// Returns the minimizer length. Panics if not yet initialized.
#[inline]
pub fn m() -> usize {
    state::m()
}
