import {
  arrow,
  computePosition,
  type FlipOptions,
  offset,
  type OffsetOptions,
  type Placement,
  shift,
  type ShiftOptions,
  size,
} from '@floating-ui/dom';

export type FloatingOptions = {
  trigger: string | HTMLElement;
  triggerEvent?: string[];
  floatContent: string | HTMLElement;
  placement?: Placement;
  offsetOptions?: OffsetOptions;
  flipOptions?: FlipOptions;
  shiftOptions?: ShiftOptions;
  interactive?: boolean;
  showArrow?: boolean;
  onShow?: () => void;
  onHide?: () => void;
  syncWidth?: boolean;
  syncWidthMode?: 'min' | 'equal';
};

export type FloatingInstance = {
  destroy: () => void;
  hide: () => void;
  isVisible: () => boolean;
};

export const createFloating = ({
  trigger,
  triggerEvent,
  floatContent,
  placement = 'bottom',
  offsetOptions,
  flipOptions,
  shiftOptions,
  interactive,
  showArrow,
  onShow,
  onHide,
  syncWidth = false,
  syncWidthMode = 'min',
}: FloatingOptions): FloatingInstance => {
  if (typeof trigger === 'string') {
    const triggerEl = document.querySelector(trigger);
    if (!triggerEl) {
      throw new Error(
        "element not found by document.querySelector('" + trigger + "')",
      );
    } else {
      trigger = triggerEl as HTMLElement;
    }
  }

  let floating: HTMLElement;
  if (typeof floatContent === 'string') {
    const floatContentEl = document.querySelector(floatContent);
    if (!floatContentEl) {
      throw new Error(
        "element not found by document.querySelector('" + floatContent + "')",
      );
    } else {
      floating = floatContentEl as HTMLElement;
    }
  } else {
    floating = floatContent as HTMLElement;
  }

  let arrowElement: HTMLElement;
  if (showArrow) {
    arrowElement = document.createElement('div');
    arrowElement.style.position = 'absolute';
    arrowElement.style.backgroundColor = '#222';
    arrowElement.style.width = '8px';
    arrowElement.style.height = '8px';
    arrowElement.style.transform = 'rotate(45deg)';
    arrowElement.style.display = 'none';

    floating.firstElementChild!.before(arrowElement);
  }

  function updatePosition() {
    computePosition(trigger as Element, floating, {
      placement: placement,
      middleware: [
        offset(offsetOptions), // 手动偏移配置
        // flip(flipOptions), // 注释掉自动翻转，强制向下弹出，避免遮挡顶部工具栏
        shift(shiftOptions), //自动偏移（使得浮动元素能够进入视野）
        ...(showArrow ? [arrow({ element: arrowElement })] : []),
        ...(syncWidth
          ? [
              size({
                apply({ rects, elements }) {
                  if (syncWidthMode === 'equal') {
                    Object.assign(elements.floating.style, {
                      width: `${rects.reference.width}px`,
                      minWidth: `${rects.reference.width}px`,
                    });
                  } else {
                    Object.assign(elements.floating.style, {
                      width: '',
                      minWidth: `${rects.reference.width}px`,
                    });
                  }
                },
              }),
            ]
          : []),
      ],
    }).then(({ x, y, placement, middlewareData }) => {
      Object.assign(floating.style, {
        left: `${x}px`,
        top: `${y}px`,
        position: 'absolute',
      });

      if (showArrow) {
        const { x: arrowX, y: arrowY } = middlewareData.arrow as {
          x: number;
          y: number;
        };
        const staticSide = {
          top: 'bottom',
          right: 'left',
          bottom: 'top',
          left: 'right',
        }[placement.split('-')[0]] as string;

        Object.assign(arrowElement.style, {
          zIndex: -1,
          left: arrowX != null ? `${arrowX}px` : '',
          top: arrowY != null ? `${arrowY}px` : '',
          right: '',
          bottom: '',
          [staticSide]: '2px',
        });
      }
    });
  }

  let visible = false;

  function showTooltip() {
    floating.style.display = 'block';
    floating.style.visibility = 'visible';
    floating.style.position = 'absolute';

    if (showArrow) {
      arrowElement.style.display = 'block';
    }

    visible = true;
    updatePosition();
    onShow?.();
  }

  function hideTooltip() {
    floating.style.display = 'none';
    if (showArrow) {
      arrowElement.style.display = 'none';
    }
    visible = false;
    onHide?.();
  }

  function onTrigger(event: any) {
    if (!visible) {
      showTooltip();
    } else {
      hideTooltip();
    }
  }

  function hideTooltipCompute(event: any) {
    if (
      floating.contains(event.target as Node) ||
      (trigger as Node).contains(event.target as Node)
    ) {
      return;
    }
    hideTooltip();
  }

  if (!triggerEvent || triggerEvent.length == 0) {
    if (interactive) {
      triggerEvent = ['click'];
    } else {
      triggerEvent = ['mouseenter', 'focus'];
    }
  }

  triggerEvent.forEach((event) => {
    (trigger as HTMLElement).addEventListener(event, onTrigger);
  });

  if (interactive) {
    document.addEventListener('click', hideTooltipCompute);
  } else {
    ['mouseleave', 'blur'].forEach((event) => {
      trigger.addEventListener(event, hideTooltip);
    });
  }

  return {
    destroy() {
      triggerEvent.forEach((event) => {
        (trigger as HTMLElement).removeEventListener(event, onTrigger);
      });

      if (interactive) {
        document.removeEventListener('click', hideTooltipCompute);
      } else {
        ['mouseleave', 'blur'].forEach((event) => {
          trigger.removeEventListener(event, hideTooltip);
        });
      }
    },
    hide() {
      hideTooltip();
    },

    isVisible() {
      return visible;
    },
  };
};
