import type { FC, HTMLAttributes, MutableRefObject } from 'react';
import { createPopper, Instance as PopperInstance, Options as PopperOptions } from '@popperjs/core';
import classnames from 'classnames';
import React, { createRef, useEffect, useRef, useState } from 'react';

export interface PopoverProps extends HTMLAttributes<HTMLDivElement>, Partial<PopperOptions> {
  arrow?: boolean;
  hideEvents?: string[];
  innerRef?: MutableRefObject<HTMLDivElement>;
  showEvents?: string[];
  size?: 'xs' | 'sm' | 'lg' | 'xl';
  triggerSelector: string;
}

const Popover: FC<PopoverProps> = ({
  arrow = false,
  children,
  className,
  hideEvents = ['click'],
  innerRef = createRef(),
  modifiers = [],
  onFirstUpdate,
  placement = 'bottom',
  showEvents = ['click'],
  size,
  strategy = 'absolute',
  triggerSelector,
}) => {
  const [isVisible, setIsVisible] = useState<boolean>(false);
  const sizeString = size ? `popover-${size}` : null;
  const triggerRef = useRef<Element | null>(null);

  // Toggle "visibility" css property as "isVisible" changes

  useEffect(() => {
    if (innerRef.current) {
      // eslint-disable-next-line no-param-reassign
      innerRef.current.style.visibility = isVisible ? 'visible' : '';
    }
  }, [innerRef, isVisible]);

  // Set the "triggerRef" when "triggerSelector" changes

  useEffect(() => {
    triggerRef.current = document.querySelector(triggerSelector);
  }, [triggerSelector]);

  // Create event listeners to toggle "isVisible" based on showEvents/hideEvents (defaults to click event).

  useEffect(() => {
    function handleShowEvent() {
      setIsVisible(true);
    }

    function handleHideEvent() {
      setIsVisible(false);
    }

    function handleDocumentClick(e: any) {
      if (!triggerRef.current?.contains(e.target) && !innerRef.current?.contains(e.target)) {
        setIsVisible(false);
      }
    }

    // showEvents
    showEvents.forEach((showEvent) => {
      triggerRef.current?.addEventListener(showEvent, handleShowEvent);
    });

    // hideEvent
    hideEvents.forEach((hideEvent) => {
      triggerRef.current?.addEventListener(hideEvent, handleHideEvent);
    });

    document.addEventListener('click', handleDocumentClick);

    return () => {
      // showEvents
      showEvents.forEach((showEvent) => {
        triggerRef.current?.removeEventListener(showEvent, handleShowEvent);
      });

      // hideEvent
      hideEvents.forEach((hideEvent) => {
        triggerRef.current?.removeEventListener(hideEvent, handleHideEvent);
      });

      document.removeEventListener('click', handleDocumentClick);
    };
  }, [innerRef]);

  // Create popover

  useEffect(() => {
    let popper: PopperInstance | undefined;

    if (triggerRef.current && innerRef.current) {
      popper = createPopper(triggerRef.current, innerRef.current, {
        modifiers: [
          {
            name: 'offset',
            options: {
              offset: [0, arrow ? 15 : 0],
            },
          },
          ...modifiers,
        ],
        placement,
        strategy,
        onFirstUpdate,
      });
    }

    return () => popper?.destroy();
  }, [arrow, innerRef, modifiers, onFirstUpdate, placement, strategy]);

  return (
    <div className={classnames('popover', sizeString, className)} ref={innerRef} data-testid="Popover">
      {children}
      {arrow && <div data-popper-arrow />}
    </div>
  );
};

export default Popover;
