#![allow(unused_variables)]
//! Vector operations for batch audio processing.
//!
//! Each op has two code paths: an inline Rust fallback (always available) and a
//! host-accelerated import (used when `sesh_vec_version() > 0`). The SDK selects
//! the path at runtime. Plugin authors call the same functions regardless of platform.

#[cfg(target_arch = "wasm32")]
use smallvec::SmallVec;
#[cfg(target_arch = "wasm32")]
use std::sync::atomic::{AtomicU32, Ordering};

// ---------------------------------------------------------------------------
// Host capability detection
// ---------------------------------------------------------------------------

#[cfg(target_arch = "wasm32")]
extern "C" {
    fn sesh_vec_version() -> u32;
}

/// Cached host vec version. 0 = not yet queried, u32::MAX = stubs (web).
#[cfg(target_arch = "wasm32")]
static HOST_VEC_VERSION: AtomicU32 = AtomicU32::new(0);

#[cfg(target_arch = "wasm32")]
fn host_version() -> u32 {
    let v = HOST_VEC_VERSION.load(Ordering::Relaxed);
    if v != 0 {
        return v;
    }
    let v = unsafe { sesh_vec_version() };
    // Store non-zero so we don't re-query. If host returns 0, store a sentinel.
    let store = if v == 0 { u32::MAX } else { v };
    HOST_VEC_VERSION.store(store, Ordering::Relaxed);
    v
}

#[inline]
#[allow(dead_code)]
fn use_host_ops() -> bool {
    #[cfg(target_arch = "wasm32")]
    {
        host_version() > 0 && host_version() != u32::MAX
    }
    #[cfg(not(target_arch = "wasm32"))]
    {
        false
    }
}

// ---------------------------------------------------------------------------
// Dispatch: host-accelerated in production, inline fallback in tests
// ---------------------------------------------------------------------------

macro_rules! dispatch {
    ($host:expr, $fallback:expr) => {{
        #[cfg(target_arch = "wasm32")]
        {
            if use_host_ops() {
                $host
            } else {
                $fallback
            }
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            $fallback
        }
    }};
}

// ---------------------------------------------------------------------------
// Host imports (C ABI, raw pointers) — not linked in test builds
// ---------------------------------------------------------------------------

#[cfg(target_arch = "wasm32")]
extern "C" {
    fn sesh_vec_copy_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_fill_host(dst: *mut f32, value: f32, len: u32);
    fn sesh_vec_add_host(dst: *mut f32, a: *const f32, b: *const f32, len: u32);
    fn sesh_vec_add_scalar_host(dst: *mut f32, value: f32, len: u32);
    fn sesh_vec_mul_host(dst: *mut f32, a: *const f32, b: *const f32, len: u32);
    fn sesh_vec_mul_scalar_host(dst: *mut f32, value: f32, len: u32);
    fn sesh_vec_mul_add_host(dst: *mut f32, src: *const f32, gain: f32, len: u32);
    fn sesh_vec_clamp_host(dst: *mut f32, src: *const f32, min: f32, max: f32, len: u32);
    fn sesh_vec_ring_write_host(
        buf: *mut f32,
        buf_len: u32,
        pos: *mut u32,
        src: *const f32,
        len: u32,
    );
    fn sesh_vec_ring_read_host(
        buf: *const f32,
        buf_len: u32,
        pos: u32,
        dst: *mut f32,
        offset: u32,
        len: u32,
    );
    fn sesh_vec_delay_read_host(
        buf: *const f32,
        buf_len: u32,
        pos: u32,
        dst: *mut f32,
        time: *const f32,
        len: u32,
    );
    fn sesh_vec_osc_host(
        phase: *mut f32,
        dst: *mut f32,
        freq: f32,
        waveform: u32,
        sample_rate: f32,
        len: u32,
    );
    fn sesh_vec_biquad_host(
        state: *mut f32,
        dst: *mut f32,
        src: *const f32,
        cutoff: *const f32,
        q: *const f32,
        gain: *const f32,
        filter_type: u32,
        sample_rate: f32,
        len: u32,
    );
    fn sesh_vec_envelope_host(
        state: *mut f32,
        dst: *mut f32,
        src: *const f32,
        attack: *const f32,
        release: *const f32,
        mode: u32,
        sample_rate: f32,
        len: u32,
    );
    fn sesh_vec_tanh_host(dst: *mut f32, src: *const f32, drive: *const f32, len: u32);
    fn sesh_vec_hard_clip_host(dst: *mut f32, src: *const f32, threshold: *const f32, len: u32);
    fn sesh_vec_abs_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_neg_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_sqrt_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_recip_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_div_host(dst: *mut f32, a: *const f32, b: *const f32, len: u32);
    fn sesh_vec_pow_host(dst: *mut f32, src: *const f32, exp: *const f32, len: u32);
    fn sesh_vec_log_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_exp_host(dst: *mut f32, src: *const f32, len: u32);
    fn sesh_vec_schroeder_allpass_host(
        buf: *mut f32,
        buf_len: u32,
        pos: *mut u32,
        dst: *mut f32,
        src: *const f32,
        delay: u32,
        g: f32,
        len: u32,
    );
    fn sesh_vec_one_pole_host(
        state: *mut f32,
        dst: *mut f32,
        src: *const f32,
        coefficient: f32,
        len: u32,
    );
    fn sesh_vec_comb_host(
        buf: *mut f32,
        buf_len: u32,
        pos: *mut u32,
        damp: *mut f32,
        dst: *mut f32,
        src: *const f32,
        time: *const f32,
        feedback: f32,
        damping: f32,
        len: u32,
    );
    fn sesh_vec_comb_parallel_host(
        bufs: *const *mut f32,
        buf_lens: *const u32,
        positions: *mut u32,
        damp: *mut f32,
        dst: *const *mut f32,
        src: *const f32,
        time: *const *const f32,
        feedback: f32,
        damping: f32,
        n: u32,
        len: u32,
    );
    fn sesh_vec_comb_coupled_host(
        bufs: *const *mut f32,
        buf_lens: *const u32,
        positions: *mut u32,
        damp: *mut f32,
        dst: *const *mut f32,
        src: *const *const f32,
        time: *const *const f32,
        matrix: *const f32,
        damping: f32,
        n: u32,
        len: u32,
    );
}

// ---------------------------------------------------------------------------
// Enums and state types
// ---------------------------------------------------------------------------

/// Oscillator waveform shape.
#[repr(u32)]
#[derive(Clone, Copy)]
pub enum Waveform {
    Sine = 0,
    Triangle = 1,
    Saw = 2,
    Square = 3,
}

/// Biquad filter type.
#[repr(u32)]
#[derive(Clone, Copy)]
pub enum FilterType {
    Lowpass = 0,
    Highpass = 1,
    Bandpass = 2,
    Notch = 3,
    /// Parametric EQ band — boost/cut at cutoff frequency.
    Peak = 4,
    /// Boost/cut below cutoff frequency.
    LowShelf = 5,
    /// Boost/cut above cutoff frequency.
    HighShelf = 6,
    /// Phase shift without changing amplitude — used in phasers.
    Allpass = 7,
}

/// Internal state for a biquad filter (two-sample history).
#[derive(Clone, Copy)]
#[repr(C)]
pub struct BiquadState {
    pub x1: f32,
    pub x2: f32,
    pub y1: f32,
    pub y2: f32,
}

impl BiquadState {
    pub const fn new() -> Self {
        Self {
            x1: 0.0,
            x2: 0.0,
            y1: 0.0,
            y2: 0.0,
        }
    }
}

/// Envelope follower detection mode.
#[repr(u32)]
#[derive(Clone, Copy)]
pub enum EnvelopeMode {
    /// Track instantaneous peaks.
    Peak = 0,
    /// Track root-mean-square level.
    Rms = 1,
}

/// Internal state for an envelope follower.
#[derive(Clone, Copy)]
#[repr(C)]
pub struct EnvelopeState {
    pub current: f32,
}

impl EnvelopeState {
    pub const fn new() -> Self {
        Self { current: 0.0 }
    }
}

/// Internal state for a one-pole filter (single-sample history).
#[derive(Clone, Copy)]
#[repr(C)]
pub struct OnePoleState {
    pub y1: f32,
}

impl OnePoleState {
    pub const fn new() -> Self {
        Self { y1: 0.0 }
    }
}

// ===========================================================================
// Math ops
// ===========================================================================

/// Copy `src` into `dst`.
pub fn vec_copy(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_copy_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        dst[..len].copy_from_slice(&src[..len])
    );
}

/// Fill `dst` with a constant value.
pub fn vec_fill(dst: &mut [f32], value: f32) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_fill_host(dst.as_mut_ptr(), value, len as u32) },
        for s in dst.iter_mut() {
            *s = value;
        }
    );
}

/// Element-wise addition: `dst[i] = a[i] + b[i]`.
pub fn vec_add(dst: &mut [f32], a: &[f32], b: &[f32]) {
    let len = dst.len().min(a.len()).min(b.len());
    dispatch!(
        unsafe { sesh_vec_add_host(dst.as_mut_ptr(), a.as_ptr(), b.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = a[i] + b[i];
        }
    );
}

/// Add scalar to every element: `dst[i] += value`.
pub fn vec_add_scalar(dst: &mut [f32], value: f32) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_add_scalar_host(dst.as_mut_ptr(), value, len as u32) },
        for s in dst.iter_mut() {
            *s += value;
        }
    );
}

/// Element-wise multiplication: `dst[i] = a[i] * b[i]`.
pub fn vec_mul(dst: &mut [f32], a: &[f32], b: &[f32]) {
    let len = dst.len().min(a.len()).min(b.len());
    dispatch!(
        unsafe { sesh_vec_mul_host(dst.as_mut_ptr(), a.as_ptr(), b.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = a[i] * b[i];
        }
    );
}

/// Multiply every element by scalar: `dst[i] *= value`.
pub fn vec_mul_scalar(dst: &mut [f32], value: f32) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_mul_scalar_host(dst.as_mut_ptr(), value, len as u32) },
        for s in dst.iter_mut() {
            *s *= value;
        }
    );
}

/// Multiply and accumulate: `dst[i] += src[i] * gain`.
pub fn vec_mul_add(dst: &mut [f32], src: &[f32], gain: f32) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_mul_add_host(dst.as_mut_ptr(), src.as_ptr(), gain, len as u32) },
        for i in 0..len {
            dst[i] += src[i] * gain;
        }
    );
}

/// Clamp: `dst[i] = clamp(src[i], min, max)`.
pub fn vec_clamp(dst: &mut [f32], src: &[f32], min: f32, max: f32) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_clamp_host(dst.as_mut_ptr(), src.as_ptr(), min, max, len as u32) },
        for i in 0..len {
            dst[i] = src[i].clamp(min, max);
        }
    );
}

/// In-place clamp: `dst[i] = clamp(dst[i], min, max)`.
pub fn vec_clamp_assign(dst: &mut [f32], min: f32, max: f32) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_clamp_host(dst.as_mut_ptr(), dst.as_ptr(), min, max, len as u32) },
        for i in 0..len {
            dst[i] = dst[i].clamp(min, max);
        }
    );
}

// ===========================================================================
// Circular buffer ops
// ===========================================================================

/// Write `src` into circular buffer `buf` starting at `*pos`, wrapping at `buf.len()`.
/// Advances `*pos` by `src.len()`.
pub fn vec_ring_write(buf: &mut [f32], pos: &mut usize, src: &[f32]) {
    let buf_len = buf.len();
    let frames = src.len();
    dispatch!(
        {
            let mut pos32 = *pos as u32;
            unsafe {
                sesh_vec_ring_write_host(
                    buf.as_mut_ptr(),
                    buf_len as u32,
                    &mut pos32,
                    src.as_ptr(),
                    frames as u32,
                );
            }
            *pos = pos32 as usize;
        },
        {
            for i in 0..frames {
                buf[(*pos + i) % buf_len] = src[i];
            }
            *pos = (*pos + frames) % buf_len;
        }
    );
}

/// Read `dst.len()` contiguous samples from circular buffer at `pos - offset`, wrapping.
pub fn vec_ring_read(buf: &[f32], pos: usize, dst: &mut [f32], offset: usize) {
    let buf_len = buf.len();
    let frames = dst.len();
    dispatch!(
        unsafe {
            sesh_vec_ring_read_host(
                buf.as_ptr(),
                buf_len as u32,
                pos as u32,
                dst.as_mut_ptr(),
                offset as u32,
                frames as u32,
            );
        },
        {
            let start = (pos + buf_len - offset) % buf_len;
            for i in 0..frames {
                dst[i] = buf[(start + i) % buf_len];
            }
        }
    );
}

// ===========================================================================
// Delay op
// ===========================================================================

/// Per-sample modulated delay read with linear interpolation.
///
/// For each sample `i`, reads from circular buffer at a fractional offset
/// `time[i]` samples behind where the write head was at sample `i`.
/// `pos` should be the write head position *after* the most recent `vec_ring_write`.
pub fn vec_delay_read(buf: &[f32], pos: usize, dst: &mut [f32], time: &[f32]) {
    let buf_len = buf.len();
    let frames = dst.len().min(time.len());
    dispatch!(
        unsafe {
            sesh_vec_delay_read_host(
                buf.as_ptr(),
                buf_len as u32,
                pos as u32,
                dst.as_mut_ptr(),
                time.as_ptr(),
                frames as u32,
            );
        },
        {
            for i in 0..frames {
                // The write head was at (pos - frames + i) when sample i was written.
                let write_pos_at_i = (pos + buf_len - frames + i) % buf_len;

                let delay_int = time[i] as usize;
                let delay_frac = time[i] - delay_int as f32;

                let idx1 = (write_pos_at_i + buf_len - delay_int) % buf_len;
                let idx2 = (idx1 + buf_len - 1) % buf_len;

                dst[i] = buf[idx1] + delay_frac * (buf[idx2] - buf[idx1]);
            }
        }
    );
}

// ===========================================================================
// Schroeder allpass diffuser
// ===========================================================================

/// Schroeder allpass filter operating on a circular buffer.
///
/// Unity-gain allpass: smears transients without changing frequency balance.
/// Used in series for reverb diffusion. Each allpass needs its own buffer and
/// write position (like a delay line).
///
/// `g` is the allpass coefficient (typically 0.5–0.7). `delay` is in samples.
pub fn vec_schroeder_allpass(
    buf: &mut [f32],
    pos: &mut usize,
    dst: &mut [f32],
    src: &[f32],
    delay: usize,
    g: f32,
) {
    let buf_len = buf.len();
    let frames = dst.len().min(src.len());
    dispatch!(
        {
            let mut pos32 = *pos as u32;
            unsafe {
                sesh_vec_schroeder_allpass_host(
                    buf.as_mut_ptr(),
                    buf_len as u32,
                    &mut pos32,
                    dst.as_mut_ptr(),
                    src.as_ptr(),
                    delay as u32,
                    g,
                    frames as u32,
                );
            }
            *pos = pos32 as usize;
        },
        {
            let mut wp = *pos;
            for i in 0..frames {
                let read_idx = (wp + buf_len - delay) % buf_len;
                let buf_out = buf[read_idx];

                let v = src[i] + g * buf_out;
                dst[i] = buf_out - g * v;

                buf[wp] = v;
                wp = (wp + 1) % buf_len;
            }
            *pos = wp;
        }
    );
}

// ===========================================================================
// One-pole filter
// ===========================================================================

/// One-pole lowpass filter (6 dB/oct). Processes a block of samples.
///
/// `coefficient` controls the cutoff: 0.0 = no filtering (pass-through),
/// approaching 1.0 = heavy lowpass. Compute from cutoff frequency:
/// `coefficient = exp(-2π * cutoff_hz / sample_rate)`.
pub fn vec_one_pole(state: &mut OnePoleState, dst: &mut [f32], src: &[f32], coefficient: f32) {
    let frames = dst.len().min(src.len());
    dispatch!(
        {
            unsafe {
                sesh_vec_one_pole_host(
                    &mut state.y1 as *mut f32,
                    dst.as_mut_ptr(),
                    src.as_ptr(),
                    coefficient,
                    frames as u32,
                );
            }
        },
        {
            let mut y = state.y1;
            for i in 0..frames {
                y = src[i] + coefficient * (y - src[i]);
                dst[i] = y;
            }
            state.y1 = y;
        }
    );
}

// ===========================================================================
// Comb filters (delay with feedback)
// ===========================================================================
//
// Three variants forming a hierarchy:
//
// - `vec_comb`          — single delay line with feedback. The primitive
//                         building block: echo, flanger, karplus-strong.
//
// - `vec_comb_parallel` — N independent delay lines, same input, no
//                         cross-feedback. Schroeder/Moorer reverb topology:
//                         parallel combs → series allpasses.
//
// - `vec_comb_coupled`  — N delay lines with an N×N mixing matrix coupling
//                         their feedback paths. Feedback delay network (FDN)
//                         topology: the matrix (Hadamard, householder, etc.)
//                         controls diffusion density.
//
// All three internalize the per-sample feedback loop, so the plugin never
// needs to write sample-by-sample code for delay-with-feedback effects.

/// Single comb filter: one delay line with feedback and damping.
///
/// Reads from the delay line with interpolated modulated delay time,
/// applies one-pole damping, outputs the result, and writes
/// `input + feedback * damped` back into the buffer.
///
/// Use for: echo, flanger, chorus with feedback, karplus-strong strings,
/// single delay with feedback.
pub fn vec_comb(
    buf: &mut [f32],
    pos: &mut usize,
    damp: &mut OnePoleState,
    dst: &mut [f32],
    src: &[f32],
    time: &[f32],
    feedback: f32,
    damping: f32,
) {
    let buf_len = buf.len();
    let frames = dst.len().min(src.len()).min(time.len());
    dispatch!(
        {
            let mut pos32 = *pos as u32;
            unsafe {
                sesh_vec_comb_host(
                    buf.as_mut_ptr(),
                    buf_len as u32,
                    &mut pos32,
                    &mut damp.y1 as *mut f32,
                    dst.as_mut_ptr(),
                    src.as_ptr(),
                    time.as_ptr(),
                    feedback,
                    damping,
                    frames as u32,
                );
            }
            *pos = pos32 as usize;
        },
        {
            let mut wp = *pos;
            let mut y = damp.y1;
            for i in 0..frames {
                let delay_int = time[i] as usize;
                let delay_frac = time[i] - delay_int as f32;
                let idx1 = (wp + buf_len - delay_int) % buf_len;
                let idx2 = (idx1 + buf_len - 1) % buf_len;
                let tap = buf[idx1] + delay_frac * (buf[idx2] - buf[idx1]);

                y = tap + damping * (y - tap);

                dst[i] = y;
                buf[wp] = src[i] + feedback * y;
                wp = (wp + 1) % buf_len;
            }
            *pos = wp;
            damp.y1 = y;
        }
    );
}

/// Parallel comb filter: N independent delay lines, same input, no cross-feedback.
///
/// Each line gets the full `src` input and feeds back only into itself.
/// Outputs are written to separate buffers so the caller can sum/weight
/// them however they like (e.g. different weights for L/R stereo width).
///
/// Use for: Schroeder reverb (parallel combs → series allpasses), Moorer
/// reverb, any topology with independent delay+feedback lines.
pub fn vec_comb_parallel(
    bufs: &mut [&mut [f32]],
    positions: &mut [usize],
    damp: &mut [OnePoleState],
    dst: &mut [&mut [f32]],
    src: &[f32],
    time: &[&[f32]],
    feedback: f32,
    damping: f32,
) {
    let n = bufs.len();
    let frames = src.len();
    dispatch!(
        {
            let mut buf_ptrs: SmallVec<[*mut f32; 16]> = SmallVec::with_capacity(n);
            let mut buf_lens: SmallVec<[u32; 16]> = SmallVec::with_capacity(n);
            let mut pos32: SmallVec<[u32; 16]> = SmallVec::with_capacity(n);
            let mut damp_vals: SmallVec<[f32; 16]> = SmallVec::with_capacity(n);
            let mut dst_ptrs: SmallVec<[*mut f32; 16]> = SmallVec::with_capacity(n);
            let mut time_ptrs: SmallVec<[*const f32; 16]> = SmallVec::with_capacity(n);
            for i in 0..n {
                buf_ptrs.push(bufs[i].as_mut_ptr());
                buf_lens.push(bufs[i].len() as u32);
                pos32.push(positions[i] as u32);
                damp_vals.push(damp[i].y1);
                dst_ptrs.push(dst[i].as_mut_ptr());
                time_ptrs.push(time[i].as_ptr());
            }
            unsafe {
                sesh_vec_comb_parallel_host(
                    buf_ptrs.as_ptr(),
                    buf_lens.as_ptr(),
                    pos32.as_mut_ptr(),
                    damp_vals.as_mut_ptr(),
                    dst_ptrs.as_ptr(),
                    src.as_ptr(),
                    time_ptrs.as_ptr(),
                    feedback,
                    damping,
                    n as u32,
                    frames as u32,
                );
            }
            for i in 0..n {
                positions[i] = pos32[i] as usize;
                damp[i].y1 = damp_vals[i];
            }
        },
        {
            for line in 0..n {
                vec_comb(
                    bufs[line],
                    &mut positions[line],
                    &mut damp[line],
                    dst[line],
                    src,
                    time[line],
                    feedback,
                    damping,
                );
            }
        }
    );
}

/// Coupled comb filter (FDN): N delay lines with an N×N mixing matrix.
///
/// Each sample: reads N taps with interpolated modulated delay, applies
/// one-pole damping, multiplies through the mixing matrix, then writes
/// `src[line] + mixed[line]` back into each buffer. Outputs the damped
/// taps (pre-matrix) so the caller can weight them for stereo.
///
/// The mixing matrix encodes both the cross-coupling pattern and feedback
/// gain. For a standard FDN, use a normalized Hadamard matrix scaled by
/// the desired feedback coefficient.
///
/// Use for: FDN reverbs (Hadamard, householder), Dattorro plate reverb,
/// any architecture where delay lines cross-feed.
///
/// Maximum N is 16. Panics if `bufs.len()` exceeds this.
pub fn vec_comb_coupled(
    bufs: &mut [&mut [f32]],
    positions: &mut [usize],
    damp: &mut [OnePoleState],
    dst: &mut [&mut [f32]],
    src: &[&[f32]],
    time: &[&[f32]],
    matrix: &[f32],
    damping: f32,
) {
    let n = bufs.len();
    assert!(n <= 16, "vec_comb_coupled: max 16 lines");
    assert!(
        matrix.len() >= n * n,
        "vec_comb_coupled: matrix must be N×N"
    );

    let frames = dst[0].len();
    dispatch!(
        {
            let mut buf_ptrs: SmallVec<[*mut f32; 16]> = SmallVec::with_capacity(n);
            let mut buf_lens: SmallVec<[u32; 16]> = SmallVec::with_capacity(n);
            let mut pos32: SmallVec<[u32; 16]> = SmallVec::with_capacity(n);
            let mut damp_vals: SmallVec<[f32; 16]> = SmallVec::with_capacity(n);
            let mut dst_ptrs: SmallVec<[*mut f32; 16]> = SmallVec::with_capacity(n);
            let mut src_ptrs: SmallVec<[*const f32; 16]> = SmallVec::with_capacity(n);
            let mut time_ptrs: SmallVec<[*const f32; 16]> = SmallVec::with_capacity(n);
            for i in 0..n {
                buf_ptrs.push(bufs[i].as_mut_ptr());
                buf_lens.push(bufs[i].len() as u32);
                pos32.push(positions[i] as u32);
                damp_vals.push(damp[i].y1);
                dst_ptrs.push(dst[i].as_mut_ptr());
                src_ptrs.push(src[i].as_ptr());
                time_ptrs.push(time[i].as_ptr());
            }
            unsafe {
                sesh_vec_comb_coupled_host(
                    buf_ptrs.as_ptr(),
                    buf_lens.as_ptr(),
                    pos32.as_mut_ptr(),
                    damp_vals.as_mut_ptr(),
                    dst_ptrs.as_ptr(),
                    src_ptrs.as_ptr(),
                    time_ptrs.as_ptr(),
                    matrix.as_ptr(),
                    damping,
                    n as u32,
                    frames as u32,
                );
            }
            for i in 0..n {
                positions[i] = pos32[i] as usize;
                damp[i].y1 = damp_vals[i];
            }
        },
        {
            for i in 0..frames {
                let mut taps = [0.0f32; 16];
                let mut mixed = [0.0f32; 16];

                for line in 0..n {
                    let buf_len = bufs[line].len();
                    let wp = positions[line];
                    let t = time[line][i];
                    let delay_int = t as usize;
                    let delay_frac = t - delay_int as f32;
                    let idx1 = (wp + buf_len - delay_int) % buf_len;
                    let idx2 = (idx1 + buf_len - 1) % buf_len;
                    taps[line] =
                        bufs[line][idx1] + delay_frac * (bufs[line][idx2] - bufs[line][idx1]);
                }

                for line in 0..n {
                    damp[line].y1 = taps[line] + damping * (damp[line].y1 - taps[line]);
                    taps[line] = damp[line].y1;
                }

                for row in 0..n {
                    let mut sum = 0.0;
                    for col in 0..n {
                        sum += matrix[row * n + col] * taps[col];
                    }
                    mixed[row] = sum;
                }

                for line in 0..n {
                    dst[line][i] = taps[line];
                    bufs[line][positions[line]] = src[line][i] + mixed[line];
                    positions[line] = (positions[line] + 1) % bufs[line].len();
                }
            }
        }
    );
}

// ===========================================================================
// Oscillator
// ===========================================================================

/// Fill `dst` with oscillator output. Advances `*phase`. `freq` is in Hz.
pub fn vec_osc(phase: &mut f32, dst: &mut [f32], freq: f32, waveform: Waveform, sample_rate: f32) {
    let frames = dst.len();
    dispatch!(
        unsafe {
            sesh_vec_osc_host(
                phase as *mut f32,
                dst.as_mut_ptr(),
                freq,
                waveform as u32,
                sample_rate,
                frames as u32,
            );
        },
        {
            let phase_inc = freq / sample_rate;
            for i in 0..frames {
                dst[i] = match waveform {
                    Waveform::Sine => (*phase * std::f32::consts::TAU).sin(),
                    Waveform::Triangle => 4.0 * (*phase - (*phase + 0.5).floor()).abs() - 1.0,
                    Waveform::Saw => 2.0 * (*phase - (*phase + 0.5).floor()),
                    Waveform::Square => {
                        if *phase % 1.0 < 0.5 {
                            1.0
                        } else {
                            -1.0
                        }
                    }
                };
                *phase += phase_inc;
                if *phase >= 1.0 {
                    *phase -= 1.0;
                }
            }
        }
    );
}

// ===========================================================================
// Filter
// ===========================================================================

/// Biquad filter with per-sample modulation of cutoff, Q, and gain.
///
/// `cutoff` is in Hz, `q` is the Q factor, `gain` is in dB (used for Peak/Shelf types).
/// Coefficients are recomputed each sample from the parameter buffers.
pub fn vec_biquad(
    state: &mut BiquadState,
    dst: &mut [f32],
    src: &[f32],
    cutoff: &[f32],
    q: &[f32],
    gain: &[f32],
    filter_type: FilterType,
    sample_rate: f32,
) {
    let frames = dst
        .len()
        .min(src.len())
        .min(cutoff.len())
        .min(q.len())
        .min(gain.len());
    dispatch!(
        unsafe {
            sesh_vec_biquad_host(
                state as *mut BiquadState as *mut f32,
                dst.as_mut_ptr(),
                src.as_ptr(),
                cutoff.as_ptr(),
                q.as_ptr(),
                gain.as_ptr(),
                filter_type as u32,
                sample_rate,
                frames as u32,
            );
        },
        {
            for i in 0..frames {
                let w0 = std::f32::consts::TAU * cutoff[i] / sample_rate;
                let cos_w0 = w0.cos();
                let sin_w0 = w0.sin();
                let alpha = sin_w0 / (2.0 * q[i]);
                let a_db = gain[i];
                let a_lin = 10.0f32.powf(a_db / 40.0);

                let (b0, b1, b2, a0, a1, a2) = match filter_type {
                    FilterType::Lowpass => {
                        let b1 = 1.0 - cos_w0;
                        let b0 = b1 / 2.0;
                        (b0, b1, b0, 1.0 + alpha, -2.0 * cos_w0, 1.0 - alpha)
                    }
                    FilterType::Highpass => {
                        let b1 = -(1.0 + cos_w0);
                        let b0 = (1.0 + cos_w0) / 2.0;
                        (b0, b1, b0, 1.0 + alpha, -2.0 * cos_w0, 1.0 - alpha)
                    }
                    FilterType::Bandpass => {
                        (alpha, 0.0, -alpha, 1.0 + alpha, -2.0 * cos_w0, 1.0 - alpha)
                    }
                    FilterType::Notch => (
                        1.0,
                        -2.0 * cos_w0,
                        1.0,
                        1.0 + alpha,
                        -2.0 * cos_w0,
                        1.0 - alpha,
                    ),
                    FilterType::Peak => (
                        1.0 + alpha * a_lin,
                        -2.0 * cos_w0,
                        1.0 - alpha * a_lin,
                        1.0 + alpha / a_lin,
                        -2.0 * cos_w0,
                        1.0 - alpha / a_lin,
                    ),
                    FilterType::LowShelf => {
                        let two_sqrt_a_alpha = 2.0 * a_lin.sqrt() * alpha;
                        (
                            a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha),
                            2.0 * a_lin * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w0),
                            a_lin * ((a_lin + 1.0) - (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha),
                            (a_lin + 1.0) + (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha,
                            -2.0 * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w0),
                            (a_lin + 1.0) + (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha,
                        )
                    }
                    FilterType::HighShelf => {
                        let two_sqrt_a_alpha = 2.0 * a_lin.sqrt() * alpha;
                        (
                            a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha),
                            -2.0 * a_lin * ((a_lin - 1.0) + (a_lin + 1.0) * cos_w0),
                            a_lin * ((a_lin + 1.0) + (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha),
                            (a_lin + 1.0) - (a_lin - 1.0) * cos_w0 + two_sqrt_a_alpha,
                            2.0 * ((a_lin - 1.0) - (a_lin + 1.0) * cos_w0),
                            (a_lin + 1.0) - (a_lin - 1.0) * cos_w0 - two_sqrt_a_alpha,
                        )
                    }
                    FilterType::Allpass => (
                        1.0 - alpha,
                        -2.0 * cos_w0,
                        1.0 + alpha,
                        1.0 + alpha,
                        -2.0 * cos_w0,
                        1.0 - alpha,
                    ),
                };

                // Normalize coefficients.
                let b0 = b0 / a0;
                let b1 = b1 / a0;
                let b2 = b2 / a0;
                let a1 = a1 / a0;
                let a2 = a2 / a0;

                let x0 = src[i];
                let y0 = b0 * x0 + b1 * state.x1 + b2 * state.x2 - a1 * state.y1 - a2 * state.y2;

                state.x2 = state.x1;
                state.x1 = x0;
                state.y2 = state.y1;
                state.y1 = y0;

                dst[i] = y0;
            }
        }
    );
}

// ===========================================================================
// Dynamics
// ===========================================================================

/// Envelope follower. Tracks amplitude of `src` with attack/release smoothing.
///
/// `attack` and `release` are in seconds (per-sample buffers for modulation).
/// Output in `dst` is the smoothed envelope value.
pub fn vec_envelope(
    state: &mut EnvelopeState,
    dst: &mut [f32],
    src: &[f32],
    attack: &[f32],
    release: &[f32],
    mode: EnvelopeMode,
    sample_rate: f32,
) {
    let frames = dst
        .len()
        .min(src.len())
        .min(attack.len())
        .min(release.len());
    dispatch!(
        unsafe {
            sesh_vec_envelope_host(
                state as *mut EnvelopeState as *mut f32,
                dst.as_mut_ptr(),
                src.as_ptr(),
                attack.as_ptr(),
                release.as_ptr(),
                mode as u32,
                sample_rate,
                frames as u32,
            );
        },
        {
            for i in 0..frames {
                let input_level = match mode {
                    EnvelopeMode::Peak => src[i].abs(),
                    EnvelopeMode::Rms => src[i] * src[i],
                };

                let att_coeff = (-1.0 / (attack[i] * sample_rate)).exp();
                let rel_coeff = (-1.0 / (release[i] * sample_rate)).exp();

                let coeff = if input_level > state.current {
                    att_coeff
                } else {
                    rel_coeff
                };
                state.current = coeff * state.current + (1.0 - coeff) * input_level;

                dst[i] = match mode {
                    EnvelopeMode::Peak => state.current,
                    EnvelopeMode::Rms => state.current.sqrt(),
                };
            }
        }
    );
}

// ===========================================================================
// Waveshaping
// ===========================================================================

/// Soft saturation: `dst[i] = tanh(src[i] * drive[i])`.
pub fn vec_tanh(dst: &mut [f32], src: &[f32], drive: &[f32]) {
    let len = dst.len().min(src.len()).min(drive.len());
    dispatch!(
        unsafe { sesh_vec_tanh_host(dst.as_mut_ptr(), src.as_ptr(), drive.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = (src[i] * drive[i]).tanh();
        }
    );
}

/// Hard clipping: clamp `src` to `±threshold[i]`.
pub fn vec_hard_clip(dst: &mut [f32], src: &[f32], threshold: &[f32]) {
    let len = dst.len().min(src.len()).min(threshold.len());
    dispatch!(
        unsafe {
            sesh_vec_hard_clip_host(
                dst.as_mut_ptr(),
                src.as_ptr(),
                threshold.as_ptr(),
                len as u32,
            )
        },
        for i in 0..len {
            dst[i] = src[i].clamp(-threshold[i], threshold[i]);
        }
    );
}

// ===========================================================================
// Unary / additional math ops
// ===========================================================================

/// Absolute value: `dst[i] = |src[i]|`.
pub fn vec_abs(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_abs_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = src[i].abs();
        }
    );
}

/// Negate: `dst[i] = -src[i]`. Phase inversion.
pub fn vec_neg(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_neg_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = -src[i];
        }
    );
}

/// Square root: `dst[i] = sqrt(src[i])`.
pub fn vec_sqrt(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_sqrt_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = src[i].sqrt();
        }
    );
}

/// Reciprocal: `dst[i] = 1.0 / src[i]`.
pub fn vec_recip(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_recip_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = 1.0 / src[i];
        }
    );
}

/// Element-wise division: `dst[i] = a[i] / b[i]`.
pub fn vec_div(dst: &mut [f32], a: &[f32], b: &[f32]) {
    let len = dst.len().min(a.len()).min(b.len());
    dispatch!(
        unsafe { sesh_vec_div_host(dst.as_mut_ptr(), a.as_ptr(), b.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = a[i] / b[i];
        }
    );
}

/// Element-wise power: `dst[i] = src[i].powf(exp[i])`.
pub fn vec_pow(dst: &mut [f32], src: &[f32], exp: &[f32]) {
    let len = dst.len().min(src.len()).min(exp.len());
    dispatch!(
        unsafe { sesh_vec_pow_host(dst.as_mut_ptr(), src.as_ptr(), exp.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = src[i].powf(exp[i]);
        }
    );
}

/// Natural logarithm: `dst[i] = ln(src[i])`.
pub fn vec_log(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_log_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = src[i].ln();
        }
    );
}

/// Natural exponential: `dst[i] = exp(src[i])`.
pub fn vec_exp(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_exp_host(dst.as_mut_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = src[i].exp();
        }
    );
}

// ===========================================================================
// In-place (_assign) variants
// ===========================================================================
//
// These are Rust convenience wrappers that call the same host imports with
// dst aliased as src. Raw pointer aliasing is fine — this is purely a Rust
// borrow-checker workaround. No additional C/host API surface.

/// In-place element-wise addition: `dst[i] += src[i]`.
pub fn vec_add_assign(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_add_host(dst.as_mut_ptr(), dst.as_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] += src[i];
        }
    );
}

/// In-place element-wise multiplication: `dst[i] *= src[i]`.
pub fn vec_mul_assign(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_mul_host(dst.as_mut_ptr(), dst.as_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] *= src[i];
        }
    );
}

/// In-place soft saturation: `dst[i] = tanh(dst[i] * drive[i])`.
pub fn vec_tanh_assign(dst: &mut [f32], drive: &[f32]) {
    let len = dst.len().min(drive.len());
    dispatch!(
        unsafe { sesh_vec_tanh_host(dst.as_mut_ptr(), dst.as_ptr(), drive.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = (dst[i] * drive[i]).tanh();
        }
    );
}

/// In-place hard clipping: clamp `dst` to `±threshold[i]`.
pub fn vec_hard_clip_assign(dst: &mut [f32], threshold: &[f32]) {
    let len = dst.len().min(threshold.len());
    dispatch!(
        unsafe {
            sesh_vec_hard_clip_host(
                dst.as_mut_ptr(),
                dst.as_ptr(),
                threshold.as_ptr(),
                len as u32,
            )
        },
        for i in 0..len {
            dst[i] = dst[i].clamp(-threshold[i], threshold[i]);
        }
    );
}

/// In-place absolute value: `dst[i] = |dst[i]|`.
pub fn vec_abs_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_abs_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = dst[i].abs();
        }
    );
}

/// In-place negate: `dst[i] = -dst[i]`.
pub fn vec_neg_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_neg_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = -dst[i];
        }
    );
}

/// In-place square root: `dst[i] = sqrt(dst[i])`.
pub fn vec_sqrt_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_sqrt_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = dst[i].sqrt();
        }
    );
}

/// In-place reciprocal: `dst[i] = 1.0 / dst[i]`.
pub fn vec_recip_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_recip_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = 1.0 / dst[i];
        }
    );
}

/// In-place element-wise division: `dst[i] /= src[i]`.
pub fn vec_div_assign(dst: &mut [f32], src: &[f32]) {
    let len = dst.len().min(src.len());
    dispatch!(
        unsafe { sesh_vec_div_host(dst.as_mut_ptr(), dst.as_ptr(), src.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] /= src[i];
        }
    );
}

/// In-place element-wise power: `dst[i] = dst[i].powf(exp[i])`.
pub fn vec_pow_assign(dst: &mut [f32], exp: &[f32]) {
    let len = dst.len().min(exp.len());
    dispatch!(
        unsafe { sesh_vec_pow_host(dst.as_mut_ptr(), dst.as_ptr(), exp.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = dst[i].powf(exp[i]);
        }
    );
}

/// In-place natural logarithm: `dst[i] = ln(dst[i])`.
pub fn vec_log_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_log_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = dst[i].ln();
        }
    );
}

/// In-place natural exponential: `dst[i] = exp(dst[i])`.
pub fn vec_exp_assign(dst: &mut [f32]) {
    let len = dst.len();
    dispatch!(
        unsafe { sesh_vec_exp_host(dst.as_mut_ptr(), dst.as_ptr(), len as u32) },
        for i in 0..len {
            dst[i] = dst[i].exp();
        }
    );
}
