/* eslint-disable */
import React from "react";
import { AttributeSelect, AttributeValue } from "./AttributeSelect";
import { ValueSelect } from "./valueselect/ValueSelect";
import { withConfig, WithConfigProps } from "../_util/config-context";
import { mergeRefs } from "../_util/merge-refs";
import { Popover } from "../popover";
import { TagSearchBoxContext } from "./TagSearchBoxContext";
import { Input } from "../input";

export interface TagInputProps extends WithConfigProps {
  /**
   * 触发标签相关事件
   */
  dispatchTagEvent: (type: string, payload?: any) => void;

  /**
   * 所有属性集合
   */
  attributes: Array<AttributeValue>;

  /**
   * 是否为 Focus 态
   */
  isFocused: boolean;

  /**
   * 搜索框是否处于展开状态
   */
  active: boolean;

  /**
   * 输入框类型（用于修改标签值的 Input type 为 "edit"）
   */
  type?: "edit" | "add";

  /**
   * 是否隐藏
   */
  hidden?: boolean;

  /**
   * 最大宽度
   */
  maxWidth: number;

  /**
   * 处理按键事件
   */
  handleKeyDown?: (e: any) => void;

  /**
   * 位置偏移
   */
  inputOffset?: number;
}

export interface InputState {
  inputWidth: number;
  inputValue: string;
  // 包含 Input 输入法未完成是值
  fullInputValue: string;
  attribute: AttributeValue;
  values: Array<any>;
  showAttrSelect: boolean;
  showValueSelect: boolean;
  valueSelectOffset: number;
}

const keys = {
  "8": "backspace",
  "9": "tab",
  "13": "enter",
  "27": "esc",
  "37": "left",
  "38": "up",
  "39": "right",
  "40": "down",
};

const INPUT_MIN_SIZE = 0;
const SELECT_MIN_HEIGHT = 242;

export class ITagInput extends React.Component<TagInputProps, any> {
  static contextType = TagSearchBoxContext;
  state: InputState = {
    inputWidth: INPUT_MIN_SIZE,
    inputValue: "",
    fullInputValue: "",
    attribute: null,
    values: [],
    showAttrSelect: false,
    showValueSelect: false,
    valueSelectOffset: 0,
  };

  _scheduleUpdate: () => any;
  wrapperRef: React.RefObject<HTMLDivElement>;

  constructor(props) {
    super(props);
    this._scheduleUpdate = () => null;
    this.wrapperRef = React.createRef();
  }

  componentDidMount() {}

  /**
   * 刷新下拉列表位置
   */
  scheduleUpdate = () => this._scheduleUpdate();

  /**
   * 刷新选择组件显示
   */
  refreshShow = (): void => {
    const { inputValue, attribute } = this.state;

    const input = this["input"] as HTMLInputElement;
    let start = input?.selectionStart,
      end = input?.selectionEnd;

    const { pos } = this.getAttrStrAndValueStr(inputValue);

    if (pos < 0 || start <= pos) {
      this.setState({ showAttrSelect: true, showValueSelect: false });
      return;
    }

    if (attribute && end > pos) {
      this.setState({ showAttrSelect: false, showValueSelect: true });
    }
  };

  focusInput = (): void => {
    if (!this["input"]) return;
    const input = this["input"] as HTMLElement;
    input?.focus();
  };

  moveToEnd = (): void => {
    const input = this["input"] as HTMLInputElement;
    input?.focus();
    const value = this.state.inputValue;
    setTimeout(() => input?.setSelectionRange(value.length, value.length), 0);
  };

  selectValue = (): void => {
    const input = this["input"] as HTMLInputElement;
    input?.focus();
    const value = this.state.inputValue;
    let { pos } = this.getAttrStrAndValueStr(value);
    if (pos < 0) pos = -2;
    setTimeout(() => {
      input?.setSelectionRange(pos + 2, value.length);
      this.refreshShow();
    }, 0);
  };

  selectAttr = (): void => {
    const input = this["input"] as HTMLInputElement;
    input?.focus();
    const value = this.state.inputValue;
    let { pos } = this.getAttrStrAndValueStr(value);
    if (pos < 0) pos = 0;
    setTimeout(() => {
      input?.setSelectionRange(0, pos);
      this.refreshShow();
    }, 0);
  };

  setInfo(info: any, callback?: Function) {
    const attribute = info.attr;
    const values = info.values;
    this.setState({ attribute, values }, () => {
      if (attribute) {
        this.setInputValue(
          `${attribute.name}: ${values.map(item => item.name).join(" | ")}`,
          callback
        );
      } else {
        this.setInputValue(
          `${values.map(item => item.name).join(" | ")}`,
          callback
        );
      }
    });
  }

  setInputValue = (value: string, callback?: Function): void => {
    if (this.props.type === "edit" && value.trim().length <= 0) {
      return this.props.dispatchTagEvent("del", "edit");
    }

    const attributes = this.props.attributes;

    let attribute = null,
      valueStr = value;

    const input = this["input"] as HTMLElement;
    const mirror = this["input-mirror"] as HTMLElement;

    // attribute 是否存在
    for (let i = 0; i < attributes.length; ++i) {
      if (
        value.indexOf(attributes[i].name + ":") === 0 ||
        value.indexOf(attributes[i].name + "：") === 0
      ) {
        // 获取属性/值
        attribute = attributes[i];
        valueStr = value.substr(attributes[i].name.length + 1);

        // 计算 offset
        if (mirror) {
          mirror.innerText = attribute.name + ": ";
          let width = mirror.clientWidth;
          if (this.props.inputOffset) width += this.props.inputOffset;
          this.setState({ valueSelectOffset: width }, () =>
            this.scheduleUpdate()
          );
        }
        break;
      }
    }

    // 处理前导空格
    if (attribute && valueStr.replace(/^\s+/, "").length > 0) {
      value = `${attribute.name}: ${valueStr.replace(/^\s+/, "")}`;
    } else if (attribute) {
      value = `${attribute.name}:${valueStr}`;
    }

    // attribute 变化时刷新 values
    if (attribute !== this.state.attribute) {
      this.setState({
        values: valueStr.split("|").map(item => ({ name: item.trim() })),
      });
    }

    this.setState({ attribute }, this.refreshShow);

    if (this.props.type === "edit") {
      this.props.dispatchTagEvent("editing", { attr: attribute });
    }

    mirror.innerText = value;
    const width =
      mirror.clientWidth > INPUT_MIN_SIZE ? mirror.clientWidth : INPUT_MIN_SIZE;
    this.setState(
      { inputValue: value, fullInputValue: value, inputWidth: width },
      () => {
        if (callback) callback();
      }
    );
  };

  /**
   * 包含输入法过程的输入值
   */
  setFullInputValue = (value: string): void => {
    const attributes = this.props.attributes;

    let attribute = null,
      valueStr = value;

    const mirror = this["input-mirror"] as HTMLElement;

    // attribute 是否存在
    for (let i = 0; i < attributes.length; ++i) {
      if (
        value.indexOf(attributes[i].name + ":") === 0 ||
        value.indexOf(attributes[i].name + "：") === 0
      ) {
        // 获取属性/值
        attribute = attributes[i];
        valueStr = value.substr(attributes[i].name.length + 1);

        // 计算 offset
        if (mirror) {
          mirror.innerText = attribute.name + ": ";
          let width = mirror.clientWidth;
          if (this.props.inputOffset) width += this.props.inputOffset;
          this.setState({ valueSelectOffset: width }, () =>
            this.scheduleUpdate()
          );
        }
        break;
      }
    }

    // 处理前导空格
    if (attribute && valueStr.replace(/^\s+/, "").length > 0) {
      value = `${attribute.name}: ${valueStr.replace(/^\s+/, "")}`;
    } else if (attribute) {
      value = `${attribute.name}:${valueStr}`;
    }

    if (mirror) {
      mirror.innerText = value;
      const width =
        mirror.clientWidth > INPUT_MIN_SIZE
          ? mirror.clientWidth
          : INPUT_MIN_SIZE;
      this.setState({ fullInputValue: value, inputWidth: width });
    }
  };

  resetInput = (callback?: Function): void => {
    this.setInputValue("", callback);
    this.setState({ inputWidth: INPUT_MIN_SIZE });
  };

  getInputValue = (): string => {
    return this.state.inputValue;
  };

  addTagByInputValue = (): boolean => {
    const { attribute, values, inputValue } = this.state;
    const type = this.props.type || "add";
    // 属性值搜索
    if (
      attribute &&
      this.props.attributes.filter(item => item.key === attribute.key).length >
        0
    ) {
      if (values.length <= 0) {
        return false;
      }
      this.props.dispatchTagEvent(type, { attr: attribute, values: values });
    } else {
      // 关键字搜索
      if (inputValue.trim().length <= 0) {
        return false;
      }
      const list = inputValue
        .split("|")
        .filter(item => item.trim().length > 0)
        .map(item => {
          return { name: item.trim() };
        });
      this.props.dispatchTagEvent(type, { attr: null, values: list });
    }
    this.setState({ showAttrSelect: false, showValueSelect: false });
    if (this.props.type !== "edit") {
      this.resetInput();
    }
    return true;
  };

  handleInputChange = (value): void => {
    this.setInputValue(value);
  };

  handleInputClick = (e): void => {
    this.props.dispatchTagEvent("click-input", this.props.type);
    e.stopPropagation();
    this.focusInput();
  };

  handleAttrSelect = (attr: AttributeValue): void => {
    if (attr && attr.key) {
      const str = `${attr.name}: `;
      const inputValue = this.state.inputValue;
      if (inputValue.indexOf(str) >= 0) {
        this.selectValue();
      } else {
        this.setInputValue(str);
      }
      this.setState({ values: [] });
    }

    this.focusInput();
  };

  handleValueChange = (values: Array<any>): void => {
    this.setState({ values }, () => {
      this.setInputValue(
        `${this.state.attribute.name}: ${values
          .map(item => item.name)
          .join(" | ")}`
      );
      this.focusInput();
    });
  };

  /**
   * 值选择组件完成选择
   */
  handleValueSelect = (values: Array<any>): void => {
    this.setState({ values });
    const inputValue = this.state.inputValue;

    if (values.length <= 0) {
      this.setInputValue(this.state.attribute.name + ": ");
      return;
    }

    if (values.length > 0) {
      const key = this.state.attribute.key;
      if (this.props.attributes.filter(item => item.key === key).length > 0) {
        const type = this.props.type || "add";
        this.props.dispatchTagEvent(type, {
          attr: this.state.attribute,
          values,
        });
      }
      this.focusInput();
    }

    if (this.props.type !== "edit") {
      this.resetInput();
    }
  };

  /**
   * 值选择组件取消选择
   */
  handleValueCancel = () => {
    if (this.props.type === "edit") {
      const { attribute, values } = this.state;
      this.props.dispatchTagEvent("edit-cancel", {
        attr: attribute,
        values: values,
      });
    } else {
      this.resetInput(() => {
        this.focusInput();
      });
    }
  };

  /**
   * 处理粘贴事件
   */
  handlePaste = (e): void => {
    e.stopPropagation();
    e.preventDefault();

    const { attribute } = this.state;

    if (!attribute || attribute.type === "input") {
      let value: string = "";
      try {
        const clipboardData = e.clipboardData;
        value = clipboardData.getData("Text") || "";
      } catch (_) {}

      if (/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/.test(value)) {
        value = value.replace(/[\r\n\t,，\s]+/g, "|");
      } else {
        value = value.replace(/[\r\n\t,，]+/g, "|");
      }
      value = value
        .split("|")
        .map(item => item.trim())
        .filter(item => item.length > 0)
        .join(" | ");

      const input = this["input"] as HTMLInputElement;
      const start = input?.selectionStart,
        end = input?.selectionEnd;
      const inputValue = this.state.inputValue;
      // 覆盖选择区域
      const curValue =
        inputValue.substring(0, start) +
        value +
        inputValue.substring(end, inputValue.length);

      // input 属性情况
      if (attribute && attribute.type === "input") {
        this.setInputValue(curValue, this.focusInput);
        return;
      }

      if (inputValue.length > 0) {
        this.setInputValue(curValue, this.focusInput);
      } else {
        this.setInputValue(curValue, this.addTagByInputValue);
      }
    }
  };

  handleKeyDown = (e: React.KeyboardEvent): void => {
    if (!keys[e.keyCode]) return;

    if (this.props.hidden) {
      return this.props.handleKeyDown(e);
    }

    const inputValue = this.state.inputValue;

    if (keys[e.keyCode] === "backspace" && inputValue.length > 0) return;

    if (
      (keys[e.keyCode] === "left" || keys[e.keyCode] === "right") &&
      inputValue.length > 0
    ) {
      setTimeout(this.refreshShow, 0);
      return;
    }

    if (keys[e.keyCode] === "esc") {
      if (!inputValue) {
        this.context.close();
      }
      return this.handleValueCancel();
    }

    e.preventDefault();

    // 事件下传
    if (this["attr-select"]) {
      if (this["attr-select"].handleKeyDown(e.keyCode) === false) return;
    }
    if (this["value-select"]) {
      this["value-select"].handleKeyDownForRenderMode(e.key);
      if (this["value-select"].handleKeyDown(e.keyCode) === false) return;
    }

    switch (keys[e.keyCode]) {
      case "enter":
      case "tab":
        if (!this.props.isFocused) {
          this.props.dispatchTagEvent("click-input");
        }
        this.addTagByInputValue();
        break;

      case "backspace":
        this.props.dispatchTagEvent("del", "keyboard");
        break;

      case "up":
        break;

      case "down":
        break;
    }
  };

  getAttrStrAndValueStr = (str: string): any => {
    let attrStr = str,
      valueStr = "",
      pos = -1;

    const attributes = this.props.attributes;
    for (let i = 0; i < attributes.length; ++i) {
      if (str.indexOf(attributes[i].name + ":") === 0) {
        // 获取属性/值
        attrStr = attributes[i].name;
        valueStr = str.substr(attrStr.length + 1);
        pos = attributes[i].name.length;
      }
    }

    return { attrStr, valueStr, pos };
  };

  render() {
    const {
      inputWidth,
      inputValue,
      fullInputValue,
      showAttrSelect,
      showValueSelect,
      attribute,
      valueSelectOffset,
    } = this.state;

    const {
      config: { classPrefix },
      active,
      attributes,
      isFocused,
      hidden,
      maxWidth,
      type,
    } = this.props;

    const { attrStr, valueStr } = this.getAttrStrAndValueStr(inputValue);

    const wrapper = this.wrapperRef.current;
    let maxHeight = SELECT_MIN_HEIGHT;
    try {
      if (wrapper) {
        maxHeight =
          window.innerHeight - wrapper.getBoundingClientRect().bottom - 60;
      }
    } catch (_) {}

    maxHeight = Math.max(maxHeight, SELECT_MIN_HEIGHT);

    const input =
      type !== "edit" ? (
        <Input
          ref={input => (this["input"] = input)}
          type="text"
          className={`${classPrefix}-input--tag`}
          placeholder=""
          style={{
            width: hidden ? 0 : inputWidth + 6,
            display: active ? "" : "none",
            maxWidth: maxWidth ? maxWidth - 36 : 435,
          }}
          value={inputValue}
          onChange={this.handleInputChange}
          onInput={e => this.setFullInputValue(e.currentTarget.value)}
          onKeyDown={this.handleKeyDown}
          onFocus={this.refreshShow}
          onClick={this.refreshShow}
          onPaste={this.handlePaste}
        />
      ) : (
        <div style={{ display: hidden ? "none" : "" }}>
          <pre style={{ display: "block", visibility: "hidden" }}>
            <div
              style={{
                fontSize: 12,
                width: hidden ? 0 : inputWidth + 36,
                maxWidth: maxWidth ? maxWidth - 36 : 435,
                whiteSpace: "normal",
                fontFamily: `Roboto,"San Francisco","Helvetica Neue",Helvetica,Arial,PingFangSC-Light,"Hiragina Sans GB","WenQuanYi Micro Hei",'microsoft yahei ui',"microsoft yahei",sans-serif`,
              }}
            >
              {fullInputValue}
            </div>
            <br style={{ clear: "both" }} />
          </pre>
          <Input
            multiline
            ref={input => (this["input"] = input)}
            className={`${classPrefix}-input--tag`}
            placeholder=""
            style={{
              width: hidden ? 0 : inputWidth + 30,
              display: active ? "" : "none",
              maxWidth: maxWidth ? maxWidth - 36 : 435,
              position: "absolute",
              top: 0,
              left: 0,
              height: "100%",
              resize: "none",
              minHeight: 20,
              marginTop: 4,
            }}
            value={inputValue}
            onChange={this.handleInputChange}
            onKeyDown={this.handleKeyDown}
            onFocus={this.refreshShow}
            onClick={this.refreshShow}
            onPaste={this.handlePaste}
          />
        </div>
      );

    return (
      <Popover
        trigger="empty"
        placement="bottom-start"
        overlayClassName="ignore-outside-click"
        placementOffset={type === "edit" ? 12 : 2}
        transitionTimeout={{ enter: 50, exit: 0 }}
        visible={
          active &&
          isFocused &&
          (showAttrSelect ||
            (showValueSelect && !!attribute && !!attribute.type))
        }
        onVisibleChange={visible => {
          if (!visible) {
            this.context.close();
          }
        }}
        overlay={({ scheduleUpdate }) => {
          this._scheduleUpdate = scheduleUpdate;
          return (
            <>
              {showAttrSelect && (
                <AttributeSelect
                  ref={select => (this["attr-select"] = select)}
                  attributes={attributes}
                  inputValue={attrStr}
                  onSelect={this.handleAttrSelect}
                  maxHeight={maxHeight}
                />
              )}
              {showValueSelect && !!attribute && !!attribute.type && (
                <ValueSelect
                  type={attribute.type}
                  ref={select => (this["value-select"] = select)}
                  values={attribute.values}
                  render={attribute.render}
                  inputValue={valueStr.trim()}
                  offset={valueSelectOffset}
                  onChange={this.handleValueChange}
                  onSelect={this.handleValueSelect}
                  onCancel={this.handleValueCancel}
                  maxHeight={maxHeight}
                />
              )}
            </>
          );
        }}
      >
        <div
          ref={mergeRefs(this.wrapperRef)}
          style={{
            display: "inline-block",
            verticalAlign: type === "edit" ? "" : "top",
            position: "relative",
            width: hidden ? 0 : active ? inputWidth + 6 : 6,
            maxWidth: maxWidth ? maxWidth - 36 : 435,
            padding: type === "edit" && !hidden ? "0 8px" : "",
          }}
          onClick={this.handleInputClick}
        >
          {input}
          <span
            ref={input => (this["input-mirror"] = input)}
            style={{
              position: "absolute",
              top: -9999,
              left: 0,
              whiteSpace: "pre",
              fontSize: 12,
            }}
          />
        </div>
      </Popover>
    );
  }
}

export const TagInput = withConfig(ITagInput);
