//! 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
//! `thread_local!` [`Cell`]s instead.  Each test thread gets its own
//! independent copies of `K` and `M`, so tests can use arbitrary values
//! without coordinating with one another and without any reset mechanism.
//! The `OnceLock` constraint is deliberately absent: test isolation is
//! provided by thread locality, not by write-once semantics.

// ── 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 ───────────────────────────────────────────────────────
//
// Each test thread owns its private K and M via thread_local!, so tests may
// call set_k / set_m with any value without affecting other tests.

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

    thread_local! {
        static K: Cell<usize> = Cell::new(0);
        static M: Cell<usize> = Cell::new(0);
    }

    pub fn set_k(k: usize) { K.with(|c| c.set(k)); }
    pub fn k()    -> usize { K.with(|c| c.get()) }
    pub fn set_m(m: usize) { M.with(|c| c.set(m)); }
    pub fn m()    -> usize { M.with(|c| c.get()) }
}

// ── 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()
}
