use {
    crate::{
        async_engine::SpawnedFuture,
        backend::transaction::{
            BackendConnectorTransaction, BackendConnectorTransactionError,
            BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn,
        },
        cmm::cmm_primaries::Primaries,
        drm_feedback::DrmFeedback,
        fixed::Fixed,
        format::Format,
        gfx_api::{FdSync, GfxApi, GfxFramebuffer},
        ifs::{
            wl_output::OutputId,
            wl_seat::{
                tablet::{
                    PadButtonState, TabletInit, TabletPadId, TabletPadInit, TabletRingEventSource,
                    TabletStripEventSource, TabletToolChanges, TabletToolId, TabletToolInit,
                    ToolButtonState,
                },
                wl_pointer::{CONTINUOUS, FINGER, HORIZONTAL_SCROLL, VERTICAL_SCROLL, WHEEL},
            },
        },
        libinput::consts::DeviceCapability,
        utils::static_text::StaticText,
        video::drm::{
            ConnectorType, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT,
            DrmConnector, DrmError, DrmVersion, HDMI_EOTF_SMPTE_ST2084,
            HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
        },
    },
    jay_config::input::SwitchEvent,
    linearize::Linearize,
    std::{
        any::Any,
        error::Error,
        fmt::{Debug, Display, Formatter},
        hash::Hash,
        rc::Rc,
    },
    uapi::{OwnedFd, Packed, Pod, c},
};

pub mod transaction;

linear_ids!(ConnectorIds, ConnectorId);
linear_ids!(InputDeviceIds, InputDeviceId);
linear_ids!(DrmDeviceIds, DrmDeviceId);

pub trait Backend: Any {
    fn run(self: Rc<Self>) -> SpawnedFuture<Result<(), Box<dyn Error>>>;
    fn clear(&self) {
        // nothing
    }

    fn switch_to(&self, vtnr: u32) {
        let _ = vtnr;
    }

    fn import_environment(&self) -> bool {
        false
    }

    fn supports_presentation_feedback(&self) -> bool {
        false
    }

    fn get_input_fds(&self) -> Vec<Rc<OwnedFd>> {
        vec![]
    }
}

#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct Mode {
    pub width: i32,
    pub height: i32,
    pub refresh_rate_millihz: u32,
}

impl Mode {
    pub fn refresh_nsec(&self) -> u64 {
        match self.refresh_rate_millihz {
            0 => u64::MAX,
            n => 1_000_000_000_000 / (n as u64),
        }
    }

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

impl Display for Mode {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}x{}@{}",
            self.width,
            self.height,
            self.refresh_rate_millihz as f64 / 1000.0,
        )
    }
}

#[derive(Clone, Debug)]
pub struct MonitorInfo {
    pub modes: Option<Vec<Mode>>,
    pub output_id: Rc<OutputId>,
    pub width_mm: i32,
    pub height_mm: i32,
    pub non_desktop: bool,
    pub non_desktop_effective: bool,
    pub vrr_capable: bool,
    pub eotfs: Vec<BackendEotfs>,
    pub color_spaces: Vec<BackendColorSpace>,
    pub primaries: Primaries,
    pub luminance: Option<BackendLuminance>,
    pub state: BackendConnectorState,
}

#[derive(Copy, Clone, Debug)]
pub struct ConnectorKernelId {
    pub ty: ConnectorType,
    pub idx: u32,
}

impl Display for ConnectorKernelId {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}-{}", self.ty, self.idx)
    }
}

bitflags! {
    ConnectorCaps: u32;
        CONCAP_CONNECTOR,
        CONCAP_MODE_SETTING,
        CONCAP_PHYSICAL_DISPLAY,
}

pub trait Connector: Any {
    fn id(&self) -> ConnectorId;
    fn kernel_id(&self) -> ConnectorKernelId;
    fn event(&self) -> Option<ConnectorEvent>;
    fn on_change(&self, cb: Rc<dyn Fn()>);
    fn damage(&self);
    fn drm_dev(&self) -> Option<DrmDeviceId>;
    fn effectively_locked(&self) -> bool;
    fn state(&self) -> BackendConnectorState;
    fn caps(&self) -> ConnectorCaps {
        ConnectorCaps::none()
    }
    fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
        None
    }
    fn drm_object_id(&self) -> Option<DrmConnector> {
        None
    }
    fn before_non_desktop_override_update(&self, overrd: Option<bool>) {
        let _ = overrd;
    }
    fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
        #[derive(Hash, Eq, PartialEq)]
        struct UnimplementedConnectorTransactionType;
        impl BackendConnectorTransactionType for UnimplementedConnectorTransactionType {}
        Box::new(UnimplementedConnectorTransactionType)
    }
    fn create_transaction(
        &self,
    ) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
        Err(BackendConnectorTransactionError::TransactionsNotSupported(
            self.kernel_id(),
        ))
    }
    fn gamma_lut_size(&self) -> Option<u32> {
        None
    }
    fn name(&self) -> String {
        self.kernel_id().to_string()
    }
}

#[derive(Debug)]
pub enum ConnectorEvent {
    Connected(MonitorInfo),
    HardwareCursor(Option<Rc<dyn HardwareCursor>>),
    Disconnected,
    Removed,
    Unavailable,
    Available,
    State(BackendConnectorState),
    FormatsChanged(Rc<Vec<&'static Format>>),
}

pub trait HardwareCursorUpdate {
    fn set_enabled(&mut self, enabled: bool);
    fn get_buffer(&self) -> Rc<dyn GfxFramebuffer>;
    fn set_position(&mut self, x: i32, y: i32);
    fn swap_buffer(&mut self, sync: Option<FdSync>);
    fn size(&self) -> (i32, i32);
}

pub trait HardwareCursor: Debug {
    fn damage(&self);
}

pub type TransformMatrix = [[f64; 2]; 2];

linear_ids!(InputDeviceGroupIds, InputDeviceGroupId, usize);

pub trait InputDevice {
    fn id(&self) -> InputDeviceId;
    fn removed(&self) -> bool;
    fn event(&self) -> Option<InputEvent>;
    fn on_change(&self, cb: Rc<dyn Fn()>);
    fn grab(&self, grab: bool);
    fn has_capability(&self, cap: InputDeviceCapability) -> bool;
    fn left_handed(&self) -> Option<bool> {
        None
    }
    fn set_left_handed(&self, left_handed: bool);
    fn accel_profile(&self) -> Option<InputDeviceAccelProfile> {
        None
    }
    fn set_accel_profile(&self, profile: InputDeviceAccelProfile);
    fn accel_speed(&self) -> Option<f64> {
        None
    }
    fn set_accel_speed(&self, speed: f64);
    fn transform_matrix(&self) -> Option<TransformMatrix> {
        None
    }
    fn set_transform_matrix(&self, matrix: TransformMatrix);
    fn calibration_matrix(&self) -> Option<[[f32; 3]; 2]> {
        None
    }
    fn set_calibration_matrix(&self, m: [[f32; 3]; 2]) {
        let _ = m;
    }
    fn name(&self) -> Rc<String>;
    fn dev_t(&self) -> Option<c::dev_t> {
        None
    }
    fn tap_enabled(&self) -> Option<bool> {
        None
    }
    fn set_tap_enabled(&self, enabled: bool);
    fn drag_enabled(&self) -> Option<bool> {
        None
    }
    fn set_drag_enabled(&self, enabled: bool);
    fn drag_lock_enabled(&self) -> Option<bool> {
        None
    }
    fn set_drag_lock_enabled(&self, enabled: bool);
    fn natural_scrolling_enabled(&self) -> Option<bool> {
        None
    }
    fn set_natural_scrolling_enabled(&self, enabled: bool);
    fn click_method(&self) -> Option<InputDeviceClickMethod> {
        None
    }
    fn set_click_method(&self, method: InputDeviceClickMethod);
    fn middle_button_emulation_enabled(&self) -> Option<bool> {
        None
    }
    fn set_middle_button_emulation_enabled(&self, enabled: bool);
    fn tablet_info(&self) -> Option<Box<TabletInit>> {
        None
    }
    fn tablet_pad_info(&self) -> Option<Box<TabletPadInit>> {
        None
    }

    fn set_enabled_leds(&self, leds: Leds) {
        let _ = leds;
    }
}

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)]
pub enum InputDeviceCapability {
    Keyboard,
    Pointer,
    Touch,
    TabletTool,
    TabletPad,
    Gesture,
    Switch,
}

impl StaticText for InputDeviceCapability {
    fn text(&self) -> &'static str {
        match self {
            InputDeviceCapability::Keyboard => "keyboard",
            InputDeviceCapability::Pointer => "pointer",
            InputDeviceCapability::Touch => "touch",
            InputDeviceCapability::TabletTool => "tablet tool",
            InputDeviceCapability::TabletPad => "tablet pad",
            InputDeviceCapability::Gesture => "gesture",
            InputDeviceCapability::Switch => "switch",
        }
    }
}

impl InputDeviceCapability {
    pub fn to_libinput(self) -> DeviceCapability {
        use crate::libinput::consts::*;
        match self {
            InputDeviceCapability::Keyboard => LIBINPUT_DEVICE_CAP_KEYBOARD,
            InputDeviceCapability::Pointer => LIBINPUT_DEVICE_CAP_POINTER,
            InputDeviceCapability::Touch => LIBINPUT_DEVICE_CAP_TOUCH,
            InputDeviceCapability::TabletTool => LIBINPUT_DEVICE_CAP_TABLET_TOOL,
            InputDeviceCapability::TabletPad => LIBINPUT_DEVICE_CAP_TABLET_PAD,
            InputDeviceCapability::Gesture => LIBINPUT_DEVICE_CAP_GESTURE,
            InputDeviceCapability::Switch => LIBINPUT_DEVICE_CAP_SWITCH,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceAccelProfile {
    Flat,
    Adaptive,
}

impl StaticText for InputDeviceAccelProfile {
    fn text(&self) -> &'static str {
        match self {
            InputDeviceAccelProfile::Flat => "Flat",
            InputDeviceAccelProfile::Adaptive => "Adaptive",
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Linearize)]
pub enum InputDeviceClickMethod {
    None,
    ButtonAreas,
    Clickfinger,
}

impl StaticText for InputDeviceClickMethod {
    fn text(&self) -> &'static str {
        match self {
            InputDeviceClickMethod::None => "none",
            InputDeviceClickMethod::ButtonAreas => "button-areas",
            InputDeviceClickMethod::Clickfinger => "clickfinger",
        }
    }
}

pub enum BackendEvent {
    NewDrmDevice(Rc<dyn BackendDrmDevice>),
    NewConnector(Rc<dyn Connector>),
    NewInputDevice(Rc<dyn InputDevice>),
    DevicesEnumerated,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum KeyState {
    Released,
    Pressed,
    Repeated,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ButtonState {
    Released,
    Pressed,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Linearize)]
pub enum ScrollAxis {
    Horizontal = HORIZONTAL_SCROLL as _,
    Vertical = VERTICAL_SCROLL as _,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AxisSource {
    Wheel = WHEEL as _,
    Finger = FINGER as _,
    Continuous = CONTINUOUS as _,
}

pub const AXIS_120: i32 = 120;

bitflags! {
    Leds: u32;
        LED_NUM_LOCK,
        LED_CAPS_LOCK,
        LED_SCROLL_LOCK,
        LED_COMPOSE,
        LED_KANA,
}

#[derive(Debug)]
pub enum InputEvent {
    Key {
        time_usec: u64,
        key: u32,
        state: KeyState,
    },
    ConnectorPosition {
        time_usec: u64,
        connector: ConnectorId,
        x: Fixed,
        y: Fixed,
    },
    Motion {
        time_usec: u64,
        dx: Fixed,
        dy: Fixed,
        dx_unaccelerated: Fixed,
        dy_unaccelerated: Fixed,
    },
    MotionAbsolute {
        time_usec: u64,
        x_normed: f32,
        y_normed: f32,
    },
    Button {
        time_usec: u64,
        button: u32,
        state: ButtonState,
    },

    AxisPx {
        dist: Fixed,
        axis: ScrollAxis,
        inverted: bool,
    },
    AxisSource {
        source: AxisSource,
    },
    AxisStop {
        axis: ScrollAxis,
    },
    Axis120 {
        dist: i32,
        axis: ScrollAxis,
        inverted: bool,
    },
    AxisFrame {
        time_usec: u64,
    },
    SwipeBegin {
        time_usec: u64,
        finger_count: u32,
    },
    SwipeUpdate {
        time_usec: u64,
        dx: Fixed,
        dy: Fixed,
        dx_unaccelerated: Fixed,
        dy_unaccelerated: Fixed,
    },
    SwipeEnd {
        time_usec: u64,
        cancelled: bool,
    },
    PinchBegin {
        time_usec: u64,
        finger_count: u32,
    },
    PinchUpdate {
        time_usec: u64,
        dx: Fixed,
        dy: Fixed,
        dx_unaccelerated: Fixed,
        dy_unaccelerated: Fixed,
        scale: Fixed,
        rotation: Fixed,
    },
    PinchEnd {
        time_usec: u64,
        cancelled: bool,
    },
    HoldBegin {
        time_usec: u64,
        finger_count: u32,
    },
    HoldEnd {
        time_usec: u64,
        cancelled: bool,
    },

    SwitchEvent {
        time_usec: u64,
        event: SwitchEvent,
    },

    TabletToolAdded {
        time_usec: u64,
        init: Box<TabletToolInit>,
    },
    TabletToolChanged {
        time_usec: u64,
        id: TabletToolId,
        changes: Box<TabletToolChanges>,
    },
    TabletToolButton {
        time_usec: u64,
        id: TabletToolId,
        button: u32,
        state: ToolButtonState,
    },
    TabletToolRemoved {
        time_usec: u64,
        id: TabletToolId,
    },

    TabletPadButton {
        time_usec: u64,
        id: TabletPadId,
        button: u32,
        state: PadButtonState,
    },
    TabletPadModeSwitch {
        time_usec: u64,
        pad: TabletPadId,
        group: u32,
        mode: u32,
    },
    TabletPadRing {
        time_usec: u64,
        pad: TabletPadId,
        ring: u32,
        source: Option<TabletRingEventSource>,
        angle: Option<f64>,
    },
    TabletPadStrip {
        time_usec: u64,
        pad: TabletPadId,
        strip: u32,
        source: Option<TabletStripEventSource>,
        position: Option<f64>,
    },
    TabletPadDial {
        time_usec: u64,
        pad: TabletPadId,
        dial: u32,
        value120: i32,
    },
    TouchDown {
        time_usec: u64,
        id: i32,
        x_normed: Fixed,
        y_normed: Fixed,
    },
    TouchUp {
        time_usec: u64,
        id: i32,
    },
    TouchMotion {
        time_usec: u64,
        id: i32,
        x_normed: Fixed,
        y_normed: Fixed,
    },
    TouchCancel {
        time_usec: u64,
        id: i32,
    },
    TouchFrame {
        time_usec: u64,
    },
}

pub enum DrmEvent {
    #[expect(dead_code)]
    Removed,
    GfxApiChanged,
}

pub trait BackendDrmDevice {
    fn id(&self) -> DrmDeviceId;
    fn event(&self) -> Option<DrmEvent>;
    fn on_change(&self, cb: Rc<dyn Fn()>);
    fn dev_t(&self) -> c::dev_t;
    fn make_render_device(&self);
    fn set_gfx_api(&self, api: GfxApi);
    fn gtx_api(&self) -> GfxApi;
    fn version(&self) -> Result<DrmVersion, DrmError>;
    fn set_direct_scanout_enabled(&self, enabled: bool);
    fn is_render_device(&self) -> bool;
    fn direct_scanout_enabled(&self) -> bool {
        false
    }
    fn gfx_fast_ram_access(&self) -> bool {
        false
    }
    fn create_lease(
        self: Rc<Self>,
        lessee: Rc<dyn BackendDrmLessee>,
        connector_ids: &[ConnectorId],
    ) {
        let _ = lessee;
        let _ = connector_ids;
    }
    fn set_flip_margin(&self, margin: u64) {
        let _ = margin;
    }
}

pub trait BackendDrmLease {
    fn fd(&self) -> &Rc<OwnedFd>;
}

pub trait BackendDrmLessee {
    fn created(&self, lease: Rc<dyn BackendDrmLease>);
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)]
pub enum BackendEotfs {
    #[default]
    Default,
    Pq,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Linearize)]
pub enum BackendColorSpace {
    #[default]
    Default,
    Bt2020,
}

#[derive(Copy, Clone, Debug)]
pub struct BackendLuminance {
    pub min: f64,
    pub max: f64,
    pub max_fall: f64,
}

impl BackendEotfs {
    pub fn to_drm(self) -> u8 {
        match self {
            BackendEotfs::Default => HDMI_EOTF_TRADITIONAL_GAMMA_SDR,
            BackendEotfs::Pq => HDMI_EOTF_SMPTE_ST2084,
        }
    }

    pub const fn name(self) -> &'static str {
        match self {
            BackendEotfs::Default => "default",
            BackendEotfs::Pq => "pq",
        }
    }
}

impl BackendColorSpace {
    pub fn to_drm(self) -> u64 {
        match self {
            BackendColorSpace::Default => DRM_MODE_COLORIMETRY_DEFAULT,
            BackendColorSpace::Bt2020 => DRM_MODE_COLORIMETRY_BT2020_RGB,
        }
    }

    pub const fn name(self) -> &'static str {
        match self {
            BackendColorSpace::Default => "default",
            BackendColorSpace::Bt2020 => "bt2020",
        }
    }
}

// kernel: struct drm_color_lut
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct BackendGammaLutElement {
    pub red: u16,
    pub green: u16,
    pub blue: u16,
    pub reserved: u16,
}

unsafe impl Pod for BackendGammaLutElement {}
unsafe impl Packed for BackendGammaLutElement {}

#[derive(Debug, Eq)]
pub struct BackendGammaLut {
    id: [u8; 32],
    pub gamma_lut: Vec<BackendGammaLutElement>,
}

impl BackendGammaLut {
    pub fn new(mut gamma_lut: Vec<BackendGammaLutElement>) -> Self {
        for element in &mut gamma_lut {
            element.reserved = 0;
        }
        let gamma_lut_bytes = uapi::as_bytes(&gamma_lut as &[_]);
        let id = *blake3::hash(gamma_lut_bytes).as_bytes();
        Self { id, gamma_lut }
    }
}

impl PartialEq for BackendGammaLut {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

linear_ids!(
    BackendConnectorStateSerials,
    BackendConnectorStateSerial,
    u64
);

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackendConnectorState {
    pub serial: BackendConnectorStateSerial,
    pub enabled: bool,
    pub active: bool,
    pub mode: Mode,
    pub non_desktop_override: Option<bool>,
    pub vrr: bool,
    pub tearing: bool,
    pub format: &'static Format,
    pub color_space: BackendColorSpace,
    pub eotf: BackendEotfs,
    pub gamma_lut: Option<Rc<BackendGammaLut>>,
}
