import { OriginalTreeData, StringOrNumber } from "./tree.type";
import { Nullable } from "../../types";
import { Ref, ref } from "vue";
import { check } from "prettier";

type CustomKey = string | number;
type CustomString = (() => string) | string;

export interface TreeData {
  id: CustomKey;
  title: CustomString;
  children: TreeData[];
  parentKey: Nullable<StringOrNumber>;
  isRoot: boolean;
  isChecked: boolean;
  isDisabled: boolean;
  isLeaf: boolean;
  hasNextSibling: boolean;
  parentNode: Nullable<TreeData>;
}

interface ReplaceFields {
  id: string;
  title: string;
  children: string;
}

interface TreeConfig {
  checkStrictly: boolean | string;
  showCheckbox: boolean;
  checkedKeys: StringOrNumber[];
  expandKeys: StringOrNumber[];
  nodeMap: Map<StringOrNumber, TreeData>;
  originMap: Map<StringOrNumber, OriginalTreeData>;
  replaceFields: ReplaceFields;
}

class Tree {
  protected config: TreeConfig;
  protected treeData: TreeData[];

  constructor(
    config: TreeConfig,
    origin: OriginalTreeData | OriginalTreeData[]
  ) {
    this.config = config;
    this.treeData = [];
    this.init(origin);
  }

  init(origin: OriginalTreeData | OriginalTreeData[]): void {
    const tree = this.createTree(origin);
    this.treeData = tree;
  }

  createTree(
    origin: OriginalTreeData | OriginalTreeData[],
    parentKey: StringOrNumber = ""
  ): TreeData[] {
    let data;
    if (!Array.isArray(origin)) {
      data = Array.of(Object.assign({}, origin));
    } else {
      data = origin;
    }
    const nodeList: TreeData[] = [];
    const { children } = this.config.replaceFields;

    const len = data.length;
    for (let i = 0; i < len; i++) {
      const node = this.getNode(data[i], parentKey, i < len - 1);
      const nodeChildren = Reflect.get(node, children);
      const nodeHasChildren = !!Reflect.get(node, children);

      if (nodeHasChildren) {
        Reflect.set(node, children, this.createTree(nodeChildren, node.id));
      }

      nodeList.push(node);
    }
    return nodeList;
  }

  getNode(
    origin: OriginalTreeData,
    parentKey: StringOrNumber,
    hasNextSibling: boolean
  ): TreeData {
    const {
      nodeMap,
      originMap,
      checkedKeys,
      expandKeys,
      checkStrictly,
      replaceFields: { children, id, title },
    } = this.config;

    const nodeKey = Reflect.get(origin, id);
    const nodeTitle = Reflect.get(origin, title);
    const nodeChildren = Reflect.get(origin, children);
    const nodeDisabled = !!Reflect.get(origin, "disabled");
    const nodeIsLeaf = !!Reflect.get(origin, "spread");
    const parentNode = nodeMap.get(parentKey);

    const node = Object.assign({}, origin, {
      id: nodeKey,
      title: nodeTitle,
      children: nodeChildren ? nodeChildren : [],
      parentKey: parentKey,
      isRoot: parentKey === "",
      isDisabled: false,
      isChecked: false,
      isLeaf: false,
      hasNextSibling: hasNextSibling,
      parentNode: parentNode || null,
    });

    node.isDisabled = nodeDisabled;
    node.isChecked = checkedKeys.includes(nodeKey);
    node.isLeaf = parentNode ? parentNode.isLeaf : expandKeys.includes(nodeKey);
    node.isLeaf = nodeIsLeaf;

    if (!nodeMap.has(nodeKey)) {
      nodeMap.set(nodeKey, node);
    }
    if (!originMap.has(nodeKey)) {
      originMap.set(nodeKey, origin);
    }
    return node;
  }

  treeForeach(tree: any, func: Function) {
    tree.forEach((data: any) => {
      data.children && this.treeForeach(data.children, func);
      func(data);
    });
  }

  setChildrenChecked(checked: boolean, nodes: TreeData[]) {
    var ableCount = 0;
    var checkCount = 0;
    const len = nodes.length;
    this.treeForeach(nodes, (node: any) => {
      if (!node.isDisabled) {
        ableCount = ableCount + 1;
        if (node.isChecked) {
          checkCount = checkCount + 1;
        }
      }
    });
    checkCount < ableCount ? (checked = true) : (checked = false);
    for (let i = 0; i < len; i++) {
      if (
        !nodes[i].isDisabled ||
        (nodes[i].isDisabled && nodes[i].children.length > 0)
      ) {
        nodes[i].isChecked = checked;
      }
      nodes[i].children &&
        nodes[i].children.length > 0 &&
        this.setChildrenChecked(checked, nodes[i].children);
    }
  }

  setParentChecked(checked: boolean, parent: TreeData) {
    if (!parent) {
      return;
    }
    parent.isChecked = checked;
    const pChild = parent.children;
    const pChildChecked = pChild.some((c) => c.isChecked);
    if (pChildChecked) {
      parent.isChecked = true;
    }
    if (parent.parentNode) {
      this.setParentChecked(checked, parent.parentNode);
    }
  }

  setCheckedKeys(
    checked: boolean,
    checkStrictly: boolean | string,
    node: TreeData
  ) {
    node.isChecked = checked;
    if (!checkStrictly) {
      if (node.parentNode) {
        this.setParentChecked(checked, node.parentNode);
      }
      if (node.children) {
        this.setChildrenChecked(checked, node.children);
      }
    }
  }

  getData() {
    return this.treeData;
  }

  getKeys() {
    const checkedKeys = [];
    const expandKeys = [];
    const iterator = this.config.nodeMap[Symbol.iterator]();
    let next = iterator.next();
    while (!next.done) {
      const [, node] = next.value;
      const id = Reflect.get(node, this.config.replaceFields.id);
      if (node.isChecked) {
        checkedKeys.push(id);
      }
      if (node.isLeaf) {
        expandKeys.push(id);
      }
      next = iterator.next();
    }
    return { checkedKeys, expandKeys };
  }

  getOriginData(key: StringOrNumber): OriginalTreeData {
    return this.config.originMap.get(key)!;
  }
}

export { Tree };
