//! `Column<T>` — a fixed-capacity, bounded-lookback SoA ring with financial
//! indexing (index 0 = newest), and its zero-copy read view `Window` (C5/C8).

/// A fixed-capacity bounded-lookback column. Pre-sized at construction
/// (C8: no realloc in the hot loop); index 0 = newest; carries a monotonic
/// `run_count` of total pushes — the freshness primitive (C5).
pub struct Column<T> {
    buf: Box<[T]>,
    head: usize,    // next write slot
    len: usize,     // valid elements, saturates at capacity
    run_count: u64, // monotonic total pushes (C5)
}

impl<T: Copy + Default> Column<T> {
    /// Create a column sized to `lookback` (must be >= 1). The engine sizes this
    /// once at wiring; it is never reallocated (C8).
    pub fn with_capacity(lookback: usize) -> Self {
        assert!(lookback >= 1, "Column lookback must be >= 1");
        Column {
            buf: vec![T::default(); lookback].into_boxed_slice(),
            head: 0,
            len: 0,
            run_count: 0,
        }
    }

    /// Append the newest value. O(1), allocation-free. On overflow the oldest
    /// value is overwritten (true ring). Bumps `run_count`.
    pub fn push(&mut self, v: T) {
        let cap = self.buf.len();
        self.buf[self.head] = v;
        self.head = (self.head + 1) % cap;
        if self.len < cap {
            self.len += 1;
        }
        self.run_count += 1;
    }

    /// Financial index: 0 = newest, `len()-1` = oldest. `None` if `k >= len`
    /// (C8: not-yet-warmed-up / out of range). Zero-copy.
    pub fn get(&self, k: usize) -> Option<T> {
        if k >= self.len {
            return None;
        }
        let cap = self.buf.len();
        Some(self.buf[(self.head + cap - 1 - k) % cap])
    }

    pub fn len(&self) -> usize {
        self.len
    }

    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub fn capacity(&self) -> usize {
        self.buf.len()
    }

    /// The monotonic push counter — the freshness primitive (C5). Never moved
    /// by a read.
    pub fn run_count(&self) -> u64 {
        self.run_count
    }

    /// A zero-copy read view handed to a reading node (C8).
    pub fn window(&self) -> Window<'_, T> {
        Window { col: self }
    }
}

/// Read-only, zero-copy view into a [`Column`] — what a reading node sees (C8).
/// Carries no storage, only a borrow.
pub struct Window<'a, T> {
    col: &'a Column<T>,
}

impl<'a, T: Copy + Default> Window<'a, T> {
    /// Financial index, 0 = newest; `None` if cold / out of range.
    pub fn get(&self, k: usize) -> Option<T> {
        self.col.get(k)
    }

    pub fn len(&self) -> usize {
        self.col.len()
    }

    pub fn is_empty(&self) -> bool {
        self.col.is_empty()
    }

    pub fn run_count(&self) -> u64 {
        self.col.run_count()
    }
}

/// `window[k]` — newest-at-0 indexed access; panics on out-of-range like a
/// slice. Nodes that may be cold use [`Window::get`] instead.
impl<'a, T: Copy + Default> core::ops::Index<usize> for Window<'a, T> {
    type Output = T;

    fn index(&self, k: usize) -> &T {
        let col = self.col;
        assert!(k < col.len, "Window index {k} out of range (len {})", col.len);
        let cap = col.buf.len();
        &col.buf[(col.head + cap - 1 - k) % cap]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn newest_at_zero_oldest_at_len_minus_one() {
        let mut c = Column::<i64>::with_capacity(4);
        c.push(10);
        c.push(20);
        c.push(30);
        assert_eq!(c.len(), 3);
        assert_eq!(c.get(0), Some(30)); // newest
        assert_eq!(c.get(1), Some(20));
        assert_eq!(c.get(2), Some(10)); // oldest
        assert_eq!(c.get(3), None); // beyond len
    }

    #[test]
    fn wraparound_keeps_newest_capacity_values() {
        let mut c = Column::<i64>::with_capacity(3);
        for v in [1, 2, 3, 4, 5] {
            c.push(v);
        }
        assert_eq!(c.len(), 3); // saturated
        assert_eq!(c.capacity(), 3);
        assert_eq!(c.get(0), Some(5)); // newest
        assert_eq!(c.get(1), Some(4));
        assert_eq!(c.get(2), Some(3)); // oldest retained
        assert_eq!(c.get(3), None); // 1 and 2 were dropped
    }

    #[test]
    fn run_count_counts_pushes_not_len() {
        let mut c = Column::<i64>::with_capacity(2);
        assert_eq!(c.run_count(), 0);
        for v in [1, 2, 3, 4, 5] {
            c.push(v);
        }
        assert_eq!(c.run_count(), 5); // counts pushes, past the ring wrap
        assert_eq!(c.len(), 2); // while len stays at capacity
        let _ = c.get(0);
        assert_eq!(c.run_count(), 5); // a read never moves it
    }

    #[test]
    fn window_is_a_view_matching_the_column() {
        let mut c = Column::<f64>::with_capacity(4);
        c.push(1.0);
        c.push(2.0);
        let cap_before = c.capacity();
        let w = c.window();
        assert_eq!(w.len(), 2);
        assert_eq!(w.get(0), Some(2.0));
        assert_eq!(w[1], 1.0); // Index, newest-at-0
        assert_eq!(w.run_count(), 2);
        assert_eq!(c.capacity(), cap_before); // taking a window did not realloc
    }

    #[test]
    fn empty_column_reads_none() {
        let c = Column::<bool>::with_capacity(2);
        assert!(c.is_empty());
        assert_eq!(c.get(0), None);
        assert_eq!(c.run_count(), 0);
    }
}
