<template>
  <div class="designer">
    <div class="designer-aside">
      <el-space direction="vertical">
        <span>节点类型</span>
        <el-button type="success" @click="onAddApprove">
          <template #icon>
            <el-icon><Avatar /></el-icon>
          </template>
          <span>审批</span>
        </el-button>
        <el-button type="success" @click="onAddBranch">
          <template #icon>
            <el-icon><Share /></el-icon>
          </template>
          <span>分支</span>
        </el-button>
        <el-button type="success" @click="onAddVirtual">
          <template #icon>
            <el-icon><Finished /></el-icon>
          </template>
          <span>虚拟</span>
        </el-button>
        <el-button type="success" @click="onAddCopy">
          <template #icon>
            <el-icon><CopyDocument /></el-icon>
          </template>
          <span>抄送</span>
        </el-button>
      </el-space>
    </div>

    <div class="designer-main">
      <div class="designer-main-toolbar">
        <el-button type="primary" @click="onZoomIn">
          <template #icon>
            <el-icon><ZoomIn /></el-icon>
          </template>
        </el-button>
        <el-button type="primary" @click="onZoomOut">
          <template #icon>
            <el-icon><ZoomOut /></el-icon>
          </template>
        </el-button>
        <el-button type="primary" @click="onCenter">
          <template #icon>
            <el-icon><FullScreen /></el-icon>
          </template>
        </el-button>
        <el-button type="primary" @click="onZoomFit">
          <template #icon>
            <el-icon><Rank /></el-icon>
          </template>
        </el-button>
        <el-button type="primary" @click="onView">
          <template #icon>
            <el-icon><View /></el-icon>
          </template>
        </el-button>
        <el-button type="primary" @click="onSave">
          <template #icon>
            <el-icon><Download /></el-icon>
          </template>
        </el-button>
      </div>
      <div class="designer-main-canvas" ref="canvasRef">
      </div>
    </div>

    <div class="designer-property">
      <design-property :graph="graphInstance" :mode="state.mode" :current="state.current" @on-save-process="onSaveProcess" @on-save-node="onSaveNode" @on-save-flow="onSaveFlow" />
    </div>

    <design-json :visible="state.visible" :mode="state.mode" :json="state.json" @on-save="onSaveJson" @on-close="onCloseJson"></design-json>
  </div>
</template>

<script lang="ts" setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
import { Graph, Node, Edge, Cell, Model } from '@antv/x6'
import { cloneDeep } from 'lodash-es'

import { type OperMode, type FlowDescriptor, type NodeDescriptor, type ProcessDescriptor, SuccessResultCode, useLoading } from '@cacp/ui'

import * as apis from '@/apis/workflow/definition'
import { useDesignerStore } from '@/stores'
import { initProcessDescriptor } from '@/types/process'
import * as utils from './utils'
import DesignProperty from './ProcessDesignProperty.vue'
import DesignJson from './ProcessDesignJson.vue'

const $route = useRoute()
const graphInstance = ref<Graph>()
const canvasRef = ref<HTMLDivElement>()

const { loading, setLoading } = useLoading()
const designerStore = useDesignerStore()

const state = reactive<{
  visible: boolean,
  mode: OperMode,
  descriptor: ProcessDescriptor,
  json: string
  current: {
    type: 'PROCESS' | 'NODE' | 'FLOW',
    data: ProcessDescriptor | NodeDescriptor | FlowDescriptor
  }
}>({
  visible: false,
  mode: 'show',
  descriptor: initProcessDescriptor,
  json: '',
  current: {
    type: 'PROCESS',
    data: initProcessDescriptor
  }
})

onMounted(async () => {
  setLoading(true)

  await designerStore.init()
  await initProcessData()
  initRenderGraph()

  setLoading(false)
})

async function initProcessData() {
  const processCode: string = $route.query.processCode as string
  const mode = $route.query.mode as string

  if (mode === 'create') {
    state.mode = mode
    state.descriptor = cloneDeep(initProcessDescriptor)
  } else if (mode === 'oper') {
    const res = await apis.getDefinition(processCode, 0)
    if (res.code === SuccessResultCode && res.data) {
      state.descriptor = res.data!
    }
    state.mode = 'oper'
  } else {
    const res = await apis.getDefinition(processCode, 0)
    if (res.code === SuccessResultCode && res.data) {
      state.descriptor = res.data!
    }
    state.mode = 'show'
  }

  state.current.data = state.descriptor
  state.current.type = 'PROCESS'
}

function initRenderGraph() {
  graphInstance.value = utils.initGraph(canvasRef.value!, (graph) => {
    graph.on("selection:changed", handleSelectionChange)
      .on("node:mouseenter", handleMouseEnter)
      .on("node:mouseleave", handleMouseLeave)
      .on("edge:connected", handleConnected)
      .on("node:removed", handleDelete)
      .on("edge:removed", handleDelete)

    const json = toGraphData(state.descriptor)
    graph.fromJSON(json)
  })
}

function toGraphData(descriptor: ProcessDescriptor) : Model.FromJSONData {
  const data: Model.FromJSONData = {
    nodes: [],
    edges: []
  }

  for (const item of descriptor.nodes) {
    const node: Node.Metadata = {
      id: item.code,
      shape: item.type.toUpperCase(),
      position: { x: (item.attrs?.x ?? 50) as number, y: (item.attrs?.y ?? 5) as number },
      label: item.name,
      data: {...item}
    }

    data.nodes.push(node)
  }

  for (const item of descriptor.flows) {
    const edge: Edge.Metadata = {
      id: item.code,
      shape: 'FLOW',
      source: item.source,
      target: item.target,
      label: (item.name ?? '') + (item.type === 'CONDITION' ? '<条件>': ''),
      data: {...item}
    }

    data.edges.push(edge)
  }

  return data
}
function toDescriptor(data: Model.ToJSONData): ProcessDescriptor {
  const cells = data.cells

  const nodes = cells.filter(c => c.shape !== 'FLOW')
    .map(c => {
      const node: NodeDescriptor = cloneDeep(c.data)
      node.attrs = Object.assign({}, node.attrs, {
        x: c.position.x, y: c.position.y
      })
      return node
    })
  const flows = cells.filter(c => c.shape === 'FLOW')
    .map(c => {
      const flow: FlowDescriptor = cloneDeep(c.data)
      Object.assign(flow, {
        source: c.source.cell,
        target: c.target.cell
      })
      return flow
    })

  for (const node of nodes) {
    if (node.backCode && !nodes.some(n => n.code === node.backCode)) {
      node.backCode = ''
    }
  }

  return Object.assign({}, state.descriptor, { nodes: nodes, flows: flows })
}

function onAddApprove() {
  if (!graphInstance.value) {
    return
  }

  const id = `n_${utils.nanoId()}`
  const nodeData: NodeDescriptor = {
    type: 'APPROVE',
    code: id,
    name: '审批节点',
    backCode: '',
    seceneCode: '',
    routeOptional: false,
    completeStrategy: {
      strategy: 'ONE'
    },
    expireDays: 0,
    assignment: {
      type: 'NONE'
    },
    keepTrace: false,
    listeners: [],
    options: {},
    attrs: { x: 300, y: 30 }
  }

  graphInstance.value.addNode({
    id: id,
    data: nodeData,
    shape: nodeData.type.toUpperCase(),
    position: { x: (nodeData.attrs?.x ?? 50) as number, y: (nodeData.attrs?.y ?? 5) as number },
    label: nodeData.name
  })
}

function onAddBranch() {
  if (!graphInstance.value) {
    return
  }

  const id = `n_${utils.nanoId()}`
  const nodeData: NodeDescriptor = {
    type: 'BRANCH',
    code: id,
    name: '分支节点',
    branchCode: '',
    completeStrategy: {
      strategy: 'BRANCH'
    },
    assignment: {
      type: 'BRANCH_DEPT',
      branchDepts: ''
    },
    listeners: [],
    options: {},
    attrs: { x: 300, y: 30 }
  }

  graphInstance.value.addNode({
    id: id,
    data: nodeData,
    shape: nodeData.type.toUpperCase(),
    position: { x: (nodeData.attrs?.x ?? 50) as number, y: (nodeData.attrs?.y ?? 5) as number },
    label: nodeData.name
  })
}

function onAddVirtual() {
  if (!graphInstance.value) {
    return
  }

  const id = `n_${utils.nanoId()}`
  const nodeData: NodeDescriptor = {
    type: 'VIRTUAL',
    code: id,
    name: '虚拟节点',
    returnable: false,
    listeners: [],
    options: {},
    attrs: { x: 300, y: 30 }
  }

  graphInstance.value.addNode({
    id: id,
    data: nodeData,
    shape: nodeData.type.toUpperCase(),
    position: { x: (nodeData.attrs?.x ?? 50) as number, y: (nodeData.attrs?.y ?? 5) as number },
    label: nodeData.name
  })
}

function onAddCopy() {
  if (!graphInstance.value) {
    return
  }

  const id = `n_${utils.nanoId()}`
  const nodeData: NodeDescriptor = {
    type: 'COPY',
    code: id,
    name: '抄送节点',
    routeOptional: false,
    assignment: {
      type: 'NONE'
    },
    listeners: [],
    options: {},
    attrs: { x: 300, y: 30 }
  }

  graphInstance.value.addNode({
    id: id,
    data: nodeData,
    shape: nodeData.type.toUpperCase(),
    position: { x: (nodeData.attrs?.x ?? 50) as number, y: (nodeData.attrs?.y ?? 5) as number },
    label: nodeData.name
  })
}

function onZoomIn() {
  graphInstance.value?.zoom(0.2)
}
function onZoomOut() {
  graphInstance.value?.zoom(-0.2)
}
function onZoomFit() {
  graphInstance.value?.zoomToFit()
}
function onCenter() {
  graphInstance.value?.centerContent()
}
function onView() {
  state.visible = true

  debugger
  const data = graphInstance.value!.toJSON()
  const descriptor = toDescriptor(data)
  state.json = JSON.stringify(descriptor, null, '  ')
}
function onSaveJson(json: string) {
  state.visible = false

  const descriptor: ProcessDescriptor = JSON.parse(json)
  const data = toGraphData(descriptor)
  graphInstance.value!.fromJSON(data)
}
function onCloseJson() {
  state.visible = false
}
async function onSave() {
  const data = graphInstance.value!.toJSON()
  state.descriptor = toDescriptor(data)

  if (state.mode === 'create' || state.mode === 'oper') {
    const res = await apis.saveDefinition(state.descriptor)
    if (res.code === SuccessResultCode && res.data > 0) {
      ElMessage.success('保存成功')
    }
  }
}


async function handleSelectionChange(args: { selected: Cell[] }) {
  const selected: Cell[] = args.selected

  if (!selected || selected.length === 0) {
    state.current.type = 'PROCESS'
    state.current.data = state.descriptor
  } else {
    state.current.type = 'PROCESS'
    state.current.data = state.descriptor

    await nextTick()

    const item = selected[0]
    if (item.shape === 'FLOW') {
      state.current.type = 'FLOW'
      state.current.data = item.data as FlowDescriptor
    } else {
      state.current.type = 'NODE'
      state.current.data = item.data as NodeDescriptor
    }
  }
}
function handleMouseEnter(args: { node: Node }) {
  const node: Node = args.node
  for (const port of node.getPorts()) {
    node.setPortProp(port.id!, 'attrs/circle/style/visibility', 'visible')
  }
}
function handleMouseLeave(args: { node: Node }) {
  const node: Node = args.node
  for (const port of node.getPorts()) {
    node.setPortProp(port.id!, 'attrs/circle/style/visibility', 'hidden')
  }
}
function handleConnected(args: { edge: Edge, currentCell?: Cell | null }) {
  const edge: Edge = args.edge
  const source = edge.getSourceCell()
  const target = args.currentCell

  Object.assign(edge.data, {
    source: source!.data.code,
    target: target!.data.code
  })
}
function handleDelete() {
  state.current.type = 'PROCESS'
  state.current.data = state.descriptor
}

function onSaveProcess(info: ProcessDescriptor) {
  state.descriptor = Object.assign({}, info, { nodes: state.descriptor.nodes, flows: state.descriptor.nodes })
}
function onSaveNode(info: NodeDescriptor) {
  const item = graphInstance.value!.getCellById(info.code) as Node
  if (!item) {
    return
  }

  item.setData(info, {
    deep: false,
    silent: true
  })
  item.setAttrs({
    text: { text: info.name }
  })
}
function onSaveFlow(info: FlowDescriptor) {
  const item = graphInstance.value!.getCellById(info.code) as Edge
  if (!item) {
    return
  }

  item.setData(info, {
    deep: false,
    silent: true
  })
  item.setLabels({
    attrs: {
      label: {
        fill: utils.colors.flowColor,
        fontSize: 12
      },
      text: {
        text: (info.name ?? '') + (info.type === 'CONDITION' ? '<条件>' : '')
      }
    }
  })
}
</script>
<style lang="less" scoped>
  .designer {
    margin: 5px;
    display: flex;
    width: 100%;
    overflow: hidden;
    
    &-aside {
      padding: 0 5px;
    }

    &-main {
      padding: 0 5px;
      overflow: hidden;
      flex: 1;

      &-canvas {
        height: 100%;
      }
    }

    &-property {
      padding: 0 5px;
      width: 400px;
      height: 100%;
      overflow: auto;
      scrollbar-width: none;
      &::-webkit-scrollbar {
        display: none;
      }
    }
  }
</style>