'use client';

import * as React from 'react';
import { hasReactNode } from '@vkontakte/vkjs';
import { mergeStyle } from '../../helpers/mergeStyle';
import { useExternRef } from '../../hooks/useExternRef';
import { useGlobalEscKeyDown } from '../../hooks/useGlobalEscKeyDown';
import { usePatchChildren } from '../../hooks/usePatchChildren';
import { createPortal } from '../../lib/createPortal';
import {
  autoUpdateFloatingElement,
  convertFloatingDataToReactCSSProperties,
  type FloatingComponentProps,
  useFloating,
  useFloatingMiddlewaresBootstrap,
  usePlacementChangeCallback,
} from '../../lib/floating';
import { LockFloatingPositionContext } from '../../lib/floating/LockFloatingPosition/LockFloatingPosition';
import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
import { warnOnce } from '../../lib/warnOnce';
import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING } from '../FloatingArrow/DefaultIcon';
import type { FloatingArrowProps } from '../FloatingArrow/FloatingArrow';
import { FocusTrap } from '../FocusTrap/FocusTrap';
import { useNavTransition } from '../NavTransitionContext/NavTransitionContext';
import { RootComponent } from '../RootComponent/RootComponent';
import { TOOLTIP_MAX_WIDTH, TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase';
import { onboardingTooltipContainerAttr } from './OnboardingTooltipContainer';
import styles from './OnboardingTooltip.module.css';

const warn = warnOnce('OnboardingTooltip');

type AllowedFloatingComponentProps = Pick<
  FloatingComponentProps,
  | 'arrowHeight'
  | 'arrowPadding'
  | 'arrowRef'
  | 'placement'
  | 'offsetByMainAxis'
  | 'offsetByCrossAxis'
  | 'shown'
  | 'children'
  | 'onPlacementChange'
  | 'disableFlipMiddleware'
  | 'disableShiftMiddleware'
  | 'disableFocusTrap'
  | 'overflowPadding'
>;

type AllowedTooltipBaseProps = Omit<
  TooltipBaseProps,
  'arrowProps' | 'closeIconLabel' | 'onCloseIconClick'
>;

type AllowedFloatingArrowProps = {
  /**
   * Сдвиг стрелки относительно текущих координат.
   */
  arrowOffset?: FloatingArrowProps['offset'] | undefined;
  /**
   * Включает абсолютное смещение по `arrowOffset`.
   */
  isStaticArrowOffset?: FloatingArrowProps['isStaticOffset'] | undefined;
};

export interface OnboardingTooltipProps
  extends AllowedFloatingComponentProps,
    AllowedTooltipBaseProps,
    AllowedFloatingArrowProps {
  /**
   * Управление поведением возврата фокуса при закрытии всплывающего окна.
   * @default true
   */
  restoreFocus?: boolean | (() => boolean | HTMLElement) | undefined;
  /**
   * Скрывает стрелку, указывающую на якорный элемент.
   */
  disableArrow?: boolean | undefined;
  /**
   * Обработчик, который вызывается при нажатии по любому месту в пределах экрана.
   */
  onClose?: ((this: void) => void) | undefined;
  /**
   * [a11y] Метка для подложки-кнопки, для описания того, что произойдёт при нажатии.
   */
  overlayLabel?: string | undefined;
}

/**
 * @see https://vkui.io/components/onboarding-tooltip
 */
export const OnboardingTooltip = ({
  'id': idProp,
  children,
  'shown': shownProp = true,
  arrowPadding = DEFAULT_ARROW_PADDING,
  arrowHeight = DEFAULT_ARROW_HEIGHT,
  offsetByMainAxis = 0,
  offsetByCrossAxis = 0,
  arrowOffset = 0,
  isStaticArrowOffset = false,
  onClose,
  'placement': placementProp = 'bottom-start',
  maxWidth = TOOLTIP_MAX_WIDTH,
  'style': styleProp,
  getRootRef,
  disableArrow = false,
  onPlacementChange,
  disableFlipMiddleware = false,
  disableShiftMiddleware = false,
  overlayLabel = 'Закрыть',
  title,
  'aria-label': ariaLabel,
  'aria-labelledby': ariaLabelledBy,
  restoreFocus,
  disableFocusTrap,
  overflowPadding,
  ...restProps
}: OnboardingTooltipProps): React.ReactNode => {
  const generatedId = React.useId();
  const tooltipId = idProp || generatedId;
  const { entering } = useNavTransition();

  const [arrowRef, setArrowRef] = React.useState<HTMLDivElement | null>(null);
  const [tooltipContainer, setTooltipContainer] = React.useState<HTMLElement | null>(null);
  const [positionStrategy, setPositionStrategy] = React.useState<'fixed' | 'absolute'>('absolute');
  const shown = shownProp && tooltipContainer && !entering;

  const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({
    placement: placementProp,
    offsetByMainAxis,
    offsetByCrossAxis,
    arrowRef,
    arrow: !disableArrow,
    arrowHeight,
    arrowPadding,
    disableFlipMiddleware,
    disableShiftMiddleware,
    overflowPadding,
  });

  const isLock = React.useContext(LockFloatingPositionContext);

  const {
    x: floatingDataX,
    y: floatingDataY,
    isPositioned,
    refs,
    placement: resolvedPlacement,
    middlewareData: { arrow: arrowCoords },
  } = useFloating({
    strategy: positionStrategy,
    ...(strictPlacement !== undefined && { placement: strictPlacement }),
    middleware: middlewares,
    ...(!isLock && {
      whileElementsMounted: (...args) =>
        autoUpdateFloatingElement(...args, { elementResize: true }),
    }),
  });

  const tooltipRef = useExternRef<HTMLDivElement>(getRootRef, refs.setFloating);
  const focusTrapRootRef = React.useRef<HTMLDivElement | null>(null);
  const [childRef, child] = usePatchChildren(children, {
    'aria-describedby': shown ? tooltipId : undefined,
  });

  usePlacementChangeCallback(placementProp, resolvedPlacement, onPlacementChange);

  const titleId = React.useId();
  if (process.env.NODE_ENV === 'development' && !title && !ariaLabel && !ariaLabelledBy) {
    warn(
      'Если "title" не используется, то необходимо задать либо "aria-label", либо "aria-labelledby" (см. правило axe aria-dialog-name)',
    );
  }

  useGlobalEscKeyDown(!!shown, onClose);

  let tooltip: React.ReactPortal | null = null;
  if (shown) {
    const floatingStyle = convertFloatingDataToReactCSSProperties({
      strategy: positionStrategy,
      x: floatingDataX,
      y: floatingDataY,
    });

    if (!isPositioned) {
      floatingStyle.visibility = 'hidden';
    }

    tooltip = createPortal(
      <FocusTrap rootRef={focusTrapRootRef} disabled={disableFocusTrap} restoreFocus={restoreFocus}>
        <RootComponent
          tabIndex={-1}
          getRootRef={focusTrapRootRef}
          role="dialog"
          aria-modal="true"
          aria-label={ariaLabel}
          aria-labelledby={title ? titleId : ariaLabel ? undefined : ariaLabelledBy}
        >
          <button aria-label={overlayLabel} className={styles.overlay} onClickCapture={onClose} />
          <TooltipBase
            {...restProps}
            id={tooltipId}
            title={title}
            titleId={title ? titleId : undefined}
            getRootRef={tooltipRef}
            style={mergeStyle(floatingStyle, styleProp)}
            maxWidth={maxWidth}
            arrowProps={
              disableArrow
                ? undefined
                : {
                    offset: arrowOffset,
                    isStaticOffset: isStaticArrowOffset,
                    coords: arrowCoords,
                    placement: resolvedPlacement,
                    getRootRef: setArrowRef,
                  }
            }
          />
        </RootComponent>
      </FocusTrap>,
      tooltipContainer,
    );
  }

  useIsomorphicLayoutEffect(
    function initialize() {
      const referenceEl = childRef.current;
      if (referenceEl) {
        setTooltipContainer(
          referenceEl.closest<HTMLDivElement>(`[${onboardingTooltipContainerAttr}]`), // eslint-disable-line no-restricted-properties
        );
        setPositionStrategy(referenceEl.style.position === 'fixed' ? 'fixed' : 'absolute');
        refs.setReference(referenceEl);
      }
    },
    [childRef],
  );

  if (process.env.NODE_ENV === 'development') {
    const multiChildren = React.Children.count(children) > 1;
    // Empty children is a noop
    const primitiveChild = hasReactNode(children) && typeof children !== 'object';
    (multiChildren || primitiveChild) &&
      warn(
        [
          'children должен быть одним React элементом, получено',
          multiChildren && 'несколько',
          primitiveChild && JSON.stringify(children),
        ]
          .filter(Boolean)
          .join(' '),
        'error',
      );

    if (refs.reference.current && !tooltipContainer) {
      throw new Error('Use TooltipContainer for Tooltip outside Panel (see docs)');
    }
  }

  return (
    <React.Fragment>
      {child}
      {tooltip}
    </React.Fragment>
  );
};
