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

#[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);
    fn sesh_load_image_host(ptr: u32, len: u32) -> u32;
    fn sesh_draw_image_host(id: u32, x: f32, y: f32);
    fn sesh_draw_bg_image_host(id: u32);
    fn sesh_draw_knob_strip_host(
        param_index: u32,
        id: u32,
        x: f32,
        y: f32,
        w: f32,
        h: f32,
        frame_count: u32,
        touch_x: f32,
        touch_y: f32,
        touch_w: f32,
        touch_h: f32,
    );
}

/// Opaque handle to a host-loaded image
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageId(u32);

/// Lazy-initialized image handle for use in statics
///
/// Stores a reference to the embedded image bytes and caches the host-returned ID on first
/// load. Safe to share across multiple plugin instances since image data is per-module, not
/// per-instance.
///
/// ```rust,ignore
/// static BG: sesh_sdk::Image = sesh_sdk::Image::new(include_bytes!("../assets/bg.png"));
///
/// fn draw(&mut self, ctx: &DrawContext) {
///     ctx.bg_image(ctx.load_image(&BG));
/// }
/// ```
pub struct Image {
    id: AtomicU32,
    bytes: &'static [u8],
}

impl Image {
    pub const fn new(bytes: &'static [u8]) -> Self {
        Self {
            id: AtomicU32::new(u32::MAX),
            bytes,
        }
    }
}

/// 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);
        }
    }

    /// Load an image, returning its handle
    ///
    /// On first call for a given `Image`, sends the bytes to the host for decoding. Subsequent
    /// calls return the cached handle.
    pub fn load_image(&self, image: &Image) -> ImageId {
        let raw = image.id.load(Ordering::Relaxed);
        if raw != u32::MAX {
            return ImageId(raw);
        }
        #[cfg(target_arch = "wasm32")]
        let id = ImageId(unsafe {
            sesh_load_image_host(image.bytes.as_ptr() as u32, image.bytes.len() as u32)
        });
        #[cfg(not(target_arch = "wasm32"))]
        let id = ImageId(1);
        image.id.store(id.0, Ordering::Relaxed);
        id
    }

    pub fn image(&self, id: ImageId, x: f32, y: f32) {
        #[cfg(target_arch = "wasm32")]
        unsafe {
            sesh_draw_image_host(id.0, x, y);
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = (id, x, y);
        }
    }

    /// Draw a filmstrip knob
    ///
    /// The host selects the frame to display based on the parameter's normalized value
    /// (0.0-1.0 mapped to frame index). The filmstrip image is a vertical stack of frames.
    /// The host scales the source frame to fit the destination `(w, h)`.
    ///
    /// `touch_x/y/w/h` define the drag-interactive region relative to the plugin viewport.
    /// The host uses this for hit-testing instead of the full knob bounding box.
    pub fn knob_strip(
        &self,
        param_index: u32,
        id: ImageId,
        x: f32,
        y: f32,
        w: f32,
        h: f32,
        frame_count: u32,
        touch_x: f32,
        touch_y: f32,
        touch_w: f32,
        touch_h: f32,
    ) {
        #[cfg(target_arch = "wasm32")]
        unsafe {
            sesh_draw_knob_strip_host(
                param_index,
                id.0,
                x,
                y,
                w,
                h,
                frame_count,
                touch_x,
                touch_y,
                touch_w,
                touch_h,
            );
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = (
                param_index,
                id,
                x,
                y,
                w,
                h,
                frame_count,
                touch_x,
                touch_y,
                touch_w,
                touch_h,
            );
        }
    }

    /// Draw an image as the plugin background
    ///
    /// The host renders this behind all other draw calls and applies corner rounding
    /// to fit the plugin container.
    pub fn bg_image(&self, id: ImageId) {
        #[cfg(target_arch = "wasm32")]
        unsafe {
            sesh_draw_bg_image_host(id.0);
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let _ = id;
        }
    }
}

/// 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()
    }
}
