import React, {
  useMemo,
  useCallback,
  useRef,
  useState,
  useEffect,
} from "react";
import classNames from "classnames";
import { StyledProps } from "../_type";
import { Icon } from "../icon";
import {
  Popover,
  PopoverProps,
  Trigger,
  TriggerProps,
  TriggerWithProps,
} from "../popover";
import { createRocket } from "../_util/create-rocket";
import { useOutsideClick } from "../_util/use-outside-click";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { useDefault } from "../_util/use-default";
import { useConfig } from "../_util/config-context";
import { KeyMap } from "../_util/key-map";
import { mergeRefs } from "../_util/merge-refs";
import { noop } from "../_util/noop";
import { SizeType } from "../_type/Size";
import { getARIAProps } from "../_util/get-aria-props";
import { useScheduleUpdate } from "../_util/use-schedule-update";
import { mergeEventProps } from "../_util/merge-event-props";
import { isMobile } from "../_util/is-mobile";

/**
 * 下拉类组件通用 Props
 */
export interface CommonDropdownProps
  extends Pick<
    PopoverProps,
    | "escapeWithReference"
    | "flipped"
    | "popupContainer"
    | "openDelay"
    | "closeDelay"
    | "destroyOnClose"
    | "overlayClassName"
    | "overlayStyle"
    | "positionFixed"
  > {
  /**
   * 是否默认展开
   */
  defaultOpen?: boolean;

  /**
   * 是否展开（受控）
   */
  open?: boolean;

  /**
   * 展开变化回调（受控）
   */
  onOpenChange?: (open: boolean) => void;

  /**
   * 下拉出现的位置
   * @default "bottom-start"（从底部弹出，左侧对齐）
   */
  placement?: PopoverProps["placement"];

  /**
   * 弹出位置偏离参考位置的位移
   *
   * 如： `10`、`"50%"`、`"10 + 10%"`、`[10, 10]`
   * @default 5
   */
  placementOffset?: PopoverProps["placementOffset"];

  /**
   * 是否在父容器滚动时关闭
   * @default true
   */
  closeOnScroll?: PopoverProps["closeOnScroll"];
}

export interface DropdownKeyDownContext {
  /**
   * 当前是否展开
   */
  open: boolean;
}

export interface DropdownProps
  extends Omit<CommonDropdownProps, "onKeyDown">,
    StyledProps {
  /**
   * 下拉按钮文本
   */
  button: React.ReactNode;

  /**
   * 触发方式
   *
   * - `2.7.0` 支持自定义 Trigger
   *
   * @default "click"
   */
  trigger?: "click" | "hover" | Trigger | TriggerWithProps;

  /**
   * 下拉内容
   * @docType React.ReactNode | ((close: () => void) => React.ReactNode)
   */
  children: React.ReactNode | ((close: () => void) => React.ReactNode);

  /**
   * 弹出区域底部内容
   * @since 2.6.3
   */
  footer?: React.ReactNode;

  /**
   * 下拉按钮的外观：
   *
   * - `default` 无边框，适用于页面标题和表格内
   * - `button` 为按钮风格，有边框，多用于操作栏中
   * - `link` 为超链接风格
   * - `filter` 为过滤组件风格，多用于表头筛选
   * - `pure` 无额外样式
   *
   * 原有 `raw` 类型建议使用 `pure` 进行改造
   *
   * @default "default"
   * @since 2.0.9
   */
  appearance?: "default" | "button" | "link" | "filter" | "pure";

  /**
   * 下拉框尺寸，使用 `"full"` 撑满容器宽度
   */
  size?: SizeType | "auto";

  /**
   * 是否在下拉内容点击时关闭
   * @default true
   */
  clickClose?: boolean;

  /**
   * 是否禁用
   * @default false
   */
  disabled?: boolean;

  /**
   * 弹出区域是否与下拉按钮同宽
   * @default false
   */
  matchButtonWidth?: boolean;

  /**
   * 弹出区域自定义类名
   */
  boxClassName?: string;

  /**
   * 弹出区域自定义样式
   */
  boxStyle?: React.CSSProperties;

  /**
   * 弹出区域是否自适应可视区域大小
   *
   * 内容超出时可视区域大小出现内部滚动
   *
   * @default false
   * @since 2.2.4
   */
  boxAdaptable?: boolean;

  /**
   * 是否支持清空
   * @default false
   * @since 2.3.2
   */
  clearable?: boolean;

  /**
   * 点击清空回调
   * @since 2.3.2
   */
  onClear?: (event: React.MouseEvent) => void;

  /**
   * 打开时回调
   */
  onOpen?: () => void;

  /**
   * 关闭时回调
   */
  onClose?: () => void;

  /**
   * 是否在 `children` 变化时更新下拉浮层位置
   *
   * **更新位置可防止浮层溢出屏幕或产生偏移**
   *
   * @default false
   */
  updateOnChildrenChange?: boolean;

  /**
   * 触发下拉浮层位置更新的变量依赖列表
   *
   * **更新位置可防止浮层溢出屏幕或产生偏移**
   *
   * @since 2.6.14
   * @default []
   */
  updateDeps?: any[];

  /**
   * 当焦点在 Dropdown 按钮时的键盘事件，返回 `false` 禁用默认行为
   */
  onKeyDown?: (
    event: React.KeyboardEvent<HTMLDivElement>,
    context: DropdownKeyDownContext
  ) => void | boolean;

  /**
   * 阻止下拉面板中 MouseDown 事件
   *
   * **防止点击下拉面板触发 Dropdown 按钮 onBlur**
   *
   * @since 2.7.0
   * @default false
   */
  preventMouseDown?: boolean;

  /**
   * 元素聚焦事件
   */
  onFocus?: React.DOMAttributes<HTMLElement>["onFocus"];

  /**
   * 元素失焦事件
   * @since 2.7.0
   */
  onBlur?: React.DOMAttributes<HTMLElement>["onBlur"];

  /**
   * **\[Deprecated\]** 请使用 `appearance` 属性
   * @deprecated
   */
  appearence?: "default" | "button" | "link" | "filter" | "pure";

  /**
   * **\[Deprecated\]** 请使用 `matchButtonWidth` 属性
   * @default false
   * @deprecated
   */
  boxSizeSync?: boolean;
}

const getAppearanceConfig = (classPrefix: string) => ({
  // appearance  headerClassName         icon
  default: [`${classPrefix}-dropdown-default`, "arrowdown"],
  button: [`${classPrefix}-dropdown-btn`, "arrowdown"],
  link: [`${classPrefix}-dropdown-link`, "arrowdown"],
  filter: [`${classPrefix}-dropdown-filter`, "filter"],
  // 兼容原有 raw 类型
  raw: [null, null],
});

export const DropdownBox = React.forwardRef(function DropdownBox(
  {
    className,
    style = {},
    children,
    adaptable,
    footer,
    preventMouseDown,
    ...props
  }: {
    onClick?: (event: React.MouseEvent) => void;
    children?: React.ReactNode;
    footer?: React.ReactNode;
    adaptable?: boolean;
    preventMouseDown?: boolean;
  } & StyledProps,
  ref: React.Ref<HTMLDivElement>
) {
  const { classPrefix } = useConfig();

  if (!children) {
    return null;
  }

  return (
    <div
      ref={ref}
      className={classNames(
        `${classPrefix}-dropdown-box`,
        { [`${classPrefix}-dropdown-box--adapte`]: adaptable },
        className
      )}
      style={{ position: "relative", ...style }}
      // 阻止点击时触发按钮 onBlur
      onMouseDown={event => {
        if (preventMouseDown) {
          event.preventDefault();
        }
      }}
      {...props}
    >
      {children}
      {footer && <Dropdown.Footer>{footer}</Dropdown.Footer>}
    </div>
  );
});

DropdownBox.displayName = "DropdownBox";

const ClickTrigger = ({
  childrenElementRef,
  overlayElementRef,
  visible,
  setVisible,
  openDelay = 0,
  closeDelay = 0,
  render,
  onClose,
  onOpen,
}: TriggerProps & DropdownProps) => {
  const { listen, ignoreProps } = useOutsideClick([
    childrenElementRef,
    overlayElementRef,
  ]);

  listen(() => {
    if (visible) {
      onClose();
      setVisible(false, closeDelay);
    }
  });

  return render({
    overlayProps: ignoreProps,
    childrenProps: {},
  });
};

ClickTrigger.displayName = "DropdownClickTrigger";

const HoverTrigger = (props: TriggerProps & DropdownProps) => {
  if (isMobile) {
    return <ClickTrigger {...props} />;
  }

  const {
    visible,
    setVisible,
    openDelay = 50,
    closeDelay = 100,
    render,
    onClose,
    onOpen,
  } = props;
  const commonProps = {
    onMouseEnter: () => {
      setVisible(true, openDelay).then(done => done && !visible && onOpen());
    },
    onMouseLeave: () => {
      setVisible(false, closeDelay).then(done => done && onClose());
    },
  };
  return render({
    overlayProps: commonProps,
    childrenProps: commonProps,
  });
};

HoverTrigger.displayName = "DropdownHoverTrigger";

export const Dropdown = forwardRefWithStatics(
  (props: DropdownProps, ref: React.Ref<HTMLDivElement>) => {
    const { classPrefix } = useConfig();
    const {
      defaultOpen = false,
      open,
      onOpenChange,
      appearence,
      appearance = appearence,
      button,
      size,
      placement = "bottom-start",
      placementOffset = 5,
      trigger = "click",
      children,
      footer,
      disabled,
      onOpen = noop,
      onClose = noop,
      onKeyDown = noop,
      onFocus = noop,
      onBlur = noop,
      preventMouseDown,
      clickClose = true,
      closeOnScroll = true,
      escapeWithReference,
      popupContainer,
      destroyOnClose,
      style,
      className,
      boxClassName,
      boxSizeSync,
      matchButtonWidth = boxSizeSync,
      boxAdaptable,
      updateOnChildrenChange,
      updateDeps = [],
      openDelay,
      closeDelay,
      overlayClassName,
      overlayStyle,
      clearable,
      onClear = noop,
      flipped,
      positionFixed,
      ...restProps
    } = props;
    let { boxStyle = {} } = props;

    const [hover, setHover] = useState<boolean>(false);
    const [isOpened, setIsOpened] = useDefault(open, defaultOpen, onOpenChange);
    // 是否打开一段时间，防止连续 focus/click 连续两次触发开启导致关闭
    const [stayedOpen, setStayedOpen] = useState(false);
    useEffect(() => {
      if (isOpened) {
        const timer = setTimeout(() => setStayedOpen(true), 150);
        return () => {
          clearTimeout(timer);
        };
      }
      setStayedOpen(false);
      return noop;
    }, [isOpened]);

    const dropdownRef = useRef<HTMLDivElement>(null);
    const [dropdownWidth, setDropdownWidth] = useState<number>(null);

    const close = useCallback(() => {
      setIsOpened(false);
      onClose();
    }, [onClose, setIsOpened]);

    const appearanceConfig = getAppearanceConfig(classPrefix);

    const [headerClassName, icon] =
      appearanceConfig[appearance] || appearanceConfig.default;

    const boxChildren = useMemo(() => {
      return typeof children === "function"
        ? children.call(null, close)
        : children;
    }, [children, close]);

    useEffect(() => {
      if (dropdownRef.current) {
        setDropdownWidth(dropdownRef.current.clientWidth);
      }
    }, [button, size]);

    if (matchButtonWidth && dropdownWidth) {
      boxStyle = {
        ...boxStyle,
        minWidth: dropdownWidth,
        maxWidth: dropdownWidth,
      };
    }

    const scheduleUpdateRef = useScheduleUpdate(
      updateOnChildrenChange ? [boxChildren, ...updateDeps] : updateDeps
    );

    const dropdownTrigger = useMemo(() => {
      if (disabled) {
        return "empty";
      }
      const innerTrigger = trigger === "hover" ? HoverTrigger : ClickTrigger;
      // 自定义 Trigger
      if (trigger && typeof trigger !== "string") {
        return trigger;
      }

      return [
        innerTrigger,
        {
          onClose,
          onOpen,
        },
      ] as TriggerWithProps;
    }, [disabled, onClose, onOpen, trigger]);

    return (
      <Popover
        trigger={dropdownTrigger}
        visible={isOpened}
        onVisibleChange={setIsOpened}
        placement={placement}
        placementOffset={placementOffset}
        closeOnScroll={closeOnScroll}
        escapeWithReference={escapeWithReference}
        popupContainer={popupContainer}
        destroyOnClose={destroyOnClose}
        openDelay={openDelay}
        closeDelay={closeDelay}
        overlayClassName={overlayClassName}
        overlayStyle={overlayStyle}
        flipped={flipped}
        positionFixed={positionFixed}
        overlay={({ scheduleUpdate }) => {
          scheduleUpdateRef.current = scheduleUpdate;
          return disabled ? null : (
            <DropdownBox
              className={boxClassName}
              style={boxStyle}
              onClick={clickClose ? close : null}
              adaptable={boxAdaptable}
              footer={footer}
              preventMouseDown={preventMouseDown}
            >
              {boxChildren}
            </DropdownBox>
          );
        }}
      >
        <div
          ref={mergeRefs(ref, dropdownRef)}
          className={classNames(`${classPrefix}-dropdown`, className, {
            [`size-${
              size === "full" || size === "auto" ? `${size}-width` : size
            }`]: size,
            "is-expanded": isOpened,
            "is-disabled": disabled,
          })}
          style={style}
          tabIndex={disabled ? -1 : 0}
          onKeyDown={event => {
            if (onKeyDown(event, { open: isOpened }) === false) {
              return;
            }
            switch (event.key) {
              case KeyMap.Space:
              case KeyMap.Enter:
                if (!isOpened) {
                  setIsOpened(true);
                  onOpen();
                }
                if (isOpened) {
                  close();
                }
                break;
              case KeyMap.Up:
              case KeyMap.Down:
                event.preventDefault();
                if (!isOpened) {
                  setIsOpened(true);
                  onOpen();
                }
                break;
              case KeyMap.Esc:
                if (isOpened) {
                  close();
                }
                break;
            }
          }}
          {...getARIAProps(restProps)}
          {...mergeEventProps(restProps, {
            onClick: event => {
              event.preventDefault();
              event.stopPropagation();
              if (disabled) {
                return;
              }
              if (!isOpened) {
                onOpen();
                setIsOpened(true);
              } else if (stayedOpen) {
                onClose();
                setIsOpened(false);
              }
            },
            onMouseEnter: () => {
              setHover(true);
            },
            onMouseLeave: () => {
              setHover(false);
            },
            onFocus,
            onBlur,
          })}
        >
          {appearance === "pure" ? (
            button
          ) : (
            <div
              className={classNames(
                `${classPrefix}-dropdown__header`,
                headerClassName
              )}
            >
              <div className={`${classPrefix}-dropdown__value`}>{button}</div>
              {clearable && !disabled && hover ? (
                <Icon
                  type="dismiss"
                  onClick={e => {
                    e.stopPropagation();
                    onClear(e);
                  }}
                />
              ) : (
                icon && <Icon type={icon} />
              )}
            </div>
          )}
        </div>
      </Popover>
    );
  },
  {
    Footer: createRocket(
      "DropdownFooter",
      "div.@{prefix}-dropdown-box__footer"
    ),
  }
);

Dropdown.displayName = "Dropdown";
