import React, { useState, useRef, useEffect, Fragment } from "react";
import { Dropdown } from "../dropdown";
import { Checkbox } from "../checkbox";
import { useDefaultValue, ChangeContext } from "../form";
import { SelectOptionWithGroup } from "./SelectOption";
import { CheckTree } from "../checktree";
import { Button } from "../button";
import { Text } from "../text";
import { useTranslation } from "../i18n";
import { useConfig } from "../_util/config-context";
import { EmptyTip } from "../tips";
import { SearchBox } from "../searchbox";
import { SelectMultipleProps } from "./SelectProps";
import { injectValue } from "../_util/inject-value";
import { Tooltip } from "../tooltip";
import { useDefault } from "../_util/use-default";
import { noop } from "../_util/noop";
import { searchFilter } from "../_util/search-filter";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { isMobile } from "../_util/is-mobile";
import { useUnmounted } from "../_util/use-unmounted";
import { getListItems } from "./util";
import { VirtualizedList } from "../list/VirtualizedList";

export const SelectMultiple = forwardRefWithStatics(
  function SelectMultiple(
    props: SelectMultipleProps,
    ref: React.Ref<HTMLDivElement>
  ) {
    const t = useTranslation();
    const { classPrefix } = useConfig();
    const {
      staging = true,
      value,
      onChange,
      options = [],
      groups,
      allOption,
      shouldOptionExcludeFromAll = "disabled",
      allowEmpty = true,
      onOpen = noop,
      onClose = noop,
      searchable,
      searchPlaceholder = "",
      onSearch = noop,
      filter: customizeFilter,
      filterRender,
      autoClearSearchValue = true,
      defaultSearchValue = "",
      searchValue,
      onSearchValueChange,
      clearable,
      onScrollBottom = noop,
      bottomTips,
      footer,
      virtual = false,
      listWidth,
      listHeight,
      boxStyle = {},
      onReset,
      onlySubmitFilterFromAll,
      ...dropdownProps
    } = useDefaultValue(props, []);

    // 移动端 focus 可能产生滚动导致关闭
    if (isMobile && searchable) {
      dropdownProps.closeOnScroll = false;
    }

    const vListRef = useRef(null);

    const [stagingValue, setStagingValue] = useState<string[]>(value);

    const InputRef = useRef<HTMLInputElement>(null);
    const [inputValue, setInputValue] = useDefault<string, [ChangeContext]>(
      searchValue,
      defaultSearchValue,
      onSearchValueChange
    );

    const unmountedRef = useUnmounted();

    const filter =
      customizeFilter ||
      ((inputValue: string, { text, value }: SelectOptionWithGroup) => {
        const optionText = String(typeof text === "string" ? text : value);
        return !searchable || searchFilter(optionText, inputValue);
      });

    const hasGroup = !!options.find(opt => !!opt.groupKey);

    function focus() {
      if (!isMobile && searchable) {
        setTimeout(() => {
          if (InputRef.current) {
            InputRef.current.focus();
          }
        }, 100); // 第一次展开时 Input 还未渲染
      }
    }
    const handleChange = (value: string[], context: ChangeContext) => {
      if (staging === false) {
        onChange(value, context);
        return;
      }
      setStagingValue(value);
    };

    // 「全部」选项和其余选项是数关系
    const relations = {};
    const excludeFromAll = [];
    if (allOption) {
      for (const option of options) {
        if (
          // 用户可以指定哪些选项从全选逻辑中排除
          (typeof shouldOptionExcludeFromAll === "function" &&
            shouldOptionExcludeFromAll(option)) ||
          (shouldOptionExcludeFromAll === "disabled" && option.disabled)
        ) {
          excludeFromAll.push(option.value);
          // continue
        } else {
          relations[option.value] = allOption.value;
        }
      }
    }

    // 筛选
    const filteredOptions = options.filter(options =>
      filter(inputValue, options)
    );

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

    const items = getListItems({
      tips: tips ? injectValue(tips)(filteredOptions) : null,
      bottomTips: injectValue(bottomTips)(filteredOptions),
      groups,
      options: filteredOptions,
      allOption,
    }).map(item => {
      if (item.type === "option" && searchable && filterRender) {
        return { ...item, text: filterRender(inputValue, item.option) };
      }
      return item;
    });

    if (listWidth) {
      boxStyle.width = listWidth;
      boxStyle.minWidth = listWidth;
    }

    // 重置数据
    const resetData = context => {
      if (inputValue !== "") {
        setInputValue("", context);
      }
      let defaultData = [];
      if (onReset && typeof onReset === "function") {
        const originResetData = onReset(context);
        // 非受控模式，在reset的时候 可以处置数据
        if (originResetData) {
          defaultData = Array.isArray(originResetData)
            ? originResetData
            : defaultData;
        } else {
          // 受控场景无需走内置场景
          return;
        }
      }
      handleChange(defaultData, context);
    };

    return (
      <Dropdown
        ref={ref}
        {...dropdownProps}
        preventMouseDown={!searchable && !footer}
        updateOnChildrenChange
        button={<ValueBrief {...props} value={value} />}
        onOpen={() => {
          setStagingValue(value);
          focus();
          onOpen();
        }}
        onClose={() => {
          if (autoClearSearchValue && inputValue !== "") {
            setTimeout(() => {
              if (!unmountedRef.current) {
                setInputValue("", {} as any);
              }
            }, 100);
          }
          onClose();
        }}
        clickClose={false}
        clearable={value.length && clearable}
        boxStyle={boxStyle}
        onClear={event => {
          onChange([], { event });
          setStagingValue([]);
        }}
      >
        {close => (
          <CheckTree
            relations={relations}
            value={(staging ? stagingValue : value) || value}
            onChange={handleChange}
            disabledNames={options.filter(x => x.disabled).map(x => x.value)}
          >
            {searchable && (
              <SearchBox
                simple
                ref={InputRef}
                value={inputValue}
                onChange={(value, context) => {
                  setInputValue(value, context);
                  onSearch(value, context);
                }}
                onClear={focus}
                placeholder={searchPlaceholder}
                onSearch={onSearch}
              />
            )}
            <VirtualizedList
              virtual={virtual}
              containerStyle={boxStyle}
              className={`${classPrefix}-list--checkoption`}
              containerClassName={dropdownProps.boxClassName}
              items={items.map(item => {
                if (item.type === "option") {
                  const { option } = item;
                  const content = option.text || option.value;
                  return {
                    ...item,
                    text: (
                      <Checkbox
                        onClick={evt => evt.stopPropagation()}
                        name={option.value}
                        title={
                          !option.tooltip && typeof content === "string"
                            ? content
                            : undefined
                        }
                      >
                        {content}
                      </Checkbox>
                    ),
                    props: {
                      disabled: option.disabled,
                      tooltip: option.tooltip,
                    },
                  };
                }
                return item;
              })}
              virtualizedRef={vListRef}
              type={hasGroup ? "option-group" : "option"}
              onScrollBottom={onScrollBottom}
              listHeight={listHeight}
            />
            {(footer || staging !== false) && (
              <Dropdown.Footer>
                {footer}
                {staging !== false && (
                  <>
                    <Button
                      type="primary"
                      htmlType="button"
                      disabled={!allowEmpty && stagingValue.length === 0}
                      onClick={event => {
                        onChange(
                          stagingValue.filter(
                            v =>
                              !onlySubmitFilterFromAll ||
                              !excludeFromAll.includes(v)
                          ),
                          { event }
                        );
                        close();
                      }}
                    >
                      {t.okText}
                    </Button>
                    <Button
                      type="weak"
                      htmlType="button"
                      onClick={event => {
                        resetData({ event });
                      }}
                    >
                      {t.resetText}
                    </Button>
                  </>
                )}
              </Dropdown.Footer>
            )}
          </CheckTree>
        )}
      </Dropdown>
    );
  },
  {
    defaultLabelAlign: "middle",
  }
);

SelectMultiple.displayName = "SelectMultiple";

function ValueBrief(props: SelectMultipleProps): JSX.Element {
  const t = useTranslation();
  const {
    value,
    allOption,
    options,
    button,
    placeholder = t.pleaseSelect,
    appearance = props.appearence, // eslint-disable-line react/destructuring-assignment
  } = props;

  let buttonPlaceholder: React.ReactNode = placeholder;
  if (!appearance || appearance === "button" || appearance === "default") {
    buttonPlaceholder = <Text theme="weak">{placeholder}</Text>;
  }

  const [selectedOptions, setSelectedOptions] = useState<
    SelectOptionWithGroup[]
  >((value || []).map(x => options.find(y => y.value === x) || { value: x }));

  useEffect(() => {
    setSelectedOptions(selectedOptions => {
      return (value || [])
        .map(
          v =>
            options.find(option => option.value === v) ||
            selectedOptions.find(option => option.value === v)
        )
        .filter(Boolean);
    });
  }, [options, value]);

  if (button) {
    return <div>{injectValue(button)(selectedOptions)}</div>;
  }
  if (!value || !value.length) {
    return <>{buttonPlaceholder}</>;
  }
  if (
    allOption &&
    value.length === options.length &&
    value.filter(v => options.find(o => o.value === v)).length ===
      options.length
  ) {
    return <span>{allOption.text || allOption.value}</span>;
  }

  const exceed = selectedOptions.length > 5;

  return (
    <Tooltip
      title={selectedOptions.map((o, index) => (
        <Fragment key={o.value}>
          {o.text || o.value}
          {index < selectedOptions.length - 1 && ", "}
        </Fragment>
      ))}
    >
      <Text overflow>
        {selectedOptions.slice(0, 5).map((o, index) => (
          <span key={o.value}>
            {o.text || o.value}
            {index < selectedOptions.length - 1 && ", "}
          </span>
        ))}
        {exceed && <span>...</span>}
      </Text>
    </Tooltip>
  );
}

ValueBrief.displayName = "SelectValueBrief";
