<template>
  <div class="chart">
    <div class="chart-block">
      <div class="chart-ref" ref="ref_chart"></div>
    </div>
    <div class="tools">
      <div class="mini-map" ref="ref_miniMap"/>
      <div class="operations">
        <div class="zoom">
          <el-tooltip content="缩小" effect="light" placement="top" :show-arrow="false">
            <div class="__hover-bg" :class="{__disabled: state.zoom <= 0.25}" @click="graphZoom(state.zoom - 0.1)">
              <SvgIcon name="zoom-"/>
            </div>
          </el-tooltip>
          <el-dropdown :teleported="false" placement="top" :popper-options="{
            modifiers: [
              {
                name: 'offset',
                options: {
                  offset: [0, 20],
                },
              },
              {
                name: 'arrow',
              },
            ],
          }">
            <div class="__hover-bg px-0.5">{{(state.zoom * 100).toFixed(0)}}%</div>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item @click="graphZoom(2)">200%</el-dropdown-item>
                <el-dropdown-item @click="graphZoom(1)">100%</el-dropdown-item>
                <el-dropdown-item @click="graphZoom(0.75)">75%</el-dropdown-item>
                <el-dropdown-item @click="graphZoom(0.5)">50%</el-dropdown-item>
                <el-dropdown-item divided @click="graphZoom(0)">自适应视图</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
          <el-tooltip content="放大" effect="light" placement="top" :show-arrow="false">
            <div class="__hover-bg" :class="{__disabled: state.zoom >= 2}" @click="graphZoom(state.zoom + 0.1)">
              <SvgIcon name="zoom+"/>
            </div>
          </el-tooltip>
        </div>
        <div>
          <el-tooltip content="撤销" effect="light" placement="top" :show-arrow="false">
            <div class="__hover-bg" :class="{__disabled: !state.history.canUndo}" @click="onHistoryDo(-1)">
              <SvgIcon name="back"/>
            </div>
          </el-tooltip>
          <el-tooltip content="重做" effect="light" placement="top" :show-arrow="false">
            <div class="__hover-bg" :class="{__disabled: !state.history.canRedo}" @click="onHistoryDo(1)">
              <SvgIcon name="back" rotate="180"/>
            </div>
          </el-tooltip>
          <el-popover
            :show-arrow="false"
            :width="240"
            :popper-style="{
              padding: 0,
            }">
            <template #reference>
              <div class="__hover-bg">
                <SvgIcon name="history"/>
              </div>
            </template>
            <div class="">
              <div class="text-[18px] p-3 pb-1">变更历史</div>
              <div class="px-1">
                <template v-if="state.history.steps.length < 2">
                  <div class="mt-2 h-[120px] flex flex-col justify-center items-center">
                    <SvgIcon name="history" size="30" class="mb-3"/>
                    尚未更改任何内容
                  </div>
                </template>
                <template v-else>
                  <div class="mt-2 max-h-[300px] overflow-y-auto flex flex-col gap-1">
                    <template v-for="(item, index) in state.history.steps">
                      <div :class="`__hover-bg px-2 py-1.5 rounded-md text-[13px] ${state.history.current === index && 'bg-[#c8ceda33]'}`" @click="onHistoryDo(state.history.current - index)">
                        {{MapGraphHistoryStep.get(item)}}
                        （
                        <template v-if="state.history.current < index">{{index - state.history.current}} 步后退</template>
                        <template v-else-if="state.history.current > index">{{state.history.current - index}} 步前进</template>
                        <template v-else>当前状态</template>
                        )
                      </div>
                    </template>
                  </div>
                  <div class="bg-[#eaecf0] w-full h-[1px] my-2"/>
                  <div class="__hover-bg px-2 py-1.5 rounded-md mb-2 text-[13px]" @click="onHistoryClear(state.history.steps[0])">清空历史记录</div>
                </template>
              </div>
              <div class="text-[12px] text-[#676f83] p-3">
                提示<br/>
                您的编辑操作将被跟踪并存储在您的设备上，直到您离开编辑器。此历史记录将在您离开编辑器时被清除。
              </div>
            </div>
          </el-popover>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import {createVNode, getCurrentInstance, nextTick, onMounted, reactive, ref, render, watch} from "vue";
import {Graph, Path, Shape} from '@antv/x6'
import {getNodeDefault, lineStyle} from "@/views/workflow/config";
import nodeAdd from './node-add.vue'
import {register} from "@antv/x6-vue-shape";
import WorkflowNode from "./node-index.vue";
import {handleEdge, handleNode} from "@/views/workflow/handle";
import {Snapline} from '@antv/x6-plugin-snapline'
import {ContextMenuTool} from "./context-menu-tool";
import {useWorkflowStore} from "@/stores/modules/workflow";
import {GraphHistoryStep, MapGraphHistoryStep, NodeType} from "@/views/workflow/types";
import { MiniMap } from '@antv/x6-plugin-minimap'
import { History } from '@antv/x6-plugin-history'
import {v4} from "uuid";

register({
  shape: 'workflow-node',
  component: WorkflowNode,
})
let edgeTimer: any = null
Graph.registerPortLayout('start', (portsPositionArgs, elemBBox) => {
  return portsPositionArgs.map((_, index) => {
    return {
      position: {
        x: 3,
        y: 25 + 7,
      },
    }
  })
})
Graph.registerPortLayout('end', (portsPositionArgs, elemBBox) => {
  if (edgeTimer) {
    clearTimeout(edgeTimer)
  }
  edgeTimer = setTimeout(() => {
    initEdges()
  }, 100)
  return portsPositionArgs.map((_, index) => {
    return {
      position: {
        x: elemBBox.width + 11,
        y: 25 + 7,
      },
    }
  })
})
Graph.registerPortLayout('more', (portsPositionArgs, elemBBox) => {
  return portsPositionArgs.map((_, index) => {
    nextTick(() => {
      WorkflowStore.layoutPort(_.nodeId, _.portId)
      if (edgeTimer) {
        clearTimeout(edgeTimer)
      }
      edgeTimer = setTimeout(() => {
        initEdges()
      }, 100)
    })
    return {
      position: {
        x: elemBBox.width + 11,
        y: _.dy,
      },
    }
  })
})
Graph.registerConnector(
  'algo-connector',
  (s, e) => {
    const offset = 6
    const deltaX = Math.abs(e.x - s.x)
    const control = Math.floor((deltaX / 3) * 2)
    const v1 = { x: s.x + offset + control, y: s.y }
    const v2 = { x: e.x - offset - control, y: e.y }
    return Path.normalize(
      `M ${s.x} ${s.y}
       L ${s.x} ${s.y}
       C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y}
       L ${e.x} ${e.y}
      `,
    )
  },
  true,
)
Graph.registerNodeTool('contextmenu', ContextMenuTool, true)
Graph.registerEdgeTool('contextmenu', ContextMenuTool, true)
const WorkflowStore = useWorkflowStore()
const emits = defineEmits([])
const props = defineProps({
  data: <any>{}
})
const {proxy}: any = getCurrentInstance()
const state: any = reactive({
  graph: null,
  isPort: false,
  isInitEdges: false,
  zoom: 1,
  history: {
    canUndo: false,
    canRedo: false,
    steps: [],
    current: 0
  }
})
const ref_chart = ref()
const ref_miniMap = ref()
const initChart = () => {
  state.graph = new Graph({
    container: ref_chart.value,
    autoResize: true,
    panning: true,
    mousewheel: {
      enabled: true,
      factor: 1.05
    },
    scaling: {
      min: 0.25,
      max: 2
    },
    grid: {
      visible: true,
      type: 'doubleMesh',
      args: [
        {
          color: 'rgba(238,238,238,0.3)', // 主网格线颜色
          thickness: 1, // 主网格线宽度
        },
        {
          color: 'rgba(221,221,221,0.3)', // 次网格线颜色
          thickness: 1, // 次网格线宽度
          factor: 2, // 主次网格线间隔
        },
      ],
    },
    highlighting: {
      // 连接桩可以被连接时在连接桩外围围渲染一个包围框
      magnetAvailable: {
        name: 'stroke',
        args: {
          rx: 100,
          ry: 100,
          attrs: {
            fill: '#fff',
            stroke: 'rgba(var(--czr-main-color-rgb), 0.5)',
            'stroke-width': 3,
          },
        },
      },
      // 连接桩吸附连线时在连接桩外围围渲染一个包围框
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          rx: 100,
          ry: 100,
          attrs: {
            fill: '#fff',
            stroke: 'rgba(var(--czr-main-color-rgb), 1)',
            'stroke-width': 3,
          },
        },
      },
    },
    connecting: {
      snap: { //  连线过程中自动吸附
        radius: 20,
      },
      allowBlank: false,  //  是否允许连接到画布空白位置的点
      allowLoop: false,  //  是否允许创建循环连线，即边的起始节点和终止节点为同一节点
      allowNode: false,  //  是否允许边连接到节点
      allowEdge: false,  //  是否允许边链接到另一个边
      allowPort: ({sourcePort, targetPort}: any) => {  //  是否允许边链接到连接桩
        return !sourcePort?.includes('start') && targetPort?.includes('start')
      },
      allowMulti: 'withPort', //  当设置为 'withPort' 时，在起始和终止节点的相同连接桩之间只允许创建一条边（即，起始和终止节点之间可以创建多条边，但必须要要链接在不同的连接桩上）
      highlight: true,
      // router: {
      //   name: 'manhattan',
      //   args: {
      //     startDirections: ['right'],
      //     endDirections: ['left'],
      //   },
      // },
      connector: 'algo-connector',
      connectionPoint: 'anchor',
      anchor: 'center',
      // connector: {
      //   name: 'rounded',
      //   args: {
      //     radius: 20,
      //   },
      // },
      createEdge: (args) => {
        const id = v4()
        return new Shape.Edge({
          id,
          attrs: lineStyle,
          zIndex: -1,
          tools: [
            {
              name: 'contextmenu',
              args: {
                delFlag: GraphHistoryStep.EdgeDel,
                data: {id},
              }
            }
          ]
        })
      }
    },
    onPortRendered: (args: any) => {
      const selectors = args.contentSelectors
      const container = selectors && selectors.foContent
      if (container) {
        render(createVNode(nodeAdd, {
          port: args.port,
          node: args.node,
          graph: state.graph,
        }), container)
      }
    }
  })
  WorkflowStore.init(state.graph)
  initPlug()
  initWatch()
  initNodes()
  graphZoom(0)
}
const initNodes = () => {
  props.data.nodes.forEach(v => {
    state.graph.addNode(handleNode(v))
  })
  if (props.data.nodes.length === 0) {
    const node = getNodeDefault(NodeType.Root)
    state.graph.addNode(handleNode(node))
  }
}
const initEdges = () => {
  if (!state.isInitEdges) {
    props.data.edges.forEach(v => {
      const targetNode = state.graph.getCellById(v.target)
      targetNode.setData({
        edgeSource: v.port || v.source
      }, {deep: false})
      state.graph.addEdge(handleEdge(v))
    })
    state.isInitEdges = true
    onHistoryClear()
  }
}
const initWatch = () => {
  state.graph.on('node:click', ({ e, x, y, node, view, port }) => {
    setTimeout(() => {
      if (!state.isPort) {
        WorkflowStore.nodePanel(node)
      }
    }, 50)
  })
  state.graph.on('node:port:click', ({ e, x, y, node, view, port }) => {
    state.isPort = true;
    setTimeout(() => {
      state.isPort = false
    }, 100)
  })
  state.graph.on('scale', ({ sx, sy, ox, oy }) => {
    state.zoom = sx
  })
  state.graph.on('history:change', ({cmds, options}) => {
    if (state.isInitEdges) {
      state.history.canRedo = state.graph.canRedo()
      state.history.canUndo = state.graph.canUndo()
      if (options.name) {
        state.history.steps.unshift(options.name)
      }
    }
  })
}
const initPlug = () => {
  state.graph.use(
    new Snapline({
      enabled: true,
      clean: false,
    }),
  )
  state.graph.use(
    new MiniMap({
      container: ref_miniMap.value,
      width: 100,
      height: 70,
      padding: 6
    }),
  )
  state.graph.use(
    new History({
      enabled: true,
      beforeAddCommand: (event, args: any) => {
        if (
          args.key === 'ports' && args.options.propertyPath.includes('ports/items') ||
          args.key === 'attrs'
        ) {
          return false
        }
      }
    }),
  )
}
const graphZoom = (z) => {
  if (z) {
    state.graph.zoomTo(z)
  } else {
    state.graph.zoomToFit({ maxScale: 1 })
  }
}
const onHistoryDo = (step) => {
  if (step > 0) {
    for (let i = 1; i <= step; i++) {
      if (state.history.canRedo) {
        state.graph.redo()
        state.history.current -= 1
      }
    }
  } else {
    for (let i = -1; i >= step; i--) {
      if (state.history.canUndo) {
        state.graph.undo()
        state.history.current += 1
      }
    }
  }
}
const onHistoryClear = (first = '') => {
  state.graph.cleanHistory()  // 初始化完成后清空历史队列
  state.history.steps = [first || GraphHistoryStep.Root]
  state.history.current = 0
}
watch(() => props.data, (n) => {
  if (n) {
    initChart()
  }
}, {immediate: true})
onMounted(() => {
})
defineExpose({
  toJSON: () => state.graph.toJSON()
})
</script>

<style lang="scss" scoped>
.chart {
  width: 100%;
  height: 100%;
  background-color: #f2f4f7;
  position: relative;
  .chart-block {
    width: 100%;
    height: 100%;
    .chart-ref {
      width: 100%;
      height: 100%;
    }
  }
  .tools {
    position: absolute;
    left: 20px;
    bottom: 20px;
    z-index: 2;
    .mini-map {
      position: absolute;
      bottom: calc(100% + 10px);
    }
    :deep(.operations) {
      display: flex;
      align-items: center;
      font-size: 14px;
      color: #676f83;
      gap: 10px;
      .el-popper__arrow {
        display: none;
      }
      >div {
        display: flex;
        align-items: center;
        background-color: rgba(255, 255, 255, 0.95);
        box-shadow: 0 0px 8px rgba(0, 0, 0, 0.1);
        border-radius: 6px;
        padding: 4px;
        >div {
          min-width: 32px;
          height: 32px;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    }
  }
}
</style>