import React, { useRef, useState, useEffect } from "react";
import classNames from "classnames";
import { useTranslation } from "../i18n";
import { SelectOptionWithGroup } from "./SelectOption";
import { SearchBox } from "../searchbox";
import { Dropdown, DropdownKeyDownContext } from "../dropdown";
import { useDefaultValue, ChangeContext } from "../form";
import { VirtualizedList } from "../list/VirtualizedList";
import { SelectProps } from "./SelectProps";
import { Text } from "../text";
import { EmptyTip } from "../tips";
import { Button } from "../button";
import { KeyMap } from "../_util/key-map";
import { injectValue } from "../_util/inject-value";
import { useDefault } from "../_util/use-default";
import { noop } from "../_util/noop";
import { warn } from "../_util/warn";
import { useConfig } from "../_util/config-context";
import { searchFilter } from "../_util/search-filter";
import { mergeRefs } from "../util";
import { isMobile } from "../_util/is-mobile";
import { useUnmounted } from "../_util/use-unmounted";
import { getListItems } from "./util";

const defaultFilter = (
  inputValue: string,
  { text, value }: SelectOptionWithGroup
) => {
  const optionText = String(typeof text === "string" ? text : value);
  return searchFilter(optionText, inputValue);
};

const trueFilter = () => true;

/**
 * 模拟 Select
 */
export const SimulateSelect = React.forwardRef(function SimulateSelect(
  props: SelectProps,
  ref: React.Ref<HTMLElement>
) {
  const t = useTranslation();
  const { classPrefix } = useConfig();
  const {
    virtual = true,
    value,
    onChange,
    options = [],
    groups,
    placeholder = t.pleaseSelect,
    button,
    size,
    disabled,
    onScrollBottom,
    listHeight,
    listWidth,
    bottomTips,
    boxStyle = {},
    onOpen = noop,
    onClose = noop,
    searchable,
    searchPlaceholder = "",
    onSearch = noop,
    filter: customizeFilter = defaultFilter,
    filterRender,
    defaultSearchValue = "",
    searchValue,
    onSearchValueChange,
    autoClearSearchValue = true,
    clearable,
    refreshable,
    onRefresh = noop,
    footer,
    appearance = props.appearence, // eslint-disable-line react/destructuring-assignment
    ...dropdownProps
  } = useDefaultValue(props, null);

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

  const [refreshing, setRefreshing] = useState<boolean>(false);

  const dropdownRef = useRef<HTMLDivElement>(null);

  const vListRef = useRef(null);

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

  const unmountedRef = useUnmounted();

  const filter = searchable ? customizeFilter : trueFilter;

  function focus() {
    if (!isMobile && searchable) {
      setTimeout(() => {
        if (InputRef.current) {
          InputRef.current.focus();
        }
      }, 100); // 第一次展开时 Input 还未渲染
    }
  }

  const selected =
    value != null ? options.find(x => x.value === value) : undefined;

  // 选项消失不影响已选中值显示
  const [buttonOption, setButtonOption] = useState<SelectOptionWithGroup>(
    () => {
      if (!selected) {
        return undefined;
      }
      return selected;
    }
  );

  // options 中有选中项时改变 buttonOption
  useEffect(() => {
    if (selected) {
      setButtonOption(selected);
    }
  }, [options]); // eslint-disable-line react-hooks/exhaustive-deps

  // value 变化时改变 buttonOption 及搜索框状态
  useEffect(() => {
    if (selected) {
      setButtonOption(selected);
    } else {
      setButtonOption(undefined);
    }
  }, [value]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    if (options.length > 10 && !searchable) {
      warn("Select 中 `options` 的项数较多，建议开启搜索功能");
    }
  }, [options, searchable]);

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

  // 按钮文字
  const buttonText: React.ReactNode =
    // eslint-disable-next-line no-nested-ternary
    buttonOption
      ? typeof buttonOption.text === "undefined"
        ? buttonOption.value
        : buttonOption.text
      : buttonPlaceholder || t.pleaseSelect;

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

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

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

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

  const selectedIndex = items.findIndex(
    x => x.type === "option" && x.option.value === value
  );

  const [currentIndex, setCurrentIndex] = useState<number>(-1);

  useEffect(() => {
    setCurrentIndex(selectedIndex);
  }, [selectedIndex]);

  const count = items.length;

  function handleKeyDown(
    source: "dropdown" | "input",
    event: React.KeyboardEvent,
    // input 托管事件时没有 context，此时一定为 open 状态
    context: DropdownKeyDownContext = { open: true },
    close: () => void = () => null
  ) {
    const getOptionIndex = (current: number, step: number = 1) => {
      let flag = 1;
      let index = (current + step + count) % count;
      let item = items[index % count];
      while (
        flag < count &&
        (item.type !== "option" || item.option?.disabled)
      ) {
        flag += 1;
        index = (index + step + count) % count;
        item = items[index % count];
      }
      return index;
    };

    const ok = () => {
      const item = items[currentIndex % count];

      if (item && item.type === "option" && context.open) {
        // 输入时不能使用空格选中
        if (item.option.disabled) {
          return false;
        }
        onChange(item.option.value, { event, option: item.option });
        close();
      }
      return true;
    };

    switch (event.key) {
      case KeyMap.Space:
        if (source === "input") {
          return false;
        }
        event.preventDefault();
        return ok();

      case KeyMap.Enter:
        return ok();

      case KeyMap.Up:
        event.preventDefault();
        if (count && context.open) {
          setCurrentIndex(c => {
            const index = getOptionIndex(c, -1);
            if (vListRef.current) {
              vListRef.current.scrollToItem(index);
            }
            return index;
          });
        }
        break;

      case KeyMap.Down:
        event.preventDefault();
        if (count && context.open) {
          setCurrentIndex(c => {
            const index = getOptionIndex(c);
            if (vListRef.current) {
              vListRef.current.scrollToItem(index);
            }
            return index;
          });
        }
        break;

      case KeyMap.Esc:
        close();
        break;
    }
    return true;
  }

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

  const select = (
    <Dropdown
      updateOnChildrenChange
      preventMouseDown={!searchable && !footer}
      ref={mergeRefs(ref, dropdownRef)}
      clickClose={false}
      size={size}
      boxStyle={boxStyle}
      appearance={appearance}
      footer={footer}
      button={
        typeof button !== "undefined"
          ? injectValue(button)(buttonOption)
          : buttonText
      }
      onOpen={() => {
        onOpen();
        focus();
      }}
      onClose={() => {
        onClose();
        // 已经未空值不再次置回初始
        if (autoClearSearchValue) {
          setTimeout(() => {
            if (!unmountedRef.current) {
              setInputValue("", {} as any);
            }
          }, 100);
        }
      }}
      onKeyDown={(event, context) => handleKeyDown("dropdown", event, context)}
      clearable={value != null && clearable}
      onClear={event => {
        onChange(null, { event, option: null });
        setButtonOption(null);
      }}
      disabled={disabled || refreshing}
      {...dropdownProps}
    >
      {close => (
        <>
          {searchable && (
            <SearchBox
              simple
              ref={InputRef}
              value={inputValue}
              onChange={(value, context) => {
                setInputValue(value, context);
                onSearch(value, context);
              }}
              onClear={focus}
              placeholder={searchPlaceholder}
              onKeyDown={event =>
                handleKeyDown("input", event, undefined, close)
              }
              onSearch={onSearch}
            />
          )}

          <VirtualizedList
            virtual={virtual}
            containerStyle={boxStyle}
            containerClassName={dropdownProps.boxClassName}
            items={items.map((item, index) => {
              if (item.type === "option") {
                const { option } = item;
                return {
                  ...item,
                  props: {
                    disabled: option.disabled,
                    selected: option === selected,
                    current: currentIndex === index,
                    onClick: event => {
                      onChange(option.value, { event, option });
                      close();
                    },
                    tooltip: option.tooltip,
                    className:
                      hasGroup && !option.groupKey
                        ? `${classPrefix}-list__item--single`
                        : "",
                    "aria-selected": option === selected,
                  },
                };
              }
              return item;
            })}
            virtualizedRef={vListRef}
            type={hasGroup ? "option-group" : "option"}
            onScrollBottom={onScrollBottom}
            listHeight={listHeight}
            initialScrollOffsetIndex={selectedIndex}
          />
        </>
      )}
    </Dropdown>
  );

  if (refreshable) {
    return (
      <div
        className={classNames(`${classPrefix}-select-refresh`, {
          [`${classPrefix}-select-refresh--borderless`]:
            appearance !== "button",
        })}
        style={size === "full" ? { display: "block" } : undefined}
      >
        {select}
        <Button
          type="icon"
          icon="refresh"
          htmlType="button"
          className={classNames({ "is-loading": refreshing })}
          disabled={disabled || refreshing}
          onClick={async () => {
            setRefreshing(true);
            await onRefresh();
            setRefreshing(false);
          }}
        />
      </div>
    );
  }

  return select;
});

SimulateSelect.displayName = "SimulateSelect";
