use std::time::Duration;

use smithay::backend::renderer::gles::GlesRenderer;

use crate::cursor::CursorRender;
use crate::protocols::export_dmabuf::ExportDmabufCapture;
use crate::protocols::image_copy_capture::ImageCopyCapture;
use crate::protocols::screencopy::frame::Screencopy;
use crate::render_context::RenderContext;
use crate::udev::{RenderedFrame, Udev};
use crate::windows::Windows;

#[cfg(feature = "winit")]
use crate::winit::WinitBackend;

pub enum Backend {
    Udev(Udev),
    #[cfg(feature = "winit")]
    Winit(WinitBackend),
}

impl Backend {
    /// Panics if not the Udev variant. Only call from udev-specific code paths.
    pub fn udev(&mut self) -> &mut Udev {
        match self {
            Self::Udev(u) => u,
            #[cfg(feature = "winit")]
            _ => unreachable!("expected udev backend"),
        }
    }

    /// The active DRM device's GBM handle, for allocating exportable buffers
    /// (screencast dmabufs). `None` for winit or before a device exists.
    pub fn gbm(&self) -> Option<smithay::backend::allocator::gbm::GbmDevice<smithay::backend::drm::DrmDeviceFd>> {
        match self {
            Self::Udev(u) => u.output_device.as_ref().map(|d| d.gbm_device()),
            #[cfg(feature = "winit")]
            Self::Winit(_) => None,
        }
    }

    pub fn seat_name(&self) -> String {
        match self {
            Self::Udev(u) => u.seat_name(),
            #[cfg(feature = "winit")]
            Self::Winit(_) => "seat0".into(),
        }
    }

    /// DPMS state actually applied to the hardware; `None` for
    /// backends with no CRTC of their own.
    pub fn applied_display_status(&self) -> Option<bool> {
        match self {
            Self::Udev(u) => Some(u.applied_display_status()),
            #[cfg(feature = "winit")]
            Self::Winit(_) => None,
        }
    }

    pub fn renderer(&mut self) -> Option<&mut GlesRenderer> {
        match self {
            Self::Udev(u) => u.renderer(),
            #[cfg(feature = "winit")]
            Self::Winit(w) => Some(w.renderer()),
        }
    }

    pub fn render(
        &mut self,
        windows: &mut Windows,
        cursor_position: Option<CursorRender>,
        ctx: &mut RenderContext,
        pipewire: Option<&mut crate::pw::PipeWire>,
    ) -> RenderedFrame {
        match self {
            Self::Udev(u) => {
                u.render(windows, cursor_position, ctx, pipewire)
            },
            #[cfg(feature = "winit")]
            Self::Winit(w) => {
                w.render(windows, cursor_position, ctx)
            },
        }
    }

    pub fn schedule_redraw(&mut self, duration: Duration) {
        match self {
            Self::Udev(u) => u.schedule_redraw(duration),
            #[cfg(feature = "winit")]
            Self::Winit(w) => w.schedule_redraw(duration),
        }
    }

    pub fn cancel_scheduled_redraws(&mut self) {
        match self {
            Self::Udev(u) => u.cancel_scheduled_redraws(),
            #[cfg(feature = "winit")]
            Self::Winit(w) => w.cancel_scheduled_redraws(),
        }
    }

    pub fn reset_buffer_ages(&mut self) {
        match self {
            Self::Udev(u) => u.reset_buffer_ages(),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

    pub fn set_display_status(&mut self, on: bool, soft: bool) {
        match self {
            Self::Udev(u) => u.set_display_status(on, soft),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

    /// Connector inventory for Settings; empty for backends without real
    /// outputs (nested winit sessions).
    pub fn output_infos(&self) -> Vec<crate::udev::OutputInfo> {
        match self {
            Self::Udev(u) => u.output_infos(),
            #[cfg(feature = "winit")]
            Self::Winit(_) => Vec::new(),
        }
    }

    pub fn has_external_display(&self) -> bool {
        match self {
            Self::Udev(u) => u.has_external_display(),
            // In winit (nested compositor) the "display" is the host window,
            // so lid-switch DPMS logic from the host hardware must not apply.
            #[cfg(feature = "winit")]
            Self::Winit(_) => true,
        }
    }

    pub fn change_vt(&mut self, vt: i32) {
        match self {
            Self::Udev(u) => u.change_vt(vt),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

    pub fn request_screencopy(&mut self, screencopy: Screencopy) {
        match self {
            Self::Udev(u) => u.request_screencopy(screencopy),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

    pub fn request_export_dmabuf(&mut self, capture: ExportDmabufCapture) {
        match self {
            Self::Udev(u) => u.request_export_dmabuf(capture),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

    pub fn request_image_copy(&mut self, capture: ImageCopyCapture) {
        match self {
            Self::Udev(u) => u.request_image_copy(capture),
            #[cfg(feature = "winit")]
            Self::Winit(_) => {},
        }
    }

}
