//! ISV bus — 32-slot device-resident f32 bus shared across kernels.
//!
//! Slot allocation lives in the design spec Section 7. Slow-path
//! `snapshot()` performs a single mapped-pinned DtoD copy per
//! `feedback_no_htod_htoh_only_mapped_pinned.md` (observability only;
//! never on the hot path).

use std::sync::Arc;

use anyhow::{Context, Result};
use cudarc::driver::{CudaSlice, CudaStream};
use cudarc::driver::sys::CUstream;
use ml_core::device::MlDevice;

use crate::pinned_mem::MappedF32Buffer;
use crate::trainer::raw_launch::{raw_memcpy_dtod_async, raw_stream_sync};

pub const SLOTS: usize = 32;

pub struct IsvBus {
    _stream: Arc<CudaStream>,
    raw_stream: CUstream,
    buffer: CudaSlice<f32>,
}

impl IsvBus {
    pub fn new(dev: &MlDevice) -> Result<Self> {
        let stream = dev.cuda_stream().context("ISV: CUDA stream")?.clone();
        let raw_stream = stream.cu_stream();
        let buffer = stream
            .alloc_zeros::<f32>(SLOTS)
            .context("ISV: alloc 32 slots")?;
        Ok(Self { _stream: stream, raw_stream, buffer })
    }

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

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

    /// Device-side buffer; pass to kernels that read ISV slots.
    pub fn buffer(&self) -> &CudaSlice<f32> {
        &self.buffer
    }

    pub fn buffer_mut(&mut self) -> &mut CudaSlice<f32> {
        &mut self.buffer
    }

    /// Slow path — write a single slot (config/control plane only).
    pub fn write_slot(&mut self, idx: usize, value: f32) -> Result<()> {
        assert!(idx < SLOTS, "ISV slot {idx} out of bounds (max {SLOTS})");
        let staging = unsafe { MappedF32Buffer::new(1) }
            .map_err(|e| anyhow::anyhow!("ISV staging alloc: {e}"))?;
        staging.write_from_slice(&[value]);
        let nbytes = std::mem::size_of::<f32>();
        unsafe {
            let dst_ptr = self.buffer.raw_ptr();
            raw_memcpy_dtod_async(
                dst_ptr + (idx * nbytes) as u64,
                staging.dev_ptr,
                nbytes,
                self.raw_stream,
            )
            .map_err(|e| anyhow::anyhow!("ISV write DtoD: {e:?}"))?;
            raw_stream_sync(self.raw_stream)
                .map_err(|e| anyhow::anyhow!("ISV write sync: {e:?}"))?;
        }
        Ok(())
    }

    /// Slow path — read all 32 slots into a host array.
    pub fn snapshot(&self) -> Result<[f32; SLOTS]> {
        let staging = unsafe { MappedF32Buffer::new(SLOTS) }
            .map_err(|e| anyhow::anyhow!("ISV snapshot alloc: {e}"))?;
        let nbytes = SLOTS * std::mem::size_of::<f32>();
        unsafe {
            let src_ptr = self.buffer.raw_ptr();
            raw_memcpy_dtod_async(
                staging.dev_ptr,
                src_ptr,
                nbytes,
                self.raw_stream,
            )
            .map_err(|e| anyhow::anyhow!("ISV snapshot DtoD: {e:?}"))?;
            raw_stream_sync(self.raw_stream)
                .map_err(|e| anyhow::anyhow!("ISV snapshot sync: {e:?}"))?;
        }
        let v = staging.read_all();
        let mut out = [0f32; SLOTS];
        out.copy_from_slice(&v);
        Ok(out)
    }
}
