//! wlr-export-dmabuf-unstable-v1 protocol implementation.
//!
//! Allows clients to capture frames as DMA-BUFs for efficient screencasting.
//! The compositor allocates a GBM buffer, blits the rendered frame into it,
//! exports the DMA-BUF fd, and sends it to the client.

use std::os::fd::AsFd;
use std::time::UNIX_EPOCH;

use smithay::reexports::wayland_protocols_wlr::export_dmabuf::v1::server::{
    zwlr_export_dmabuf_frame_v1::{self, ZwlrExportDmabufFrameV1},
    zwlr_export_dmabuf_manager_v1::{self, ZwlrExportDmabufManagerV1},
};
use smithay::reexports::wayland_server::{
    Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New,
};
use tracing::debug;

const MANAGER_VERSION: u32 = 1;

pub struct ExportDmabufFrameData;

pub struct ExportDmabufManagerState;

/// A pending export-dmabuf frame capture queued for the next render.
pub struct ExportDmabufCapture {
    pub frame: ZwlrExportDmabufFrameV1,
    pub submitted: bool,
}

impl Drop for ExportDmabufCapture {
    fn drop(&mut self) {
        if !self.submitted {
            self.frame.cancel(zwlr_export_dmabuf_frame_v1::CancelReason::Temporary);
        }
    }
}

impl ExportDmabufCapture {
    /// Send the captured frame data to the client.
    ///
    /// `width`/`height`: frame dimensions in pixels.
    /// `format`: DRM fourcc format (e.g. `Fourcc::Argb8888 as u32`).
    /// `modifier`: DRM format modifier.
    /// `fd`: exported DMA-BUF file descriptor.
    /// `stride`: row stride in bytes.
    /// `size`: total buffer size in bytes.
    pub fn submit(
        mut self,
        width: u32,
        height: u32,
        format: u32,
        modifier: u64,
        fd: std::os::fd::OwnedFd,
        stride: u32,
        size: u32,
    ) {
        let mod_high = (modifier >> 32) as u32;
        let mod_low = modifier as u32;

        // Send frame event: dimensions, format, modifier, num_objects
        self.frame.frame(
            width,
            height,
            0, // offset_x
            0, // offset_y
            0, // buffer_flags
            zwlr_export_dmabuf_frame_v1::Flags::Transient,
            format,
            mod_high,
            mod_low,
            1, // num_objects (single plane)
        );

        // Send object event: fd, size, offset, stride
        self.frame.object(
            0, // index
            fd.as_fd(),
            size,
            0, // offset
            stride,
            0, // plane_index
        );

        // Send ready event with current timestamp
        let now = UNIX_EPOCH.elapsed().unwrap();
        let secs = now.as_secs();
        self.frame.ready(
            (secs >> 32) as u32,
            secs as u32,
            now.subsec_nanos(),
        );

        self.submitted = true;
    }
}

pub trait ExportDmabufHandler {
    fn export_dmabuf_frame(&mut self, capture: ExportDmabufCapture);
}

impl ExportDmabufManagerState {
    pub fn new<D>(display: &DisplayHandle) -> Self
    where
        D: GlobalDispatch<ZwlrExportDmabufManagerV1, ()>
            + Dispatch<ZwlrExportDmabufManagerV1, ()>
            + Dispatch<ZwlrExportDmabufFrameV1, ExportDmabufFrameData>
            + ExportDmabufHandler
            + 'static,
    {
        display.create_global::<D, ZwlrExportDmabufManagerV1, _>(MANAGER_VERSION, ());
        Self
    }
}

impl<D> GlobalDispatch<ZwlrExportDmabufManagerV1, (), D> for ExportDmabufManagerState
where
    D: GlobalDispatch<ZwlrExportDmabufManagerV1, ()>
        + Dispatch<ZwlrExportDmabufManagerV1, ()>
        + Dispatch<ZwlrExportDmabufFrameV1, ExportDmabufFrameData>
        + ExportDmabufHandler
        + 'static,
{
    fn bind(
        _state: &mut D,
        _display: &DisplayHandle,
        _client: &Client,
        resource: New<ZwlrExportDmabufManagerV1>,
        _data: &(),
        data_init: &mut DataInit<'_, D>,
    ) {
        data_init.init(resource, ());
        debug!("wlr-export-dmabuf-manager bound");
    }
}

impl<D> Dispatch<ZwlrExportDmabufManagerV1, (), D> for ExportDmabufManagerState
where
    D: Dispatch<ZwlrExportDmabufManagerV1, ()>
        + Dispatch<ZwlrExportDmabufFrameV1, ExportDmabufFrameData>
        + ExportDmabufHandler
        + 'static,
{
    fn request(
        state: &mut D,
        _client: &Client,
        _resource: &ZwlrExportDmabufManagerV1,
        request: zwlr_export_dmabuf_manager_v1::Request,
        _data: &(),
        _display: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            zwlr_export_dmabuf_manager_v1::Request::CaptureOutput {
                frame,
                overlay_cursor: _,
                output: _,
            } => {
                let frame_resource = data_init.init(frame, ExportDmabufFrameData);
                state.export_dmabuf_frame(ExportDmabufCapture {
                    frame: frame_resource,
                    submitted: false,
                });
            }
            zwlr_export_dmabuf_manager_v1::Request::Destroy => {}
            _ => {}
        }
    }
}

impl<D> Dispatch<ZwlrExportDmabufFrameV1, ExportDmabufFrameData, D> for ExportDmabufManagerState
where
    D: Dispatch<ZwlrExportDmabufFrameV1, ExportDmabufFrameData> + 'static,
{
    fn request(
        _state: &mut D,
        _client: &Client,
        _resource: &ZwlrExportDmabufFrameV1,
        request: zwlr_export_dmabuf_frame_v1::Request,
        _data: &ExportDmabufFrameData,
        _display: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
        match request {
            zwlr_export_dmabuf_frame_v1::Request::Destroy => {}
            _ => {}
        }
    }
}

#[macro_export]
macro_rules! delegate_export_dmabuf {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::export_dmabuf::v1::server::zwlr_export_dmabuf_manager_v1::ZwlrExportDmabufManagerV1: ()
        ] => $crate::protocols::export_dmabuf::ExportDmabufManagerState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::export_dmabuf::v1::server::zwlr_export_dmabuf_manager_v1::ZwlrExportDmabufManagerV1: ()
        ] => $crate::protocols::export_dmabuf::ExportDmabufManagerState);

        smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            smithay::reexports::wayland_protocols_wlr::export_dmabuf::v1::server::zwlr_export_dmabuf_frame_v1::ZwlrExportDmabufFrameV1: $crate::protocols::export_dmabuf::ExportDmabufFrameData
        ] => $crate::protocols::export_dmabuf::ExportDmabufManagerState);
    };
}
