//! Iterator that consumes a normalised rope and yields `(minimizer, SuperKmer)` pairs.
//!
//! The normalised format is `ACGT…\x00ACGT…\x00` — uppercase ACGT segments of
//! length ≥ k separated by NUL bytes.
//!
//! # Cut conditions (checked in order per nucleotide, once the k-mer window is full)
//!
//! In all three cases the triggering nucleotide is **not** added to the current
//! super-kmer; the super-kmer built so far is emitted; then the rope cursor is
//! retreated so the next segment can overlap correctly:
//!
//! | Condition              | cursor retreat |
//! |------------------------|----------------|
//! | entropy(kmer) ≤ θ      | k−1            |
//! | minimizer changed      | k              |
//! | super-kmer length = 256| k              |

use obikrope::{ForwardCursor, Rope, RopeCursor};
use obikseq::RoutableSuperKmer;
use obikseq::kmer::Minimizer;

use crate::rolling_stat::RollingStat;
use crate::scratch::SuperKmerScratch;

/// Iterator over [`RoutableSuperKmer`] values.
pub struct SuperKmerIter<'a> {
    cursor: ForwardCursor<'a>,
    k: usize,
    rope_len: usize,
    theta: f64,
    scratch: SuperKmerScratch,
    stat: RollingStat,
    prev_min: Option<Minimizer>,
    prev_min_pos: usize,
}

impl<'a> SuperKmerIter<'a> {
    /// Build an iterator from a normalised rope chunk.
    ///
    /// - `k`: k-mer size (1–31)
    /// - `m`: minimizer size (1 < m < k)
    /// - `level_max`: maximum sub-word size for entropy (1–6)
    /// - `theta`: entropy threshold; k-mers with score ≤ theta are rejected
    pub fn new(rope: &'a Rope, k: usize, level_max: usize, theta: f64) -> Self {
        Self {
            cursor: rope.fw_cursor(),
            k,
            rope_len: rope.len(),
            theta,
            scratch: SuperKmerScratch::new(),
            stat: RollingStat::new(level_max),
            prev_min: None,
            prev_min_pos: 0,
        }
    }

    fn reset(&mut self) {
        self.stat.reset();
        self.scratch.reset();
        self.prev_min = None;
        self.prev_min_pos = 0;
    }

    fn try_emit(&mut self) -> Option<RoutableSuperKmer> {
        if self.scratch.len() < self.k {
            return None;
        }
        self.prev_min?;
        Some(self.scratch.emit(self.prev_min_pos))
    }
}

impl Iterator for SuperKmerIter<'_> {
    type Item = RoutableSuperKmer;

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, Some(self.rope_len / self.k))
    }

    fn next(&mut self) -> Option<RoutableSuperKmer> {
        loop {
            let byte = match self.cursor.read_next().ok() {
                None => {
                    return self.try_emit();
                }
                Some(0x00) => {
                    let result = self.try_emit();
                    self.reset();
                    if result.is_some() {
                        return result;
                    }
                    continue;
                }
                Some(b) => b,
            };

            self.stat.push(byte);

            if !self.stat.ready() {
                self.scratch.push(byte);
                continue;
            }

            // ── 1. Entropy check ─────────────────────────────────────────────
            if self.stat.normalized_entropy().unwrap_or(1.0) < self.theta {
                let result = self.try_emit();
                self.cursor.rewind(self.k - 1).ok();
                self.reset();
                if result.is_some() {
                    return result;
                }
                continue;
            }

            let min = self.stat.canonical_minimizer().unwrap(); // always Some after ready()
            let min_pos = self.stat.minimizer_position().unwrap_or(0);

            // ── 2. Minimizer change check ─────────────────────────────────────
            if let Some(prev) = self.prev_min {
                if min != prev {
                    let result = self.try_emit();
                    self.cursor.rewind(self.k).ok();
                    self.reset();
                    if result.is_some() {
                        return result;
                    }
                    continue;
                }
            }

            // ── 3. Super-kmer length check ────────────────────────────────────
            if self.scratch.len() == 256 {
                let result = self.try_emit();
                self.cursor.rewind(self.k).ok();
                self.reset();
                if result.is_some() {
                    return result;
                }
                continue;
            }

            self.prev_min = Some(min);
            self.prev_min_pos = min_pos;
            self.scratch.push(byte);
        }
    }
}

// ── tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
#[path = "tests/iter.rs"]
mod tests;
