use bumpalo::boxed::Box as BumpBox;
use std::{
    any::Any,
    cell::{Cell, RefCell},
    fmt::Debug,
    rc::Rc,
};

pub struct UiEvent<T: 'static + ?Sized> {
    pub(crate) bubbles: Rc<Cell<bool>>,
    pub(crate) data: Rc<T>,
}

impl UiEvent<dyn Any> {
    pub fn downcast<T: 'static + Sized>(self) -> Option<UiEvent<T>> {
        Some(UiEvent {
            bubbles: self.bubbles,
            data: self.data.downcast().ok()?,
        })
    }
}
impl<T: ?Sized> Clone for UiEvent<T> {
    fn clone(&self) -> Self {
        Self {
            bubbles: self.bubbles.clone(),
            data: self.data.clone(),
        }
    }
}
impl<T> UiEvent<T> {
    pub fn cancel_bubble(&self) {
        self.bubbles.set(false);
    }
}

impl<T> std::ops::Deref for UiEvent<T> {
    type Target = Rc<T>;

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

impl<T: Debug> std::fmt::Debug for UiEvent<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UiEvent")
            .field("bubble_state", &self.bubbles)
            .field("data", &self.data)
            .finish()
    }
}

type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;

/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
///
///
/// # Example
///
/// ```rust, ignore
///
/// rsx!{
///     MyComponent { onclick: move |evt| log::info!("clicked"), }
/// }
///
/// #[derive(Props)]
/// struct MyProps<'a> {
///     onclick: EventHandler<'a, MouseEvent>,
/// }
///
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
///     cx.render(rsx!{
///         button {
///             onclick: move |evt| cx.props.onclick.call(evt),
///         }
///     })
/// }
///
/// ```
pub struct EventHandler<'bump, T = ()> {
    /// The (optional) callback that the user specified
    /// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
    pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
}

impl<'a, T> Default for EventHandler<'a, T> {
    fn default() -> Self {
        Self {
            callback: RefCell::new(None),
        }
    }
}

impl<T> EventHandler<'_, T> {
    /// Call this event handler with the appropriate event type
    pub fn call(&self, event: T) {
        if let Some(callback) = self.callback.borrow_mut().as_mut() {
            callback(event);
        }
    }

    /// Forcibly drop the internal handler callback, releasing memory
    pub fn release(&self) {
        self.callback.replace(None);
    }
}
