﻿<template>
  <el-card class="section-card" shadow="never">
    <template #header>
      <div class="section-header">
        <span>通道配对</span>
        <span class="section-tip">选择设备后默认连接到第一个通道，如需调整可删除后重新配对</span>
      </div>
    </template>

    <div class="toolbar">
      <div class="toolbar-item">
        <span class="toolbar-label">设备</span>
        <el-select
          v-model="selectedDeviceId"
          class="device-select"
          filterable
          :loading="loading"
          placeholder="请选择设备"
        >
          <el-option
            v-for="item in deviceOptions"
            :key="item.id"
            :label="item.name"
            :value="item.id"
          />
        </el-select>
      </div>

    </div>

    <div v-if="selectedDevice" ref="flowContainerRef" class="flow-container" :style="{height: `${flowHeight}px`}">
      <VueFlow
        :nodes="nodes"
        :edges="edges"
        :edge-types="edgeTypes"
        :nodes-draggable="false"
        :elements-selectable="true"
        :zoom-on-scroll="false"
        :pan-on-drag="false"
        :zoom-on-double-click="false"
        :prevent-scrolling="true"
        :min-zoom="0.35"
        :max-zoom="1"
        fit-view-on-init
        @connect="handleConnect"
      />
    </div>

    <el-empty v-else description="请选择设备后进行通道配对" />
  </el-card>
</template>

<script lang="ts" setup>
import {ElMessage} from 'element-plus'
import {computed, h, nextTick, onBeforeUnmount, onMounted, ref, watch} from 'vue'
import {Position, VueFlow, useVueFlow, type Connection, type Edge, type Node} from '@vue-flow/core'
import {getPqDevListAll} from '@/api/device/device'
import {type Device} from '@/api/device/interface/device'
import {type FreqConverter} from '@/api/device/interface/freqConverter'
import FreqConverterDetectEdge from '@/views/machine/freqConverter/components/freqConverterDetectEdge.vue'

const props = defineProps<{
  freqConverter: FreqConverter.ResFreqConverter | null;
}>()

const loading = ref(false)
const deviceOptions = ref<Device.ResPqDev[]>([])
const selectedDeviceId = ref('')
const nodes = ref<Node[]>([])
const edges = ref<Edge[]>([])
const flowContainerRef = ref<HTMLElement | null>(null)
const selectedTargetChannelId = ref('')
const {fitView} = useVueFlow()
const flowHeight = 340
let flowContainerResizeObserver: ResizeObserver | null = null
const edgeTypes = {
  deletable: FreqConverterDetectEdge
}

const selectedDevice = computed(() => {
  return deviceOptions.value.find(item => item.id === selectedDeviceId.value) || null
})

const createDeviceLabel = (title: string, lines: string[]) => {
  return h(
    'div',
    {class: 'device-node-label'},
    [
      h('div', {class: 'device-node-title'}, title),
      ...lines.map(line => h('div', {class: 'device-node-line'}, line))
    ]
  ) as any
}

const createChannelLabel = (title: string) => {
  return h(
    'div',
    {class: 'channel-node-label'},
    h('span', {class: 'channel-node-text'}, title)
  ) as any
}

const createConnectionEdge = (source: string, target: string): Edge => ({
  id: `${source}-${target}`,
  source,
  target,
  type: 'deletable',
  selectable: true,
  focusable: true,
  data: {
    onDelete: removeConnectionById
  },
  style: {
    stroke: 'var(--el-color-primary)'
  }
})

const getDeviceChannels = (device: Device.ResPqDev | null) => {
  if (!device) {
    return [] as string[]
  }

  const channelCount = Math.max(Number(device.devChns) || 0, 0)
  return Array.from({length: channelCount}, (_, index) => `${index + 1}`)
}

const buildNodes = (device: Device.ResPqDev) => {
  const channelList = getDeviceChannels(device)
  const flowNodes: Node[] = []
  const channelCount = Math.max(channelList.length, 1)
  const flowCanvasHeight = flowHeight
  const gapY = 42
  const channelHeight = 20
  const cardHeight = 120
  const cardWidth = 210
  const layoutCenterY = flowCanvasHeight / 2
  const channelGroupHeight = channelHeight + (channelCount - 1) * gapY
  const startY = layoutCenterY - channelGroupHeight / 2
  const cardY = layoutCenterY - cardHeight / 2

  flowNodes.push({
    id: `freq-converter-${props.freqConverter?.id || 'default'}`,
    type: 'output',
    data: {
      label: createDeviceLabel('变频器', [
        `名称：${props.freqConverter?.name || '-'}`,
        `串口：${props.freqConverter?.portName || '-'}`,
        `从机地址：${props.freqConverter?.slaveAddress ?? '-'}`,
        `波特率：${props.freqConverter?.baudRate ?? '-'}`
      ])
    },
    position: {x: 84, y: cardY},
    sourcePosition: Position.Right,
    draggable: false,
    selectable: false,
    class: 'freq-converter-node',
    style: {width: `${cardWidth}px`, height: `${cardHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
  })

  flowNodes.push({
    id: `device-card-${device.id}`,
    data: {
      label: createDeviceLabel('设备', [
        `名称：${device.name || '-'}`,
        `类型：${device.devType || '-'}`,
        `IP：${device.ip || '-'}`,
        `通道数：${channelList.length}`
      ])
    },
    position: {x: 654, y: cardY},
    draggable: false,
    selectable: false,
    class: 'no-handle-node',
    style: {width: `${cardWidth}px`, height: `${cardHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
  })

  channelList.forEach((channel, index) => {
    const y = startY + index * gapY

    flowNodes.push({
      id: `device-channel-${device.id}-${channel}`,
      type: 'input',
      data: {label: createChannelLabel(`通道${channel}`)},
      position: {x: 584, y},
      targetPosition: Position.Left,
      draggable: false,
      selectable: false,
      class: 'channel-node',
      style: {width: '68px', height: `${channelHeight}px`, border: 'none', boxShadow: 'none', background: 'transparent'}
    })
  })

  return flowNodes
}

const fitFlowView = async () => {
  if (!nodes.value.length) {
    return
  }

  await nextTick()
  await fitView({
    padding: 0.18,
    duration: 0
  })
}

const observeFlowContainer = () => {
  flowContainerResizeObserver?.disconnect()
  flowContainerResizeObserver = null

  if (!flowContainerRef.value || typeof ResizeObserver === 'undefined') {
    return
  }

  flowContainerResizeObserver = new ResizeObserver(() => {
    void fitFlowView()
  })
  flowContainerResizeObserver.observe(flowContainerRef.value)
}

const getExpectedSourceId = () => `freq-converter-${props.freqConverter?.id || 'default'}`

const getFirstChannelId = (device: Device.ResPqDev | null) => {
  if (!device) {
    return ''
  }

  const [firstChannel] = getDeviceChannels(device)
  return firstChannel ? `device-channel-${device.id}-${firstChannel}` : ''
}

const syncEdges = () => {
  const source = getExpectedSourceId()
  edges.value = selectedTargetChannelId.value ? [createConnectionEdge(source, selectedTargetChannelId.value)] : []
}

const rebuildNodes = async () => {
  const currentDevice = selectedDevice.value
  nodes.value = currentDevice ? buildNodes(currentDevice) : []

  if (currentDevice && selectedTargetChannelId.value && !selectedTargetChannelId.value.startsWith(`device-channel-${currentDevice.id}-`)) {
    selectedTargetChannelId.value = getFirstChannelId(currentDevice)
  }

  syncEdges()
  await nextTick()
  observeFlowContainer()
  await fitFlowView()
}

const clearConnections = () => {
  selectedTargetChannelId.value = ''
  edges.value = []
}

const removeConnectionById = (edgeId: string) => {
  if (edges.value.some(item => item.id === edgeId)) {
    selectedTargetChannelId.value = ''
  }
  edges.value = edges.value.filter(item => item.id !== edgeId)
}

const handleConnect = (params: Connection) => {
  if (!params.source || !params.target) {
    return
  }

  const expectedSource = getExpectedSourceId()
  const connectionNodeIds = [params.source, params.target]
  const hasFreqConverterNode = connectionNodeIds.includes(expectedSource)
  const deviceChannelId = connectionNodeIds.find(item => item.startsWith('device-channel-'))
  const sourceNode = nodes.value.find(item => item.id === expectedSource)
  const targetNode = nodes.value.find(item => item.id === deviceChannelId)

  if (!hasFreqConverterNode || !deviceChannelId || sourceNode?.type !== 'output' || targetNode?.type !== 'input') {
    ElMessage.warning('只能从左侧变频器连线到右侧设备通道')
    return
  }

  if (edges.value.length > 0) {
    ElMessage.warning('变频器只能选择一个设备通道，如需重选请先删除配对')
    return
  }

  selectedTargetChannelId.value = deviceChannelId
  syncEdges()
}

const loadDeviceOptions = async () => {
  loading.value = true
  try {
    const result = await getPqDevListAll()
    const records = Array.isArray(result?.data) ? result.data : []
    deviceOptions.value = records
    if (records.length) {
      selectedDeviceId.value = records[0].id
      return
    }

    selectedDeviceId.value = ''
  } finally {
    loading.value = false
  }
}

const getChannelMapping = () => {
  const edge = edges.value[0]
  if (!edge) {
    return null
  }

  const targetParts = edge.target.split('-')

  return {
    deviceId: selectedDevice.value?.id || '',
    deviceName: selectedDevice.value?.name || '',
    deviceChannel: targetParts[targetParts.length - 1],
    freqConverterId: props.freqConverter?.id || '',
    freqConverterName: props.freqConverter?.name || ''
  }
}

watch(selectedDeviceId, async () => {
  selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
  await rebuildNodes()
})

watch(
  () => props.freqConverter?.id,
  async () => {
    selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
    await rebuildNodes()
  }
)

onMounted(async () => {
  await loadDeviceOptions()
  selectedTargetChannelId.value = getFirstChannelId(selectedDevice.value)
  await rebuildNodes()
})

onBeforeUnmount(() => {
  flowContainerResizeObserver?.disconnect()
})

defineExpose({
  getSelectedDevice: () => selectedDevice.value,
  hasValidConnection: () => edges.value.length === 1,
  getChannelMapping,
  clearConnections,
  resetSelection: () => {
    const firstDevice = deviceOptions.value[0] || null
    selectedDeviceId.value = firstDevice?.id || ''
    selectedTargetChannelId.value = getFirstChannelId(firstDevice)
    void rebuildNodes()
  }
})
</script>

<style scoped>
.section-card {
  border: 1px solid var(--el-border-color-light);
}

:deep(.section-card .el-card__header) {
  padding: 10px 14px;
}

:deep(.section-card .el-card__body) {
  padding: 10px 14px 14px;
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.section-tip {
  color: var(--el-text-color-secondary);
  font-size: 12px;
}

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.toolbar-item {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
}

.toolbar-label {
  color: var(--el-text-color-regular);
  white-space: nowrap;
}

.device-select {
  width: 280px;
  max-width: 100%;
}

.flow-container {
  border: 1px solid var(--el-border-color-light);
  border-radius: 8px;
  overflow: hidden;
}

:deep(.vue-flow__node) {
  border-radius: 8px;
  border: 1px solid var(--el-border-color);
  background: var(--el-bg-color-page);
  box-shadow: none;
}

:deep(.vue-flow__node.no-handle-node .vue-flow__handle) {
  display: none;
}

:deep(.vue-flow__handle) {
  width: 0;
  height: 0;
  min-width: 0;
  min-height: 0;
  background: transparent;
  border: none;
  overflow: visible;
}

:deep(.vue-flow__handle::before) {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--el-color-primary);
  transform: translate(-50%, -50%);
}

:deep(.vue-flow__node.channel-node) {
  border: none;
  background: transparent;
  box-shadow: none;
}

:deep(.vue-flow__node.channel-node .vue-flow__handle) {
  top: 50%;
  left: 0;
  transform: translate(-50%, -50%);
}

:deep(.vue-flow__node.freq-converter-node .vue-flow__handle) {
  top: 50%;
  right: 0;
  left: auto;
  bottom: auto;
  transform: translate(50%, -50%);
}

:deep(.device-node-label) {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  height: 100%;
  box-sizing: border-box;
  border: 1px solid var(--el-border-color);
  border-radius: 10px;
  background: var(--el-fill-color-light);
  padding: 10px 12px;
  color: var(--el-text-color-primary);
}

:deep(.device-node-title) {
  font-weight: 600;
}

:deep(.device-node-line) {
  font-size: 12px;
  line-height: 1.4;
}

:deep(.channel-node-label) {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  height: 100%;
  padding: 0 0 0 10px;
  font-size: 12px;
  color: var(--el-text-color-primary);
  text-align: left;
}

:deep(.channel-node-text) {
  display: inline-flex;
  align-items: center;
  min-height: 20px;
  line-height: 1;
}

@media (max-width: 1200px) {
  .section-header {
    align-items: flex-start;
    flex-direction: column;
  }

  .toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .toolbar-item {
    flex-wrap: wrap;
  }

  .device-select {
    width: 100%;
  }
}
</style>
