use crate::triple_buffer::{self, Consumer, Producer};
use std::any::Any;
use std::cell::UnsafeCell;

#[cfg(target_arch = "wasm32")]
extern "C" {
    fn sesh_draw_rect_host(x: f32, y: f32, w: f32, h: f32, color: u32);
    fn sesh_draw_knob_host(param_index: u32, x: f32, y: f32, size: f32);
}

/// Implement this on a struct to provide custom GUI rendering for a plugin
pub trait Draw: Default + Clone + Send + 'static {
    /// Width of the plugin GUI in logical pixels
    const WIDTH: f32;

    /// Called each frame on the GUI thread
    fn draw(&mut self, ctx: &DrawContext);
}

/// Drawing primitives backed by host imports
///
/// Methods are no-ops on non-wasm32 targets (tests).
pub struct DrawContext {
    pub width: f32,
    pub height: f32,
}

impl DrawContext {
    pub fn new(width: f32, height: f32) -> Self {
        Self { width, height }
    }

    pub fn size(&self) -> (f32, f32) {
        (self.width, self.height)
    }

    pub fn rect(&self, x: f32, y: f32, w: f32, h: f32, color: u32) {
        #[cfg(target_arch = "wasm32")]
        unsafe {
            sesh_draw_rect_host(x, y, w, h, color);
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = (x, y, w, h, color);
        }
    }

    pub fn knob(&self, param_index: u32, x: f32, y: f32, size: f32) {
        #[cfg(target_arch = "wasm32")]
        unsafe {
            sesh_draw_knob_host(param_index, x, y, size);
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = (param_index, x, y, size);
        }
    }
}

/// Type-erased interface for the display, used by macro-generated code
///
/// All methods take `&self` and use interior mutability, so that `sesh_process` (audio thread)
/// and `sesh_draw` (GUI thread) can both hold `&PluginInstance` without aliasing `&mut`.
pub trait DisplaySlot {
    /// Publish the write slot to ready (audio thread only)
    fn publish(&self);
    /// Read latest state and call Draw::draw (GUI thread only)
    fn draw_gui(&self);
    /// Returns Draw::WIDTH
    fn width(&self) -> f32;
    /// Downcast support for ProcessContext::draw::<T>()
    fn as_any(&self) -> &dyn Any;
}

/// Concrete display slot holding a producer/consumer pair over a triple buffer
///
/// The producer (audio thread) and consumer (GUI thread) are wrapped in `UnsafeCell` so that
/// `DisplaySlot` methods can take `&self`. Safety: audio thread only touches `producer`, GUI
/// thread only touches `consumer`.
pub struct DisplayInner<T: Draw> {
    producer: UnsafeCell<Producer<T>>,
    consumer: UnsafeCell<Consumer<T>>,
}

/// SAFETY: Producer is only accessed by the audio thread, Consumer only by the GUI thread.
/// The triple buffer's atomic protocol ensures they never touch the same slot.
unsafe impl<T: Draw> Sync for DisplayInner<T> {}

impl<T: Draw> DisplayInner<T> {
    pub fn new() -> Self {
        let (producer, consumer) = triple_buffer::triple_buffer(T::default());
        Self {
            producer: UnsafeCell::new(producer),
            consumer: UnsafeCell::new(consumer),
        }
    }
}

impl<T: Draw> DisplaySlot for DisplayInner<T> {
    fn publish(&self) {
        // SAFETY: only called from audio thread
        let producer = unsafe { &mut *self.producer.get() };
        producer.publish();
    }

    fn draw_gui(&self) {
        // SAFETY: only called from GUI thread
        let consumer = unsafe { &mut *self.consumer.get() };
        let ctx = DrawContext::new(T::WIDTH, 244.0);
        consumer.read_mut().draw(&ctx);
    }

    fn width(&self) -> f32 {
        T::WIDTH
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

/// Context passed to `Plugin::process()`
///
/// Provides access to audio channels, parameter buffers, and an optional draw state.
pub struct ProcessContext<'a, 'b> {
    pub channels: &'a mut [&'b mut [f32]],
    pub params: &'a [&'b [f32]],
    display: &'a mut Option<Box<dyn DisplaySlot>>,
}

impl<'a, 'b> ProcessContext<'a, 'b> {
    pub fn new(
        channels: &'a mut [&'b mut [f32]],
        params: &'a [&'b [f32]],
        display: &'a mut Option<Box<dyn DisplaySlot>>,
    ) -> Self {
        Self {
            channels,
            params,
            display,
        }
    }

    /// Access the draw state's write slot
    ///
    /// Lazily creates the `DisplayInner<T>` on first call, so plugins that never call this pay
    /// no cost. The returned reference points directly into the triple buffer's write slot. After
    /// `process()` returns, the macro calls `publish()` to make the written state available to
    /// the GUI thread.
    pub fn draw<T: Draw>(&mut self) -> &mut T {
        if self.display.is_none() {
            *self.display = Some(Box::new(DisplayInner::<T>::new()));
        }
        let slot = self.display.as_ref().unwrap();
        let inner = slot
            .as_any()
            .downcast_ref::<DisplayInner<T>>()
            .expect("draw type mismatch");
        // SAFETY: only called from audio thread, which exclusively owns the producer
        let producer = unsafe { &mut *inner.producer.get() };
        producer.write()
    }
}
