use leptos_reactive::Oco;
use std::marker::PhantomData;
use wasm_bindgen::convert::FromWasmAbi;
pub trait EventDescriptor: Clone {
    type EventType: FromWasmAbi;
    const BUBBLES: bool;
    fn name(&self) -> Oco<'static, str>;
    fn event_delegation_key(&self) -> Oco<'static, str>;
    #[inline(always)]
    fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
        &None
    }
}
#[derive(Clone, Debug)]
#[allow(non_camel_case_types)]
pub struct undelegated<Ev: EventDescriptor>(pub Ev);
impl<Ev: EventDescriptor> EventDescriptor for undelegated<Ev> {
    type EventType = Ev::EventType;
    #[inline(always)]
    fn name(&self) -> Oco<'static, str> {
        self.0.name()
    }
    #[inline(always)]
    fn event_delegation_key(&self) -> Oco<'static, str> {
        self.0.event_delegation_key()
    }
    const BUBBLES: bool = false;
}
#[derive(Debug)]
pub struct Custom<E: FromWasmAbi = web_sys::Event> {
    name: Oco<'static, str>,
    options: Option<web_sys::AddEventListenerOptions>,
    _event_type: PhantomData<E>,
}
impl<E: FromWasmAbi> Clone for Custom<E> {
    fn clone(&self) -> Self {
        Self {
            name: self.name.clone(),
            options: self.options.clone(),
            _event_type: PhantomData,
        }
    }
}
impl<E: FromWasmAbi> EventDescriptor for Custom<E> {
    type EventType = E;
    fn name(&self) -> Oco<'static, str> {
        self.name.clone()
    }
    fn event_delegation_key(&self) -> Oco<'static, str> {
        format!("$$${}", self.name).into()
    }
    const BUBBLES: bool = false;
    #[inline(always)]
    fn options(&self) -> &Option<web_sys::AddEventListenerOptions> {
        &self.options
    }
}
impl<E: FromWasmAbi> Custom<E> {
    pub fn new(name: impl Into<Oco<'static, str>>) -> Self {
        Self {
            name: name.into(),
            options: None,
            _event_type: PhantomData,
        }
    }
    pub fn options_mut(&mut self) -> &mut web_sys::AddEventListenerOptions {
        self.options
            .get_or_insert_with(web_sys::AddEventListenerOptions::new)
    }
}
pub trait DOMEventResponder: Sized {
    fn add<E: EventDescriptor + 'static>(
        self,
        event: E,
        handler: impl FnMut(E::EventType) + 'static,
    ) -> Self;
    #[inline]
    fn add_handler(self, handler: impl EventHandler) -> Self {
        handler.attach(self)
    }
}
impl<T> DOMEventResponder for crate::HtmlElement<T>
where
    T: crate::html::ElementDescriptor + 'static,
{
    #[inline(always)]
    fn add<E: EventDescriptor + 'static>(
        self,
        event: E,
        handler: impl FnMut(E::EventType) + 'static,
    ) -> Self {
        self.on(event, handler)
    }
}
impl DOMEventResponder for crate::View {
    #[inline(always)]
    fn add<E: EventDescriptor + 'static>(
        self,
        event: E,
        handler: impl FnMut(E::EventType) + 'static,
    ) -> Self {
        self.on(event, handler)
    }
}
pub trait EventHandler {
    fn attach<T: DOMEventResponder>(self, target: T) -> T;
}
impl<T, const N: usize> EventHandler for [T; N]
where
    T: EventHandler,
{
    #[inline]
    fn attach<R: DOMEventResponder>(self, target: R) -> R {
        let mut target = target;
        for item in self {
            target = item.attach(target);
        }
        target
    }
}
impl<T> EventHandler for Option<T>
where
    T: EventHandler,
{
    #[inline]
    fn attach<R: DOMEventResponder>(self, target: R) -> R {
        match self {
            Some(event_handler) => event_handler.attach(target),
            None => target,
        }
    }
}
macro_rules! tc {
  ($($ty:ident),*) => {
    impl<$($ty),*> EventHandler for ($($ty,)*)
    where
      $($ty: EventHandler),*
    {
      #[inline]
      fn attach<RES: DOMEventResponder>(self, target: RES) -> RES {
        ::paste::paste! {
          let (
          $(
            [<$ty:lower>],)*
          ) = self;
          $(
            let target = [<$ty:lower>].attach(target);
          )*
          target
        }
      }
    }
  };
}
tc!(A);
tc!(A, B);
tc!(A, B, C);
tc!(A, B, C, D);
tc!(A, B, C, D, E);
tc!(A, B, C, D, E, F);
tc!(A, B, C, D, E, F, G);
tc!(A, B, C, D, E, F, G, H);
tc!(A, B, C, D, E, F, G, H, I);
tc!(A, B, C, D, E, F, G, H, I, J);
tc!(A, B, C, D, E, F, G, H, I, J, K);
tc!(A, B, C, D, E, F, G, H, I, J, K, L);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
#[rustfmt::skip]
tc!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
macro_rules! collection_callback {
  {$(
    $collection:ident
  ),* $(,)?} => {
    $(
      impl<T> EventHandler for $collection<T>
      where
        T: EventHandler
      {
        #[inline]
        fn attach<R: DOMEventResponder>(self, target: R) -> R {
          let mut target = target;
          for item in self {
            target = item.attach(target);
          }
          target
        }
      }
    )*
  };
}
use std::collections::{BTreeSet, BinaryHeap, HashSet, LinkedList, VecDeque};
collection_callback! {
  Vec,
  BTreeSet,
  BinaryHeap,
  HashSet,
  LinkedList,
  VecDeque,
}
macro_rules! generate_event_types {
  {$(
    $( #[$does_not_bubble:ident] )?
    $( $event:ident )+ : $web_event:ident
  ),* $(,)?} => {
    ::paste::paste! {
      $(
        #[doc = "The `" [< $($event)+ >] "` event, which receives [" $web_event "](web_sys::" $web_event ") as its argument."]
        #[derive(Copy, Clone, Debug)]
        #[allow(non_camel_case_types)]
        pub struct [<$( $event )+ >];
        impl EventDescriptor for [< $($event)+ >] {
          type EventType = web_sys::$web_event;
          #[inline(always)]
          fn name(&self) -> Oco<'static, str> {
            stringify!([< $($event)+ >]).into()
          }
          #[inline(always)]
          fn event_delegation_key(&self) -> Oco<'static, str> {
            concat!("$$$", stringify!([< $($event)+ >])).into()
          }
          const BUBBLES: bool = true $(&& generate_event_types!($does_not_bubble))?;
        }
      )*
      #[non_exhaustive]
      pub enum GenericEventHandler {
        $(
          #[doc = "Variant mapping [`struct@" [< $($event)+ >] "`] to its event handler type."]
          [< $($event:camel)+ >]([< $($event)+ >], Box<dyn FnMut($web_event) + 'static>),
        )*
      }
      impl ::core::fmt::Debug for GenericEventHandler {
        fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
          match self {
            $(
              Self::[< $($event:camel)+ >](event, _) => f
                .debug_tuple(stringify!([< $($event:camel)+ >]))
                .field(&event)
                .field(&::std::any::type_name::<Box<dyn FnMut($web_event) + 'static>>())
                .finish(),
            )*
          }
        }
      }
      impl EventHandler for GenericEventHandler {
        fn attach<T: DOMEventResponder>(self, target: T) -> T {
          match self {
            $(
              Self::[< $($event:camel)+ >](event, handler) => target.add(event, handler),
            )*
          }
        }
      }
      $(
        impl<F> From<([< $($event)+ >], F)> for GenericEventHandler
        where
          F: FnMut($web_event) + 'static
        {
          fn from(value: ([< $($event)+ >], F)) -> Self {
            Self::[< $($event:camel)+ >](value.0, Box::new(value.1))
          }
        }
        impl<F> EventHandler for ([< $($event)+ >], F)
        where
          F: FnMut($web_event) + 'static
        {
          fn attach<L: DOMEventResponder>(self, target: L) -> L {
            target.add(self.0, self.1)
          }
        }
      )*
    }
  };
  (does_not_bubble) => { false }
}
generate_event_types! {
  #[does_not_bubble]
  after print: Event,
  #[does_not_bubble]
  before print: Event,
  #[does_not_bubble]
  before unload: BeforeUnloadEvent,
  #[does_not_bubble]
  gamepad connected: GamepadEvent,
  #[does_not_bubble]
  gamepad disconnected: GamepadEvent,
  hash change: HashChangeEvent,
  #[does_not_bubble]
  language change: Event,
  #[does_not_bubble]
  message: MessageEvent,
  #[does_not_bubble]
  message error: MessageEvent,
  #[does_not_bubble]
  offline: Event,
  #[does_not_bubble]
  online: Event,
  #[does_not_bubble]
  page hide: PageTransitionEvent,
  #[does_not_bubble]
  page show: PageTransitionEvent,
  pop state: PopStateEvent,
  rejection handled: PromiseRejectionEvent,
  #[does_not_bubble]
  storage: StorageEvent,
  #[does_not_bubble]
  unhandled rejection: PromiseRejectionEvent,
  #[does_not_bubble]
  unload: Event,
  #[does_not_bubble]
  abort: UiEvent,
  animation cancel: AnimationEvent,
  animation end: AnimationEvent,
  animation iteration: AnimationEvent,
  animation start: AnimationEvent,
  aux click: MouseEvent,
  before input: InputEvent,
  #[does_not_bubble]
  blur: FocusEvent,
  #[does_not_bubble]
  can play: Event,
  #[does_not_bubble]
  can play through: Event,
  change: Event,
  click: MouseEvent,
  #[does_not_bubble]
  close: Event,
  composition end: CompositionEvent,
  composition start: CompositionEvent,
  composition update: CompositionEvent,
  context menu: MouseEvent,
  #[does_not_bubble]
  cue change: Event,
  dbl click: MouseEvent,
  drag: DragEvent,
  drag end: DragEvent,
  drag enter: DragEvent,
  drag leave: DragEvent,
  drag over: DragEvent,
  drag start: DragEvent,
  drop: DragEvent,
  #[does_not_bubble]
  duration change: Event,
  #[does_not_bubble]
  emptied: Event,
  #[does_not_bubble]
  ended: Event,
  #[does_not_bubble]
  error: ErrorEvent,
  #[does_not_bubble]
  focus: FocusEvent,
  #[does_not_bubble]
  focus in: FocusEvent,
  #[does_not_bubble]
  focus out: FocusEvent,
  form data: Event, #[does_not_bubble]
  got pointer capture: PointerEvent,
  input: Event,
  #[does_not_bubble]
  invalid: Event,
  key down: KeyboardEvent,
  key press: KeyboardEvent,
  key up: KeyboardEvent,
  #[does_not_bubble]
  load: Event,
  #[does_not_bubble]
  loaded data: Event,
  #[does_not_bubble]
  loaded metadata: Event,
  #[does_not_bubble]
  load start: Event,
  lost pointer capture: PointerEvent,
  mouse down: MouseEvent,
  #[does_not_bubble]
  mouse enter: MouseEvent,
  #[does_not_bubble]
  mouse leave: MouseEvent,
  mouse move: MouseEvent,
  mouse out: MouseEvent,
  mouse over: MouseEvent,
  mouse up: MouseEvent,
  #[does_not_bubble]
  pause: Event,
  #[does_not_bubble]
  play: Event,
  #[does_not_bubble]
  playing: Event,
  pointer cancel: PointerEvent,
  pointer down: PointerEvent,
  #[does_not_bubble]
  pointer enter: PointerEvent,
  #[does_not_bubble]
  pointer leave: PointerEvent,
  pointer move: PointerEvent,
  pointer out: PointerEvent,
  pointer over: PointerEvent,
  pointer up: PointerEvent,
  #[does_not_bubble]
  progress: ProgressEvent,
  #[does_not_bubble]
  rate change: Event,
  reset: Event,
  #[does_not_bubble]
  resize: UiEvent,
  #[does_not_bubble]
  scroll: Event,
  #[does_not_bubble]
  scroll end: Event,
  security policy violation: SecurityPolicyViolationEvent,
  #[does_not_bubble]
  seeked: Event,
  #[does_not_bubble]
  seeking: Event,
  select: Event,
  #[does_not_bubble]
  selection change: Event,
  select start: Event,
  slot change: Event,
  #[does_not_bubble]
  stalled: Event,
  submit: SubmitEvent,
  #[does_not_bubble]
  suspend: Event,
  #[does_not_bubble]
  time update: Event,
  #[does_not_bubble]
  toggle: Event,
  touch cancel: TouchEvent,
  touch end: TouchEvent,
  touch move: TouchEvent,
  touch start: TouchEvent,
  transition cancel: TransitionEvent,
  transition end: TransitionEvent,
  transition run: TransitionEvent,
  transition start: TransitionEvent,
  #[does_not_bubble]
  volume change: Event,
  #[does_not_bubble]
  waiting: Event,
  webkit animation end: Event,
  webkit animation iteration: Event,
  webkit animation start: Event,
  webkit transition end: Event,
  wheel: WheelEvent,
  D O M Content Loaded: Event, #[does_not_bubble]
  device motion: DeviceMotionEvent,
  #[does_not_bubble]
  device orientation: DeviceOrientationEvent,
  #[does_not_bubble]
  orientation change: Event,
  copy: Event, cut: Event, paste: Event, fullscreen change: Event,
  fullscreen error: Event,
  pointer lock change: Event,
  pointer lock error: Event,
  #[does_not_bubble]
  ready state change: Event,
  visibility change: Event,
}
pub use web_sys::{
    AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent,
    DeviceMotionEvent, DeviceOrientationEvent, DragEvent, ErrorEvent, Event,
    FocusEvent, GamepadEvent, HashChangeEvent, InputEvent, KeyboardEvent,
    MessageEvent, MouseEvent, PageTransitionEvent, PointerEvent, PopStateEvent,
    ProgressEvent, PromiseRejectionEvent, SecurityPolicyViolationEvent,
    StorageEvent, SubmitEvent, TouchEvent, TransitionEvent, UiEvent,
    WheelEvent,
};