import React, { useEffect, memo, useRef, useState, useCallback, useLayoutEffect } from 'react';
import styled, { css } from 'styled-components';

import type { JSX, PropsWithChildren } from 'react';
import type { TooltipProps } from '@redocly/theme/core/types';

import { useControl, useOutsideClick } from '@redocly/theme/core/hooks';
import { Portal } from '@redocly/theme/components/Portal/Portal';
import { calcAnchorPoint, resolvePlacement } from '@redocly/theme/core/utils';

function TooltipComponent({
  children,
  isOpen,
  tip,
  withArrow = true,
  placement = 'top',
  fallbackPlacements,
  className = 'default',
  width,
  dataTestId,
  disabled = false,
  arrowPosition = 'center',
  onClick,
}: PropsWithChildren<TooltipProps>): JSX.Element {
  const wrapperRef = useRef<HTMLDivElement | null>(null);
  const tooltipBodyRef = useRef<HTMLSpanElement | null>(null);
  const { isOpened, handleOpen, handleClose } = useControl(isOpen);
  const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
  const [activePlacement, setActivePlacement] = useState(placement);

  const activeArrowPosition = activePlacement === placement ? arrowPosition : 'center';

  useOutsideClick(wrapperRef, handleClose);

  const isControlled = isOpen !== undefined;

  const updateTooltipPosition = useCallback((): void => {
    if (!isOpened || !wrapperRef.current) return;

    const triggerRect = wrapperRef.current.getBoundingClientRect();
    const tooltipWidth = tooltipBodyRef.current?.offsetWidth ?? 0;
    const tooltipHeight = tooltipBodyRef.current?.offsetHeight ?? 0;

    const resolved = resolvePlacement({
      triggerRect,
      tooltipWidth,
      tooltipHeight,
      placement,
      arrowPosition,
      fallbackPlacements,
    });
    const resolvedArrow = resolved === placement ? arrowPosition : 'center';

    setTooltipPosition(calcAnchorPoint(triggerRect, resolved, resolvedArrow));
    setActivePlacement(resolved);
  }, [isOpened, placement, arrowPosition, fallbackPlacements]);

  useLayoutEffect(() => {
    if (isOpened && wrapperRef.current) {
      updateTooltipPosition();

      const handleScroll = () => updateTooltipPosition();
      const handleResize = () => updateTooltipPosition();

      window.addEventListener('scroll', handleScroll, true);
      window.addEventListener('resize', handleResize);

      return () => {
        window.removeEventListener('scroll', handleScroll, true);
        window.removeEventListener('resize', handleResize);
      };
    }
  }, [isOpened, placement, updateTooltipPosition]);

  useEffect(() => {
    if (isOpen && !disabled) {
      handleOpen();
    } else {
      handleClose();
    }
  }, [isOpen, handleOpen, handleClose, disabled]);

  const controllers = !isControlled &&
    !disabled && {
      onMouseEnter: handleOpen,
      onMouseLeave: handleClose,
      onClick: (e: React.MouseEvent<HTMLDivElement>) => {
        onClick?.(e);
        handleClose();
      },
      onFocus: handleOpen,
      onBlur: handleClose,
    };

  return (
    <TooltipWrapper
      ref={wrapperRef}
      {...controllers}
      className={`tooltip-${className}`}
      data-component-name="Tooltip/Tooltip"
    >
      {children}
      {isOpened && !disabled && (
        <Portal>
          <TooltipBody
            ref={tooltipBodyRef}
            data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
            placement={activePlacement}
            width={width}
            withArrow={withArrow}
            arrowPosition={activeArrowPosition}
            style={{
              position: 'fixed',
              top: tooltipPosition.top,
              left: tooltipPosition.left,
            }}
          >
            {tip}
          </TooltipBody>
        </Portal>
      )}
    </TooltipWrapper>
  );
}

export const Tooltip = memo<PropsWithChildren<TooltipProps>>(TooltipComponent);

const PLACEMENTS = {
  top: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
    ${({ withArrow, arrowPosition }) =>
      withArrow && arrowPosition === 'left'
        ? css`
            transform: translate(0, -100%);
            margin-top: -10px;
          `
        : arrowPosition === 'right'
          ? css`
              transform: translate(-100%, -100%);
              margin-top: -10px;
            `
          : css`
              transform: translate(-50%, -100%);
              margin-top: -10px;
            `}

    ${({ withArrow, arrowPosition }) =>
      withArrow &&
      css`
        &::after {
          border-left: 14px solid transparent;
          border-right: 14px solid transparent;
          border-top-width: 8px;
          border-top-style: solid;
          border-radius: 2px;
          bottom: 0;
          ${arrowPosition === 'left' && 'left: 16px; transform: translateY(99%);'}
          ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 99%);'}
          ${arrowPosition === 'right' && 'right: 16px; transform: translateY(99%);'}
        }
      `}
  `,
  bottom: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
    ${({ withArrow, arrowPosition }) =>
      withArrow && arrowPosition === 'left'
        ? css`
            transform: translate(0, 10px);
            margin-top: 0;
          `
        : arrowPosition === 'right'
          ? css`
              transform: translate(-100%, 10px);
              margin-top: 0;
            `
          : css`
              transform: translate(-50%, 10px);
              margin-top: 0;
            `}

    ${({ withArrow, arrowPosition }) =>
      withArrow &&
      css`
        &::after {
          border-left: 14px solid transparent;
          border-right: 14px solid transparent;
          border-bottom-width: 8px;
          border-bottom-style: solid;
          border-radius: 0 0 2px 2px;
          top: 0;
          ${arrowPosition === 'left' && 'left: 16px; transform: translateY(-99%);'}
          ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -99%);'}
          ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-99%);'}
        }
      `}
  `,
  left: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
    transform: translate(-100%, -50%);
    margin-left: -10px;

    ${({ withArrow, arrowPosition }) =>
      withArrow &&
      css`
        &::after {
          border-top: 14px solid transparent;
          border-bottom: 14px solid transparent;
          border-left-width: 8px;
          border-left-style: solid;
          border-radius: 2px 0 0 2px;
          top: 50%;
          right: 0;
          ${arrowPosition === 'top' && 'top: 16px; transform: translateX(99%);'}
          ${arrowPosition === 'center' && 'top: 50%; transform: translate(99%, -50%);'}
          ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(99%);'}
        }
      `}
  `,
  right: css<Pick<TooltipProps, 'withArrow' | 'arrowPosition'>>`
    transform: translate(0, -50%);
    margin-left: 10px;

    ${({ withArrow, arrowPosition }) =>
      withArrow &&
      css`
        &::after {
          border-top: 14px solid transparent;
          border-bottom: 14px solid transparent;
          border-right-width: 8px;
          border-right-style: solid;
          border-radius: 0 2px 2px 0;
          top: 50%;
          left: 0;
          ${arrowPosition === 'top' && 'top: 16px; transform: translateX(-99%);'}
          ${arrowPosition === 'center' && 'top: 50%; transform: translate(-99%, -50%);'}
          ${arrowPosition === 'bottom' && 'bottom: 16px; transform: translateX(-99%);'}
        }
      `}
  `,
};

const TooltipWrapper = styled.div`
  position: relative;
  display: flex;
`;
const TooltipBody = styled.span<
  Pick<Required<TooltipProps>, 'placement' | 'withArrow' | 'arrowPosition'> & {
    width?: string;
  }
>`
  display: inline-block;

  padding: var(--tooltip-padding);
  max-width: ${({ width }) => width || 'var(--tooltip-max-width)'};
  white-space: normal;
  word-break: normal;
  overflow-wrap: break-word;

  border-radius: var(--border-radius-md);
  transition: opacity 0.3s ease-out;

  font-size: var(--font-size-base);
  line-height: var(--line-height-base);

  z-index: var(--z-index-overlay);

  &::after {
    position: absolute;

    content: ' ';
    display: inline-block;
    width: 0;
    height: 0;
    border-color: var(--tooltip-arrow-color, var(--tooltip-bg-color));
  }

  background: var(--tooltip-bg-color);
  color: var(--tooltip-text-color);
  border: var(--tooltip-border-width, 0) var(--tooltip-border-style, solid)
    var(--tooltip-border-color, transparent);
  box-shadow: var(--bg-raised-shadow);

  width: ${({ width }) => width || 'max-content'};
  ${({ placement }) => css`
    ${PLACEMENTS[placement]};
  `}
`;
