import React, { memo, useEffect, useRef, useId } 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,
  useTooltipFallbackPlacement,
} from '@redocly/theme/core/hooks';
import { Portal } from '@redocly/theme/components/Portal/Portal';

type Props = Exclude<TooltipProps, 'arrowPosition'> & {
  arrowPosition?: 'left' | 'right' | 'center';
};

function TooltipComponent({
  children,
  isOpen,
  tip,
  withArrow = true,
  placement = 'top',
  fallbackPlacements,
  className = 'default',
  width,
  dataTestId,
  disabled = false,
  arrowPosition = 'center',
  onClick,
}: PropsWithChildren<Props>): JSX.Element {
  const tooltipWrapperRef = useRef<HTMLDivElement | null>(null);
  const tooltipBodyRef = useRef<HTMLDivElement | null>(null);
  const { isOpened, handleOpen, handleClose } = useControl(isOpen);
  const anchorName = `--tooltip${useId().replace(/:/g, '')}`;

  const { activePlacement, activeArrowPosition } = useTooltipFallbackPlacement({
    isOpened,
    placement,
    arrowPosition,
    fallbackPlacements,
    tooltipBodyRef,
  });

  useOutsideClick(isOpened ? [tooltipWrapperRef, tooltipBodyRef] : tooltipWrapperRef, handleClose);

  const isControlled = isOpen !== undefined;

  useEffect(() => {
    if (!isControlled) return;

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

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

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

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

const PLACEMENTS = {
  top: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
    bottom: anchor(top);
    ${({ withArrow, arrowPosition }) =>
      withArrow && arrowPosition === 'left'
        ? css`
            transform: translate(-32px, -6px);
            left: anchor(center);
          `
        : arrowPosition === 'right'
          ? css`
              transform: translate(32px, -6px);
              right: anchor(center);
            `
          : css`
              transform: translate(-50%, -6px);
              left: anchor(center);
            `}

    ${({ 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(100%);'}
          ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 100%);'}
          ${arrowPosition === 'right' && 'right: 16px; transform: translateY(100%);'}
        }
      `}
  `,
  bottom: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
    top: anchor(bottom);
    ${({ withArrow, arrowPosition }) =>
      withArrow && arrowPosition === 'left'
        ? css`
            transform: translate(-32px, 6px);
            left: anchor(center);
          `
        : arrowPosition === 'right'
          ? css`
              transform: translate(32px, 6px);
              right: anchor(center);
            `
          : css`
              transform: translate(-50%, 6px);
              left: anchor(center);
            `}

    ${({ 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(-100%);'}
          ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -100%);'}
          ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-100%);'}
        }
      `}
  `,
  left: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
    transform: translate(-100%, -50%);
    margin-left: -7px;
    top: anchor(center);
    left: anchor(left);

    ${({ withArrow }) =>
      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;
          right: -9px;
          top: 50%;
          transform: translateY(-50%);
        }
      `}
  `,
  right: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
    transform: translate(0, -50%);
    margin-left: 7px;
    top: anchor(center);
    left: anchor(right);

    ${({ withArrow }) =>
      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;
          left: -9px;
          top: 50%;
          transform: translateY(-50%);
        }
      `}
  `,
};

const TooltipWrapper = styled.div.attrs<{ anchorName: string }>(({ anchorName }) => ({
  style: {
    anchorName: anchorName,
  } as React.CSSProperties,
}))<{ anchorName: string }>`
  display: flex;
`;

const TooltipBody = styled.span.attrs<{ anchorName: string }>(({ anchorName }) => ({
  style: {
    positionAnchor: anchorName,
  } as React.CSSProperties,
}))<
  Pick<Required<Props>, 'placement' | 'withArrow' | 'arrowPosition'> & {
    width?: string;
    anchorName: string;
  }
>`
  position: fixed;
  min-width: 64px;
  padding: var(--tooltip-padding);
  max-width: var(--tooltip-max-width);
  white-space: normal;
  word-break: normal;
  overflow-wrap: break-word;
  text-align: left;

  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]};
  `}
`;
