import React, {
  useMemo,
  useContext,
  forwardRef,
  useCallback,
  useEffect,
} from "react";
import classNames from "classnames";
import { TreeNode, TreeNodeInnerProps } from "./TreeNode";
import { CheckTree, CheckTreeRelation } from "../checktree";
import { TreeContext } from "./TreeContext";
import { useDefault } from "../_util/use-default";
import { isChildOfType } from "../_util/is-child-of-type";
import { useConfig } from "../_util/config-context";
import { forwardRefWithStatics } from "../_util/forward-ref-with-statics";
import { VirtualizedTreeList } from "./VirtualizedTreeList";
import { TreeNodeEventData, TreeData, TreeProps } from "./TreeProps";
import { Tree as DeprecatedTree } from "./deprecated/Tree";
import {
  checkTreeNode,
  getDefaultExpandedIds,
  getFilteredExpandedIds,
  getNodeShowState,
} from "./util";
import { noop } from "../_util/noop";

type InnerTreeData = TreeData & TreeNodeInnerProps;

interface TreeInfo {
  nodes: InnerTreeData[];
  relations: CheckTreeRelation;
  disabledIds: string[];
}

/**
 * 构造树节点
 */
function renderTree(
  data: TreeData[],
  level: number,
  pid?: string,
  pPathKey?: string,
  // 添加过滤函数
  filter?: (node: TreeData) => boolean
): TreeInfo {
  const nodes: InnerTreeData[] = [];
  const relations: CheckTreeRelation = {};
  const disabledIds: string[] = [];

  data.forEach(({ children, id, disableSelect, ...props }) => {
    const pathKey = pid ? `${pPathKey}$${id}` : id;

    // isFiltered 是否是过滤剩下的
    const isFiltered = filter
      ? filter({ ...props, children, id, disableSelect })
      : true;

    nodes.push({
      ...props,
      isFiltered,
      level,
      id,
      children,
      disableSelect,
      pathKey,
    });

    if (pid) {
      relations[id] = pid;
    }
    if (disableSelect) {
      disabledIds.push(id);
    }
    if (children && children.length > 0) {
      const childInfo = renderTree(children, level + 1, id, pathKey, filter);
      // 合并子级信息
      childInfo.nodes.forEach(i => nodes.push(i));
      childInfo.disabledIds.forEach(i => disabledIds.push(i));
      Object.entries(childInfo.relations).forEach(([key, value]) => {
        relations[key] = value;
      });
    }
  });
  return { nodes, relations, disabledIds };
}

/**
 * TreeNode -> Data
 */
function getTreeData(children: React.ReactNode): TreeData[] {
  const data: TreeData[] = [];
  React.Children.forEach(children, child => {
    if (isChildOfType(child, TreeNode)) {
      const props = {
        ...child.props,
        children: child.props.children
          ? getTreeData(child.props.children)
          : null,
      };
      data.push(props);
    }
  });
  return data;
}

export const Tree = forwardRefWithStatics(
  function Tree(props: TreeProps, ref: React.Ref<HTMLDivElement>) {
    const isValid = useMemo(() => checkTreeNode(props.children), [
      props.children,
    ]);
    if (!isValid) {
      return <DeprecatedTree {...props} ref={ref} />;
    }
    return <TreeInner {...props} ref={ref} />;
  },
  {
    Node: TreeNode,
  }
);

Tree.displayName = "Tree";

const TreeInner = forwardRef(function TreeInner(
  {
    data,
    children,
    selectable,
    selectedIds,
    defaultSelectedIds = [],
    selectStrictly,
    onSelect,
    selectValueMode = "all",
    fullActivable,
    activable = fullActivable,
    activeIds,
    defaultActiveIds = [],
    onActive,
    expandedIds,
    defaultExpandedIds = [],
    onExpand = noop,
    fullExpandable,
    defaultExpandAll,
    defaultExpandParent = true,
    forceExpandParent,
    onLoad,
    onLoadError,
    switcherIcon,
    className,
    height,
    filterState,
    filter,
    ...props
  }: TreeProps,
  ref: React.Ref<HTMLDivElement>
) {
  const { nodes, relations, disabledIds } = useMemo(
    () => renderTree(data || getTreeData(children), 0, "", "", filter),
    [children, data, filter]
  );

  // 选择关系和结构关系可能不同
  const selectRelations = useMemo(() => (selectStrictly ? {} : relations), [
    relations,
    selectStrictly,
  ]);

  const { classPrefix } = useConfig();
  const [selectedNodes, setSelectedNodes] = useDefault(
    selectedIds,
    defaultSelectedIds,
    onSelect
  );

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

  const [rawExpandedNodes, setExpandedNodes] = useDefault<string[]>(
    expandedIds,
    getDefaultExpandedIds({
      relations,
      expandedIds,
      defaultExpandedIds,
      defaultExpandAll,
      defaultExpandParent,
      forceExpandParent,
    }),
    onExpand
  );

  const expandedNodes = useMemo(() => {
    if (forceExpandParent) {
      const idSet = new Set(rawExpandedNodes);
      rawExpandedNodes.forEach(id => {
        let child = id;
        while (child in relations && !idSet.has(relations[child])) {
          idSet.add(relations[child]);
          child = relations[child];
        }
      });
      return [...idSet];
    }
    return rawExpandedNodes;
  }, [forceExpandParent, rawExpandedNodes, relations]);

  const visibleNodes = useMemo(() => {
    return nodes.filter(({ id, isFiltered }) =>
      getNodeShowState(id, relations, expandedNodes, isFiltered)
    );
  }, [expandedNodes, nodes, relations]);

  // 处理过滤
  useEffect(() => {
    if (filterState && typeof filter === "function") {
      const expandedIds = getFilteredExpandedIds(nodes, relations);
      setExpandedNodes([...expandedIds]);
    }
  }, [filterState, filter, nodes, relations]);

  const [List, ListProps] = useMemo(
    () => (height ? [VirtualizedTreeList, { height }] : [TreeList, {}]),
    [height]
  );

  const handleActive = useCallback(
    (activeId: string, event: React.MouseEvent, data: TreeNodeEventData) =>
      setActiveNodes([activeId], {
        active: true,
        nodeId: activeId,
        event,
        data,
      }),
    [setActiveNodes]
  );

  const handleExpand = useCallback(
    (
      nodeId: string,
      expanded: boolean,
      event: React.MouseEvent,
      data: TreeNodeEventData
    ) =>
      setExpandedNodes(
        rawExpandedNodes => {
          let expandedNodes = rawExpandedNodes || [];
          if (forceExpandParent) {
            const idSet = new Set(rawExpandedNodes);
            rawExpandedNodes.forEach(id => {
              let child = id;
              while (child in relations && !idSet.has(relations[child])) {
                idSet.add(relations[child]);
                child = relations[child];
              }
            });
            expandedNodes = [...idSet];
          }
          return expanded
            ? [...expandedNodes, nodeId]
            : expandedNodes.filter(id => id !== nodeId);
        },
        { nodeId, expanded, event, data }
      ),
    [forceExpandParent, relations, setExpandedNodes]
  );

  const handleSelectChange = useCallback(
    (value, { check, event }) =>
      setSelectedNodes(value, {
        nodeId: check.name,
        selected: check.value,
        event,
      }),
    [setSelectedNodes]
  );

  return (
    <div
      ref={ref}
      className={classNames(`${classPrefix}-tree`, className, {
        [`${classPrefix}-tree--checkbox-separate`]: selectable && activable,
        [`${classPrefix}-tree--full-line`]: fullExpandable || fullActivable,
      })}
      {...props}
    >
      <TreeContext.Provider
        value={{
          flatten: true,
          selectable,
          activable,
          activeIds: activeNodes,
          expandedIds: expandedNodes,
          onActive: handleActive,
          onExpand: handleExpand,
          onLoad,
          onLoadError,
          switcherIcon,
          fullExpandable,
          fullActivable,
        }}
      >
        {selectable ? (
          <CheckTree
            valueMode={selectValueMode}
            relations={selectRelations}
            value={selectedNodes}
            disabledNames={disabledIds}
            onChange={handleSelectChange}
          >
            <List nodes={visibleNodes} {...ListProps}>
              {visibleNodes}
            </List>
          </CheckTree>
        ) : (
          <List nodes={visibleNodes} {...ListProps}>
            {visibleNodes}
          </List>
        )}
      </TreeContext.Provider>
    </div>
  );
});

TreeInner.displayName = "TreeInner";

function TreeList({ nodes }: { nodes: InnerTreeData[] }) {
  const { classPrefix } = useConfig();
  const { activable, activeIds } = useContext(TreeContext);

  return (
    <ul>
      {nodes.map(
        ({ pure, style = {}, className, height, pathKey, ...props }) => (
          <li
            key={pathKey || props.id}
            className={classNames(`${classPrefix}-tree__node`, className, {
              // 高亮
              "is-selected": activable && activeIds.includes(props.id),
              [`${classPrefix}-tree__node--transparent`]: pure,
            })}
            style={{ height, ...style }}
          >
            <TreeNode {...props} />
          </li>
        )
      )}
    </ul>
  );
}
