<!--<template>-->
<!--  <div class="_root">-->
<!--    &lt;!&ndash; 渲染流程树 &ndash;&gt;-->
<!--    &lt;!&ndash; Vue2 渲染函数在 script 中 &ndash;&gt;-->
<!--  </div>-->
<!--</template>-->

<script>
import Vue from "vue";
import { Button, message } from "ant-design-vue";
import { mapState } from "vuex";

import RootNode from "@/views/workFlow/designer/common/process/nodes/RootNode.vue";
import ApprovalNode from "@/views/workFlow/designer/common/process/nodes/ApprovalNode.vue";
import CcNode from "@/views/workFlow/designer/common/process/nodes/CcNode.vue";
import ConditionNode from "@/views/workFlow/designer/common/process/nodes/ConditionNode.vue";
import EndNode from "@/views/workFlow/designer/common/process/nodes/EndNode.vue";
import EmptyNode from "@/views/workFlow/designer/common/process/nodes/EmptyNode.vue";
import DefaultProps from "./DefaultNodeProps";

Vue.use(Button);

export default {
  name: "ProcessTree",
  components: {
    RootNode,
    ApprovalNode,
    CcNode,
    ConditionNode,
    EndNode,
    EmptyNode,
  },
  computed: {
    ...mapState("workflow", ["nodeMap", "design", "selectedNode"]),
    dom() {
      return this.design.process;
    },
  },
  data() {
    return {
      nodeTypeMap: {
        "-1": EndNode,
        0: RootNode,
        1: ApprovalNode,
        2: CcNode,
        3: ConditionNode,
        empty: EmptyNode,
      },
      nodeRefs: {},
      valid: false,
    };
  },
  methods: {
    // ========================= 渲染 =========================
    renderTree(h) {
      const processTrees = this.getDomTree(this.dom);
      processTrees.push(
        <div style="text-align:center">
          <div class="process-end">流程结束</div>
        </div>
      );
      return <div class="_root" ref="_root">{processTrees}</div>;
    },

    getDomTree(node) {
      if (!node) return [];
      this.toMapping(node);

      if (this.isPrimaryNode(node)) {
        const childDoms = this.getDomTree(node.childNode);
        this.decodeAppendDom(node, childDoms);
        return [<div class="primary-node">{childDoms}</div>];
      } else if (this.isBranchNode(node)) {
        let index = 0;
        const branchItems = node.conditionNodes.map((branchNode) => {
          this.toMapping(branchNode);
          const childDoms = this.getDomTree(branchNode.childNode);
          this.decodeAppendDom(branchNode, childDoms, {
            size: node.conditionNodes.length,
          });
          this.insertCoverLine(index, childDoms, node.conditionNodes);
          index++;
          return <div class="branch-node-item">{childDoms}</div>;
        });

        branchItems.unshift(
          <div class="add-branch-btn">
            <a-button
              class="add-branch-btn-el"
              size="small"
              shape="round"
              onClick={() => this.addBranchNode(node)}
            >
              添加{this.isConditionNode(node) ? "条件" : "分支"}
            </a-button>
          </div>
        );

        const bchDom = [<div class="branch-node">{branchItems}</div>];
        const afterChildDoms = this.getDomTree(node.childNode);
        this.decodeAppendDom({ type: "empty", node }, afterChildDoms);
        const emptyDom = <div class="empty-node">{afterChildDoms}</div>;

        return [<div>{[bchDom, emptyDom]}</div>];
      }
      return [];
    },

    decodeAppendDom(node, dom, props = {}) {
      const Comp = this.nodeTypeMap[node.type];
      const vnode = this.$createElement(Comp, {
        props: { ...props, config: node },
        key: node.id,
        ref: node.id,
        on: {
          insertNode: (type) => this.insertNode(type, node),
          delNode: () => this.delNode(node),
          selected: () => this.selectNode(node),
          copy: () => this.copyBranch(node),
          leftMove: () => this.branchMove(node, -1),
          rightMove: () => this.branchMove(node, 1),
        },
      });
      dom.unshift(vnode);

      // 延迟设置 nodeRefs，避免 render 阶段修改响应式
      this.$nextTick(() => {
        if (!this.nodeRefs[node.id]) {
          this.$set(this.nodeRefs, node.id, null);
        }
      });
    },

    toMapping(node) {
      if (node && node.id) {
        // Vue2 Map 对象兼容性问题，用普通对象或 $set
        if (!this.nodeMap.set) this.$set(this.nodeMap, node.id, node);
        else this.nodeMap.set(node.id, node);
      }
    },

    insertCoverLine(index, doms, conditionNodes) {
      if (index === 0) {
        doms.unshift(<div class="line-top-left" />);
        doms.unshift(<div class="line-bot-left" />);
      } else if (index === conditionNodes.length - 1) {
        doms.unshift(<div class="line-top-right" />);
        doms.unshift(<div class="line-bot-right" />);
      }
    },

    // ========================= 节点操作 =========================
    deepCopy(obj) {
      return JSON.parse(JSON.stringify(obj));
    },
    getRandomId() {
      return `node_${new Date().getTime().toString().substring(5)}${Math.floor(
        Math.random() * 9000 + 1000
      )}`;
    },
    getNodeKey() {
      return `flk_${new Date().getTime().toString().substring(5)}${Math.floor(
        Math.random() * 9000 + 1000
      )}`;
    },
    isPrimaryNode(node) {
      return node && [-1, 0, 1, 2].includes(node.type);
    },
    isBranchNode(node) {
      return node && node.type === 4;
    },
    isConditionNode(node) {
      return node.type === 4;
    },
    isBranchSubNode(node) {
      return node && node.type === 3;
    },

    selectNode(node) {
      this.$store.state.workflow.selectedNode = node;
      this.$emit("selectedNode", node);
    },

    insertNode(type, parentNode) {
      if (parentNode.type === "empty") parentNode = parentNode.node;
      const afterNode = parentNode.childNode;
      parentNode.childNode = { id: this.getRandomId(), parentId: parentNode.id, type };

      switch (type) {
        case -1:
          this.insertEndNode(parentNode);
          break;
        case 1:
          this.insertApprovalNode(parentNode);
          break;
        case 2:
          this.insertCcNode(parentNode);
          break;
        case 4:
          this.insertConditionsNode(parentNode);
          break;
      }

      if (afterNode && afterNode.id) afterNode.parentId = parentNode.childNode.id;
      if (afterNode && Object.keys(afterNode).length) parentNode.childNode.childNode = afterNode;

      this.$forceUpdate();
    },

    insertEndNode(parentNode) {
      parentNode.childNode.nodeName = "结束";
      parentNode.childNode.nodeKey = this.getNodeKey();
      parentNode.childNode.showClose = true;
    },
    insertApprovalNode(parentNode) {
      parentNode.childNode.nodeName = "审批人";
      parentNode.childNode.nodeKey = this.getNodeKey();
      parentNode.childNode = { ...parentNode.childNode, ...this.deepCopy(DefaultProps.APPROVAL_PROPS) };
    },
    insertCcNode(parentNode) {
      parentNode.childNode.nodeName = "抄送人";
      parentNode.childNode.nodeKey = this.getNodeKey();
      parentNode.childNode = { ...parentNode.childNode, ...this.deepCopy(DefaultProps.CC_PROPS) };
    },
    insertConditionsNode(parentNode) {
      parentNode.childNode.nodeName = "条件分支";
      parentNode.childNode.nodeKey = this.getNodeKey();
      parentNode.childNode.conditionNodes = [
        {
          id: this.getRandomId(),
          parentId: parentNode.childNode.id,
          nodeName: "条件1",
          nodeKey: this.getNodeKey(),
          type: 3,
          priorityLevel: 1,
          conditionList: [],
        },
        {
          id: this.getRandomId(),
          parentId: parentNode.childNode.id,
          nodeName: "默认条件",
          nodeKey: this.getNodeKey(),
          type: 3,
          priorityLevel: 2,
          conditionList: [],
        },
      ];
    },

    addBranchNode(node) {
      if (node.conditionNodes.length >= 8) return message.warning("最多只能添加 8 项!");
      if (this.isConditionNode(node)) {
        node.conditionNodes.splice(node.conditionNodes.length - 1, 0, {
          id: this.getRandomId(),
          parentId: node.id,
          nodeName: `条件${node.conditionNodes.length}`,
          nodeKey: this.getNodeKey(),
          priorityLevel: 0,
          conditionList: [],
          type: 3,
        });
        let level = 1;
        node.conditionNodes.forEach((item) => (item.priorityLevel = level++));
      }
      this.$forceUpdate();
    },

    delNode(node) {
      const parentNode = this.nodeMap.get(node.parentId);
      if (!parentNode) return message.warning("出现错误，找不到上级节点😥");

      if (this.isBranchSubNode(node) && this.isBranchNode(parentNode)) {
        parentNode.conditionNodes.splice(parentNode.conditionNodes.indexOf(node), 1);
        if (parentNode.conditionNodes.length < 2) {
          const ppNode = this.nodeMap.get(parentNode.parentId);
          if (parentNode.conditionNodes[0].childNode && parentNode.conditionNodes[0].childNode.id) {
            ppNode.childNode = parentNode.conditionNodes[0].childNode;
            ppNode.childNode.parentId = ppNode.id;
            const endNode = this.getBranchEndNode(parentNode.conditionNodes[0]);
            endNode.childNode = parentNode.childNode;
            if (endNode.childNode && endNode.childNode.id) endNode.childNode.parentId = endNode.id;
          } else {
            ppNode.childNode = parentNode.childNode;
            if (ppNode.childNode && ppNode.childNode.id) ppNode.childNode.parentId = ppNode.id;
          }
        } else {
          let level = 1;
          parentNode.conditionNodes.forEach((item) => (item.priorityLevel = level++));
        }
      } else {
        if (node.childNode && node.childNode.id) node.childNode.parentId = parentNode.id;
        parentNode.childNode = node.childNode;
      }
      this.$forceUpdate();
    },

    getBranchEndNode(node) {
      if (!node.childNode || !node.childNode.id) return node;
      return this.getBranchEndNode(node.childNode);
    },

    copyBranch(node) {
      const parentNode = this.nodeMap.get(node.parentId);
      const branchNode = this.deepCopy(node);
      branchNode.nodeName += "-copy";
      this.forEachNode(parentNode, branchNode, (parent, n) => {
        n.id = this.getRandomId();
        n.parentId = parent.id;
      });
      parentNode.conditionNodes.splice(parentNode.conditionNodes.indexOf(node), 0, branchNode);
      let level = 1;
      parentNode.conditionNodes.forEach((item) => (item.priorityLevel = level++));
      this.$forceUpdate();
    },

    branchMove(node, offset) {
      const parentNode = this.nodeMap.get(node.parentId);
      const index = parentNode.conditionNodes.indexOf(node);
      const branch = parentNode.conditionNodes[index + offset];
      parentNode.conditionNodes[index + offset] = parentNode.conditionNodes[index];
      parentNode.conditionNodes[index] = branch;
      let level = 1;
      parentNode.conditionNodes.forEach((item) => (item.priorityLevel = level++));
      this.$forceUpdate();
    },

    forEachNode(parent, node, callback) {
      if (!node) return;
      if (this.isBranchNode(node)) {
        callback(parent, node);
        this.forEachNode(node, node.childNode, callback);
        node.conditionNodes.forEach((branchNode) => {
          callback(node, branchNode);
          this.forEachNode(branchNode, branchNode.childNode, callback);
        });
      } else if (this.isPrimaryNode(node) || this.isBranchSubNode(node)) {
        callback(parent, node);
        this.forEachNode(node, node.childNode, callback);
      }
    },

    validate(err, node) {
      if (!node) return;
      if (this.isPrimaryNode(node)) {
        this.validateNode(err, node);
        this.validate(err, node.childNode);
      } else if (this.isBranchNode(node)) {
        node.conditionNodes.forEach((branchNode) => {
          this.validateNode(err, branchNode);
          this.validate(err, branchNode.childNode);
        });
        this.validate(err, node.childNode);
      }
    },

    validateNode(err, node) {
      if (this.$refs[node.id] && this.$refs[node.id].validate) {
        this.valid = this.$refs[node.id].validate(err);
      }
    },

    validateProcess() {
      this.valid = true;
      const err = [];
      this.validate(err, this.dom);
      return err;
    },
  },

  render(h) {
    return this.renderTree(h);
  },
};
</script>

<style lang="less" scoped>
._root {
  margin: 0 auto;
}
.process-end {
  display: none;
  width: 60px;
  margin: 0 auto;
  margin-bottom: 20px;
  border-radius: 15px;
  padding: 5px 10px;
  font-size: small;
  color: #747474;
  background-color: #f2f2f2;
  box-shadow: 0 0 10px 0 #bcbcbc;
  box-sizing: content-box;
}
.primary-node {
  display: flex;
  align-items: center;
  flex-direction: column;
}
.branch-node {
  display: flex;
  justify-content: center;
}
.branch-node-item {
  position: relative;
  display: flex;
  background: #f5f6f6;
  flex-direction: column;
  align-items: center;
  border-top: 2px solid #cccccc;
  border-bottom: 2px solid #cccccc;
}
.branch-node-item:before {
  content: "";
  position: absolute;
  top: 0;
  left: calc(50% - 1px);
  margin: auto;
  width: 2px;
  height: 100%;
  background-color: #cacaca;
}
.line-top-left,
.line-top-right,
.line-bot-left,
.line-bot-right {
  position: absolute;
  width: 50%;
  height: 8px;
  background-color: #f5f6f6;
}
.line-top-left {
  top: -4px;
  left: -1px;
}
.line-top-right {
  top: -4px;
  right: -1px;
}
.line-bot-left {
  bottom: -4px;
  left: -1px;
}
.line-bot-right {
  bottom: -4px;
  right: -1px;
}
.add-branch-btn {
  position: absolute;
  width: 80px;
}
.add-branch-btn-el {
  background-color: #fff;
  z-index: 999;
  position: absolute;
  top: -15px;
}
.empty-node {
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}
</style>
