import React, {
  ReactElement,
  useState,
  useContext,
  useEffect,
  useRef,
} from 'react';
import { usePopper } from 'react-popper';
import { ThemeContext } from 'styled-components';
import css from '../../utils/css';

import { TooltipContainer, TooltipWrapper, Arrow } from './StyledTooltip';

import { CommonProps } from '../common';

export interface TooltipProps extends CommonProps {
  /**
   * Content of tooltip.
   */
  content: string | ReactElement;
  /**
   * Whether the tooltip content can be interactive, allowing you to hover over and click inside it.
   */
  interactive?: boolean;
  /**
   * Milliseconds for interaction timeout, this only takes effect when `interactive` is enabled.
   */
  interactiveTimeout?: number;
  /**
   * Position of tooltip.
   */
  placement?: 'top' | 'bottom' | 'right' | 'left';
  /**
   The target where tooltip is relatively placed to.
  */
  target: string | ReactElement;
  /**
   Whether or not to show Tooltip when hovering the target.
  */
  visible?: boolean;
}

const FALLBACK_PLACEMENT_MAP: {
  bottom: 'top';
  left: 'right';
  right: 'left';
  top: 'bottom';
} = {
  bottom: 'top',
  top: 'bottom',
  left: 'right',
  right: 'left',
};

const Tooltip = ({
  content,
  placement = 'top',
  target,
  interactive = false,
  interactiveTimeout = 200,
  id,
  className,
  style,
  sx = {},
  'data-test-id': dataTestId,
  visible = true,
}: TooltipProps): ReactElement => {
  const [
    containerElement,
    setContainerElement,
  ] = useState<HTMLDivElement | null>(null);
  const [tooltipElement, setTooltipElement] = useState<HTMLDivElement | null>(
    null
  );
  const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
  const [open, setOpen] = useState<boolean>(false);
  const mouseEnteredContentRef = useRef<boolean>(false);
  const interactiveTimerId = useRef<number>();
  const theme = useContext(ThemeContext);

  const { styles, attributes } = usePopper(containerElement, tooltipElement, {
    strategy: 'fixed',
    placement,
    modifiers: [
      {
        name: 'arrow',
        options: { element: arrowElement },
      },
      {
        name: 'offset',
        options: {
          offset: [0, theme.space.tooltip.margin],
        },
      },
      {
        name: 'flip',
        options: {
          fallbackPlacements: [FALLBACK_PLACEMENT_MAP[placement]],
        },
      },
      {
        name: 'computeStyles',
        options: {
          adaptive: false,
          gpuAcceleration: false,
        },
      },
    ],
  });

  const delayCloseTooltipContent = React.useCallback(() => {
    const closeTooltip = (): void => setOpen(false);

    if (interactive === true) {
      interactiveTimerId.current = window.setTimeout(() => {
        if (mouseEnteredContentRef.current === false) {
          closeTooltip();
        }
      }, interactiveTimeout);
    } else {
      interactiveTimerId.current = undefined;
      closeTooltip();
    }
  }, [interactive, interactiveTimeout]);

  useEffect(() => {
    if (tooltipElement !== null) {
      tooltipElement.addEventListener('mouseleave', () => {
        mouseEnteredContentRef.current = false;
      });

      tooltipElement.addEventListener('mouseenter', () => {
        mouseEnteredContentRef.current = true;
      });
    }
  }, [tooltipElement]);

  useEffect(() => {
    containerElement?.addEventListener('mouseleave', delayCloseTooltipContent);

    return () => {
      containerElement?.removeEventListener(
        'mouseleave',
        delayCloseTooltipContent
      );
    };
  }, [containerElement, delayCloseTooltipContent]);

  useEffect(() => {
    return () => window.clearTimeout(interactiveTimerId.current);
  }, []);

  return (
    <TooltipContainer
      ref={setContainerElement}
      onMouseEnter={(): void => setOpen(true)}
      onTouchStart={(): void => setOpen(true)}
      id={id}
      className={className}
      style={{ ...style, ...css(sx) }}
      data-test-id={dataTestId}
    >
      {target}
      {open === true && visible === true && (
        <TooltipWrapper
          {...attributes['popper']}
          style={styles['popper']}
          ref={setTooltipElement}
        >
          {content}
          <Arrow ref={setArrowElement} style={styles['arrow']} />
        </TooltipWrapper>
      )}
    </TooltipContainer>
  );
};

export default Tooltip;
