import React, { HTMLProps, useRef } from "react";
import ReactDOM from "react-dom";
import classNames from "classnames";
import { TransitionProps } from "react-transition-group/Transition";
import { Placement, Modifiers } from "popper.js";
import { Popper, PopperChildrenProps, PopperProps } from "react-popper";
import { Omit } from "../_type";
import { AttachContainer, getOverlayRoot } from "../_util/get-overlay-root";
import { mergeStyle } from "../_util/merge-style";
import { callBoth } from "../_util/call-both";
import { ScaleTransition } from "../transition";
import { useVisibleTransition } from "../_util/use-visible-transition";
import { injectValue } from "../_util/inject-value";
import { useConfig } from "../_util/config-context";
import { noop } from "../_util/noop";
import { mergeRefs } from "../util";

export interface OverlayLayerProps {
  /**
   * 覆盖层内容
   * @docType React.ReactNode | ((props: OverlayContentProps) => React.ReactNode)
   */
  content: React.ReactNode | ((props: OverlayContentProps) => React.ReactNode);

  /**
   * 参考的定位内容
   *
   * 如果提供了 referenceElement，则不通过 `children` 定位，可以实现自定义定位参考
   * @see https://github.com/FezVrasta/react-popper#usage-without-a-reference-htmlelement
   */
  referenceElement?: PopperProps["referenceElement"];

  /**
   * 挂载组件的结点
   * @default document.body
   * @since 2.5.0
   */
  popupContainer?: AttachContainer;

  /**
   * 用于获取覆盖层 DOM Ref
   */
  overlayRef?: React.Ref<any>;

  /**
   * 覆盖层自定义属性，会附加到覆盖层的 div 上
   *
   * 要使用 `ref`，请传入 `overlayRef`
   */
  overlayProps?: HTMLProps<HTMLElement>;

  /**
   * 覆盖层相对于定位元素的位置
   * @default "bottom-start"
   */
  placement?: Placement;

  /**
   * 覆盖层偏离定位元素的距离
   *
   * 如： `10`、`"50%"`、`"10 + 10%"`、`[10, 10]`
   * @default 5
   */
  placementOffset?: number | string | [number | string, number | string];

  /**
   * 覆盖层是否可见
   */
  visible?: boolean;

  /**
   * 关闭时是否销毁内容元素
   * @default true
   * @since 2.6.0
   */
  destroyOnClose?: boolean;

  /**
   * 出现时渐变动画时长
   * @default { enter: 50, exit: 300 }
   */
  transitionTimeout?: TransitionProps["timeout"];

  /**
   * 是否在 `resize` 和 `scroll` 事件发生的时候更新位置
   * @default true
   */
  updateOnDimensionChange?: boolean;

  /**
   * 出现动画滑动距离
   * @default 2
   */
  animationScaleFrom?: number;

  /**
   * 是否可以跟随参考元素离开可视范围
   * @default false
   */
  escapeWithReference?: boolean;

  /**
   * 距离可视范围空间不足时是否翻转 `placement`
   * @default true
   * @since 2.7.0
   */
  flipped?: boolean;

  /**
   * 浮层是否使用 `fixed` 定位
   * @default true
   * @since 2.7.0
   */
  positionFixed?: PopperProps["positionFixed"];

  /**
   * 关闭动画结束时回调
   * @since 2.6.0
   */
  onExited?: () => void;

  /**
   * https://popper.js.org/popper-documentation.html#modifiers
   */
  modifiers?: Modifiers;
}

export interface OverlayContentProps extends Omit<PopperChildrenProps, "ref"> {
  visible: boolean;
}

const scaleOriginForPlacement = (originMap => (placement: Placement) => {
  const basePlacement = placement.split("-").shift();
  return originMap[basePlacement];
})({
  top: "bottom",
  bottom: "top",
  left: "right",
  right: "left",
});

/**
 * 为定位元素创建一个覆盖层
 *
 * @example
 *
  ```js
  const [visible, setVisible] = useState(false);
  const open = () => setVisible(true);
  const close = () => setVisible(false);
  <Overlay
    visible={visible}
    content={<div>我是浮层内容，<a onClick={close}>关闭</a></div>}
    children={ref => <a ref={ref} onClick={open}>点击弹出浮层</a>}
  />
  ```
 */
export const OverlayLayer = React.forwardRef(function OverlayLayer(
  {
    content,
    overlayRef,
    overlayProps = {},
    placement = "bottom-start",
    visible,
    placementOffset = 5,
    transitionTimeout = { enter: 50, exit: 300 },
    destroyOnClose = true,
    updateOnDimensionChange,
    referenceElement,
    animationScaleFrom = 0.94, // 为什么？因为此时效果比较好
    escapeWithReference,
    popupContainer,
    modifiers = {},
    flipped = true,
    positionFixed = true,
    onExited = noop,
  }: OverlayLayerProps,
  ref: React.Ref<HTMLDivElement>
) {
  const { classPrefix, popupContainer: globalPopupContainer } = useConfig();
  const firstRender = useRef(!!visible);

  if (visible && !firstRender.current) {
    firstRender.current = true;
  }

  // visible 启动时，才开始渲染内容，进行动画
  const {
    contentIn,
    shouldContentRender,
    shouldContentEnter,
    onContentExit,
  } = useVisibleTransition(visible);

  if (
    !shouldContentRender &&
    (destroyOnClose || (!destroyOnClose && !firstRender.current))
  ) {
    return null;
  }

  // 渲染定位组件
  return ReactDOM.createPortal(
    <Popper
      referenceElement={referenceElement}
      placement={placement}
      modifiers={{
        offset: {
          enabled: true,
          offset: Array.isArray(placementOffset)
            ? `${placementOffset[0]}, ${placementOffset[1]}`
            : `0,${placementOffset}`,
        },
        preventOverflow: {
          escapeWithReference,
          boundariesElement: "viewport",
        },
        computeStyle: {
          // 使用 translate3d() 在 Windows 下有 DPI 缩放时会导致模糊
          // 只有在 resize 和 scroll 的时候才会有更新的需求，暂时关闭 GPU 加速
          gpuAcceleration: false,
        },
        flip: { enabled: flipped },
        hide: { enabled: flipped },
        ...modifiers,
      }}
      eventsEnabled={updateOnDimensionChange}
      positionFixed={positionFixed}
    >
      {popper => {
        const overlayContent = injectValue(content)({ ...popper, visible });

        return (
          <ScaleTransition
            from={animationScaleFrom}
            origin={scaleOriginForPlacement(placement)}
            timeout={transitionTimeout}
            in={shouldContentEnter}
            onEnter={() => popper.scheduleUpdate()}
            onExited={callBoth(onContentExit, onExited)}
            unmountOnExit={destroyOnClose}
          >
            <div
              {...overlayProps}
              // 覆盖层类名，样式表中包含 z-index 来确定覆盖层的层级
              className={classNames(
                `${classPrefix}-overlay`,
                overlayProps.className
              )}
              // ref 同时提供给 popper 和 contentRef
              ref={mergeRefs(popper.ref, overlayRef, ref)}
              // popper.style 为 overlay 浮层提供定位
              // contentStyle 提供给用户改写的可能
              style={mergeStyle(
                popper.style,
                {
                  willChange: null,
                  display: !destroyOnClose && !contentIn ? "none" : undefined,
                },
                overlayProps.style
              )}
              // popper API 要求提供 data-placement
              data-placement={popper.placement || placement}
            >
              {React.isValidElement(overlayContent) ? (
                overlayContent
              ) : (
                <span>{overlayContent}</span>
              )}
            </div>
          </ScaleTransition>
        );
      }}
    </Popper>,
    getOverlayRoot(popupContainer || globalPopupContainer)
  );
});

OverlayLayer.displayName = "OverlayLayer";
