//! Stack-allocated scratch buffer for building a SuperKmer before heap emission.

use crate::encoding::{BYTE_LEN_MAX, encode_nuc};
use obikseq::RoutableSuperKmer;

/// Maximum nucleotides in a super-kmer (fits one `u64` segment window, kept ≤ 256).
pub const MAX_SUPERKMER_LEN: usize = 256;

/// Stack buffer accumulating 2-bit–packed nucleotides for one super-kmer.
///
/// Nucleotides are packed MSB-first, four per byte, matching `SuperKmer` layout.
/// Call [`SuperKmerScratch::push`] to append, [`SuperKmerScratch::emit`] to
/// finalise and produce a heap-allocated [`SuperKmer`].
pub struct SuperKmerScratch {
    buf: [u8; BYTE_LEN_MAX],
    len: usize,
}

impl SuperKmerScratch {
    /// Create an empty scratch buffer.
    #[inline]
    pub fn new() -> Self {
        Self {
            buf: [0u8; BYTE_LEN_MAX],
            len: 0,
        }
    }

    /// Number of nucleotides accumulated so far.
    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    /// `true` when no nucleotides have been accumulated.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Append one uppercase ASCII nucleotide (A/C/G/T).
    ///
    /// # Panics
    /// Panics in debug mode if `base` is not one of `A`, `C`, `G`, `T` or if
    /// the buffer is full (256 nucleotides).
    #[inline]
    pub fn push(&mut self, base: u8) {
        debug_assert!(self.len < MAX_SUPERKMER_LEN, "SuperKmerScratch overflow");
        let slot = self.len / 4;
        let shift = 6 - 2 * (self.len % 4);
        self.buf[slot] |= encode_nuc(base) << shift;
        self.len += 1;
    }

    /// Consume the accumulated nucleotides and produce a [`SuperKmer`].
    ///
    /// The heap allocation (`Box<[u8]>`) is exactly sized to the sequence.
    /// Resets the buffer to empty afterward.
    pub fn emit(&mut self, min_pos: usize) -> RoutableSuperKmer {
        let seql = self.len;
        debug_assert!(seql >= 1 && seql <= MAX_SUPERKMER_LEN);
        let n = (seql + 3) / 4;
        let seq: Box<[u8]> = self.buf[..n].into();
        self.buf[..n].fill(0);
        self.len = 0;
        RoutableSuperKmer::build(min_pos, seql, seq)
    }
    /// Discard all accumulated nucleotides without producing a [`SuperKmer`].
    pub fn reset(&mut self) {
        let n = (self.len + 3) / 4;
        self.buf[..n].fill(0);
        self.len = 0;
    }
}

impl Default for SuperKmerScratch {
    fn default() -> Self {
        Self::new()
    }
}
