/* eslint-disable @typescript-eslint/ban-ts-comment */
import { clsx } from 'clsx';
import {
  AriaAttributes,
  PropsWithChildren,
  ReactElement,
  ReactNode,
  cloneElement,
  useEffect,
  useId,
  useRef,
  useState,
} from 'react';
import { usePopper } from 'react-popper';

import { CommonProps, Position } from '../common';
import {
  PositionBottom,
  PositionLeft,
  PositionRight,
  PositionTop,
} from '../common/propsValues/position';

export type TooltipProps = PropsWithChildren<{
  position?: PositionTop | PositionRight | PositionBottom | PositionLeft;
  label: ReactNode;
  id?: string;
}> &
  CommonProps;

const Tooltip = ({
  position = Position.TOP,
  children = undefined,
  label,
  id,
  className,
}: TooltipProps) => {
  const [open, setOpen] = useState(false);
  const anchorReference = useRef(null);
  const [arrowElement, setArrowElement] = useState(null);
  const [popperElement, setPopperElement] = useState(null);

  const fallbackId = useId();
  const tooltipId = id ?? fallbackId;
  const modifiers = [];

  modifiers.push({
    name: 'arrow',
    options: {
      element: arrowElement,
      options: {
        padding: 8, // 8px from the edges of the popper
      },
    },
  });
  // This lets you displace a popper element from its reference element.
  modifiers.push({ name: 'offset', options: { offset: [0, 16] } });
  modifiers.push({
    name: 'flip',
    options: {
      fallbackPlacements: Position.TOP,
    },
  });

  const { styles, attributes, forceUpdate } = usePopper(anchorReference.current, popperElement, {
    placement: position,
    modifiers,
  });

  // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example),
  // it is most simple just to always position before opening
  useEffect(() => {
    if (open && forceUpdate) {
      forceUpdate();
    }
  }, [open]);

  return (
    <span
      ref={anchorReference}
      className="tw-tooltip-container"
      onMouseOver={() => setOpen(true)}
      onFocus={() => setOpen(true)}
      onMouseOut={() => setOpen(false)}
      onBlur={() => setOpen(false)}
    >
      {children
        ? cloneElement(children as ReactElement<Pick<AriaAttributes, 'aria-describedby'>>, {
            'aria-describedby': `${tooltipId}-tooltip`,
          })
        : null}
      <div
        // @ts-expect-error
        ref={setPopperElement}
        className={clsx(
          'np-tooltip',
          'np-panel',
          open ? `np-panel--open np-tooltip--open` : null,
          className,
        )}
        style={{ ...styles.popper }}
        {...attributes.popper}
        aria-hidden={!open}
        role="tooltip"
        id={`${tooltipId}-tooltip`}
      >
        <div className="np-panel__content tooltip-inner">
          {label}
          <div
            // @ts-expect-error
            ref={setArrowElement}
            className={clsx('np-panel__arrow')}
            style={styles.arrow}
          />
        </div>
      </div>
    </span>
  );
};

export default Tooltip;
