<template>
  <div class="topo-container" :style="{ height: containerHeight }">
    <!-- 左侧分组面板 -->
    <div class="topo-sidebar">
      <group-panel
        :groups="topoGroups"
        :selected-group="selectedGroup"
        :expanded-groups="expandedGroups"
        :nodes="nodes"
        :is-auto-topo="isAutoTopo"
        @select-group="handleSelectGroup"
        @toggle-group="toggleGroup"
        @group-change="handleTopologyGroupChange"
      />
    </div>

    <!-- 主拓扑区域 -->
    <div class="topo-main">
      <!-- 工具栏 -->
      <toolbar
        @zoom-in="zoomIn"
        @zoom-out="zoomOut"
        @fit-view="fitView"
        @add-device="handleAddDevice"
        @layout="handleLayout"
        @edge-style="setEdgeType"
        @refresh="refreshTopology"
        @export="exportTopology"
        @reset="resetTopology"
      />

      <!-- Vue Flow 画布 -->
      <div ref="reactFlowWrapper" class="flow-wrapper">
        <vue-flow
          v-model:nodes="nodes"
          :edges="styledEdges"
          :node-types="nodeTypes"
          :default-edge-options="defaultEdgeOptions"
          :fit-view-on-init="true"
          :min-zoom="0.2"
          :max-zoom="2"
          @node-click="onNodeClick"
          @edge-click="onEdgeClick"
          @connect="onConnect"
        >
          <background pattern-color="#aaa" :gap="16" />
          <mini-map :node-color="getNodeColor" node-stroke-color="#555" />
          <!-- <controls /> -->
        </vue-flow>
      </div>
    </div>

    <aside class="impact-sidebar">
      <div class="impact-header">
        <div class="impact-title">影响分析</div>
        <a-tag v-if="currentIncidentId" color="red">INC #{{ currentIncidentId }}</a-tag>
      </div>
      <a-spin :loading="impactLoading" class="impact-spin">
        <template v-if="impactData">
          <a-space direction="vertical" fill :size="12">
            <div class="impact-resource">{{ impactData.resource_uid }}</div>
            <section class="impact-section">
              <div class="impact-section-title">上游资源</div>
              <a-empty v-if="!impactData.upstream_resources.length" />
              <a-list v-else :bordered="false" size="small">
                <a-list-item v-for="item in impactData.upstream_resources" :key="item.resource_uid">
                  <a-list-item-meta :title="item.display_name || item.resource_uid" :description="item.resource_uid" />
                </a-list-item>
              </a-list>
            </section>
            <section class="impact-section">
              <div class="impact-section-title">受影响资源</div>
              <a-empty v-if="!impactData.downstream_resources.length" />
              <a-list v-else :bordered="false" size="small">
                <a-list-item v-for="item in impactData.downstream_resources" :key="item.resource_uid">
                  <a-list-item-meta :title="item.display_name || item.resource_uid" :description="item.resource_uid" />
                </a-list-item>
              </a-list>
            </section>
            <section class="impact-section">
              <div class="impact-section-title">受影响 IP</div>
              <a-empty v-if="!impactData.affected_ips.length" />
              <a-space v-else wrap>
                <a-tag v-for="item in impactData.affected_ips" :key="item.ip_address" color="arcoblue">{{ item.ip_address }}</a-tag>
              </a-space>
            </section>
          </a-space>
        </template>
        <a-empty v-else />
      </a-spin>
    </aside>

    <!-- ==================== 对话框组件 ==================== -->

    <!-- ==================== 节点对话框 ==================== -->

    <node-action-dialog
      v-model:open="nodeActionDialogOpen"
      :node="selectedNode"
      @view-detail="handleViewDetail"
      @edit="handleEdit"
      @delete="handleDeleteNode"
    />

    <node-detail-dialog v-model:visible="nodeDetailDialogOpen" :node-data="selectedNode?.data" />

    <node-edit-dialog v-model:visible="nodeEditDialogOpen" :node="selectedNode" @save="handleSaveNode" />

    <delete-confirm-dialog
      v-model:visible="deleteDialogOpen"
      :node-name="selectedNode?.data?.label || '未命名'"
      @confirm="handleDeleteNodeConfirm"
    />

    <!-- ==================== 边对话框 ==================== -->

    <edge-action-dialog v-model:visible="edgeActionDialogOpen" @edit="handleEditEdge" @delete="handleDeleteEdge" />

    <edge-edit-dialog
      v-model:visible="edgeEditDialogOpen"
      :edge="selectedEdge"
      :is-new-edge="isNewEdge"
      @change="setSelectedEdge"
      @save="handleSaveEdge"
    />

    <delete-confirm-dialog v-model:visible="deleteEdgeDialogOpen" node-name="链路" @confirm="handleDeleteEdgeConfirm" />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { MiniMap } from '@vue-flow/minimap'
import { Controls } from '@vue-flow/controls'
import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'
import { Message } from '@arco-design/web-vue'
import * as TopoAPI from '@/api/ops/netarchTopo'

import { NodeData, DeviceType, EdgeType } from './types'
import { DEVICE_TYPE_CONFIG } from './config'
import { CustomNode } from './components'
import { useTopoLayout, useEdgeStyles } from './hooks'
import { buildGroupTreeFromNodes, filterByGroup } from './utils/buildGroupTree'

// 导入所有组件
import GroupPanel from './components/GroupPanel.vue'
import Toolbar from './components/Toolbar.vue'
import AddNodeMenu from './components/AddNodeMenu.vue'
import LayoutMenu from './components/LayoutMenu.vue'
import EdgeStyleMenu from './components/EdgeStyleMenu.vue'
import NodeActionDialog from './components/NodeActionDialog.vue'
import NodeDetailDialog from './components/NodeDetailDialog.vue'
import NodeEditDialog from './components/NodeEditDialog.vue'
import DeleteConfirmDialog from './components/DeleteConfirmDialog.vue'
import EdgeActionDialog from './components/EdgeActionDialog.vue'
import EdgeEditDialog from './components/EdgeEditDialog.vue'

// 注册自定义节点类型
const nodeTypes = {
  custom: CustomNode,
}

// 默认边样式
const defaultEdgeOptions = {
  style: { strokeWidth: 2 },
}

// Vue Flow 实例
const { fitView, zoomIn, zoomOut } = useVueFlow()

const route = useRoute()
const reactFlowWrapper = ref<HTMLDivElement>()

// ==================== 状态管理 ====================

// 图数据状态
const nodes = ref<any[]>([])
const edges = ref<any[]>([])

// UI控制状态
const selectedGroup = ref<string | null>(null)
const expandedGroups = ref<Set<string>>(new Set())
const edgeType = ref<EdgeType>('default')

// 节点操作状态
const selectedNode = ref<any>(null)
const nodeActionDialogOpen = ref(false)
const nodeDetailDialogOpen = ref(false)
const nodeEditDialogOpen = ref(false)
const deleteDialogOpen = ref(false)

// 边操作状态
const selectedEdge = ref<any>(null)
const edgeActionDialogOpen = ref(false)
const edgeEditDialogOpen = ref(false)
const deleteEdgeDialogOpen = ref(false)

// 布局钩子
const { applyLayout } = useTopoLayout()

// ==================== 计算属性 ====================

// 从URL参数获取拓扑ID
const currentTopologyId = computed(() => {
  const id = route.query.id
  return id ? parseInt(id as string) : null
})

const currentIncidentId = computed(() => {
  const id = route.query.incident_id
  return id ? parseInt(id as string) : null
})

// 根据路由判断高度
const containerHeight = computed(() => {
  return route.path.includes('/netarch/auto-topo') ? 'calc(100vh - 170px)' : '100vh'
})

// 判断是否为自动拓扑路由
const isAutoTopo = computed(() => {
  return route.path.includes('/netarch/auto-topo')
})

// 从节点自动生成分组树
const topoGroups = computed(() => {
  const groups = buildGroupTreeFromNodes(nodes.value, edges.value)
  console.log('[topoGroups] 分组树:', {
    nodes: nodes.value.length,
    edges: edges.value.length,
    groups: groups.length,
    sampleGroup: groups[0],
  })
  return groups
})

// 根据选中的分组筛选显示的节点和边
const filteredResult = computed(() => filterByGroup(nodes.value, edges.value, selectedGroup.value))

// 使用响应式计算
const displayNodes = computed(() => filteredResult.value.nodes)
const displayEdges = computed(() => filteredResult.value.edges)

// 计算边样式
const styledEdges = useEdgeStyles(displayEdges, edgeType)

// 是否为新边
const isNewEdge = computed(() => {
  return !edges.value.some((e: any) => e.id === selectedEdge.value?.id)
})

const impactLoading = ref(false)
const impactData = ref<TopoAPI.ImpactAnalysis | null>(null)

// ==================== 生命周期 ====================

// 初始化数据
const loadData = async () => {
  if (!currentTopologyId.value) {
    return
  }
  try {
    let nodesData: any[] = []
    let edgesData: any[] = []

    // 从 graph 接口获取节点和边数据
    const graphResponse: any = await TopoAPI.fetchTopologyGraph(currentTopologyId.value)

    if (graphResponse.code === 0) {
      // 获取边数据 - res.details.edges
      const edgesFromGraph = graphResponse.details?.edges || graphResponse.data?.edges || []
      edgesData =
        edgesFromGraph.map((edge: any) => ({
          id: String(edge.id),
          source: edge.source,
          target: edge.target,
          label: edge.label || '',
          data: { ...edge },
        })) || []

      // 获取节点数据 - res.details.nodes 或 res.details.data
      const nodesFromGraph = graphResponse.details?.nodes || graphResponse.details?.data || graphResponse.data?.nodes || []

      if (Array.isArray(nodesFromGraph)) {
        nodesData = nodesFromGraph.map((node: any) => ({
          id: node.id,
          type: 'custom',
          position: node.position || { x: Math.random() * 800, y: Math.random() * 600 },
          data: {
            label: node.label,
            type: node.type,
            ip: node.ip,
            resource_uid: node.resource_uid,
            status: node.status || 'normal',
            alerts: node.alerts || 0,
            traffic: node.traffic,
            description: node.description,
            parentId: node.parentId,
            level: node.level ?? 0,
            position: node.position,
            ref_type: node.ref_type,
            ref_id: node.ref_id,
            sub_topology_id: node.sub_topology_id,
          },
        }))
      }
    }

    // 设置数据
    nodes.value = nodesData
    edges.value = edgesData

    console.log('[loadData] 加载完成:', {
      nodes: nodes.value.length,
      edges: edges.value.length,
      sampleNode: nodes.value[0],
    })
  } catch (error) {
    console.error('加载拓扑数据失败:', error)
    Message.warning('加载拓扑数据失败，使用默认数据')
    nodes.value = []
    edges.value = []
  }

  nextTick(() => {
    fitView({ duration: 500 })
  })
}

// 自动保存节点位置到后端（防抖）
let saveTimer: number | null = null
watch(
  nodes,
  () => {
    if (nodes.value.length === 0) return

    if (saveTimer) clearTimeout(saveTimer)
    saveTimer = setTimeout(async () => {
      try {
        if (!currentTopologyId.value) return
        const positions = nodes.value.map((node) => ({
          id: node.id,
          position: node.position,
        }))
        await TopoAPI.updateNodesPositions(currentTopologyId.value, positions)
      } catch (error) {
        console.error('保存节点位置失败:', error)
      }
    }, 1000)
  },
  { deep: true }
)

// 自动展开所有一级分组
watch(
  topoGroups,
  (newGroups) => {
    if (newGroups.length > 0) {
      const rootGroupIds = newGroups.map((g: any) => g.id)
      expandedGroups.value = new Set(rootGroupIds)
    }
  },
  { immediate: true }
)

onMounted(() => {
  loadData()
})

// ==================== 事件处理 ====================

// 连接处理 - 创建新链路
const onConnect = async (connection: any) => {
  try {
    if (!currentTopologyId.value) return
    const response: any = await TopoAPI.createLink(currentTopologyId.value, {
      source: connection.source,
      target: connection.target,
      type: 'physical',
    })

    if (response.code === 0) {
      // 创建成功后刷新接口
      await loadData()
      Message.success('链路创建成功')
    } else {
      Message.error('链路创建失败')
    }
  } catch (error) {
    console.error('创建链路失败:', error)
    Message.error('创建链路失败')
  }
}

// 节点点击
const onNodeClick = (event: any) => {
  selectedNode.value = event.node
  nodeActionDialogOpen.value = true
  loadImpactAnalysis(event.node)
}

// 边点击
const onEdgeClick = (event: any) => {
  selectedEdge.value = event.edge
  edgeActionDialogOpen.value = true
}

// ==================== 业务操作 ====================

// 布局处理
const handleLayout = (value: string | number | Record<string, any> | undefined) => {
  const layoutType = value as 'grid' | 'hierarchical' | 'circular'
  const updatedNodes = applyLayout(nodes.value, layoutType)
  nodes.value = updatedNodes
  nextTick(() => {
    fitView({ duration: 500 })
  })
}

// 设置边类型
const setEdgeType = (value: string | number | Record<string, any> | undefined) => {
  const type = value as EdgeType
  edgeType.value = type
}

// 添加设备
const handleAddDevice = async (value: string | number | Record<string, any> | undefined) => {
  const type = value as DeviceType
  const config = DEVICE_TYPE_CONFIG[type]
  const position = { x: Math.random() * 400 + 200, y: Math.random() * 300 + 100 }

  try {
    if (!currentTopologyId.value) return
    await TopoAPI.createNode(currentTopologyId.value, {
      label: config.label,
      type,
      ip: '',
      status: 'normal',
      alerts: 0,
      level: 0,
      position,
    })

    await loadData()
    Message.success('设备添加成功')
  } catch (error) {
    console.error('添加设备失败:', error)
    Message.error('添加设备失败')
  }
}

// 自定义添加
const handleCustomAdd = () => {
  selectedNode.value = null
  nodeEditDialogOpen.value = true
}

// 保存节点
const handleSaveNode = async (nodeId: string | null, nodeData: Partial<NodeData>) => {
  try {
    if (!currentTopologyId.value) return
    if (nodeId) {
      await TopoAPI.updateNode(currentTopologyId.value, nodeId, {
        label: nodeData.label!,
        type: nodeData.type!,
        ip: nodeData.ip,
        resource_uid: nodeData.resource_uid,
        status: nodeData.status,
        alerts: nodeData.alerts,
        traffic: nodeData.traffic,
        description: nodeData.description,
        parentId: nodeData.parentId,
        level: nodeData.level,
      })
      nodes.value = nodes.value.map((n: any) => (n.id === nodeId ? { ...n, data: { ...n.data, ...nodeData } } : n))
      Message.success('节点更新成功')
    } else {
      await TopoAPI.createNode(currentTopologyId.value, {
        label: nodeData.label!,
        type: nodeData.type!,
        ip: nodeData.ip,
        resource_uid: nodeData.resource_uid,
        status: nodeData.status || 'normal',
        alerts: nodeData.alerts || 0,
        traffic: nodeData.traffic,
        description: nodeData.description,
        parentId: nodeData.parentId,
        level: nodeData.level ?? 0,
        position: { x: 400, y: 300 },
      })

      await loadData()
      Message.success('节点创建成功')
    }
  } catch (error) {
    console.error('保存节点失败:', error)
    Message.error('保存节点失败')
  }
  nodeEditDialogOpen.value = false
  selectedNode.value = null
}

const loadImpactAnalysis = async (node: any) => {
  const resourceUID = node?.data?.resource_uid
  if (!resourceUID) {
    impactData.value = null
    return
  }
  try {
    impactLoading.value = true
    const response: any = await TopoAPI.fetchImpactAnalysis({
      resource_uid: resourceUID,
      topology_id: currentTopologyId.value || undefined,
      incident_id: currentIncidentId.value || undefined,
      depth: 2,
    })
    impactData.value = response.details || response.data || null
  } catch (error) {
    console.error('加载影响分析失败:', error)
    impactData.value = null
  } finally {
    impactLoading.value = false
  }
}

// 删除节点
const handleDeleteNode = () => {
  nodeActionDialogOpen.value = false
  deleteDialogOpen.value = true
}

const handleDeleteNodeConfirm = async () => {
  if (!selectedNode.value) return

  try {
    if (!currentTopologyId.value) return
    await TopoAPI.deleteNode(currentTopologyId.value, selectedNode.value.id)
    await loadData()
    Message.success('节点删除成功')
  } catch (error) {
    console.error('删除节点失败:', error)
    Message.error('删除节点失败')
  }

  deleteDialogOpen.value = false
  selectedNode.value = null
}

// 查看详情
const handleViewDetail = () => {
  nodeActionDialogOpen.value = false
  nodeDetailDialogOpen.value = true
}

// 编辑
const handleEdit = () => {
  nodeActionDialogOpen.value = false
  nodeEditDialogOpen.value = true
}

// 保存边
const handleSaveEdge = async () => {
  if (!selectedEdge.value) return

  try {
    if (!currentTopologyId.value) return
    if (isNewEdge.value) {
      const response: any = await TopoAPI.createLink(currentTopologyId.value, {
        source: selectedEdge.value.source,
        target: selectedEdge.value.target,
        type: selectedEdge.value.data?.type || 'physical',
        label: selectedEdge.value.data?.label || `${selectedEdge.value.source}-${selectedEdge.value.target}`,
      })
      if (response.code === 0 && response.data?.id) {
        const newEdge = {
          ...selectedEdge.value,
          id: String(response.data.id),
          data: { ...selectedEdge.value.data, ...response.data },
        }
        edges.value.push(newEdge)
        // 强制触发响应式更新
        edges.value = [...edges.value]

        await nextTick()
      } else {
        edges.value.push(selectedEdge.value)
        edges.value = [...edges.value]

        await nextTick()
      }
      Message.success('链路创建成功')
    } else {
      const linkId = Number(selectedEdge.value.id)
      if (linkId) {
        await TopoAPI.updateLink(currentTopologyId.value, linkId, {
          type: selectedEdge.value.data?.type,
          label: selectedEdge.value.data?.label,
        })
      }
      // 只更新边数据，不重新加载节点
      edges.value = edges.value.map((e: any) => (e.id === selectedEdge.value.id ? { ...e, data: selectedEdge.value.data } : e))
      // 强制触发响应式更新
      edges.value = [...edges.value]

      await nextTick()
      Message.success('链路更新成功')
    }
  } catch (error) {
    console.error('保存链路失败:', error)
    Message.error('保存链路失败')
  }

  edgeEditDialogOpen.value = false
  selectedEdge.value = null
}

// 删除边
const handleDeleteEdge = () => {
  edgeActionDialogOpen.value = false
  deleteEdgeDialogOpen.value = true
}

const handleDeleteEdgeConfirm = async () => {
  if (!selectedEdge.value) return

  try {
    const linkId = selectedEdge.value.id
    if (linkId && currentTopologyId.value) {
      await TopoAPI.deleteLink(currentTopologyId.value, linkId)
    }
    // 只删除边，不重新加载节点，保持节点位置
    edges.value = edges.value.filter((e: any) => e.id !== selectedEdge.value.id)
    // 强制触发响应式更新
    edges.value = [...edges.value]

    await nextTick()
    Message.success('链路删除成功')
  } catch (error) {
    console.error('删除链路失败:', error)
    Message.error('删除链路失败')
  }

  deleteEdgeDialogOpen.value = false
  selectedEdge.value = null
}

// 编辑边
const handleEditEdge = () => {
  edgeActionDialogOpen.value = false
  edgeEditDialogOpen.value = true
}

// 设置选中的边
const setSelectedEdge = (edge: any) => {
  selectedEdge.value = edge
}

// 刷新拓扑
const refreshTopology = () => {
  fitView({ duration: 500 })
}

// 导出拓扑
const exportTopology = () => {
  const data = { nodes: nodes.value, edges: edges.value }
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `topology-${Date.now()}.json`
  a.click()
  URL.revokeObjectURL(url)
}

// 重置拓扑
const resetTopology = async () => {
  if (confirm('确定要重置拓扑图吗?这将重新加载服务器数据!')) {
    try {
      if (!currentTopologyId.value) return
      const graphResponse: any = await TopoAPI.fetchTopologyGraph(currentTopologyId.value)
      if (graphResponse.code === 0) {
        const edgesFromGraph = graphResponse.details?.edges || graphResponse.data?.edges || []
        const edgesData =
          edgesFromGraph.map((edge: any) => ({
            id: String(edge.id),
            source: edge.source_node_id || edge.source,
            target: edge.target_node_id || edge.target,
            label: edge.name || edge.label || '',
            data: {
              linkId: edge.id,
              linkType: edge.type,
              bandwidth: edge.bandwidth,
              description: edge.description,
              ...edge,
            },
          })) || []

        let nodesData: any[] = []
        const nodesFromGraph = graphResponse.details?.data || graphResponse.data?.nodes || []
        if (typeof nodesFromGraph === 'string') {
          try {
            const parsedData = JSON.parse(nodesFromGraph)
            nodesData =
              parsedData.nodes?.map((node: any) => ({
                id: node.id,
                type: 'custom',
                position: node.data?.position || { x: Math.random() * 800, y: Math.random() * 600 },
                data: {
                  label: node.label,
              type: node.type,
              resource_uid: node.resource_uid,
              ...node.data,
                },
              })) || []
          } catch (e) {
            console.error('解析节点数据失败:', e)
          }
        }

        nodes.value = nodesData
        edges.value = edgesData
        Message.success('重置成功')
      }
    } catch (error) {
      console.error('重置失败:', error)
      Message.error('重置失败')
    }
    nextTick(() => {
      fitView({ duration: 500 })
    })
  }
}

// 分组操作
const handleSelectGroup = (groupId: string | null) => {
  selectedGroup.value = groupId
}

const toggleGroup = (groupId: string) => {
  const next = new Set(expandedGroups.value)
  if (next.has(groupId)) {
    next.delete(groupId)
  } else {
    next.add(groupId)
  }
  expandedGroups.value = next
}

// 拓扑分组变化处理
const handleTopologyGroupChange = async (topologyId: number | null) => {
  nodes.value = []
  edges.value = []
  if (topologyId) {
    await loadData()
  }
}

// 获取节点颜色
const getNodeColor = (node: any) => {
  const config = DEVICE_TYPE_CONFIG[node.data?.type as DeviceType]
  return config?.color || '#888'
}
</script>

<style scoped lang="less">
.topo-container {
  display: flex;
  width: 100%;
  overflow: hidden;
}

.topo-sidebar {
  flex-shrink: 0;
  height: 100%;
  overflow: hidden;
}

.topo-main {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  height: 100%;
  border-left: 1px solid var(--color-border-2);
  position: relative;
}

.impact-sidebar {
  flex: 0 0 300px;
  height: 100%;
  padding: 16px;
  overflow: auto;
  border-left: 1px solid var(--color-border-2);
  background: var(--color-bg-1);
}

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

.impact-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--color-text-1);
}

.impact-spin {
  width: 100%;
}

.impact-resource {
  padding: 8px;
  font-size: 13px;
  color: var(--color-text-2);
  word-break: break-all;
  background: var(--color-fill-2);
  border-radius: 4px;
}

.impact-section-title {
  margin-bottom: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-1);
}

.flow-wrapper {
  flex-grow: 1;
  width: 100%;
  height: 100%;
}

:deep(.vue-flow) {
  background-color: var(--color-bg-2);
}

:deep(.vue-flow__minimap) {
  background-color: var(--color-bg-2) !important;
  border: 1px solid var(--color-border-2) !important;
}

:deep(.vue-flow__controls) {
  background-color: var(--color-bg-2) !important;
  border: 1px solid var(--color-border-2) !important;
}
</style>
