import React, { useState, useRef, useEffect, useCallback } from "react";
import classNames from "classnames";
import { StyledProps } from "../_type";
import { ControlledProps, useDefaultValue } from "../form";
import { CommonDropdownProps, DropdownProps } from "../dropdown";
import { useTranslation } from "../i18n";
import { useConfig } from "../_util/config-context";
import { Popover, TriggerProps } from "../popover";
import { Tag } from "../tag";
import { useOutsideClick } from "../_util/use-outside-click";
import { EmptyTip } from "../tips";
import { useDefault } from "../_util/use-default";
import { SelectOptionWithGroup } from "../select/SelectOption";
import { TagSelectInput } from "./TagSelectInput";
import { TagSelectBox, TagSelectBoxProps, getListItems } from "./TagSelectBox";
import { KeyMap } from "../_util/key-map";
import { noop } from "../_util/noop";
import { mergeEventProps } from "../_util/merge-event-props";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { mergeRefs } from "../util";

export interface TagOption extends SelectOptionWithGroup {
  /**
   * 是否可移除
   * 
   * @default true
   * 
   * @since 2.7.7
   */
  removeable?: boolean
}

// 整个 标签选择输入框的 属性
export interface TagSelectProps
  extends ControlledProps<
      string[],
      React.SyntheticEvent,
      { event: React.SyntheticEvent; option: SelectOptionWithGroup }
    >,
    Omit<CommonDropdownProps, "onChange">,
    StyledProps {
  /**
   * 下拉选项列表
   */
  options?: TagOption[];

  /**
   * 是否仅能选择 `options` 中的值
   *
   * @default false
   */
  optionsOnly?: boolean;

  /**
   * 分组
   * @since 2.3.0
   */
  groups?: {
    [groupKey: string]: React.ReactNode;
  };

  /**
   * 自定义搜索筛选规则
   *
   * 默认根据输入值筛选
   */
  filter?: (inputValue: string, option: SelectOptionWithGroup) => boolean;

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

  /**
   * 搜索值变化回调
   */
  onSearch?: (inputValue: string) => void;

  /**
   * 输入框中的提示
   * @default "请选择"（已处理国际化）
   */
  placeholder?: string;

  /**
   * 弹出区域自定义类名
   */
  boxClassName?: DropdownProps["boxClassName"];

  /**
   * 弹出区域自定义样式
   */
  boxStyle?: DropdownProps["boxStyle"];

  /**
   * 状态提示
   *
   * 可使用字符串或 [StatusTip](/component/tips) 相关组件
   */
  tips?: React.ReactNode;

  /**
   * 是否在选项选中后自动清空搜索值
   * @default true
   */
  autoClearSearchValue?: boolean;

  /**
   * 滚动到底部事件
   * @since 2.1.0
   */
  onScrollBottom?: TagSelectBoxProps["onScrollBottom"];

  /**
   * 当输入框获得焦点时调用此函数
   */
  onFocus?: (e: React.FocusEvent) => void;

  /**
   * 当输入框获得焦点时调用此函数
   * @since 2.7.0
   */
  onBlur?: (e: React.FocusEvent) => void;

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

  /**
   * 隐藏 Tag 上关闭按钮
   * @default false
   * @since 2.7.3
   */
  hideCloseButton?: boolean;

  /**
   * 删除单个标签的回调
   *
   * 返回 `false` 阻止删除
   *
   * @since 2.7.4
   */
  onDeleteTag?: (tag: SelectOptionWithGroup) => Promise<boolean> | boolean;
}

// hooks 依赖了该值更新
const empty = [];

export const TagSelect = forwardRefWithStatics(
  function TagSelect(props: TagSelectProps, ref: React.Ref<HTMLDivElement>) {
    const t = useTranslation();
    const { classPrefix } = useConfig();
    const {
      options = empty,
      optionsOnly,
      groups,
      value,
      onChange,
      onDeleteTag,
      placeholder = t.pleaseSelect,
      className,
      style,
      disabled,
      boxClassName,
      boxStyle = {},
      placement = "bottom-start",
      placementOffset = 5,
      closeOnScroll = true,
      escapeWithReference,
      popupContainer,
      onSearch = noop,
      onFocus = noop,
      onBlur = noop,
      filter = (inputValue: string, { text, value }: SelectOptionWithGroup) => {
        const optionText = String(typeof text === "string" ? text : value);
        return optionText.includes(inputValue);
      },
      defaultOpen = false,
      open,
      onOpenChange,
      autoClearSearchValue = true,
      overlayClassName,
      overlayStyle,
      onScrollBottom = noop,
      footer,
      hideCloseButton,
      ...restProps
    } = useDefaultValue(props, []);

    const [isOpened, setIsOpened] = useDefault(open, defaultOpen, onOpenChange);
    const [currentIndex, setCurrentIndex] = useState<number>(0);
    const [wrapperWidth, setWrapperWidth] = useState<number>(0);
    const inputRef = useRef<HTMLInputElement>(null);
    const [inputValue, setInputValue] = useState<string>("");

    const listRef = useRef(null);

    const filteredOptions = options
      .filter(options => filter(inputValue, options))
      .filter(option => !value.includes(option.value));

    // 支持输入任意值
    if (!optionsOnly && inputValue.trim()) {
      // 是否有和搜索值相同的项
      const hasEqualOption = options.find(({ text, value }) => {
        const optionText = String(typeof text === "string" ? text : value);
        return optionText.trim() === inputValue.trim();
      });
      // 是否有和搜索值相同的值
      const hasEqualValue = value.find(v => v.trim() === inputValue.trim());

      if (!hasEqualOption && !hasEqualValue) {
        filteredOptions.unshift({ value: inputValue.trim() });
      }
    }

    // 列表收起行为
    useEffect(() => {
      if (!isOpened) {
        if (!optionsOnly) {
          const content = inputValue.trim();
          if (content && !value.includes(content)) {
            onChange([...value, content], {
              event: null,
              option: { value: content },
            });
          }
        }
        setInputValue("");
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isOpened]);

    // options 变化时不影响已选择的 value 显示
    const [displayOptions, setDisplayOptions] = useState<
      TagOption[]
    >(
      value.map(v => options.find(option => option.value === v) || { value: v })
    );

    const updateDisplayOptions = useCallback(
      (value: string[]) =>
        setDisplayOptions(displayOptions => {
          return value.map(
            v =>
              options.find(option => option.value === v) ||
              displayOptions.find(option => option.value === v) || {
                value: v,
              }
          );
        }),
      [options]
    );

    let { tips } = props;
    if (typeof tips === "undefined" && filteredOptions.length === 0) {
      tips = <EmptyTip />;
    }

    const items = getListItems({
      tips,
      options: filteredOptions,
      groups,
    });
    const count = items.length;

    const getOptionIndex = useCallback(
      (current: number, step: number = 1) => {
        let flag = 1;
        let index = (current + step + count) % count;
        let item = items[index % count];
        while (flag < count && (!item.option || item.option.disabled)) {
          flag += 1;
          index = (index + step + count) % count;
          item = items[index % count];
        }
        return index;
      },
      [count, items]
    );

    // onChange 前更新 displayOptions 保证 onChange 中改变 options 时信息不丢失
    const handleChange: TagSelectProps["onChange"] = (value, context) => {
      updateDisplayOptions(value);
      onChange(value, context);
    };

    useEffect(() => updateDisplayOptions(value), [value, updateDisplayOptions]);

    // value 变化矫正高亮项（跳过 disable 项）
    useEffect(() => {
      if (currentIndex >= 0) {
        setCurrentIndex(getOptionIndex(currentIndex - 1));
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [value, inputValue]);

    // 渲染数据
    return (
      <div
        ref={mergeRefs(ref, ref => ref && setWrapperWidth(ref.clientWidth))}
        className={classNames(`${classPrefix}-tag-input`, className, {
          "is-active": !disabled && isOpened,
          "is-disabled": disabled,
        })}
        style={style}
        {...mergeEventProps(restProps, {
          onClick: () => {
            if (inputRef.current) {
              inputRef.current.focus();
            }
          },
        })}
        onFocus={noop}
        onBlur={noop}
      >
        <Popover
          trigger={disabled ? "empty" : TagSelectTrigger}
          visible={isOpened}
          onVisibleChange={visible => {
            if (disabled) {
              return;
            }
            if (!visible && inputRef.current) {
              inputRef.current.blur();
            }
          }}
          placement={placement}
          closeOnScroll={closeOnScroll}
          escapeWithReference={escapeWithReference}
          popupContainer={popupContainer}
          placementOffset={placementOffset}
          overlayClassName={overlayClassName}
          overlayStyle={overlayStyle}
          overlay={({ scheduleUpdate }) => (
            <TagSelectBox
              listRef={listRef}
              items={items}
              currentIndex={currentIndex % count}
              width={wrapperWidth}
              onChange={(optionValue, context) => {
                handleChange([...value, optionValue], context);
                if (autoClearSearchValue && inputValue) {
                  setInputValue("");
                  setCurrentIndex(getOptionIndex(-1));
                }
              }}
              scheduleUpdate={scheduleUpdate}
              className={boxClassName}
              style={boxStyle}
              tips={tips}
              onScrollBottom={onScrollBottom}
              footer={footer}
            />
          )}
        >
          <div className={`${classPrefix}-tag-input__inner`}>
            <div className={`${classPrefix}-tag-group`}>
              {displayOptions.map(option => (
                <Tag
                  key={option.value}
                  onClose={
                    option.disabled || hideCloseButton || option.removeable === false
                      ? undefined
                      : async event => {
                          event.stopPropagation();
                          /** 传入onDeleteTag，就通过onDeleteTag控制是否删除 */
                          const canDeleteTag = await onDeleteTag?.(option);
                          if (disabled || (onDeleteTag && !canDeleteTag)) {
                            return;
                          }
                          handleChange(
                            value.filter(v => v !== option.value),
                            {
                              event,
                              option,
                            }
                          );
                        }
                  }
                >
                  {option.text || option.value}
                </Tag>
              ))}
              <TagSelectInput
                ref={inputRef}
                disabled={disabled}
                className={`${classPrefix}-input--tag`}
                placeholder={value.length === 0 ? placeholder : ""}
                onFocus={e => {
                  if (!isOpened) {
                    setIsOpened(true);
                  }
                  onFocus(e);
                }}
                onBlur={e => {
                  if (isOpened) {
                    setIsOpened(false);
                  }
                  onBlur(e);
                }}
                value={inputValue}
                onChange={value => {
                  onSearch(value);
                  setInputValue(value);
                  setCurrentIndex(getOptionIndex(-1));
                }}
                maxWidth={Math.max(wrapperWidth - 12, 0)}
                onKeyDown={async (event: React.KeyboardEvent<HTMLInputElement>) => {
                  const { option } = items[currentIndex % count] || {};

                  switch (event.key) {
                    case KeyMap.Backspace:
                      if (
                        event.currentTarget.value.length === 0 &&
                        value.length > 0
                      ) {
                        const option = options.find(
                          option => option.value === value[value.length - 1]
                        );
                        /** 传入onDeleteTag，就通过onDeleteTag控制是否删除 */
                        const canDeleteTag = await onDeleteTag?.(option);
                        if ((onDeleteTag && !canDeleteTag)) {
                          break;
                        }
                        handleChange(value.slice(0, -1), {
                          event,
                          option,
                        });
                        setCurrentIndex(getOptionIndex(-1));
                      }
                      break;

                    case KeyMap.Enter:
                      event.preventDefault();
                      if (isOpened && option && !option.disabled) {
                        handleChange([...value, option.value], {
                          event,
                          option,
                        });
                        if (autoClearSearchValue && inputValue) {
                          setInputValue("");
                          setCurrentIndex(getOptionIndex(-1));
                        }
                      }
                      break;

                    case KeyMap.Up:
                      event.preventDefault();
                      setCurrentIndex(c => {
                        // fix拼音输入法完成后，按下向上箭头按键导致bug
                        if (isNaN(c)) {
                          return 0;
                        }
                        const index = getOptionIndex(c, -1);
                        if (listRef.current) {
                          listRef.current.scrollToItem(index);
                        }
                        return index;
                      });
                      break;

                    case KeyMap.Down:
                      event.preventDefault();
                      setCurrentIndex(c => {
                        // fix拼音输入法完成后，按下向上箭头按键导致bug
                        if (isNaN(c)) {
                          return 0;
                        }
                        const index = getOptionIndex(c);
                        if (listRef.current) {
                          listRef.current.scrollToItem(index);
                        }
                        return index;
                      });
                      break;

                    case KeyMap.Esc:
                      if (inputRef.current) {
                        inputRef.current.blur();
                      }
                      break;
                  }
                }}
              />
            </div>
          </div>
        </Popover>
      </div>
    );
  },
  {
    defaultLabelAlign: "middle",
  }
);

TagSelect.displayName = "TagSelect";

function TagSelectTrigger({
  overlayElementRef,
  childrenElementRef,
  visible,
  setVisible,
  closeDelay = 0,
  render,
}: TriggerProps) {
  const { listen } = useOutsideClick([childrenElementRef, overlayElementRef]);
  listen(() => visible && setVisible(false, closeDelay));
  return render({
    overlayProps: {},
    childrenProps: {},
  });
}

TagSelectTrigger.displayName = "TagSelectTrigger";
