use core::ptr;

#[allow(clippy::wildcard_imports)]
use super::*;
use crate::{bridge, ffi_impl as ffi};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HidTransactionDirection {
    Input,
    Output,
}

impl HidTransactionDirection {
    #[must_use]
    pub const fn as_raw(self) -> ffi::IOHIDTransactionDirectionType {
        match self {
            Self::Input => ffi::kIOHIDTransactionDirectionTypeInput,
            Self::Output => ffi::kIOHIDTransactionDirectionTypeOutput,
        }
    }

    #[must_use]
    pub const fn from_raw(raw: ffi::IOHIDTransactionDirectionType) -> Self {
        match raw {
            ffi::kIOHIDTransactionDirectionTypeOutput => Self::Output,
            _ => Self::Input,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HidTransactionOptions(u32);

impl HidTransactionOptions {
    pub const NONE: Self = Self(ffi::kIOHIDTransactionOptionsNone);
    pub const WEAK_DEVICE: Self = Self(ffi::kIOHIDTransactionOptionsWeakDevice);

    #[must_use]
    pub const fn bits(self) -> u32 {
        self.0
    }
}

pub struct HidTransaction {
    raw: ffi::IOHIDTransactionRef,
}

unsafe impl Send for HidTransaction {}
unsafe impl Sync for HidTransaction {}

impl Clone for HidTransaction {
    fn clone(&self) -> Self {
        unsafe { ffi::CFRetain(self.raw.cast_const()) };
        Self { raw: self.raw }
    }
}

impl Drop for HidTransaction {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            unsafe { ffi::CFRelease(self.raw.cast_const()) };
            self.raw = ptr::null_mut();
        }
    }
}

#[allow(clippy::missing_errors_doc)]
impl HidTransaction {
    #[must_use]
    pub fn type_id() -> ffi::CFTypeID {
        unsafe { bridge::iohidmanager_swift_transaction_type_id() as ffi::CFTypeID }
    }

    pub fn new(device: &HidDevice, direction: HidTransactionDirection) -> Result<Self, HidError> {
        Self::with_options(device, direction, HidTransactionOptions::NONE)
    }

    pub fn with_options(
        device: &HidDevice,
        direction: HidTransactionDirection,
        options: HidTransactionOptions,
    ) -> Result<Self, HidError> {
        let raw = unsafe {
            ffi::IOHIDTransactionCreate(
                ffi::kCFAllocatorDefault,
                device.raw,
                direction.as_raw(),
                options.bits(),
            )
        };
        if raw.is_null() {
            Err(HidError::OperationFailed("IOHIDTransactionCreate"))
        } else {
            Ok(Self { raw })
        }
    }

    #[must_use]
    pub fn device(&self) -> Option<HidDevice> {
        let device = unsafe { ffi::IOHIDTransactionGetDevice(self.raw) };
        if device.is_null() {
            None
        } else {
            unsafe { ffi::CFRetain(device.cast_const()) };
            Some(HidDevice { raw: device })
        }
    }

    #[must_use]
    pub fn direction(&self) -> HidTransactionDirection {
        HidTransactionDirection::from_raw(unsafe { ffi::IOHIDTransactionGetDirection(self.raw) })
    }

    pub fn set_direction(&self, direction: HidTransactionDirection) {
        unsafe { ffi::IOHIDTransactionSetDirection(self.raw, direction.as_raw()) };
    }

    pub fn add_element(&self, element: &HidElement) {
        unsafe { ffi::IOHIDTransactionAddElement(self.raw, element.raw) };
    }

    pub fn remove_element(&self, element: &HidElement) {
        unsafe { ffi::IOHIDTransactionRemoveElement(self.raw, element.raw) };
    }

    #[must_use]
    pub fn contains_element(&self, element: &HidElement) -> bool {
        unsafe { ffi::IOHIDTransactionContainsElement(self.raw, element.raw) }
    }

    pub fn schedule_current_run_loop(&self) {
        let run_loop = unsafe { ffi::CFRunLoopGetCurrent() };
        unsafe {
            ffi::IOHIDTransactionScheduleWithRunLoop(
                self.raw,
                run_loop,
                ffi::kCFRunLoopDefaultMode,
            );
        }
    }

    pub fn unschedule_current_run_loop(&self) {
        let run_loop = unsafe { ffi::CFRunLoopGetCurrent() };
        unsafe {
            ffi::IOHIDTransactionUnscheduleFromRunLoop(
                self.raw,
                run_loop,
                ffi::kCFRunLoopDefaultMode,
            );
        }
    }

    pub fn set_value(
        &self,
        element: &HidElement,
        value: &HidValue,
        options: ffi::IOOptionBits,
    ) {
        unsafe { ffi::IOHIDTransactionSetValue(self.raw, element.raw, value.raw, options) };
    }

    #[must_use]
    pub fn get_value(&self, element: &HidElement, options: ffi::IOOptionBits) -> Option<HidValue> {
        clone_value_ref(unsafe { ffi::IOHIDTransactionGetValue(self.raw, element.raw, options) })
    }

    pub fn commit(&self) -> Result<(), HidError> {
        let status = unsafe { ffi::IOHIDTransactionCommit(self.raw) };
        if status == ffi::kIOReturnSuccess {
            Ok(())
        } else {
            Err(HidError::IoReturn("IOHIDTransactionCommit", status))
        }
    }

    pub fn commit_with_timeout(&self, timeout_ms: f64) -> Result<(), HidError> {
        let status = unsafe {
            ffi::IOHIDTransactionCommitWithCallback(self.raw, timeout_ms, None, ptr::null_mut())
        };
        if status == ffi::kIOReturnSuccess {
            Ok(())
        } else {
            Err(HidError::IoReturn(
                "IOHIDTransactionCommitWithCallback",
                status,
            ))
        }
    }

    pub fn clear(&self) {
        unsafe { ffi::IOHIDTransactionClear(self.raw) };
    }

    #[must_use]
    pub const fn as_ptr(&self) -> ffi::IOHIDTransactionRef {
        self.raw
    }
}
