<template>
  <wrapper
    class="c-mesh"
    fill
    margin
    padding
    background
  >
    <div class="l-flex__none l-flex--row c-mesh__header c-sibling-item--v">
      <i
        class="l-flex__none c-sibling-item o-icon medium o-icon--hover el-icon-arrow-left u-bold u-pointer"
        @click="onBack"
      />
      <div class="l-flex__auto l-flex--row c-sibling-item near u-color--black u-font-size--md u-bold u-ellipsis">
        {{ name }}
      </div>
    </div>
    <div
      v-loading="loading"
      class="l-flex__fill l-flex--col c-sibling-item--v"
    >
      <template v-if="!loading">
        <warning
          v-if="error"
          class="l-flex__auto"
          @click="getMesh"
        />
        <template v-else>
          <div class="l-flex__none l-flex--row c-sibling-item--v u-line-height">
            <div class="c-sibling-item u-font-size--md u-ellipsis">节点</div>
            <i
              class="l-flex__none c-sibling-item el-icon-circle-plus-outline u-font-size--lg u-color--blue u-bold has-active"
              @click.stop="onAddNode"
            />
            <i
              class="l-flex__none c-sibling-item el-icon-refresh-left u-font-size--lg u-color--blue u-bold has-active"
              @click.stop="getMesh"
            />
          </div>
          <div
            v-if="!hasNodes"
            class="has-padding u-text--center u-bold"
          >
            暂无结点，请添加节点
          </div>
          <div
            ref="canvas"
            class="l-flex__fill"
            style="z-index: 0;"
          />
          <confirm-dialog
            ref="nodeDialog"
            :title="nodeDialogTitle"
            @confirm="onSubmit"
          >
            <template #default>
              <div class="c-grid-form u-align-self--center">
                <span class="c-grid-form__label u-required">类型</span>
                <schema-select
                  v-model="item.nodeType"
                  :schema="thirdPartyDeviceSelectSchema"
                />
                <span class="c-grid-form__label">别名</span>
                <el-input
                  v-model.trim="item.name"
                  placeholder="最多30个字符"
                  maxlength="30"
                  clearable
                />
                <template v-if="needPort">
                  <span class="c-grid-form__label u-required">端口</span>
                  <div
                    class="has-info"
                    data-info="若设备上的端口从1开始，填入时需减1处理"
                  >
                    <el-input-number
                      v-model="item.port"
                      controls-position="right"
                      :min="0"
                      step-strictly
                    />
                  </div>
                </template>
              </div>
            </template>
          </confirm-dialog>
          <confirm-dialog
            ref="nodeEditDialog"
            title="修改节点"
            @confirm="onSubmit"
          >
            <div class="c-grid-form u-align-self--center">
              <span class="c-grid-form__label">别名</span>
              <el-input
                v-model.trim="item.name"
                placeholder="最多30个字符"
                maxlength="30"
                clearable
              />
            </div>
          </confirm-dialog>
          <confirm-dialog
            ref="portEditDialog"
            title="端口设置"
            @confirm="onSubmitPort"
          >
            <template #default>
              <div class="c-grid-form auto u-align-self--center">
                <span class="c-grid-form__label u-required">端口</span>
                <div
                  class="has-info u-width--md"
                  data-info="若设备上的端口从1开始，填入时需减1处理"
                >
                  <el-input-number
                    v-model="item.port"
                    controls-position="right"
                    :min="0"
                    step-strictly
                  />
                </div>
              </div>
            </template>
          </confirm-dialog>
          <radio-table-dialog
            ref="instanceDialog"
            title="设备绑定"
            size="lg"
            message="请选择设备"
            :schema="thirdPartySchema"
            @confirm="onSubmitBind"
          />
          <component
            :is="thirdPartyComponent"
            v-if="thirdPartyComponent"
            ref="thirdPartyConfigDialog"
            @confirm="onConfirmThirdPaty"
          />
          <el-card
            v-show="menuVisible"
            ref="card"
            class="c-contentmenu"
            @click.native.stop
          >
            <template v-if="isNode">
              <div
                v-if="canHasFollow"
                class="l-flex--row c-contentmenu__item has-active"
                @click="onAddChildNode"
              >
                <i class="c-sibling-item el-icon-circle-plus-outline" />
                <span class="c-sibling-item">新增子节点</span>
              </div>
              <div
                v-if="canHasAhead"
                class="l-flex--row c-contentmenu__item has-active"
                @click="onAddParentNode"
              >
                <i class="c-sibling-item el-icon-circle-plus-outline" />
                <span class="c-sibling-item">新增父节点</span>
              </div>
              <div
                v-if="canLink"
                class="l-flex--row c-contentmenu__item has-active"
                @click="onLinkTo"
              >
                <i class="c-sibling-item el-icon-edit" />
                <span class="c-sibling-item">连接至</span>
              </div>
              <div
                class="l-flex--row c-contentmenu__item has-active"
                @click="onEditNode"
              >
                <i class="c-sibling-item el-icon-edit" />
                <span class="c-sibling-item">修改别名</span>
              </div>
              <div
                class="l-flex--row c-contentmenu__item has-active"
                @click="onToggleInstance"
              >
                <i class="c-sibling-item el-icon-edit" />
                <span class="c-sibling-item">{{ hasInstance ? '解绑' : '绑定' }}</span>
              </div>
            </template>
            <template v-else>
              <div
                v-if="hasPort"
                class="l-flex--row c-contentmenu__item has-active"
                @click="onEditPort"
              >
                <i class="c-sibling-item el-icon-edit" />
                <span class="c-sibling-item">端口</span>
              </div>
            </template>
            <div
              class="l-flex--row c-contentmenu__item has-active"
              @click="onDel"
            >
              <i class="c-sibling-item el-icon-remove-outline" />
              <span class="c-sibling-item">删除</span>
            </div>
          </el-card>
        </template>
      </template>
    </div>
  </wrapper>
</template>

<script>
import {
  ThirdPartyDevice,
  ThirdPartyDeviceInfo,
  SendingCard,
  SendingCardFeatures,
  ReceivingCard,
  ReceivingCardFeatures,
  ThirdPartyToSensorMap,
  SensorToThirdPartyMap,
  ThirdPartyToCameraMap
} from '@/constant'
import {
  getMesh,
  addNodeToMesh,
  deleteNode,
  addEdgeToMesh,
  deleteEdge,
  updateEdgePort,
  updateNode,
  addNodeBeforeNode,
  addNodeAfterNode,
  bindInstanceToNode,
  releaseInstanceFormNode
} from '@/api/mesh'
import {
  getManufacturersByType,
  getGateways,
  getScreens,
  getSendingCards,
  getReceivingCards,
  getMultifunctionCards,
  getSensors,
  getPLCs,
  getCameras
} from '@/api/external'
import { getDevices } from '@/api/device'
import * as echarts from 'echarts'
import GatewayConfigDialog from '../components/GatewayConfigDialog.vue'
import ScreenConfigDialog from '../components/ScreenConfigDialog.vue'
import SendingCardConfigDialog from '../components/SendingCardConfigDialog.vue'
import ReceivingCardConfigDialog from '../components/ReceivingCardConfigDialog.vue'
import PlcConfigDialog from '../components/PlcConfigDialog.vue'
import MultifunctionCardConfigDialog from '../components/MultifunctionCardConfigDialog.vue'
import SensorConfigDialog from '../components/SensorConfigDialog.vue'
import CameraConfigDialog from '../components/CameraConfigDialog.vue'

const sensors = Object.values(SensorToThirdPartyMap)

const PortThirdPartyDevices = [
  ThirdPartyDevice.PLC,
  ThirdPartyDevice.MULTI_FUNCTION_CARD,
  ...sensors
]

const SoloThirdPartyDevices = [
  ThirdPartyDevice.RECEIVING_CARD,
  ThirdPartyDevice.SENDING_CARD,
  ThirdPartyDevice.SCREEN,
  ThirdPartyDevice.BOX
]

const ThirdPartyDeviceOption = {
  'all': Object.values(ThirdPartyDevice),
  follow: {
    [ThirdPartyDevice.GATEWAY]: [
      ThirdPartyDevice.PLC,
      ...sensors
    ],
    [ThirdPartyDevice.RECEIVING_CARD]: [
      ThirdPartyDevice.SCREEN
    ],
    [ThirdPartyDevice.SENDING_CARD]: [
      ThirdPartyDevice.RECEIVING_CARD,
      ThirdPartyDevice.SCREEN
    ],
    [ThirdPartyDevice.RECEIVING_CARD]: [
      ThirdPartyDevice.SCREEN
    ],
    [ThirdPartyDevice.BOX]: [
      ThirdPartyDevice.RECEIVING_CARD,
      ThirdPartyDevice.SENDING_CARD,
      ThirdPartyDevice.SCREEN,
      ThirdPartyDevice.PLC,
      ThirdPartyDevice.MULTI_FUNCTION_CARD,
      ...sensors
    ],
    [ThirdPartyDevice.MULTI_FUNCTION_CARD]: [
      ThirdPartyDevice.PLC,
      ...sensors
    ]
  },
  ahead: {
    [ThirdPartyDevice.RECEIVING_CARD]: [
      ThirdPartyDevice.SENDING_CARD,
      ThirdPartyDevice.BOX
    ],
    [ThirdPartyDevice.SENDING_CARD]: [
      ThirdPartyDevice.BOX
    ],
    [ThirdPartyDevice.SCREEN]: [
      ThirdPartyDevice.RECEIVING_CARD,
      ThirdPartyDevice.BOX
    ],
    [ThirdPartyDevice.PLC]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ],
    [ThirdPartyDevice.MULTI_FUNCTION_CARD]: [
      ThirdPartyDevice.BOX
    ],
    [ThirdPartyDevice.SMOKE_SENSOR]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ],
    [ThirdPartyDevice.TEMPERATURE_SENSOR]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ],
    [ThirdPartyDevice.LIGHT_SENSOR]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ],
    [ThirdPartyDevice.FLOODING_SENSOR]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ],
    [ThirdPartyDevice.TRANSLOCATION_SENSOR]: [
      ThirdPartyDevice.GATEWAY,
      ThirdPartyDevice.BOX,
      ThirdPartyDevice.MULTI_FUNCTION_CARD
    ]
  }
}

export default {
  name: 'Mesh',
  components: {
    GatewayConfigDialog,
    ScreenConfigDialog,
    SendingCardConfigDialog,
    ReceivingCardConfigDialog,
    PlcConfigDialog,
    MultifunctionCardConfigDialog,
    SensorConfigDialog,
    CameraConfigDialog
  },
  data () {
    return {
      loading: true,
      error: false,
      mesh: null,
      defaultProps: { label: 'name' },
      thirdPartyDeviceSelectSchema: null,
      item: {},
      nodes: [],
      menuVisible: false,
      clickTargetType: '',
      targetItem: null
    }
  },
  computed: {
    meshId () {
      return this.$route.params.id
    },
    name () {
      return this.mesh?.name
    },
    hasNodes () {
      return this.nodes.length > 0
    },
    isNode () {
      return this.hasNodes && this.clickTargetType === 'node'
    },
    hasInstance () {
      return this.targetItem?.instanceId
    },
    nodeType () {
      return this.targetItem?.nodeType
    },
    canHasAhead () {
      return !!ThirdPartyDeviceOption.ahead[this.nodeType]
    },
    canHasFollow () {
      return !!ThirdPartyDeviceOption.follow[this.nodeType]
    },
    canLink () {
      if (this.isNode && this.canHasFollow) {
        const options = ThirdPartyDeviceOption.follow[this.nodeType]
        return options
          ? this.nodes.some(({ nodeType }) => options.includes(nodeType))
          : false
      }
      return false
    },
    hasPort () {
      return this.targetItem?.port >= 0
    },
    nodeSelectSchema () {
      return {
        options: this.nodes.map(({ id, name }) => {
          return {
            value: id,
            label: name
          }
        })
      }
    },
    nodeDialogTitle () {
      const { action } = this.item
      switch (action) {
        case 'addParentNode':
          return '新增父节点'
        case 'addChildNode':
          return '新增子节点'
        default:
          return '新增节点'
      }
    },
    needPort () {
      const { action } = this.item
      switch (action) {
        case 'addParentNode':
          return PortThirdPartyDevices.includes(this.targetItem.nodeType)
        case 'addChildNode':
          return PortThirdPartyDevices.includes(this.item.nodeType)
        default:
          return false
      }
    },
    thirdPartyType () {
      return this.targetItem?.nodeType
    },
    thirdPartyComponent () {
      switch (this.thirdPartyType) {
        case ThirdPartyDevice.GATEWAY:
          return 'GatewayConfigDialog'
        case ThirdPartyDevice.SCREEN:
          return 'ScreenConfigDialog'
        case ThirdPartyDevice.SENDING_CARD:
          return 'SendingCardConfigDialog'
        case ThirdPartyDevice.RECEIVING_CARD:
          return 'ReceivingCardConfigDialog'
        case ThirdPartyDevice.PLC:
          return 'PlcConfigDialog'
        case ThirdPartyDevice.MULTI_FUNCTION_CARD:
          return 'MultifunctionCardConfigDialog'
        case ThirdPartyDevice.SMOKE_SENSOR:
        case ThirdPartyDevice.TEMPERATURE_SENSOR:
        case ThirdPartyDevice.LIGHT_SENSOR:
        case ThirdPartyDevice.FLOODING_SENSOR:
        case ThirdPartyDevice.TRANSLOCATION_SENSOR:
          return 'SensorConfigDialog'
        case ThirdPartyDevice.LED_CAMERA:
        case ThirdPartyDevice.TRAFFIC_CAMERA:
          return 'CameraConfigDialog'
        default:
          return null
      }
    },
    thirdPartySchema () {
      switch (this.thirdPartyType) {
        case ThirdPartyDevice.GATEWAY:
          return {
            list: getGateways,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'ip', label: '服务器地址' },
              { prop: 'identifier', label: '唯一标识' }
            ]
          }
        case ThirdPartyDevice.SCREEN:
          return {
            list: getScreens,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { prop: 'measure', label: '屏宽高(m²)' },
              { prop: 'pitch', label: '点间距(mm)' },
              { prop: 'resolutionRatio', label: '分辨率' }
            ]
          }
        case ThirdPartyDevice.SENDING_CARD:
          return {
            list: getSendingCards,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { label: '特性', type: 'tag', render: ({ flag }) => SendingCardFeatures.map(({ key, label }) => {
                return { type: flag & SendingCard[key] ? 'success' : 'danger', label }
              }), width: 364 }
            ]
          }
        case ThirdPartyDevice.RECEIVING_CARD:
          return {
            list: getReceivingCards,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { label: '特性', type: 'tag', render: ({ flag }) => ReceivingCardFeatures.map(({ key, label }) => {
                return { type: flag & ReceivingCard[key] ? 'success' : 'danger', label }
              }), width: 364 }
            ]
          }
        case ThirdPartyDevice.PLC:
          return {
            list: getPLCs,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { prop: 'portCount', label: '端口数' }
            ]
          }
        case ThirdPartyDevice.BOX:
          return {
            list: getDevices,
            condition: { boundFlag: 0 },
            filters: [
              { key: 'serialNumber', type: 'search', placeholder: '序列号' },
              { key: 'mac', type: 'search', placeholder: 'MAC' },
              { key: 'name', type: 'search', placeholder: '设备名称' }
            ],
            cols: [
              { prop: 'name', label: '设备名称' },
              { prop: 'serialNumber', label: '序列号' },
              { prop: 'mac', label: 'MAC' }
            ]
          }
        case ThirdPartyDevice.MULTI_FUNCTION_CARD:
          return {
            list: getMultifunctionCards,
            condition: { boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { prop: 'portCount', label: '端口数', align: 'center' },
              { prop: 'rs485Count', label: '485接口数', align: 'center' }
            ]
          }
        case ThirdPartyDevice.SMOKE_SENSOR:
        case ThirdPartyDevice.TEMPERATURE_SENSOR:
        case ThirdPartyDevice.LIGHT_SENSOR:
        case ThirdPartyDevice.FLOODING_SENSOR:
        case ThirdPartyDevice.TRANSLOCATION_SENSOR:
          return {
            list: getSensors,
            condition: { sensorType: ThirdPartyToSensorMap[this.thirdPartyType], boundFlag: 0 },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' }
            ]
          }
        case ThirdPartyDevice.LED_CAMERA:
        case ThirdPartyDevice.TRAFFIC_CAMERA:
          return {
            list: getCameras,
            condition: { boundFlag: 0, cameraType: ThirdPartyToCameraMap[this.thirdPartyType] },
            buttons: [
              { type: 'add', on: this.onAddThirdParty }
            ],
            filters: [
              { key: 'manufacturerKey', type: 'select', placeholder: '厂商', remote: this.getManufacturersByType, value: 'manufacturerKey', label: 'manufacturerName' },
              { key: 'identifier', type: 'search', placeholder: '唯一标识' },
              { key: 'remark', type: 'search', placeholder: '备注' }
            ],
            cols: [
              { prop: 'manufacturerName', label: '厂商' },
              { prop: 'model', label: '型号' },
              { prop: 'identifier', label: '唯一标识' },
              { prop: 'remark', label: '备注' }
            ]
          }
        default:
          return {
            list: () => Promise.resolve({ data: [] }),
            cols: [
              { label: '唯一标识' }
            ]
          }
      }
    }
  },
  watch: {
    meshId: {
      handler () {
        this.getMesh()
      },
      immediate: true
    }
  },
  beforeDestroy () {
    this.disposeChart()
  },
  methods: {
    getManufacturersByType () {
      return getManufacturersByType(this.thirdPartyType)
    },
    onAddThirdParty () {
      let type = null
      switch (this.thirdPartyType) {
        case ThirdPartyDevice.SMOKE_SENSOR:
        case ThirdPartyDevice.TEMPERATURE_SENSOR:
        case ThirdPartyDevice.LIGHT_SENSOR:
        case ThirdPartyDevice.FLOODING_SENSOR:
        case ThirdPartyDevice.TRANSLOCATION_SENSOR:
          type = ThirdPartyToSensorMap[this.thirdPartyType]
          break
        case ThirdPartyDevice.LED_CAMERA:
        case ThirdPartyDevice.TRAFFIC_CAMERA:
          type = ThirdPartyToCameraMap[this.thirdPartyType]
          break
        default:
          break
      }
      this.$refs.thirdPartyConfigDialog.show(type)
    },
    onConfirmThirdPaty ({ manufacturerKey, identifier, remark }) {
      this.$refs.instanceDialog.getTable().resetCondition({
        manufacturerKey,
        identifier,
        remark
      })
    },
    collectNodes (nodes) {
      this.$nodeMap = {}
      if (nodes) {
        nodes.forEach((node, index) => {
          this.$nodeMap[node.id] = index
        })
      }
      return nodes || []
    },
    onBack () {
      this.$router.replace({
        name: 'mesh-list'
      })
    },
    disposeChart () {
      if (this.mesh) {
        this.mesh = null
        this.nodes = []
        this.edges = []
        this.$chart?.dispose()
        this.$chart = null
        this.closeContentMenu()
      }
      this.$nodeMap = {}
      this.$waitTarget = false
    },
    getMesh () {
      this.disposeChart()
      this.loading = true
      this.error = false
      getMesh(this.meshId).then(
        ({ data }) => {
          if (data) {
            this.mesh = data.mesh
            this.edges = data.bondList || []
            this.nodes = this.collectNodes(data.nodeList)
          } else {
            this.$message({
              type: 'warning',
              message: '数据不存在'
            })
            this.onBack()
          }
        },
        () => {
          this.error = true
        }
      ).finally(() => {
        this.loading = false
        if (this.nodes?.length) {
          this.$nextTick(this.onDraw)
        }
      })
    },
    onDraw () {
      if (!this.$chart) {
        this.$chart = echarts.init(this.$refs.canvas)
        this.$chart.on('click', this.onClick)
      }

      const options = this.$waitTarget && ThirdPartyDeviceOption.follow[this.targetItem.nodeType]
      this.$chart.setOption({
        // title: {
        //   text: 'Basic Graph'
        // },
        animationDurationUpdate: 1500,
        animationEasingUpdate: 'quinticInOut',
        tooltip: {},
        series: [
          {
            type: 'graph',
            // roam: true,
            layout: 'circular',
            // layout: 'force',
            // force: {
            //   edgeLength: 100
            // },
            label: {
              show: true,
              position: 'top'
            },
            itemStyle: {
              color ({ data, color }) {
                return data.proxy.instanceId ? '#67c23a' : color
              }
            },
            edgeSymbol: ['none', 'arrow'], // circle
            edgeSymbolSize: 8,
            // edgeLabel: {
            //   fontSize: 20
            // },
            lineStyle: {
              // color: '#ff0000',
              width: 2,
              opacity: 0.9,
              curveness: 0
            },
            nodes: this.nodes.map(node => {
              const { id, name, nodeType } = node
              const canClick = options ? options.includes(nodeType) : true
              return {
                proxy: node,
                id,
                name,
                tooltip: {
                  formatter: this.formatterNode
                },
                canClick,
                emphasis: {
                  disabled: !canClick
                },
                symbolSize: canClick ? 24 : 12
              }
            }),
            edges: this.edges.map(edge => {
              const { preNodeId, nextNodeId, port } = edge
              return {
                proxy: edge,
                value: port,
                source: this.$nodeMap[preNodeId],
                target: this.$nodeMap[nextNodeId],
                label: {
                  show: port >= 0,
                  // position: 'start',
                  formatter: '{c}'
                }
              }
            })
          }
        ]
      })
    },
    formatterNode ({ marker, data: { proxy: { nodeType, instance } } }) {
      let tooltip = `${marker} ${ThirdPartyDeviceInfo[nodeType]}`
      if (instance) {
        switch (nodeType) {
          case ThirdPartyDevice.SENDING_CARD:
          case ThirdPartyDevice.SMOKE_SENSOR:
          case ThirdPartyDevice.TEMPERATURE_SENSOR:
          case ThirdPartyDevice.LIGHT_SENSOR:
          case ThirdPartyDevice.FLOODING_SENSOR:
          case ThirdPartyDevice.TRANSLOCATION_SENSOR:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}`
            break
          case ThirdPartyDevice.RECEIVING_CARD:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>规格：${instance.rowCount} x ${instance.colCount}`
            break
          case ThirdPartyDevice.GATEWAY:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>服务器：${instance.ip}`
            break
          case ThirdPartyDevice.SCREEN:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>规格：${instance.measure}m² ${instance.pitch}mm`
            break
          case ThirdPartyDevice.PLC:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>端口数：${instance.portCount}`
            break
          case ThirdPartyDevice.BOX:
            tooltip += `<br/>设备名称：${instance.name}<br/>序列号：${instance.serialNumber}<br/>MAC：${instance.mac}`
            break
          case ThirdPartyDevice.MULTI_FUNCTION_CARD:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>端口数：${instance.portCount}<br/>485接口数：${instance.rs485Count}`
            break
          case ThirdPartyDevice.LED_CAMERA:
          case ThirdPartyDevice.TRAFFIC_CAMERA:
            tooltip += `<br/>唯一标识：${instance.identifier}<br/>厂商：${instance.manufacturerName}<br/>型号：${instance.model}<br/>备注：${instance.remark}`
            break
          default:
            break
        }
      }
      return tooltip
    },
    onClick (eventProxy) {
      console.log('click', eventProxy)
      const targetItem = eventProxy.data.proxy
      const targetType = eventProxy.dataType
      if (this.$waitTarget) {
        this.onChangeLinkStatus(false)
        if (targetType === 'node') {
          if (eventProxy.data.canClick) {
            this.addEdge(this.targetItem, targetItem)
            return
          } else if (this.targetItem.id === targetItem.id) {
            this.$message({
              type: 'warning',
              message: '节点不能连接自身'
            })
          } else {
            this.$message({
              type: 'warning',
              message: '无法连接至该节点'
            })
          }
          return
        }
      }
      this.clickTargetType = targetType
      if (this.isNode) {
        this.targetItem = this.nodes[this.$nodeMap[targetItem.id]]
      } else {
        const id = targetItem.nodeBondId
        this.targetItem = this.edges.find(edge => edge.nodeBondId === id)
      }
      const event = eventProxy.event.event
      this.$refs.card.$el.style.left = `${Math.min(event.clientX, document.body.clientWidth - 160)}px`
      this.$refs.card.$el.style.top = `${Math.min(event.clientY, document.body.clientHeight - 240)}px`
      event.stopPropagation()
      if (!this.menuVisible) {
        this.menuVisible = true
        document.addEventListener('click', this.closeContentMenu)
      }
    },
    closeContentMenu () {
      if (this.menuVisible) {
        this.menuVisible = false
        document.removeEventListener('click', this.closeContentMenu)
      }
    },
    createOptions (action) {
      let arr = null
      switch (action) {
        case 'create':
          arr = ThirdPartyDeviceOption.all
          break
        case 'addChildNode':
          arr = ThirdPartyDeviceOption.follow[this.targetItem.nodeType]
          break
        case 'addParentNode':
          arr = ThirdPartyDeviceOption.ahead[this.targetItem.nodeType]
          break
        default:
          break
      }
      return arr?.length
        ? arr.map(key => {
          return {
            value: key,
            label: ThirdPartyDeviceInfo[key]
          }
        })
        : null
    },
    showNodeDialog (action) {
      const options = this.createOptions(action)
      if (!options) {
        this.$message({
          type: 'warning',
          message: '暂未能满足执行操作的条件'
        })
        return
      }
      this.item = {
        action,
        nodeType: options[0].value,
        name: '',
        port: 0
      }
      this.thirdPartyDeviceSelectSchema = { options }
      this.$refs.nodeDialog.show()
    },
    onAddNode () {
      this.closeContentMenu()
      this.showNodeDialog('create')
    },
    onAddChildNode () {
      this.closeContentMenu()
      this.showNodeDialog('addChildNode')
    },
    onAddParentNode () {
      this.closeContentMenu()
      this.showNodeDialog('addParentNode')
    },
    onEditNode () {
      this.closeContentMenu()
      this.item = {
        action: 'update',
        ...this.targetItem
      }
      this.$refs.nodeEditDialog.show()
    },
    isExist (type) {
      return SoloThirdPartyDevices.includes(type) && this.nodes.some(({ nodeType }) => nodeType === type)
    },
    addNode (item) {
      if (this.isExist(item.nodeType)) {
        this.$message({
          type: 'warning',
          message: `${ThirdPartyDeviceInfo[item.nodeType]}仅能存在一个`
        })
        return Promise.reject()
      }
      return addNodeToMesh(this.meshId, item).then(({ data }) => {
        this.nodes.push(data)
        this.collectNodes(this.nodes)
        this.onDraw()
      })
    },
    addNodeBeforeNode (item, port) {
      if (this.isExist(item.nodeType)) {
        this.$message({
          type: 'warning',
          message: `${ThirdPartyDeviceInfo[item.nodeType]}仅能存在一个`
        })
        return Promise.reject()
      }
      return addNodeBeforeNode(this.targetItem, item, port).then(({ data }) => {
        this.nodes.push(data.node)
        this.edges.push(data.bonds[0])
        this.collectNodes(this.nodes)
        this.onDraw()
      })
    },
    addNodeAfterNode (item, port) {
      if (this.isExist(item.nodeType)) {
        this.$message({
          type: 'warning',
          message: `${ThirdPartyDeviceInfo[item.nodeType]}仅能存在一个`
        })
        return Promise.reject()
      }
      return addNodeAfterNode(this.targetItem, item, port).then(({ data }) => {
        this.nodes.push(data.node)
        this.edges.push(data.bonds[0])
        this.collectNodes(this.nodes)
        this.onDraw()
      })
    },
    onSubmit (done) {
      const { action, nodeType, name, port } = this.item
      const item = {
        name: name || ThirdPartyDeviceInfo[nodeType],
        nodeType
      }
      switch (action) {
        case 'addParentNode':
          this.addNodeBeforeNode(item, this.needPort ? port : -1).then(done)
          break
        case 'addChildNode':
          if (this.needPort && this.isPortUsed(this.targetItem, port)) {
            return
          }
          this.addNodeAfterNode(item, this.needPort ? port : -1).then(done)
          break
        case 'update':
          if (!name || this.targetItem.name === name) {
            done()
          } else {
            updateNode({
              id: this.targetItem.id,
              ...item
            }).then(() => {
              done()
              this.targetItem.name = name
              this.onDraw()
            })
          }
          break
        default:
          this.addNode(item).then(done)
          break
      }
    },
    isPortUsed ({ id }, targetPort) {
      const edge = this.edges.find(({ preNodeId, port }) => preNodeId === id && port === targetPort)
      if (edge) {
        this.$message({
          type: 'warning',
          message: `端口已被${this.nodes[this.$nodeMap[edge.nextNodeId]].name}占用`
        })
        return true
      }
      return false
    },
    onSubmitPort (done) {
      const { action, port } = this.item
      switch (action) {
        case 'edge':
          if (this.isPortUsed(this.targetItem, port)) {
            return
          }
          this.onAddEdge(this.targetItem, this.$targetNode, port).then(done)
          break
        case 'portEdit':
          if (port === this.targetItem.port) {
            done()
            return
          }
          if (this.isPortUsed(this.nodes[this.$nodeMap[this.targetItem.preNodeId]], port)) {
            return
          }
          updateEdgePort(this.targetItem, port).then(() => {
            done()
            this.targetItem.port = port
            this.onDraw()
          })
          break
        default:
          done()
          break
      }
    },
    onLinkTo () {
      this.closeContentMenu()
      this.$message({
        type: 'success',
        message: '源节点已记录，请选择目标节点'
      })
      this.onChangeLinkStatus(true)
    },
    onChangeLinkStatus (bool) {
      this.$waitTarget = bool
      this.onDraw()
    },
    addEdge (source, target) {
      const sourceId = source.id
      const targetId = target.id
      if (this.edges.some(({ preNodeId, nextNodeId }) => sourceId === preNodeId && targetId === nextNodeId)) {
        this.$message({
          type: 'warning',
          message: '连接已存在，请勿重复连接'
        })
        return
      }
      if (PortThirdPartyDevices.includes(target.nodeType)) {
        this.item = {
          action: 'edge',
          port: 0
        }
        this.$targetNode = target
        this.$refs.portEditDialog.show()
      } else {
        this.onAddEdge(source, target, -1)
      }
    },
    onAddEdge (source, target, port) {
      return addEdgeToMesh(this.meshId, source, target, port).then(({ data }) => {
        this.$targetNode = null
        this.edges.push(data)
        this.onDraw()
      })
    },
    onDel () {
      this.closeContentMenu()
      if (this.isNode) {
        deleteNode(this.targetItem).then(() => {
          const id = this.targetItem.id
          this.nodes.splice(this.nodes.findIndex(node => node.id === id), 1)
          this.edges = this.edges.filter(({ preNodeId, nextNodeId }) => preNodeId !== id && nextNodeId !== id)
          this.collectNodes(this.nodes)
          this.onDraw()
        })
      } else {
        deleteEdge(this.targetItem).then(() => {
          const id = this.targetItem.nodeBondId
          this.edges.splice(this.edges.findIndex(edge => edge.id === id), 1)
          this.collectNodes(this.nodes)
          this.onDraw()
        })
      }
    },
    onToggleInstance () {
      this.closeContentMenu()
      if (this.hasInstance) {
        releaseInstanceFormNode(this.targetItem).then(() => {
          this.targetItem.instanceId = null
          this.targetItem.instance = null
          this.onDraw()
        })
      } else {
        this.$refs.instanceDialog.show()
      }
    },
    onSubmitBind ({ value, done }) {
      if (value.id === this.targetItem.instanceId) {
        done()
        return
      }
      bindInstanceToNode(this.targetItem, value).then(() => {
        done()
        this.targetItem.instanceId = value.id
        this.targetItem.instance = value
        this.onDraw()
      })
    },
    onEditPort () {
      this.closeContentMenu()
      this.item = {
        action: 'portEdit',
        port: this.targetItem.port
      }
      this.$refs.portEditDialog.show()
    }
  }
}
</script>

<style lang="scss" scoped>
.c-mesh {
  &__header {
    height: $height--md;
    border-bottom: 1px solid $border;
  }
}

.c-contentmenu {
  position: fixed;
  width: 150px;
  z-index: 99;

  &__item {
    & + & {
      padding-top: 6px;
      margin-top: 6px;
      border-top: 1px solid $border;
    }
  }
}
</style>
