import React, { useState, useRef, useEffect, useMemo, forwardRef } from "react";
import classNames from "classnames";
import { TreeNode, TreeNodeProps } from "./TreeNode";
import { CheckTree, CheckTreeRelation } from "../../checktree";
import { TreeContext, TreeContextValue } from "./TreeContext";
import { useDefault } from "../../_util/use-default";
import { isChildOfType } from "../../_util/is-child-of-type";
import { StyledProps, Omit } from "../../_type";
import { useConfig } from "../../_util/config-context";
import { warn } from "../../_util/warn";

export interface TreeData extends Omit<TreeNodeProps, "children"> {
  /**
   * 子节点数据
   */
  children?: TreeData[];
}

export interface TreeProps extends StyledProps {
  /**
   * 树节点数据，如果设置则无需手动构造 <TreeNode />
   */
  data?: TreeData[];

  /**
   * 树节点是否支持选择
   *
   * @default false
   */
  selectable?: boolean;

  /**
   * 节点选择是否完全受控（父子节点状态取消关联）
   *
   * @default false
   */
  selectStrictly?: boolean;

  /**
   * 默认选中的节点
   */
  defaultSelectedIds?: string[];

  /**
   * 选中的节点（受控）
   */
  selectedIds?: string[];

  /**
   * 选择事件回调
   */
  onSelect?: (
    selectedIds: string[],
    context?: { selected: boolean; nodeId: string }
  ) => void;

  /**
   * 树节点是否支持点击高亮
   *
   * @default false
   */
  activable?: boolean;

  /**
   * 默认高亮的节点
   */
  defaultActiveIds?: string[];

  /**
   * 高亮的节点（受控）
   */
  activeIds?: string[];

  /**
   * 点击高亮事件回调
   */
  onActive?: (
    activeIds: string[],
    context?: { active: boolean; nodeId: string }
  ) => void;

  /**
   * 默认展开的节点
   */
  defaultExpandedIds?: string[];

  /**
   * 展开的节点（受控）
   */
  expandedIds?: string[];

  /**
   * 展开/收起事件回调
   */
  onExpand?: (
    expandedIds: string[],
    context?: { expanded: boolean; nodeId: string }
  ) => void;

  /**
   * 是否点击整个节点范围可控制展开
   *
   * *默认点击范围为图标区域*
   *
   * @default false
   * @since 2.4.0
   */
  fullExpandable?: boolean;

  /**
   * 异步加载数据
   *
   * **需要返回 Promise**
   *
   * 节点展开时在 onExpand 前被调用，直到 Promise resolve 后调用 onExpand
   */
  onLoad?: TreeContextValue["onLoad"];

  /**
   * 异步加载数据失败回调
   *
   * onLoad Promise reject 后调用
   */
  onLoadError?: TreeContextValue["onLoadError"];

  /**
   * 树节点的展开/收起图标
   *
   * @docType React.ReactNode | (context: { expanded: boolean }) => React.ReactNode
   */
  switcherIcon?: TreeContextValue["switcherIcon"];

  /**
   * 包含的树节点 <TreeNode />
   */
  children?: React.ReactNode;
}

/**
 * CheckTree 转换
 *
 * 叶节点 -> 全部选中节点
 */
function getAllIds(
  selectedIds: string[],
  relations: CheckTreeRelation,
  childrenMap: Map<string, Set<string>>
): string[] {
  const nodes = [...selectedIds];
  // 插入缺失的父节点
  for (const id of nodes) {
    const pid = relations[id];
    const childrenSet = childrenMap.get(pid);
    // 加入子节点全部选中的父节点
    if (
      pid &&
      childrenSet &&
      !nodes.includes(pid) &&
      ![...childrenSet].find(cid => !nodes.includes(cid))
    ) {
      nodes.push(pid);
    }
  }
  // 插入缺失的子节点
  for (const id of nodes) {
    const children = [...(childrenMap.get(id) || [])];
    children.forEach(cid => {
      if (!nodes.includes(cid)) {
        nodes.push(cid);
      }
    });
  }
  return nodes;
}

/**
 * CheckTree 转换
 *
 * 全部选中节点 -> 叶节点
 */
function getLeafIds(
  selectedIds: string[],
  relations: CheckTreeRelation,
  childrenMap: Map<string, Set<string>>
): string[] {
  const all = getAllIds(selectedIds, relations, childrenMap);
  const nodes = [];
  all.forEach(id => {
    if (!childrenMap.get(id)) {
      nodes.push(id);
    }
  });
  return nodes;
}

export function isTreeNode(
  node: React.ReactNode
): node is React.ReactComponentElement<typeof TreeNode, TreeNodeProps> {
  return isChildOfType(node, TreeNode);
}

/**
 * 遍历子节点
 */
function traverse(
  rootId: string,
  children: React.ReactNode,
  tree: {
    relations: CheckTreeRelation;
    childrenMap: Map<string, Set<string>>;
    disabledIds: string[];
  }
) {
  if (!children) {
    return;
  }
  const { relations, childrenMap, disabledIds } = tree;

  React.Children.forEach<React.ReactNode>(children, child => {
    if (
      isTreeNode(child) ||
      (React.isValidElement(child) && typeof child.props.id !== "undefined")
    ) {
      const { id, selectable, disableSelect } = child.props;
      if (selectable !== false) {
        // 非最外层根
        if (rootId) {
          relations[id] = rootId;
          if (!childrenMap.has(rootId)) {
            childrenMap.set(rootId, new Set());
          }
          childrenMap.get(rootId).add(id);
        }
        // 禁用
        if (disableSelect) {
          disabledIds.push(id);
        }
      }
      // 当前结点不可选时其子结点 rootId 为当前 rootId
      traverse(selectable !== false ? id : rootId, child.props.children, tree);
    } else if (React.isValidElement(child)) {
      // eslint-disable-next-line dot-notation
      traverse(rootId, child.props["children"], tree);
    }
  });
}

/**
 * 构造树节点
 */
function renderTreeNodes(data: TreeData[]) {
  return data.map(({ children, id, ...props }) => {
    if (children && children.length > 0) {
      return (
        <TreeNode key={id} id={id} {...props}>
          {renderTreeNodes(children)}
        </TreeNode>
      );
    }
    return <TreeNode key={id} id={id} {...props} />;
  });
}

export const TreeView = React.forwardRef(function TreeView(
  {
    selectable,
    selectStrictly,
    selectedIds,
    defaultSelectedIds = [],
    onSelect,
    activable,
    activeIds,
    defaultActiveIds = [],
    onActive,
    expandedIds,
    defaultExpandedIds = [],
    onExpand = () => null,
    fullExpandable,
    onLoad,
    onLoadError,
    switcherIcon,
    className,
    children,
    ...props
  }: Omit<TreeProps, "data">,
  ref: React.Ref<HTMLDivElement>
) {
  // 开启选择时内容区不能用于展开
  if (selectable) {
    // eslint-disable-next-line no-param-reassign
    fullExpandable = false;
  }

  const { classPrefix } = useConfig();
  const childrenMap = useRef<Map<string, Set<string>>>(new Map());
  const [relations, setRelations] = useState<CheckTreeRelation>({});
  const [disabledIds, setDisabledIds] = useState<string[]>([]);

  const [selectedNodes, setSelectedNodes] = useDefault(
    selectedIds,
    defaultSelectedIds,
    onSelect
  );

  const [activeNodes, setActiveNodes] = useDefault(
    activeIds,
    defaultActiveIds,
    onActive
  );

  const [expandedNodes, setExpandedNodes] = useDefault(
    expandedIds,
    defaultExpandedIds,
    onExpand
  );

  useEffect(() => {
    childrenMap.current = new Map();
    if (selectable) {
      const disabledIds = [];
      if (!selectStrictly) {
        const relations = {};
        traverse(undefined, children, {
          relations,
          childrenMap: childrenMap.current,
          disabledIds,
        });
        setRelations(relations);
      } else {
        traverse(undefined, children, {
          relations: {},
          childrenMap: new Map(),
          disabledIds,
        });
      }
      setDisabledIds(disabledIds);
    }
  }, [children, selectStrictly, selectable]);

  return (
    <div
      ref={ref}
      className={classNames(`${classPrefix}-tree`, className, {
        [`${classPrefix}-tree--checkbox-separate`]: selectable && activable,
        [`${classPrefix}-tree--full-line`]: fullExpandable,
      })}
      {...props}
    >
      <TreeContext.Provider
        value={{
          selectable,
          activable,
          activeIds: activeNodes,
          expandedIds: expandedNodes,
          onActive: activeId =>
            setActiveNodes([activeId], { active: true, nodeId: activeId }),
          onExpand: (nodeId, expanded) =>
            setExpandedNodes(
              expanded
                ? [...expandedNodes, nodeId]
                : expandedNodes.filter(id => id !== nodeId),
              { nodeId, expanded }
            ),
          onLoad,
          onLoadError,
          switcherIcon,
          fullExpandable,
        }}
      >
        <ul>
          {selectable ? (
            <CheckTree
              relations={relations}
              value={getLeafIds(selectedNodes, relations, childrenMap.current)}
              onChange={(value, { check }) =>
                setSelectedNodes(
                  getAllIds(value, relations, childrenMap.current),
                  { nodeId: check.name, selected: check.value }
                )
              }
              disabledNames={disabledIds}
            >
              {children}
            </CheckTree>
          ) : (
            children
          )}
        </ul>
      </TreeContext.Provider>
    </div>
  );
});

TreeView.displayName = "TreeView";

export const Tree = forwardRef(function Tree(
  { data, children, ...props }: TreeProps,
  ref: React.Ref<HTMLDivElement>
) {
  useEffect(() => {
    warn("请不要在 Tree 内嵌套使用自定义组件，该用法将在后续版本中被废弃。");
  }, []);

  return (
    <TreeView ref={ref} {...props}>
      {children}
    </TreeView>
  );
});

Tree.displayName = "Tree";
