<template>
  <a-card :class="$style.chart" size="small">
    <div :class="$style.wrapper">
      <div v-if="tracingInfo">
        <!-- 子流程 且 需显示切换方式 -->
        <div v-if="toggleView && webflow.FlowData.attrs.isSubflow" :class="$style.dispsign"
          >显示全流程：<a-switch :checked="logDispAll" @change="(flag) => $emit('changeView', flag)"
        /></div>
        <!-- eslint-disable-next-line vue/no-v-html -->
        <div ref="svg" :class="$style.svg" v-html="tracingInfo.svgFlowInfo" />
        <div v-if="!nodeClickable" :class="$style.overlay">
          <!-- 每个节点根据对应的位置，画一个用于响应鼠标事件的div，给popover用 -->
          <div v-for="node in nodePositions" :key="node.id">
            <a-popover
              v-if="nodeInfos[node.id] && !['结束', '条件'].includes(nodeInfos[node.id].stepType)"
            >
              <template v-slot:content>
                <div :class="$style.stepTipWrapper">
                  <div :class="$style.stepInfo">
                    <div v-if="nodeInfos[node.id].stepType === '子流程'">
                      <p
                        v-for="(operator, index) in countersignOperator(node.id)"
                        :key="index"
                        :class="$style.countersignInfo"
                        @click="checkFlowChart(operator.subFlowInstanceId)"
                        ><span v-if="operator.recipientDept" :class="$style.recipientDept">
                          {{ operator.recipientDept }}
                        </span>
                        <span :class="{ [$style.brackets]: operator.recipientDept }">
                          {{ operator.users.map((item) => item.name).join(',') }}
                        </span>
                      </p></div
                    >

                    <div v-else>
                      <p v-if="nodeInfos[node.id].isCurrent"
                        >处理人<span :class="$style.brackets">{{ operatorStrategy(node.id) }}</span
                        >：{{
                          tracingInfo.activeInfos
                            .filter((item) => item.stepId === node.id)
                            .map((item) => {
                              if (['UserList', 'User'].includes(item.operatorType)) {
                                return item.users.map((i) => i.name).join(',')
                              }
                              return item.operator
                            })
                            .join(',')
                        }}</p
                      >
                      <div v-else>
                        <p
                          >处理人<span :class="$style.brackets">{{
                            operatorStrategy(node.id)
                          }}</span
                          >：</p
                        ><p
                          v-for="(operator, index) in nodeInfos[node.id].operatorInfos"
                          :key="index"
                        >
                          <span> {{ operator.user.name }}</span
                          ><span>
                            {{ operator.finishDate | sdDateFormat }}
                          </span>
                        </p>
                      </div>
                    </div>
                  </div>
                </div>
              </template>
              <div
                :class="$style.node"
                :style="{
                  top: node.y + 'px',
                  left: node.x + 'px',
                  width: node.width + 'px',
                  height: node.height + 'px',
                }"
              />
            </a-popover>
          </div>
        </div>
        <a-modal
          destroy-on-close
          title="流程图"
          :wrap-class-name="$style.flowChartModal"
          :visible="visible"
          :footer="null"
          :width="1200"
          @cancel="visible = false"
        >
          <sd-flowchart
            :inst-id="flowInstanceId"
            :root-inst-id="flowInstanceId"
            :root-flow-id="flowId"
          />
        </a-modal>
      </div>
      <div v-else :class="$style.spin"><a-spin :spinning="spinning"/></div>
    </div>
  </a-card>
</template>

<script>
import axios from '@/common/services/axios-instance'
import { Message } from 'ant-design-vue'
import components from './_import-components/sd-flowchart-import'

// IE不支持svg上的classList
function addClassName(node, className) {
  if (node && node.className.baseVal.split(' ').indexOf(className) === -1)
    node.setAttribute('class', node.getAttribute('class') + ' ' + className)
}

function removeClassName(node, className) {
  const classes = node.getAttribute('class') || ''
  node.setAttribute(
    'class',
    classes
      .split(' ')
      .filter((str) => str !== className)
      .join(' ')
  )
}

export default {
  name: 'SdFlowchart',
  components,
  props: {
    instId: {
      type: Number,
      default: function() {
        return this.webflow.FlowData.instId
      },
    },
    mode: {
      type: String,
      default: 'VIEW',
    },
    flowId: {
      type: String,
      default: function() {
        return this.webflow.FlowData.processId
      },
    },
    nodeClickable: {
      type: Boolean,
      default: false,
    },
    disabledNodes: {
      type: Array,
      default: () => [],
    },
    // 流转记录显示方式  true 按顶层主流程显示  false 仅显示当前流程
    logDispAll: {
      type: Boolean,
      default: false,
    },
    // 允许切换显示方式：全流程、本流程
    toggleView: {
      type: Boolean,
      default: false,
    },
    // 顶层流程对应流程InstId
    rootInstId: {
      type: Number,
      default: function() {
        return this.webflow.FlowData.instId
      },
    },
    // 顶层流程对应流程InstId
    rootFlowId: {
      type: String,
      default: function() {
        return this.webflow.FlowData.processId
      },
    },
  },
  data() {
    return {
      tracingInfo: undefined,
      nodePositions: [],
      currentStep: 1, // 流程图查看历史记录的进度
      spinning: true,
      visible: false,
      flowInstanceId: -1,
    }
  },
  inject: {
    webflow: { default: () => {} },
  },
  computed: {
    currentAndHistory() {
      return this.tracingInfo.historicSteps.concat(
        this.tracingInfo.activeSteps.map((info) => {
          return {
            ...info,
            isCurrent: true,
          }
        })
      )
    },
    historicSteps() {
      // 只取历史记录的前n条激活的部分
      return this.currentAndHistory.slice(0, this.currentStep)
    },

    nodeInfos() {
      const infos = {}
      // 从历史记录中取出节点信息，转成map格式
      this.historicSteps.forEach((info) => {
        if (info.stepId) {
          infos[info.stepId] = info
        }
      })
      return infos
    },
    maxSteps() {
      return this.currentAndHistory.length
    },
    operatorStrategy() {
      return function(stepId) {
        let operatorStrategy = this.tracingInfo.flowDefinition.steps.find(
          (step) => step.stepId === stepId
        ).participantsStrategy
        switch (operatorStrategy) {
          case 'serial':
            operatorStrategy = '串行'
            break
          case 'parallel':
            operatorStrategy = '并行'
            break
          default:
            operatorStrategy = ''
        }
        return operatorStrategy
      }
    },
    countersignOperator() {
      return function(stepId) {
        const countersignOperator = []
          .concat(
            this.tracingInfo.activeInfos
              .filter((item) => stepId === item.stepId)
              .map((item) => {
                return {
                  recipientDept: item.users?.[0]?.dept || '',
                  users: item.users,
                  status: item.status,
                  subFlowInstanceId: item.subFlowinstId,
                }
              })
          )
          .concat(
            this.tracingInfo.logInfos
              .filter((item) => item.subflowLogInfo && stepId === item.stepId)
              .map((item) => {
                return {
                  recipientDept:
                    item.subflowLogInfo.recipientType === 'User'
                      ? ''
                      : item.subflowLogInfo.recipientName,
                  users: [{ name: item.name }],
                  status: item.subflowLogInfo.status,
                  subFlowInstanceId: Number(item.subInstId),
                }
              })
          )
        return countersignOperator
      }
    },
  },
  watch: {
    logDispAll() {
      this.getTracingInfo()
    },
  },
  created() {
    this.getTracingInfo()
  },
  methods: {
    getTracingInfo() {
      let myinstId = this.instId
      let flowId = this.flowId
      if (this.logDispAll) {
        myinstId = this.rootInstId
        flowId = this.rootFlowId
      }
      this.spinning = true
      return axios(
        `api/framework/v1/process-tracing/svg/process-instance/${myinstId}?flowId=${flowId}&mode=${this.mode}`
      )
        .then((res) => {
          this.tracingInfo = res.data
          this.$nextTick(this.updateFlowChart)
        })
        .catch((e) => {
          Message.error('获取流程图失败')
        })
        .finally(() => {
          this.spinning = false
        })
    },
    // 更新流程图底图相关信息
    updateFlowChart() {
      // 记录历史记录的长度
      this.currentStep = this.maxSteps
      // 更新svg图中节点的状态
      this.updateFlowStatus()
      // 计算每个节点的位置信息
      const nodePositions = []
      const svgRoot = this.$refs.svg.querySelector('svg')
      this.$emit('tracingInfoUpdate', svgRoot)
      // 防止流程图过长超出背景图
      document.querySelector(`.${this.$style.chart}`).style.minWidth =
        svgRoot.getAttribute('width') + 'px'
      const svgPosition = svgRoot.getBoundingClientRect()
      svgRoot.querySelectorAll(`.stencils .children>g`).forEach((node) => {
        const position = node.getBoundingClientRect()
        const id = node.getAttribute('stepid')
        nodePositions.push({
          id,
          x: position.left - svgPosition.left,
          y: position.top - svgPosition.top,
          width: position.width,
          height: position.height,
        })
        if (this.nodeClickable) {
          if (!this.disabledNodes.includes(id)) {
            node.addEventListener('click', (e) => {
              this.$emit('selectNode', id)
            })
            addClassName(node, 'sd-nodeclickable')
          } else {
            addClassName(node, 'sd-nodeclickdisable')
          }
        }
      })
      this.nodePositions = nodePositions
    },
    // 更新流程图状态信息
    updateFlowStatus() {
      const svgRoot = this.$refs.svg.querySelector('svg')
      // 先清空已有的状态
      svgRoot
        .querySelectorAll('.stencils .children >g,.stencils .edge >g, marker >path')
        .forEach((node) => {
          removeClassName(node, 'sd-finish')
          removeClassName(node, 'sd-current')
        })

      // 记录线的状态
      function paintLine(actionId) {
        let svgNode = svgRoot.querySelector(`.stencils .edge [stepid="${actionId}"]`)
        addClassName(svgNode, 'sd-finish')
        svgNode?.parentNode.appendChild(svgNode)
        // 单独处理下箭头
        if (svgNode) {
          svgNode
            .querySelector('path')
            .getAttribute('marker-end')
            .match(/\((.+?)\)/g)
          svgNode = svgRoot.querySelector(`${RegExp.$1.replace(/'/g, '').replace(/"/g, '')} path`)
          addClassName(svgNode, 'sd-finish')
        }
      }
      let hasSplit = false
      // 从历史记录中取出已经走过的节点和连线
      this.historicSteps.forEach((info, index, infos) => {
        // 记录节点状态
        const svgNode = svgRoot.querySelector(`.stencils .children [stepid="${info.stepId}"]`)
        addClassName(svgNode, info.isCurrent ? 'sd-current' : 'sd-finish')

        // 分支流程，后面统一处理
        if (info.actionId === 'split') {
          hasSplit = true
          return
        }
        if (info.actionId) {
          paintLine(info.actionId)
        }
      })
      // 有分支，从loginfos中取分支提交的线
      if (hasSplit) {
        this.tracingInfo.logInfos
          .filter((log) => log.actionId === 'split')
          .forEach((log) => {
            // 从分支中取出所有的actionId
            Object.keys(log.flowLogInfoProps || {})
              .filter((key) => /^successorsStep.+ActionId$/.test(key))
              .forEach((key) => {
                paintLine(log.flowLogInfoProps[key])
              })
          })
      }
    },
    checkFlowChart(instId) {
      this.visible = true
      this.flowInstanceId = instId
    },
  },
}
</script>

<style module lang="scss">
@use '@/common/design' as *;
@use '@/common/components/sd-web-print.scss' as print;
$default-color: #f3f3f3;
$finish-color: #f8fff2;
$current-color: #bfe3f8;
$default-deep-color: #d7d7d7;
$finish-deep-color: #6eac41;
$current-deep-color: #56cff8;
$tip-line-height: 30px;
@include print.wrapper {
  .check-button {
    display: none;
  }
  .overlay {
    display: none;
  }
}
.spin {
  text-align: center;
}
.svg {
  :global marker {
    path {
      fill: $default-deep-color;
      stroke: $default-deep-color;
      transition: all 0.5s ease;
    }
    path.sd-finish {
      fill: $finish-deep-color;
      stroke: $finish-deep-color;
    }
    path.sd-current {
      fill: $current-deep-color;
      stroke: $current-deep-color;
    }
  }
  :global .stencils {
    .children {
      circle,
      rect {
        fill: $default-color;
        stroke: $default-deep-color;
        transition: all 0.5s ease;
      }

      .sd-finish {
        circle,
        rect {
          fill: $finish-color;
          stroke: $finish-deep-color;
        }
      }

      .sd-current {
        circle,
        rect {
          fill: $current-color;
          stroke: $current-deep-color;
        }
      }
    }
    .edge {
      path {
        stroke: $default-deep-color;
        transition: all 0.5s ease;
      }
      .sd-finish {
        path {
          stroke: $finish-deep-color;
        }
      }

      .sd-current {
        path {
          stroke: $current-deep-color;
        }
      }
    }
  }
  :global(.sd-nodeclickable) {
    cursor: pointer;
  }
  :global(.sd-nodeclickdisable) {
    cursor: not-allowed;
  }
}
.chart {
  position: relative;
}
.wrapper {
  .overlay {
    position: absolute;
    top: $padding-sm;
    right: 0;
    bottom: 0;
    left: $padding-sm;
    .node {
      position: absolute;
    }
  }
  .check-button {
    position: absolute;
    top: 20px;
    right: 30px;
    z-index: 1;
  }
}

.step-tip-wrapper {
  font-size: $font-size-sm;
  .step-info {
    p {
      max-width: 300px;
      margin-bottom: 5px;
    }
    .countersign-info {
      cursor: pointer;
      .recipient-dept {
        color: $primary-color;
      }
    }
  }
  .brackets::before {
    content: '(';
  }
  .brackets::after {
    content: ')';
  }
  .brackets:empty {
    display: none;
  }
}
.flow-chart-modal :global(.ant-modal-body) {
  padding: 0;
}
.dispsign {
  position: absolute;
  right: 20px;
  z-index: 1;
}
</style>
