/* eslint-disable */
import React, { useMemo } from "react";
import classNames from "classnames";
import clone from "clone";
import { Tag, TagValue } from "./Tag";
import { TagInput } from "./TagInput";
import { AttributeValue } from "./AttributeSelect";
import { withOutsideClick } from "../_util/with-outside-click";
import { withTranslation, WithTranslationProps } from "../i18n";
import { Bubble } from "../bubble";
import { Modal } from "../modal";
import { StyledProps } from "../_type";
import { TagSearchBoxContext } from "./TagSearchBoxContext";
import { Button } from "../button";
import { mergeRefs, withConfig, WithConfigProps } from "../util";
import { noop } from "../_util/noop";

export interface TagSearchBoxProps
  extends WithTranslationProps,
    WithConfigProps,
    StyledProps {
  /**
   * 要选择过滤的资源属性的集合
   */
  attributes?: AttributeValue[];

  /**
   * 搜索框中默认包含的标签值的集合
   */
  defaultValue?: TagValue[];

  /**
   * 配合 onChange 作为受控组件使用
   */
  value?: TagValue[];

  /**
   * 当新增/修改/减少标签时调用此函数
   *
   * **💡 用于触发搜索**
   */
  onChange?: (tags: TagValue[]) => void;

  /**
   * 搜索框收起后宽度
   * @default 210
   */
  minWidth?: string | number;

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

  /**
   * 搜索框中提示语
   *
   * @default "多个关键字用竖线 “|” 分隔，多个过滤标签用回车键分隔" （已处理国际化）
   */
  tips?: string;

  /**
   * 资源属性选择下拉框提示
   *
   * @default "选择资源属性进行过滤" （已处理国际化）
   */
  attributesSelectTips?: string;

  /**
   * 隐藏帮助按钮
   *
   * @default false
   */
  hideHelp?: boolean;

  /**
   * 清空按钮点击回调
   *
   * @since 2.2.2
   */
  onClearButtonClick?: (e: React.MouseEvent) => void;

  /**
   * 帮助按钮点击回调
   *
   * 返回 `false` 阻止默认提示行为
   *
   * @since 2.2.2
   */
  onHelpButtonClick?: (e: React.MouseEvent) => void | false;

  /**
   * 搜索按钮点击回调
   *
   * @since 2.2.2
   */
  onSearchButtonClick?: (e: React.MouseEvent, value: TagValue[]) => void;

  /**
   * 使用禁用根据输入值过滤资源属性选择
   *
   * **新增或修改标签时将展示全部资源属性**
   *
   * @since 2.4.0
   * @default false
   */
  disableAttributesFilter?: boolean;

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

export interface TagSearchBoxState {
  /**
   * 搜索框是否为展开状态
   */
  active: boolean;

  /**
   * 是否展示提示框
   */
  dialogActive: boolean;

  /**
   * 当前光标位置
   */
  curPos: number;

  /**
   * 当前光标（焦点）所在位置的元素类型
   */
  curPosType: FocusPosType;

  /**
   * 是否展示值选择组件
   */
  showSelect: boolean;

  /**
   * 已选标签
   */
  tags: TagValue[];

  /**
   * 更新前 tags
   */
  lastValue: TagValue[];
}

/**
 * 焦点所在位置类型
 */
export enum FocusPosType {
  INPUT,
  INPUT_EDIT,
  TAG,
}

let COUNTER = 0;

@withOutsideClick("close")
class ITagSearchBox extends React.Component<
  TagSearchBoxProps & { forwardRef: React.Ref<HTMLDivElement> },
  TagSearchBoxState
> {
  state: TagSearchBoxState = {
    active: false,
    dialogActive: false,
    curPos: 0,
    curPosType: FocusPosType.INPUT,
    showSelect: true,
    tags: this.props.defaultValue
      ? this.props.defaultValue.map(item => {
          item["_key"] = COUNTER++;
          return item;
        })
      : [],
    lastValue: this.props.value,
  };

  componentDidMount() {
    this.resetTagsState(this.props);
  }

  static getDerivedStateFromProps(
    props: TagSearchBoxProps,
    state: TagSearchBoxState
  ) {
    if ("value" in props && props.value !== state.lastValue) {
      const value = props.value.map(item => {
        if (!("_key" in item)) {
          item["_key"] = COUNTER++;
        }
        return item;
      });
      return { tags: clone(value), lastValue: props.value };
    }
    return null;
  }

  resetTagsState = (props: TagSearchBoxProps, callback?: Function) => {
    if ("value" in props) {
      const value = props.value.map(item => {
        if (!("_key" in item)) {
          item["_key"] = COUNTER++;
        }
        return item;
      });
      this.setState({ tags: clone(value) }, () => {
        callback && callback();
      });
    }
  };

  open = () => {
    const { disabled } = this.props;
    if (disabled) {
      return;
    }

    const { active } = this.state;
    const tags = this.state.tags || [];

    if (!active) {
      this.setState({ active: true });
      // 展开时不激活 select 显示
      this.setState({ curPosType: FocusPosType.INPUT, curPos: tags.length });
    } else {
      this.handleTagEvent("click-input", tags.length);
    }
    this.setState({ showSelect: true });
    setTimeout(() => {
      this[`tag-${tags.length}`]?.moveToEnd();
    }, 100);
  };

  close = () => {
    // 编辑未完成的取消编辑
    const tags = this.state.tags.map((item, index) => {
      if (item["_edit"]) {
        this[`tag-${index}`]?.editDone();
        item["_edit"] = false;
      }
      return item;
    });

    this.setTags(
      tags,
      () => {
        this.setState({ showSelect: false });
        if (this.state.active) {
          this.setState({ curPos: -1 }, () =>
            this.setState({ active: false }, () => {
              if (this[`search-box`]) {
                this[`search-box`].scrollLeft = 0;
              }
            })
          );
        }
      },
      false
    );
  };

  getValue = (tags: TagValue[]): TagValue[] => {
    const result = [];
    tags.forEach(item => {
      const { values, attr = null } = item;
      if (values.length > 0) {
        result.push({ attr, values, _key: item["_key"], _edit: item["_edit"] });
      }
    });
    return result;
  };

  notify = (tags: TagValue[]) => {
    const { onChange = noop } = this.props;
    onChange(this.getValue(tags));
  };

  // Tags 发生变动
  setTags(tags: TagValue[], callback?: Function, notify = true): void {
    const cb = () => {
      notify && this.notify(tags);
      callback && callback();
    };
    // 受控模式
    if (notify && this.props.value) {
      this.resetTagsState(this.props, cb);
    } else {
      this.setState({ tags }, cb);
    }
  }

  /**
   * 点击清除按钮触发事件
   */
  handleClear = (e: React.MouseEvent): void => {
    e.stopPropagation();

    const { onClearButtonClick = noop } = this.props;
    onClearButtonClick(e);

    const { tags } = this.state;
    const nextTags = tags.filter(i => i.attr && i.attr.removeable === false);
    const index = `tag-${nextTags.length}`;

    if (tags.length <= 0) {
      this[index]?.setInputValue("");
      return;
    }

    this.setTags(nextTags, () =>
      setTimeout(() => {
        this[index]?.setInputValue("");
        this[index]?.focusInput();
      }, 0)
    );
    this.setState({ curPos: 0, curPosType: FocusPosType.INPUT });
    // 刷新下拉列表位置
    const input = this[`tag-${tags.length}`];
    if (input) {
      input?.scheduleUpdate();
    }
  };

  /**
   * 点击帮助触发事件
   */
  handleHelp = e => {
    e.stopPropagation();

    const { onHelpButtonClick = noop } = this.props;
    if (onHelpButtonClick(e) === false) {
      return;
    }

    this.setState({ dialogActive: true });
  };

  /**
   * 点击搜索触发事件
   */
  handleSearch = e => {
    if (!this.state.active) {
      // 如果监听了按钮点击，此时点击按钮不激活搜索框
      if ("onSearchButtonClick" in this.props) {
        e.stopPropagation();
        this.props.onSearchButtonClick(e, this.getValue(this.state.tags));
      }
      return;
    }

    e.stopPropagation();

    // 输入值生成标签操作会异步改变 tags
    // 此处保证 tags 状态变化完成后再进行回调
    setTimeout(() => {
      const { onSearchButtonClick = noop } = this.props;
      onSearchButtonClick(e, this.getValue(this.state.tags));
    }, 100);

    const { curPos, curPosType, tags } = this.state;
    let flag = false;

    const input = this[`tag-${tags.length}`];
    if (input && input.addTagByInputValue) {
      if (input.addTagByInputValue()) {
        flag = true;
      }
    }

    for (let i = 0; i < tags.length; ++i) {
      if (!this[`tag-${i}`] || !this[`tag-${i}`].addTagByEditInputValue) return;
      if (tags[i]["_edit"] && this[`tag-${i}`].addTagByEditInputValue())
        flag = true;
    }

    if (flag) return;

    this.notify(this.state.tags);
    input.focusInput();
  };

  /**
   *  处理Tag相关事件
   */
  handleTagEvent = async (type: string, index: number, payload?: any) => {
    const { active } = this.state;
    const tags = clone(this.state.tags);

    switch (type) {
      case "add":
        payload["_key"] = COUNTER++;
        tags.splice(++index, 0, payload);
        this.setTags(tags, () => {
          this[`tag-${index}`]?.focusInput();
        });
        this.setState({ showSelect: false });
        break;

      case "edit":
        this[`tag-${index}`]?.editDone();
        tags[index].attr = payload.attr;
        tags[index].values = payload.values;
        tags[index]["_edit"] = false;

        this.setTags(tags);
        index++;
        this.setState({ showSelect: false, curPosType: FocusPosType.INPUT });
        break;

      case "edit-cancel":
        this[`tag-${index}`]?.editDone();

        this.setTags(tags, () => null, false);
        this.setState({ showSelect: false, curPosType: FocusPosType.INPUT });
        break;

      case "editing":
        if ("attr" in payload && tags[index]) tags[index].attr = payload.attr;
        if ("values" in payload && tags[index])
          tags[index].values = payload.values;
        this.setTags(tags, null, false);
        break;

      case "del":
        if (payload === "keyboard") index--;
        if (!tags[index]) break;

        /** 传入onDeleteTag，就通过onDeleteTag控制是否删除 */
        const canDelteTag = await this.props.onDeleteTag?.(tags[index]);
        if (this.props.onDeleteTag && !Boolean(canDelteTag)) break;

        // 检查不可移除
        const { attr } = tags[index];
        if (attr && attr?.removeable === false) {
          break;
        }

        tags.splice(index, 1);
        this.setTags(tags, () => {
          this.setState({ curPosType: FocusPosType.INPUT });
        });
        if (payload !== "edit") {
          this.setState({ showSelect: false });
        }
        break;

      // payload 为点击位置
      case "click":
        if (!active) {
          this.open();
          return;
        }
        // 触发修改
        const pos = payload;
        tags[index]["_edit"] = true;
        this.setTags(
          tags,
          () => {
            this.setState({ showSelect: true }, () => {
              this[`tag-${index}`]?.edit(pos);
            });
          },
          false
        );

        this.setState({ curPosType: FocusPosType.INPUT_EDIT });
        break;

      case "click-input":
        if (payload === "edit") {
          this.setState({ curPosType: FocusPosType.INPUT_EDIT });
        } else {
          this.setState({ curPosType: FocusPosType.INPUT });
        }

        if (!active) {
          this.setState({ active: true });
        }
        this.setState({ showSelect: true });
        break;
    }

    this.setState({ curPos: index });
  };

  render() {
    const {
      active,
      tags,
      curPos,
      curPosType,
      dialogActive,
      showSelect,
    } = this.state;
    const {
      t,
      config: { classPrefix },
      className,
      style = {},
      minWidth = 210,
      attributes = [],
      hideHelp,
      tips = t.tagSearchBoxTips,
      attributesSelectTips = t.tagSearchBoxSelectTitle,
      disableAttributesFilter,
      disabled,
      forwardRef,
    } = this.props;

    // 用于计算 focused 及 isFocused, 判断是否显示选择组件
    // (直接使用 Input 组件内部 onBlur 判断会使得 click 时组件消失)
    let focusedInputIndex = -1;
    if (
      curPosType === FocusPosType.INPUT ||
      curPosType === FocusPosType.INPUT_EDIT
    ) {
      focusedInputIndex = curPos;
    }

    const tagList = tags.map((item, index) => {
      // 补全 attr 属性
      attributes.forEach(attrItem => {
        if (item.attr && attrItem.key && attrItem.key == item.attr.key) {
          item.attr = Object.assign({}, item.attr, attrItem);
        }
      });

      const selectedAttrKeys = [];
      tags.forEach(tag => {
        if (
          tag.attr &&
          item.attr &&
          item["_edit"] &&
          item.attr.key === tag.attr.key
        ) {
          return null;
        }
        if (tag.attr && tag.attr.key && !tag.attr.reusable) {
          selectedAttrKeys.push(tag.attr.key);
        }
      });

      const useableAttributes = attributes.filter(
        item => selectedAttrKeys.indexOf(item.key) < 0
      );

      return (
        <Tag
          ref={tag => (this[`tag-${index}`] = tag)}
          active={active}
          key={item["_key"]}
          attributes={useableAttributes}
          attr={item.attr}
          values={item.values}
          maxWidth={
            this["search-wrap"] ? this["search-wrap"].clientWidth : null
          }
          focused={
            focusedInputIndex === index && showSelect ? curPosType : null
          }
          dispatchTagEvent={(type, payload) =>
            this.handleTagEvent(type, index, payload)
          }
        />
      );
    });

    const selectedAttrKeys = tags
      .map(item => (item.attr && !item.attr.reusable ? item.attr.key : null))
      .filter(item => !!item);
    const useableAttributes = attributes.filter(
      item => selectedAttrKeys.indexOf(item.key) < 0
    );

    tagList.push(
      <TagInput
        key="__input__"
        ref={input => (this[`tag-${tags.length}`] = input)}
        active={active}
        maxWidth={this["search-wrap"] ? this["search-wrap"].clientWidth : null}
        attributes={useableAttributes}
        isFocused={focusedInputIndex === tags.length && showSelect}
        dispatchTagEvent={(type, payload) =>
          this.handleTagEvent(type, tags.length, payload)
        }
      />
    );

    return (
      <div
        className={classNames(
          `${classPrefix}-search ${classPrefix}-search--tags`,
          className,
          {
            "is-active": !disabled && active,
            "is-disabled": disabled,
          }
        )}
        ref={mergeRefs(div => (this["search-wrap"] = div), forwardRef)}
        style={active ? style : { ...style, width: minWidth }}
      >
        <div
          className={`${classPrefix}-search__inner`}
          onClick={this.open}
          ref={div => (this[`search-box`] = div)}
          style={disabled ? undefined : { cursor: "text" }}
        >
          <div className={`${classPrefix}-tag-group`}>
            <TagSearchBoxContext.Provider
              value={{
                attributesSelectTips,
                disableAttributesFilter,
                close: this.close,
              }}
            >
              {tagList}
            </TagSearchBoxContext.Provider>
          </div>

          <div
            className={`${classPrefix}-text-weak ${classPrefix}-search__tips`}
            style={{ position: "relative", userSelect: "none" }}
          >
            {tips}
          </div>
          {/* 清除按钮根据 hideHelp 添加类名确定位置 */}
          {!!active && tags.length > 0 && (
            <Bubble content={t.cleanText}>
              <Button
                htmlType="button"
                type="icon"
                icon="dismiss"
                className={classNames({
                  [`${classPrefix}-btn--dismiss`]: !hideHelp,
                  [`${classPrefix}-btn--info`]: !!hideHelp,
                })}
                onClick={this.handleClear}
              />
            </Bubble>
          )}
          {!!active && !hideHelp && (
            <Bubble content={t.helpText}>
              <Button
                htmlType="button"
                type="icon"
                icon="info"
                className={`${classPrefix}-btn--info`}
                onClick={this.handleHelp}
              />
            </Bubble>
          )}
          <Bubble content={active ? t.searchText : null}>
            <Button
              htmlType="button"
              type="icon"
              icon="search"
              className={`${classPrefix}-btn--search`}
              disabled={disabled}
              onClick={this.handleSearch}
            />
          </Bubble>
        </div>

        <Modal
          visible={dialogActive}
          size="auto"
          caption={t.helpText}
          onClose={() => this.setState({ dialogActive: false })}
          className="ignore-outside-click"
        >
          <Modal.Body>
            <i
              style={{
                width: 910,
                height: 476,
                backgroundImage: `url(${t.tagSearchBoxHelpImgUrl})`,
                backgroundSize: "contain",
                backgroundRepeat: "no-repeat",
                backgroundPosition: "center",
                display: "inline-block",
              }}
            />
          </Modal.Body>
        </Modal>
      </div>
    );
  }
}

export const TagSearchBox = React.forwardRef(
  (props: TagSearchBoxProps, ref: React.Ref<HTMLDivElement>) => {
    const C = useMemo(() => withConfig(withTranslation(ITagSearchBox)), []);
    return <C {...props} forwardRef={ref} />;
  }
);

TagSearchBox.displayName = "TagSearchBox";
