import * as React from 'react';
import {
  arrowMiddleware,
  autoPlacementMiddleware,
  flipMiddleware,
  type FlipMiddlewareOptions,
  hideMiddleware,
  offsetMiddleware,
  shiftMiddleware,
  type ShiftMiddlewareOptions,
  sizeMiddleware,
} from '../adapters';
import { checkIsNotAutoPlacement, getAutoPlacementAlign } from '../functions';
import {
  type ArrowOptions,
  type Placement,
  type PlacementWithAuto,
  type UseFloatingMiddleware,
} from '../types/common';

export interface UseFloatingMiddlewaresBootstrapOptions {
  /**
   * По умолчанию компонент выберет наилучшее расположение сам, но приоритетное можно задать с помощью этого свойства.
   */
  placement?: PlacementWithAuto;
  /**
   * Указанное значение `placement` форсируется, даже если для выпадающего элемента недостаточно места.
   * Не оказывает влияния при `placement` значениях - `'auto' | 'auto-start' | 'auto-end'`
   */
  disableFlipMiddleware?: boolean;
  /**
   * Позволяет отключить смещение по главной оси,
   * которое не даёт всплывающему элементу выходить за границы видимой области.
   */
  disableShiftMiddleware?: boolean;
  /**
   * Задаёт резервный вариант размещения по перпендикулярной оси.
   */
  flipMiddlewareFallbackAxisSideDirection?: FlipMiddlewareOptions['fallbackAxisSideDirection'];
  /**
   * Отступ по главной оси.
   */
  offsetByMainAxis?: number;
  /**
   * Отступ по вспомогательной оси.
   */
  offsetByCrossAxis?: number;
  /**
   * Отступ для смещения.
   */
  overflowPadding?: ShiftMiddlewareOptions['padding'];
  arrowRef?: ArrowOptions['element'];
  /**
   * Отображать ли стрелку, указывающую на якорный элемент.
   */
  arrow?: boolean;
  /**
   * Высота стрелки. Складывается с `mainAxis`, чтобы стрелка не залезала на якорный элемент.
   */
  arrowHeight?: number;
  /**
   * Безопасная зона вокруг стрелки, чтобы та не выходила за края контента.
   */
  arrowPadding?: number;
  /**
   * Выставлять ширину равной target элементу.
   */
  sameWidth?: boolean;
  /**
   * Позволяет задать или переопределить модификаторы библиотеки **Floating UI** (подробнее в документации про [middleware](https://floating-ui.com/docs/middleware)).
   */
  customMiddlewares?: UseFloatingMiddleware[];
  /**
   * Принудительно скрывает компонент если целевой элемент вышел за область видимости.
   */
  hideWhenReferenceHidden?: boolean;
}

export const useFloatingMiddlewaresBootstrap = ({
  placement = 'bottom-start',
  arrowRef = null,
  arrow,
  arrowHeight,
  arrowPadding,
  sameWidth,
  offsetByMainAxis = 0,
  offsetByCrossAxis = 0,
  customMiddlewares,
  hideWhenReferenceHidden,
  disableFlipMiddleware = false,
  disableShiftMiddleware = false,
  flipMiddlewareFallbackAxisSideDirection = 'end',
  overflowPadding,
}: UseFloatingMiddlewaresBootstrapOptions): {
  middlewares: UseFloatingMiddleware[];
  strictPlacement: Placement | undefined;
} => {
  return React.useMemo(() => {
    const isAutoPlacement = !checkIsNotAutoPlacement(placement);
    const middlewares: UseFloatingMiddleware[] = [
      offsetMiddleware({
        crossAxis: offsetByCrossAxis,
        mainAxis: arrow && arrowHeight ? offsetByMainAxis + arrowHeight : offsetByMainAxis,
      }),
    ];

    const shift = disableShiftMiddleware ? null : shiftMiddleware({ padding: overflowPadding });

    // см. https://github.com/floating-ui/floating-ui/blob/%40floating-ui/core%401.7.1/website/pages/docs/flip.mdx#conflict-with-autoplacementjs
    if (isAutoPlacement) {
      middlewares.push(autoPlacementMiddleware({ alignment: getAutoPlacementAlign(placement) }));
      if (shift) {
        middlewares.push(shift);
      }
    } else if (!disableFlipMiddleware) {
      const flip = flipMiddleware({
        crossAxis: 'alignment',
        fallbackAxisSideDirection: flipMiddlewareFallbackAxisSideDirection,
      });

      // см. https://github.com/floating-ui/floating-ui/blob/%40floating-ui/core%401.7.1/website/pages/docs/flip.mdx#combining-with-shiftjs
      if (placement.includes('-')) {
        middlewares.push(flip);
        if (shift) {
          middlewares.push(shift);
        }
      } else {
        if (shift) {
          middlewares.push(shift);
        }
        middlewares.push(flip);
      }
    } else if (shift) {
      middlewares.push(shift);
    }

    if (sameWidth) {
      middlewares.push(
        sizeMiddleware({
          apply({ rects, elements }) {
            Object.assign(elements.floating.style, {
              width: `${rects.reference.width}px`,
            });
          },
        }),
      );
    }

    if (customMiddlewares) {
      middlewares.push(...customMiddlewares);
    }

    // см. https://github.com/floating-ui/floating-ui/blob/%40floating-ui/core%401.7.1/website/pages/docs/arrow.mdx#order
    if (arrow) {
      middlewares.push(
        arrowMiddleware({
          element: arrowRef,
          padding: arrowPadding,
        }),
      );
    }

    if (hideWhenReferenceHidden) {
      middlewares.push(hideMiddleware());
    }

    return { middlewares, strictPlacement: isAutoPlacement ? undefined : placement };
  }, [
    placement,
    offsetByCrossAxis,
    arrow,
    arrowHeight,
    offsetByMainAxis,
    disableFlipMiddleware,
    disableShiftMiddleware,
    flipMiddlewareFallbackAxisSideDirection,
    sameWidth,
    customMiddlewares,
    hideWhenReferenceHidden,
    arrowRef,
    arrowPadding,
    overflowPadding,
  ]);
};
