//! 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)]
mod tests {
    use super::*;
    use obikrope::Rope;

    fn setup() {
        obikseq::params::set_k(K);
        obikseq::params::set_m(5);
    }

    fn make_rope(data: &[u8]) -> Rope {
        let mut r = Rope::new(None);
        r.push(data.to_vec());
        r
    }

    fn run_nofilter(data: &[u8], k: usize) -> Vec<Vec<u8>> {
        let rope = make_rope(data);
        SuperKmerIter::new(&rope, k, 1, 0.0)
            .map(|rsk| rsk.superkmer().to_ascii())
            .collect()
    }

    // k=11, m=5 — valeurs minimales du projet (k ∈ [11,31])
    const K: usize = 11;

    /// Collect the set of canonical k-mers from a raw ASCII sequence (no NUL).
    fn direct_canonical_kmers(seq: &[u8]) -> std::collections::HashSet<Vec<u8>> {
        (0..seq.len().saturating_sub(K - 1))
            .map(|i| obikseq::SuperKmer::from_ascii(&seq[i..i + K]).to_ascii())
            .collect()
    }

    /// Collect the set of canonical k-mers emitted by SuperKmerIter over a rope.
    fn iter_canonical_kmers(rope: &Rope) -> std::collections::HashSet<Vec<u8>> {
        SuperKmerIter::new(rope, K, 1, 0.0)
            .flat_map(|rsk| {
                rsk.superkmer()
                    .iter_canonical_kmers()
                    .map(|km| km.to_ascii())
                    .collect::<Vec<_>>()
            })
            .collect()
    }

    #[test]
    fn coverage_single_segment() {
        setup();
        let seq = b"ACGTACGTACGTACGTACGT";
        let rope = make_rope(&[seq.as_ref(), b"\x00"].concat());
        let direct = direct_canonical_kmers(seq);
        let from_iter = iter_canonical_kmers(&rope);
        let missing: Vec<_> = direct.difference(&from_iter).collect();
        assert!(
            missing.is_empty(),
            "k-mers perdus dans segment unique : {missing:?}"
        );
    }

    #[test]
    fn coverage_two_segments() {
        setup();
        let seg1 = b"ACGTACGTACGTACGTACGT";
        let seg2 = b"TGCATGCATGCATGCATGCA";
        let rope = make_rope(&[seg1.as_ref(), b"\x00", seg2.as_ref(), b"\x00"].concat());
        let mut direct = direct_canonical_kmers(seg1);
        direct.extend(direct_canonical_kmers(seg2));
        let from_iter = iter_canonical_kmers(&rope);
        let missing: Vec<_> = direct.difference(&from_iter).collect();
        assert!(
            missing.is_empty(),
            "k-mers perdus dans deux segments : {missing:?}"
        );
    }

    #[test]
    fn coverage_minimizer_boundary() {
        setup();
        // sequence assez longue pour forcer plusieurs changements de minimiseur
        let seq: Vec<u8> = (0..80).map(|i| b"ACGT"[i % 4]).collect();
        let rope = make_rope(&[seq.as_slice(), b"\x00"].concat());
        let direct = direct_canonical_kmers(&seq);
        let from_iter = iter_canonical_kmers(&rope);
        let missing: Vec<_> = direct.difference(&from_iter).collect();
        assert!(
            missing.is_empty(),
            "k-mers perdus à la frontière de minimiseur : {missing:?}"
        );
    }

    #[test]
    fn single_segment_one_superkmer() {
        setup();
        let out = run_nofilter(b"ACGTACGTACGTACGTACGT\x00", K);
        assert!(!out.is_empty());
        let total: Vec<u8> = out.into_iter().flatten().collect();
        assert!(total.len() >= K);
    }

    #[test]
    fn segment_shorter_than_k_emits_nothing() {
        setup();
        let out = run_nofilter(b"ACGTACGT\x00", K);
        assert_eq!(out, Vec::<Vec<u8>>::new());
    }

    #[test]
    fn empty_input_emits_nothing() {
        setup();
        let out = run_nofilter(b"", K);
        assert_eq!(out, Vec::<Vec<u8>>::new());
    }

    #[test]
    fn two_segments_both_emitted() {
        setup();
        let out = run_nofilter(b"ACGTACGTACGTACGT\x00TGCATGCATGCATGCA\x00", K);
        assert!(!out.is_empty());
    }

    #[test]
    fn low_complexity_kmer_is_rejected() {
        setup();
        let out_pass = run_nofilter(b"AAAAAAAAAAAACGTACGTACGT\x00", K);
        assert!(!out_pass.is_empty());

        let rope = make_rope(b"AAAAAAAAAAAAAAAAAAAA\x00");
        let out_reject: Vec<Vec<u8>> = SuperKmerIter::new(&rope, K, 6, 0.9)
            .map(|rsk| rsk.superkmer().to_ascii())
            .collect();
        assert!(out_reject.is_empty());
    }

    #[test]
    fn multi_slice_rope() {
        setup();
        let data = b"ACGTACGTACGTACGTACGT\x00";
        let mid = data.len() / 2;
        let mut rope = Rope::new(None);
        rope.push(data[..mid].to_vec());
        rope.push(data[mid..].to_vec());
        let out: Vec<Vec<u8>> = SuperKmerIter::new(&rope, K, 1, 0.0)
            .map(|rsk| rsk.superkmer().to_ascii())
            .collect();
        assert!(!out.is_empty());
    }

    #[test]
    fn yields_minimizer_value() {
        setup();
        let rope = make_rope(b"ACGTACGTACGTACGTACGT\x00");
        let results: Vec<RoutableSuperKmer> = SuperKmerIter::new(&rope, K, 1, 0.0).collect();
        assert!(!results.is_empty());
    }
}
