import { useCallback, useEffect, useRef } from 'react';
import type { PointerEvent as ReactPointerEvent } from 'react';

export interface PointerTapContext<T = unknown> {
  clientX: number;
  clientY: number;
  pointerType: string;
  event: PointerEventWithPersist;
  data?: T;
}

type PointerEventWithPersist = (PointerEvent | ReactPointerEvent) & { persist?: () => void };

type PointerTapFilter = (event?: PointerEventWithPersist | null) => boolean;

export interface UsePointerTapOptions<T = unknown> {
  onSingle?: (context: PointerTapContext<T>) => void;
  onDouble?: (context: PointerTapContext<T>) => void;
  delay?: number;
  filter?: PointerTapFilter;
}

const defaultFilter: PointerTapFilter = (event) => {
  if (!event) {
    return false;
  }
  const { type, button, pointerType, isPrimary } = event;
  const isPointerUp = type === 'pointerup';
  const buttonValid =
    button == null || button === 0 || (isPointerUp && (button === -1 || button === 0));
  if (!buttonValid) {
    return false;
  }
  if (pointerType === 'touch' && isPrimary === false) {
    return false;
  }
  return true;
};

const usePointerTap = <T,>({
  onSingle,
  onDouble,
  delay = 240,
  filter = defaultFilter,
}: UsePointerTapOptions<T> = {}) => {
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(
    () => () => {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
    },
    [],
  );

  return useCallback(
    (event?: PointerEventWithPersist | null, metadata?: T) => {
      if (!filter(event)) {
        return;
      }

      if (!event) {
        return;
      }

      event.persist?.();

      const context: PointerTapContext<T> = {
        clientX: event.clientX ?? 0,
        clientY: event.clientY ?? 0,
        pointerType: event.pointerType ?? 'mouse',
        event,
        data: metadata,
      };

      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
        onDouble?.(context);
        return;
      }

      timerRef.current = setTimeout(() => {
        timerRef.current = null;
        onSingle?.(context);
      }, delay);
    },
    [delay, filter, onDouble, onSingle],
  );
};

export default usePointerTap;
