import React, { useRef, cloneElement, useEffect } from "react";
import classnames from "classnames";

import { bem } from "../../utilities/bem";
import { useDisclosure, useKeyPress } from "../../hooks";

import { Popover, PopoverTooltipSharedProps } from "../Popover";
import { KEY_CODE } from "../../types";

export interface TooltipProps extends PopoverTooltipSharedProps {
  /**
   * Number of milliseconds to delay the disclosure of the tooltip
   */
  delay?: number;
}

const cn = "Tooltip";

export const Tooltip = (props: TooltipProps) => {
  const {
    isVisible: isVisibleProp = false,
    children,
    referenceElement,
    placement = "bottom",
    className,
    delay = 200,
    style,
    ...rest
  } = props;

  const { isOpen, onOpen, onClose } = useDisclosure(isVisibleProp);
  const isHoveringReferenceRef = useRef(false);
  const isHoveringPopoverRef = useRef(false);
  const referenceTimoutRef = useRef<null | NodeJS.Timeout>(null);
  const popoverTimoutRef = useRef<null | NodeJS.Timeout>(null);

  useEffect(() => {
    if (isVisibleProp) {
      onOpen();
    }
  }, [isVisibleProp, onOpen]);

  useEffect(() => {
    return () => {
      if (referenceTimoutRef.current) {
        clearTimeout(referenceTimoutRef.current);
      }
      if (popoverTimoutRef.current) {
        clearTimeout(popoverTimoutRef.current);
      }
    };
  });

  const close = () => {
    if (!isHoveringPopoverRef.current) {
      onClose();
    }
  };

  const handleMouseEnter = () => {
    isHoveringReferenceRef.current = true;

    if (!isOpen) {
      const referenceTimeout = setTimeout(() => {
        if (isHoveringReferenceRef.current) {
          onOpen();
        }
      }, delay);
      referenceTimoutRef.current = referenceTimeout;
    }
  };

  const handleMouseLeave = () => {
    isHoveringReferenceRef.current = false;

    if (isOpen) {
      const popoverTimeout = setTimeout(close, delay);
      popoverTimoutRef.current = popoverTimeout;
    }
  };

  const handlePopoverMouseEnter = () => {
    if (!isHoveringPopoverRef.current) {
      isHoveringPopoverRef.current = true;
    }
  };

  const handlePopoverMouseLeave = () => {
    isHoveringPopoverRef.current = false;

    if (isOpen) {
      const popoverTimeout = setTimeout(close, delay);
      popoverTimoutRef.current = popoverTimeout;
    }
  };

  useKeyPress(KEY_CODE.ESC, () => {
    if (isOpen && !isVisibleProp) {
      close();
    }
  });

  const referenceElementWithMouseHandlers = !isVisibleProp
    ? cloneElement(referenceElement, {
        onMouseEnter: handleMouseEnter,
        onMouseLeave: handleMouseLeave,
        onFocus: handleMouseEnter,
        onBlur: handleMouseLeave,
        tabIndex: 0,
      })
    : referenceElement;

  return (
    <Popover
      showArrow
      isVisible={isOpen}
      referenceElement={referenceElementWithMouseHandlers}
      placement={placement}
      className={classnames(bem(cn), className)}
      onMouseEnter={!isVisibleProp ? handlePopoverMouseEnter : undefined}
      onMouseLeave={!isVisibleProp ? handlePopoverMouseLeave : undefined}
      style={style}
      {...rest}
    >
      {children}
    </Popover>
  );
};
