import React, {
  useRef,
  useEffect,
  useState,
  useCallback,
  useMemo,
} from "react";
import classNames from "classnames";
import { useDefaultValue } from "../form";
import { Popover } from "../popover";
import { Input } from "../input";
import { Icon } from "../icon";
import { Tag } from "../tag";
import {
  CascaderMultipleProps,
  CascaderBoxProps,
  CascaderData,
  CascaderOption,
} from "./CascaderProps";
import { CascaderMenuBox } from "./CascadeMenu";
import { CascaderBox, getOptions } from "./CascaderBox";
import { CascaderSearch } from "./CascaderSearch";
import { TagSelectInput } from "../tagselect/TagSelectInput";
import { useDefault } from "../_util/use-default";
import { useConfig } from "../_util/config-context";
import { useTranslation } from "../i18n";
import { noop } from "../_util/noop";
import { mergeEventProps } from "../_util/merge-event-props";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { mergeRefs } from "../util";
import {
  defaultAllValue,
  getRelations,
  separator,
} from "./CascadeMenu/CascaderMultiMenuLists";
import {
  getAllIds,
  getRelationMap,
  useValueMode,
} from "../checktree/CheckTree";

const defaultValueRender = (options: CascaderOption[]) => {
  const leaf = options[options.length - 1];
  return leaf?.label || leaf?.value || "-";
};

function isEqual(a: string[], b: string[]) {
  return a.length === b.length && a.every((str, i) => str === b[i]);
}

export const CascaderMultiple = forwardRefWithStatics(
  function CascaderMultiple(
    props: CascaderMultipleProps & { data: CascaderData },
    ref: React.Ref<HTMLDivElement>
  ) {
    const t = useTranslation();
    const { classPrefix } = useConfig();
    const {
      data,
      value: rawValue,
      onChange: rawOnChange,
      onLoad,
      disabled,
      style,
      className,
      placeholder = t.pleaseSelect,
      defaultOpen = false,
      open,
      onOpenChange = noop,
      placement = "bottom-start",
      placementOffset = 5,
      closeOnScroll = true,
      escapeWithReference,
      popupContainer,
      overlayClassName,
      overlayStyle,
      clearable,
      searchable,
      filter,
      type,
      expandTrigger,
      all,
      valueRender = defaultValueRender,
      valueMode = "onlyLeaf",
      showMode = "onlyLeaf",
      tips,
      bottomTips,
      onScrollBottom = noop,
      onSearch = noop,
    } = useDefaultValue(props, []);
    const [hover, setHover] = useState<boolean>(false);
    const scheduleUpdateRef = useRef<() => void>(null);
    useEffect(() => {
      if (scheduleUpdateRef.current) {
        scheduleUpdateRef.current();
      }
    }, [rawValue]);

    const [isOpened, setIsOpened] = useDefault(open, defaultOpen, onOpenChange);

    const searchInputRef = useRef<HTMLInputElement>(null);
    const [searchValue, setSearchValue] = useState<string>("");
    const [fullSearchValue, setFullSearchValue] = useState<string>("");
    const [wrapperWidth, setWrapperWidth] = useState<number>(0);

    const Box = type === "menu" ? CascaderMenuBox : CascaderBox;

    /**
     * -----------
     * 值类型转换
     * -----------
     */
    const { parentMap, childrenMap } = useMemo(() => {
      const { relations } = getRelations(data, type === "menu" ? all : false);
      return getRelationMap(relations);
    }, [all, data, type]);

    // 拼接多级值
    const [joinedValue, joinedOnChange] = useMemo(() => {
      const joinedValue = rawValue.map(list => list.join(separator));
      const joinedOnChange = (value: string[], context) => {
        rawOnChange(
          value.map(i => i.split(separator)),
          {
            event: context?.event,
            options: value.map(i => getOptions(data, i.split(separator))),
          }
        );
      };
      return [joinedValue, joinedOnChange];
    }, [data, rawOnChange, rawValue]);

    // 只包含叶节点模式的拼接后的 value/onChange
    const [onlyLeafJoinedValue, onlyLeafJoinedOnChange] = useValueMode(
      joinedValue,
      joinedOnChange,
      parentMap,
      childrenMap,
      valueMode
    );

    // 只包含叶节点模式的 value/onChange
    const [onlyLeafValue, onlyLeafOnChange] = useMemo(() => {
      const onlyLeafValue = onlyLeafJoinedValue.map(i => i.split(separator));
      const onlyLeafOnChange = (value: string[][], ...args) => {
        onlyLeafJoinedOnChange(
          value.map(i => i.join(separator)),
          ...args
        );
      };
      return [onlyLeafValue, onlyLeafOnChange];
    }, [onlyLeafJoinedOnChange, onlyLeafJoinedValue]);

    const isSelected = useCallback(
      (value: string[]) => !!onlyLeafValue.find(v => isEqual(v, value)),
      [onlyLeafValue]
    );

    const onSelect: CascaderBoxProps["onSelect"] = (value, context) => {
      const next = [...onlyLeafValue, value];
      onlyLeafOnChange(next, {
        ...context,
        options: next.map(v => getOptions(data, v)),
      });
    };

    const onDeselect: CascaderBoxProps["onDeselect"] = (value, context) => {
      const next = onlyLeafValue.filter(v => !isEqual(v, value));
      onlyLeafOnChange(next, {
        ...context,
        options: next.map(v => getOptions(data, v)),
      });
    };

    // showMode 转换渲染节点
    const tags = useMemo(() => {
      // 全选父节点 + 叶节点
      const allValue = getAllIds(onlyLeafJoinedValue, parentMap, childrenMap);

      if (showMode === "parentFirst") {
        const allSet = new Set(allValue);
        const invalidSet = new Set<string>();
        allValue.forEach(id => {
          // 父节点此刻被选中则该节点无效
          if (parentMap.has(id) && allSet.has(parentMap.get(id))) {
            invalidSet.add(id);
          }
        });
        // 转换后的 valueList
        const showValueList = allValue
          .filter(id => !invalidSet.has(id))
          .map(i => i.split(separator));

        const optionsList = showValueList
          .map(v => getOptions(data, v))
          .filter(Boolean);

        return optionsList.map(options => {
          const key = options.map(i => i.value).join(separator);
          // all 配置比较特殊，无法在 getOptions 中找到
          if (key === ((all as any)?.value || defaultAllValue)) {
            options[0].label = (all as any)?.label || t.selectAllText;
          }
          return (
            <Tag
              key={key}
              onClose={event => {
                event.stopPropagation();
                if (disabled) {
                  return;
                }
                const next = onlyLeafValue.filter(value => {
                  let curKey = value.join(separator);
                  let result = true;
                  while (curKey) {
                    if (curKey === key) {
                      result = false;
                      break;
                    }
                    curKey = parentMap.get(curKey);
                  }
                  return result;
                });
                onlyLeafOnChange(next, {
                  event,
                  options: next.map(v => getOptions(data, v)),
                });
              }}
            >
              {valueRender(options)}
            </Tag>
          );
        });
      }

      // if (showMode === "onlyLeaf")
      // 转换后的 valueList
      const showValueList = allValue
        .filter(i => !childrenMap.has(i))
        .map(i => i.split(separator));

      const optionsList = showValueList
        .map(v => getOptions(data, v))
        .filter(Boolean);

      return optionsList.map(options => {
        const key = options.map(i => i.value).join(separator);
        return (
          <Tag
            key={key}
            onClose={event => {
              event.stopPropagation();
              if (disabled) {
                return;
              }
              const next = onlyLeafValue.filter(
                value => value.join(separator) !== key
              );
              onlyLeafOnChange(next, {
                event,
                options: next.map(v => getOptions(data, v)),
              });
            }}
          >
            {valueRender(options)}
          </Tag>
        );
      });
    }, [
      onlyLeafJoinedValue,
      parentMap,
      childrenMap,
      showMode,
      data,
      all,
      valueRender,
      t.selectAllText,
      disabled,
      onlyLeafValue,
      onlyLeafOnChange,
    ]);

    return (
      <Popover
        trigger={disabled ? "empty" : "click"}
        visible={isOpened}
        onVisibleChange={setIsOpened}
        onExited={() => {
          setSearchValue("");
          setFullSearchValue("");
        }}
        placement={placement || "bottom-start"}
        placementOffset={placementOffset}
        closeOnScroll={closeOnScroll}
        escapeWithReference={escapeWithReference}
        popupContainer={popupContainer}
        overlayClassName={overlayClassName}
        overlayStyle={overlayStyle}
        overlay={({ scheduleUpdate }) => {
          scheduleUpdateRef.current = scheduleUpdate;
          if (searchValue) {
            return (
              <CascaderSearch
                multiple
                data={data}
                searchValue={searchValue}
                filter={filter}
                changeOnSelect={false}
                isSelected={isSelected}
                onChange={(value, { selected, ...context }) => {
                  if (selected) {
                    onSelect(value, context);
                  } else {
                    onDeselect(value, context);
                  }
                }}
                tips={tips}
                bottomTips={bottomTips}
                onScrollBottom={onScrollBottom}
                onSearch={onSearch}
              />
            );
          }
          return (
            <Box
              multiple
              all={all}
              curValueList={onlyLeafValue}
              data={data}
              onChange={onlyLeafOnChange}
              onSelect={onSelect}
              onLoad={onLoad}
              onClose={() => setIsOpened(false)}
              changeOnSelect={false}
              scheduleUpdate={scheduleUpdate}
              isSelected={isSelected}
              onDeselect={onDeselect}
              expandTrigger={expandTrigger}
              tips={tips}
              bottomTips={bottomTips}
              onScrollBottom={onScrollBottom}
            />
          );
        }}
      >
        <div
          className={classNames(`${classPrefix}-cascader`, className, {
            "is-disabled": disabled,
          })}
          style={style}
          ref={mergeRefs(ref, ref => ref && setWrapperWidth(ref.clientWidth))}
          {...mergeEventProps(props, {
            onMouseEnter: () => setHover(true),
            onMouseLeave: () => setHover(false),
          })}
        >
          <div
            className={`${classPrefix}-cascader__input`}
            onClick={event => {
              if (disabled) {
                return;
              }
              if (searchable && searchInputRef.current) {
                event.stopPropagation();
                searchInputRef.current.focus();
                setIsOpened(true);
              }
            }}
          >
            <div className={`${classPrefix}-cascader__value`}>
              <Tag.Group>
                {tags}
                {searchable && (
                  <TagSelectInput
                    disabled={disabled}
                    className={`${classPrefix}-input--editor`}
                    ref={searchInputRef}
                    maxWidth={wrapperWidth}
                    reservedWidth={2}
                    value={searchValue}
                    onChange={value => {
                      setSearchValue(value);
                    }}
                    onInput={event => {
                      setFullSearchValue(event.currentTarget.value);
                    }}
                  />
                )}
              </Tag.Group>
            </div>
            {!fullSearchValue && (
              <Input
                readonly
                autoComplete="off"
                placeholder={onlyLeafValue.length ? "" : placeholder}
              />
            )}
          </div>
          {clearable && !disabled && hover && onlyLeafValue.length ? (
            <Icon
              type="dismiss"
              onClick={event => {
                event.stopPropagation();
                onlyLeafOnChange([], { event, options: [] });
              }}
            />
          ) : (
            <Icon type="arrowdown" />
          )}
        </div>
      </Popover>
    );
  },
  {
    defaultLabelAlign: "middle",
  }
);

CascaderMultiple.displayName = "CascaderMultiple";
