use std::cell::{Cell, UnsafeCell};

/// Heap-allocated buffer pool for intermediate results during audio processing.
///
/// Each call to `buf(frames)` returns the next available `&mut [f32]` of the
/// requested length. The cursor wraps around when all slots are used.
///
/// Uses interior mutability so multiple buffers can be live simultaneously.
///
/// # Example
///
/// ```rust
/// use sesh_sdk::Scratch;
///
/// let scratch = Scratch::new();
/// let frames = 128;
/// let a = scratch.buf(frames);
/// let b = scratch.buf(frames);
/// let c = scratch.buf(frames);
/// // a, b, c are all valid simultaneously
/// ```
pub struct ScratchPool {
    bufs: UnsafeCell<Vec<f32>>,
    frames_cap: usize,
    n: usize,
    cursor: Cell<usize>,
}

impl ScratchPool {
    /// Create a new scratch pool with `n` buffers of `frames_cap` frames each.
    pub fn with_capacity(n: usize, frames_cap: usize) -> Self {
        Self {
            bufs: UnsafeCell::new(vec![0.0; n * frames_cap]),
            frames_cap,
            n,
            cursor: Cell::new(0),
        }
    }

    /// Create a default scratch pool: 128 buffers of 2048 frames (1MB).
    pub fn new() -> Self {
        Self::with_capacity(128, 2048)
    }

    /// Get the next scratch buffer, sliced to `frames`. Wraps when all slots are used.
    pub fn buf(&self, frames: usize) -> &mut [f32] {
        assert!(
            frames <= self.frames_cap,
            "ScratchPool::buf: frames ({frames}) exceeds buffer size ({})",
            self.frames_cap
        );
        let idx = self.cursor.get() % self.n;
        self.cursor.set(idx + 1);
        let start = idx * self.frames_cap;
        unsafe {
            let bufs = &mut *self.bufs.get();
            &mut bufs[start..start + frames]
        }
    }
}

/// Default scratch pool: 128 buffers of 2048 frames each (1MB).
pub type Scratch = ScratchPool;
