import React, { useRef, useEffect } from 'react';
import { ClickAwayMouseEventHandler, ClickAwayTouchEventHandler, OutsideClickHandlerProps } from './type';
import useEventCallback from '@cleartrip/ct-design-use-event-callback';

/**
 * maps onClick -> click
 */
function mapEventPropToEvent(
  eventProp: ClickAwayMouseEventHandler | ClickAwayTouchEventHandler,
): 'click' | 'mousedown' | 'mouseup' | 'touchstart' | 'touchend' | 'pointerdown' | 'pointerup' {
  return eventProp.substring(2).toLowerCase() as
    | 'click'
    | 'mousedown'
    | 'mouseup'
    | 'touchstart'
    | 'touchend'
    | 'pointerdown'
    | 'pointerup';
}

const OutsideClickHandler = React.forwardRef<HTMLDivElement, OutsideClickHandlerProps>(
  ({ onOutsideClick, children, disabled = false, mouseEvent = 'onClick', touchEvent = 'onTouchEnd', wrapperProps }) => {
    // node ref to track element events
    const node = useRef<HTMLDivElement>(null);
    // press event ref like in mobile
    const pressedRef = useRef(false);
    // check if event tracking is activated
    const activatedRef = React.useRef(false);

    React.useEffect(() => {
      // Ensure that this component is not "activated" synchronously.
      // https://github.com/facebook/react/issues/20074
      setTimeout(() => {
        activatedRef.current = true;
      }, 0);
      return () => {
        activatedRef.current = false;
      };
    }, []);

    const childrenProps = { ref: node, ...wrapperProps };

    const handleClick = useEventCallback((event: MouseEvent | TouchEvent) => {
      // Do not act if user performed touchmove
      if (pressedRef.current) {
        pressedRef.current = false;
        return;
      }
      let insideDOM;

      if (event.composedPath) {
        insideDOM = node.current && event.composedPath().indexOf(node.current) > -1;
      } else {
        const target = event.target as Node | null;
        insideDOM = !document.documentElement.contains(target) || (node.current && node.current.contains(target));
      }
      if (!insideDOM && activatedRef.current) {
        onOutsideClick(event);
      }
    });

    // use effect for touch event
    React.useEffect(() => {
      const mappedTouchEvent = mapEventPropToEvent(touchEvent);
      const handleTouchMove = () => {
        pressedRef.current = true;
      };
      if (!disabled) {
        document.addEventListener(mappedTouchEvent, handleClick);
        document.addEventListener('touchmove', handleTouchMove);
      }

      return () => {
        document.removeEventListener(mappedTouchEvent, handleClick);
        document.removeEventListener('touchmove', handleTouchMove);
      };
    }, [handleClick, touchEvent, disabled]);
    // use effect for mouse event
    useEffect(() => {
      const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
      if (!disabled) {
        document.addEventListener(mappedMouseEvent, handleClick, false);
      }

      return () => {
        document.removeEventListener(mappedMouseEvent, handleClick, false);
      };
    }, [handleClick, mouseEvent, disabled]);

    return React.createElement('div', childrenProps, children);
  },
);

// TODO add documentation
OutsideClickHandler.displayName = 'OutsideClickHandler';

export default OutsideClickHandler;
