import React, { useState, useMemo } from "react";
import warning from "warning";
import { ControlledProps, useDefaultValue } from "../form/controlled";
import { CheckContext, CheckChangeContext, CheckProps } from "../check";

export interface CheckTreeChangeContext extends CheckChangeContext {
  /**
   * 连选相关信息
   */
  shiftSelect?: {
    /**
     * 起始节点名
     */
    startName: string;
    /**
     * 结束
     */
    endName: string;
  };
}

export type ValueMode = "all" | "onlyLeaf" | "parentFirst";

export interface CheckTreeProps extends ControlledProps<string[]> {
  /**
   * 已勾选的叶子节点的 name 集合
   */
  value?: string[];

  /**
   * 勾选的节点变更时回调
   */
  onChange?: (value: string[], context: CheckTreeChangeContext) => void;

  /**
   * 提供树的节点关系，每个子节点对应其父节点
   */
  relations: CheckTreeRelation;

  /**
   * 组件树内容，内部的 <Checkbox /> 状态将被托管
   */
  children?: React.ReactNode;

  /**
   * 禁用的控件 name
   */
  disabledNames?: string[];

  /**
   * 是否支持按 Shift 快捷键时多选
   *
   * - `"virtualized"` - 适配虚拟滚动，`onChange` 不直接返回变更后的 `value`
   *
   * @default true
   */
  shiftSelect?: boolean | "virtualized";

  /**
   * 结点选择值的描述方式
   *
   * - `"all"` - 使用全部层级
   * - `"onlyLeaf"` - 仅使用子节点
   * - `"parentFirst"` - 当子节点全部选中时，仅使用父节点
   *
   * @default "onlyLeaf"
   * @since 2.7.0
   */
  valueMode?: ValueMode;

  // /**
  //  * disabled 的组件是否从全选的范围内排除
  //  * @default true
  //  */
  // excludeDisabledFromParent?: boolean;
}

/**
 * CheckTree 关系定义 interface
  ```ts
  interface CheckTreeRelation {
    [child: string]: string;
  }
  ```
 */
export interface CheckTreeRelation {
  [child: string]: string;
}

/**
 * CheckTree 转换
 *
 * 节点 -> 全部选中节点
 */
export function getAllIds(
  selectedIds: string[],
  parentMap: Map<string, string>,
  childrenMap: Map<string, Set<string>>
): string[] {
  const allIds: string[] = [];
  const nodeSet = new Set<string>(selectedIds);

  // 遍历当前节点下子树
  const traverse = (id: string) => {
    // 待检查状态
    let checked = true;
    // 当前状态
    const selfChecked = nodeSet.has(id);

    const children = childrenMap.get(id);

    if (!children) {
      // 叶子节点只需要判断自身
      checked = selfChecked;
    } else {
      children.forEach(cid => {
        // 如果父节点已经选中则直接选中其子节点
        if (selfChecked) {
          nodeSet.add(cid);
        }
        traverse(cid);
        if (!nodeSet.has(cid)) {
          checked = false;
        }
      });
    }

    if (checked) {
      nodeSet.add(id);
      allIds.push(id);
    }
  };

  for (const [id] of childrenMap) {
    // 根节点
    if (!parentMap.has(id)) {
      traverse(id);
    }
  }

  // 单层无树结构场景
  for (const id of selectedIds) {
    if (!parentMap.has(id)) {
      traverse(id);
    }
  }

  return allIds;
}

/**
 * 值模式转换
 */
export function useValueMode(
  rawValue: string[] = [],
  rawOnChange: (value: string[], context?: any) => void,
  parentMap: Map<string, string>,
  childrenMap: Map<string, Set<string>>,
  mode: ValueMode = "onlyLeaf"
): [string[], (value: string[], context?: any) => void] {
  return useMemo(() => {
    const all = getAllIds(rawValue, parentMap, childrenMap);

    if (mode === "all") {
      const value = [];
      all.forEach(id => {
        // 叶节点
        if (!childrenMap.get(id)) {
          value.push(id);
        }
      });
      const onChange = (value, ...args) => {
        rawOnChange(getAllIds(value, parentMap, childrenMap), ...args);
      };
      return [value, onChange];
    }

    if (mode === "parentFirst") {
      const value = [];
      all.forEach(id => {
        // 叶节点
        if (!childrenMap.get(id)) {
          value.push(id);
        }
      });
      const onChange = (onlyLeafValue, ...args) => {
        const all = getAllIds(onlyLeafValue, parentMap, childrenMap);
        const allSet = new Set(all);
        const invalidSet = new Set<string>();
        all.forEach(id => {
          // 父节点此刻被选中则该节点无效
          if (parentMap.has(id) && allSet.has(parentMap.get(id))) {
            invalidSet.add(id);
          }
        });
        rawOnChange(
          all.filter(id => !invalidSet.has(id)),
          ...args
        );
      };
      return [value, onChange];
    }

    // onlyLeaf
    const value = all.filter(i => {
      return !childrenMap.has(i);
    });
    const onChange = rawOnChange;
    return [value, onChange];
  }, [childrenMap, mode, parentMap, rawOnChange, rawValue]);
}

export function getRelationMap(relations: CheckTreeRelation) {
  // 子节点 -> 父节点
  const parentMap = new Map<string, string>();
  // 父节点 -> 子节点组
  const childrenMap = new Map<string, Set<string>>();

  for (const [child, parent] of Object.entries(relations)) {
    parentMap.set(child, parent);
    let childrenSet = childrenMap.get(parent);
    if (!childrenSet) {
      childrenSet = new Set<string>();
      childrenMap.set(parent, childrenSet);
    }
    childrenSet.add(child);
  }

  return { parentMap, childrenMap };
}

export function CheckTree(props: CheckTreeProps) {
  const {
    relations,
    children,
    shiftSelect = true,
    disabledNames = [],
    valueMode = "onlyLeaf",
    ...restProps
  } = useDefaultValue(props);
  const [prevCheck, setPrevCheck] = useState<string>(null);

  const { parentMap, childrenMap } = useMemo(() => getRelationMap(relations), [
    relations,
  ]);

  // 模式转换后的 value/onChange
  const [value, onChange] = useValueMode(
    restProps.value,
    restProps.onChange,
    parentMap,
    childrenMap,
    valueMode
  );

  const disabledSet = useMemo(
    () => completeDisabledNames(disabledNames, childrenMap),
    [childrenMap, disabledNames]
  );

  const model = useMemo(
    () =>
      getTreeModel(
        parentMap,
        childrenMap,
        value,
        disabledSet,
        shiftSelect,
        prevCheck,
        setPrevCheck
      ),
    [childrenMap, disabledSet, parentMap, prevCheck, shiftSelect, value]
  );

  return (
    <CheckContext.Provider
      value={{
        inject(checkProps) {
          if (checkProps.type !== "checkbox") {
            return checkProps;
          }

          // 没有 name 时不受控
          if (typeof checkProps.name === "undefined") {
            return checkProps;
          }

          warning(
            typeof checkProps.value === "undefined",
            'Component managed by <CheckTree> will ignore the "value" prop'
          );

          // [TODO] Table seclectable
          // warning(
          //   typeof checkProps.disabled === 'undefined',
          //   'Component managed by <CheckTree> will ignore the "disabled" prop'
          // );

          model.visit(checkProps);

          const checkName = checkProps.name;

          return {
            ...model.getNodeState(checkName),
            disabled: disabledSet.has(checkProps.name),
            // 支持 checkbox 上的 onChange 处理时阻止默认的处理行为
            onChange: (checked, context) => {
              if (typeof checkProps.onChange === "function") {
                checkProps.onChange(checked, context);
                if (context.event.defaultPrevented) {
                  return;
                }
              }
              const [selection, changeContext = {}] = model.handleChange(
                checkProps.name,
                checked,
                context.event
              );

              if (typeof onChange === "function") {
                onChange(selection, { ...context, ...changeContext });
              }
            },
            ...checkProps,
          };
        },
      }}
    >
      {children}
    </CheckContext.Provider>
  );
}

export function completeDisabledNames(
  names: string[],
  childrenMap: Map<string, Set<string>>
): Set<string> {
  const nameSet = new Set<string>(names);
  for (let i = 0; i < names.length; ++i) {
    const childrenSet = childrenMap.get(names[i]);
    if (childrenSet) {
      childrenSet.forEach(child => {
        if (!nameSet.has(child)) {
          nameSet.add(child);
          names.push(child);
        }
      });
    }
  }

  return nameSet;
}

function isEventWithShiftKey(
  event: React.SyntheticEvent
): event is React.SyntheticEvent & { shiftKey: boolean } {
  return "shiftKey" in event;
}

function getTreeModel(
  parentMap: Map<string, string>,
  childrenMap: Map<string, Set<string>>,
  selection: string[],
  disabledSet: Set<string>,
  shiftSelect: CheckTreeProps["shiftSelect"],
  prevCheck: string,
  setPrevCheck: (name: string) => void
) {
  // selected leaf
  const selectionSet = new Set<string>(selection);

  // find node state via checkName
  const nodeStateMap = new Map<
    string,
    { value: boolean; indeterminate: boolean }
  >();

  // classify check by level
  let levelMap: string[][] = [[]];

  const visited: CheckProps[] = [];

  function visit(check: CheckProps) {
    if (!visited.find(({ name }) => name === check.name)) {
      visited.push(check);
    }
  }

  function calcLevel() {
    const roots = visited
      .filter(check => !parentMap.has(check.name))
      .map(c => c.name);

    if (roots.length) {
      let curLevel = 0;
      levelMap = [roots];
      const nameSet = new Set<string>(roots);

      while (true) {
        levelMap[curLevel + 1] = [];
        /* eslint-disable no-loop-func */
        levelMap[curLevel].forEach(checkName => {
          if (childrenMap.has(checkName)) {
            const children = childrenMap.get(checkName);
            visited.forEach(({ name }) => {
              if (children.has(name) && !nameSet.has(name)) {
                nameSet.add(name);
                levelMap[curLevel + 1].push(name);
              }
            });
          }
        });
        /* eslint-enable no-loop-func */
        curLevel += 1;
        if (!levelMap[curLevel].length) {
          break;
        }
      }
    }
  }

  function getVisitedIndex(checkName: string) {
    let index;
    let level;
    levelMap.forEach((checks, _level) => {
      checks.forEach((name, _index) => {
        if (name === checkName) {
          level = _level;
          index = _index;
        }
      });
    });
    return [level, index];
  }

  function getVisitedName(level: number, index: number) {
    if (!levelMap[level]) {
      return undefined;
    }
    return levelMap[level][index];
  }

  function getNodeState(checkName: string) {
    if (nodeStateMap.has(checkName)) {
      return nodeStateMap.get(checkName);
    }

    const children = childrenMap.get(checkName);

    // 叶子节点直接看是否在选取里
    if (!children) {
      const nodeState = {
        value: selectionSet.has(checkName),
        indeterminate: false,
      };
      nodeStateMap.set(checkName, nodeState);
      return nodeState;
    }

    let hasTouchedChild = false;
    let isAllChildrenChecked = !disabledSet.has(checkName);
    for (const child of children) {
      if (child === checkName) {
        console.error(
          `Warning: Each node should have a unique "value". (value: "${checkName}")`
        );
        // eslint-disable-next-line no-continue
        continue;
      }
      const childState = getNodeState(child);
      if (childState.value || childState.indeterminate) {
        hasTouchedChild = true;
      }
      /** except disabled node */
      if (!childState.value && !disabledSet.has(child)) {
        isAllChildrenChecked = false;
      }
    }
    const nodeState = {
      value: isAllChildrenChecked,
      indeterminate: !isAllChildrenChecked && hasTouchedChild,
    };
    nodeStateMap.set(checkName, nodeState);

    return nodeState;
  }

  function setNodeState(checkName: string, checked: boolean) {
    // disabled
    if (disabledSet.has(checkName)) {
      return;
    }

    const children = childrenMap.get(checkName);

    // 叶子节点，直接设置
    if (!children) {
      if (checked) {
        selectionSet.add(checkName);
      } else {
        selectionSet.delete(checkName);
      }
    }
    // 递归设置
    else {
      for (const child of children) {
        setNodeState(child, checked);
      }
    }
  }

  function getSelection() {
    return Array.from(selectionSet);
  }

  function handleChange(
    checkName: string,
    checked: boolean,
    event: React.SyntheticEvent
  ): [string[], Pick<CheckTreeChangeContext, "shiftSelect">] {
    // 多选
    if (shiftSelect) {
      if (event && isEventWithShiftKey(event) && event.shiftKey) {
        calcLevel();
        const [level, index] = getVisitedIndex(checkName);
        if (prevCheck !== null) {
          const [prevLevel, prevIndex] = getVisitedIndex(prevCheck);
          if (level === prevLevel) {
            // 清空文字选区
            if ("getSelection" in window) {
              window.getSelection().removeAllRanges();
            }

            const min = index < prevIndex ? index : prevIndex;
            const max = index > prevIndex ? index : prevIndex;
            // 虚拟滚动依赖 startName/endName 在外部处理选中
            if (shiftSelect !== "virtualized") {
              for (let i = min; i <= max; ++i) {
                setNodeState(getVisitedName(level, i), true);
              }
            }
            setPrevCheck(checkName);
            return [
              getSelection(),
              {
                shiftSelect: {
                  startName: getVisitedName(level, min),
                  endName: getVisitedName(level, max),
                },
              },
            ];
          }
        }
      }

      if (checked) {
        setPrevCheck(checkName);
      } else {
        setPrevCheck(null);
      }
    }

    setNodeState(checkName, checked);

    return [getSelection(), {}];
  }

  return {
    visit,
    getNodeState,
    setNodeState,
    getSelection,
    handleChange,
  };
}
