//! Types and functions used for handling events and adding event listeners to dom nodes.

use crate::dom::event::DomEvent;
use crate::ComponentImports;

/// Generates an `__afia__$handle_event` function that expects that a pointer to a [`Callback`]
/// was used as the context when calling [`crate::DomElement::add_event_listener`]'s context.
///
/// The generated functions behaves similar to the following:
/// ```ignore
/// use afia_component::callback::Callback;
/// use afia_component::dom::event::DomEvent;
///
/// #[export_name = "__afia__$handle_event"]
/// pub extern "C" fn handle_event(event: DomEvent, callback: &'static Callback) {
///     callback.call(event);
/// }
/// ```
#[macro_export]
macro_rules! handle_events_using_callbacks {
    () => {
        #[export_name = "__afia__$handle_event"]
        #[allow(missing_docs)]
        pub extern "C" fn handle_event(event_handle: i64, callback: *mut Callback) {
            let callback: &'static Callback = unsafe { &*callback };

            let event_handle =
                $crate::dom::event::DomEvent::_private_new(event_handle, callback.imports());

            (callback.callback())(event_handle, callback.context());
        }
    };
}

/// A callback that can be used for event listeners in the function
/// [`add_event_listener`], it gets passed down to the exported
/// `__afia__$handle_event` function.
pub struct Callback {
    context: isize,
    imports: &'static ComponentImports,
    callback: fn(DomEvent, isize),
}

impl Callback {
    /// TODO: Delete this. Just a way for us to let some of our older code make use of this newer
    ///  [`Callback`] abstraction.
    ///  This is currently used in one place. We should not use it anywhere else. Once we delete
    ///  that place we can delete this method.
    pub fn temporary_way_to_call_using_integer_event_handle(&self, event_handle: i64) {
        (self.callback)(
            DomEvent::_private_new(event_handle, self.imports),
            self.context,
        )
    }

    /// Create a [`Callback`] by providing a function that takes an event handle
    /// and a pointer to the associated context.
    pub fn new_with_context_pointer<T>(
        context: *mut T,
        imports: &'static ComponentImports,
        // TODO: Change think about whether to change this to.
        //  Just a quick idea that I'm jotting down... this commit is already large to trying to land
        //  it without any new stuff ...
        //  `callback: fn(DomEvent, ContextPtr<T>)`
        //  Using `ContextPtr<T>` allows the user to access the `T` without needing to write unsafe
        //  pointer dereferencing code.
        callback: fn(DomEvent, *mut T),
    ) -> Self {
        // SAFETY: safe because we are casting between two pointer types of the same size
        let callback: fn(DomEvent, isize) = unsafe { std::mem::transmute(callback) };

        Self {
            context: context as isize,
            callback,
            imports,
        }
    }

    /// Create a [`Callback`] by providing a function that takes an event
    /// handle as an argument.
    pub fn new_without_context(imports: &'static ComponentImports, callback: fn(DomEvent)) -> Self {
        Self {
            context: callback as isize,
            imports,
            callback: |event: DomEvent, context: isize| {
                // SAFETY: Safe because we stored the callback function pointer as the context.
                let callback: fn(DomEvent) = unsafe { std::mem::transmute(context) };

                callback(event)
            },
        }
    }

    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
    #[doc(hidden)]
    pub fn callback(&self) -> fn(DomEvent, isize) {
        self.callback
    }

    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
    #[doc(hidden)]
    pub fn context(&self) -> isize {
        self.context
    }

    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
    #[doc(hidden)]
    pub fn imports(&self) -> &'static ComponentImports {
        self.imports
    }
}
