<template>
  <div class="dep-graph">
    <div class="dep-graph__toolbar">
      <span class="dep-graph__title">{{ $t('dependencyGraph.title') }}</span>
      <div class="dep-graph__actions">
        <button v-if="modules.length >= 2" class="btn btn-ghost btn-sm" @click="showAddDep = true">+ 依赖</button>
        <button v-if="dependencies.length > 0" class="btn btn-ghost btn-sm" @click="checkCycles">🔍 环检测</button>
        <button class="btn btn-ghost btn-sm" @click="exportPNG">📷 导出</button>
        <button class="btn btn-ghost btn-sm" @click="fitContent">适应内容</button>
        <button class="btn btn-ghost btn-sm" @click="zoomIn">+</button>
        <button class="btn btn-ghost btn-sm" @click="zoomOut">-</button>
      </div>
    </div>
    <div ref="containerRef" class="dep-graph__canvas" />
    <div v-if="modules.length === 0 && !loading" class="dep-graph__empty">
      <div class="empty-icon">📦</div>
      <div>{{ $t('dependencyGraph.empty') }}</div>
    </div>

    <!-- 添加依赖弹窗 -->
    <div v-if="showAddDep" class="dep-graph__modal-overlay" @click.self="showAddDep = false">
      <div class="dep-graph__modal">
        <h3>添加工程依赖</h3>
        <div class="dep-graph__modal-field">
          <label>源工程(依赖方)</label>
          <select v-model="depFrom" class="dep-graph__modal-input">
            <option v-for="m in modules" :key="m.id" :value="m.id">{{ m.name }}</option>
          </select>
        </div>
        <div class="dep-graph__modal-field">
          <label>目标工程(被依赖)</label>
          <select v-model="depTo" class="dep-graph__modal-input">
            <option v-for="m in modules" :key="m.id" :value="m.id">{{ m.name }}</option>
          </select>
        </div>
        <div class="dep-graph__modal-field">
          <label>依赖类型</label>
          <select v-model="depType" class="dep-graph__modal-input">
            <option value="library">类库</option>
            <option value="api">API 调用</option>
            <option value="mq">消息队列</option>
            <option value="shared">共享资源</option>
            <option value="custom">自定义</option>
          </select>
        </div>
        <div class="dep-graph__modal-actions">
          <button class="btn btn-ghost btn-sm" @click="showAddDep = false">取消</button>
          <button class="btn btn-primary btn-sm" :disabled="!depFrom || !depTo || depFrom === depTo" @click="onAddDep">确认</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
/**
 * 依赖关系图组件 — 基于 AntV X6 v3 + vue-shape。
 *
 * 节点用 Vue 组件(ModuleNode.vue)渲染,通过 @antv/x6-vue-shape 注册。
 */
import { onMounted, onBeforeUnmount, ref, watch, markRaw } from 'vue'
import { useRouter } from 'vue-router'
import { Graph, Selection, Snapline, History, Scroller, MiniMap } from '@antv/x6'
import dagre from 'dagre'
import '@antv/x6-vue-shape'
import { Message } from '@arco-design/web-vue'
import { moduleApi, type ProjectModuleRecord, type ModuleDependencyRecord } from '@/api/module'
import ModuleNode from './ModuleNode.vue'

const props = defineProps<{
  projectId: string
}>()

const router = useRouter()

const emit = defineEmits<{
  (e: 'select-module', moduleId: string): void
}>()

const containerRef = ref<HTMLElement | null>(null)
const modules = ref<ProjectModuleRecord[]>([])
const dependencies = ref<ModuleDependencyRecord[]>([])
const loading = ref(true)
let graph: Graph | null = null

// 添加依赖弹窗
const showAddDep = ref(false)
const depFrom = ref('')
const depTo = ref('')
const depType = ref('library')

async function onAddDep() {
  if (!depFrom.value || !depTo.value || depFrom.value === depTo.value) return
  try {
    await moduleApi.addModuleDependency(props.projectId, depFrom.value, depTo.value, depType.value)
    showAddDep.value = false
    depFrom.value = ''
    depTo.value = ''
    depType.value = 'library'
    await loadModules()
    renderGraph()
  } catch (e) {
    console.error('[DependencyGraph] 添加依赖失败:', e)
  }
}

async function loadModules() {
  loading.value = true
  try {
    const [mods, deps] = await Promise.all([
      moduleApi.listProjectModules(props.projectId),
      moduleApi.listModuleDependencies(props.projectId),
    ])
    modules.value = mods
    dependencies.value = deps
  } catch {
    modules.value = []
    dependencies.value = []
  } finally {
    loading.value = false
  }
}

function renderGraph() {
  if (!graph) return

  const nodeWidth = 200
  const nodeHeight = 80

  // dagre 层次布局
  const g = new dagre.graphlib.Graph()
  g.setGraph({ rankdir: 'LR', nodesep: 30, ranksep: 60 })
  g.setDefaultEdgeLabel(() => ({}))
  for (const m of modules.value) {
    g.setNode(m.id, { width: nodeWidth, height: nodeHeight })
  }
  for (const d of dependencies.value) {
    g.setEdge(d.from_module_id, d.to_module_id)
  }
  dagre.layout(g)

  // 映射回 X6 节点格式
  const nodes = modules.value.map(m => {
    const pos = g.node(m.id)
    const isCycle = cycleNodes.value.has(m.id)
    return {
      id: m.id,
      shape: 'vue-shape',
      x: (pos?.x ?? 0) - nodeWidth / 2,
      y: (pos?.y ?? 0) - nodeHeight / 2,
      width: nodeWidth,
      height: nodeHeight,
      component: markRaw(ModuleNode),
      data: {
        name: m.name,
        path: m.path,
        stack: m.stack,
        gitUrl: m.git_url,
      },
      // 环节点高亮(红框);renderGraph 是画布唯一渲染入口,高亮必须在这里注入,
      // 否则会被 graph.fromJSON 整体替换抹除。
      attrs: isCycle ? {
        body: { stroke: '#e05050', strokeWidth: 3 },
      } : undefined,
    }
  })

  // 边数据(module_dependencies 表)
  const edges = dependencies.value.map(d => ({
    source: d.from_module_id,
    target: d.to_module_id,
    labels: d.label ? [{ text: d.label }] : [{ text: d.dep_type }],
    attrs: {
      line: {
        stroke: depTypeColor(d.dep_type),
        strokeWidth: 1.5,
        targetMarker: { name: 'block', width: 8, height: 6 },
      },
    },
  }))

  graph.fromJSON({ nodes, edges })
  graph.centerContent()
}

function buildGraph() {
  if (!containerRef.value) return

  graph = new Graph({
    container: containerRef.value,
    background: { color: '#1a1a2e' },
    grid: { visible: true, size: 10, type: 'dot', args: { color: '#2a2a4e' } },
    mousewheel: { enabled: true, modifiers: ['ctrl'], minScale: 0.3, maxScale: 3 },
    interacting: { nodeMovable: true },
  })

  graph.use(new Selection({ enabled: true, rubberband: true }))
  graph.use(new Snapline({ enabled: true }))
  graph.use(new History({ enabled: true }))
  graph.use(new Scroller({ enabled: true, pannable: true }))
  graph.use(new MiniMap({ width: 200, height: 120, padding: 10 }))

  graph.on('node:click', ({ node }) => {
    emit('select-module', String(node.id))
    router.push(`/projects/${props.projectId}`)
  })

  renderGraph()
}

function fitContent() {
  graph?.zoomToFit({ padding: 20, maxScale: 1.5 })
}

function zoomIn() {
  graph?.zoom(0.1)
}

function zoomOut() {
  graph?.zoom(-0.1)
}

/** 环形依赖检测。 */
const cycleNodes = ref<Set<string>>(new Set())
async function checkCycles() {
  try {
    const cycles = await moduleApi.detectModuleCycles(props.projectId)
    // 收集环节点 id,交给 renderGraph 统一渲染高亮(避免 cell.attr 被 fromJSON 抹除)。
    cycleNodes.value = new Set(cycles)
    renderGraph()
    if (cycles.length > 0) {
      Message.warning(`检测到 ${cycles.length} 个环节点(已红框高亮)`)
    } else {
      Message.success('未检测到环形依赖')
    }
  } catch (e) {
    console.error('[DependencyGraph] 环检测失败:', e)
    Message.error('环检测失败')
  }
}

/** 导出 PNG。 */
async function exportPNG() {
  if (!graph) return
  try {
    graph.toPNG((dataUri: string) => {
      const a = document.createElement('a')
      a.href = dataUri
      a.download = `dependency-graph-${props.projectId}.png`
      a.click()
    })
  } catch (e) {
    console.error('[DependencyGraph] 导出 PNG 失败:', e)
  }
}

/** 依赖类型 → 边颜色 */
function depTypeColor(depType: string): string {
  switch (depType) {
    case 'api': return '#5378e8'
    case 'mq': return '#e8a053'
    case 'shared': return '#53e8a0'
    case 'custom': return '#e853a0'
    default: return '#5c6b8a' // library
  }
}

onMounted(async () => {
  await loadModules()
  buildGraph()
})

watch(() => props.projectId, async () => {
  await loadModules()
  renderGraph()
})

onBeforeUnmount(() => {
  if (graph) {
    graph.dispose()
    graph = null
  }
})
</script>

<style scoped>
.dep-graph {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--df-bg);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-lg, 8px);
  overflow: hidden;
  position: relative;
}

.dep-graph__toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 14px;
  border-bottom: 0.5px solid var(--df-border);
  background: var(--df-bg-card);
  flex-shrink: 0;
}

.dep-graph__title {
  font-size: 14px;
  font-weight: 500;
  color: var(--df-text);
}

.dep-graph__actions {
  display: flex;
  gap: 6px;
}

.dep-graph__canvas {
  flex: 1;
  min-height: 300px;
  overflow: hidden;
}

.dep-graph__empty {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: var(--df-text-dim);
  font-size: 13px;
  pointer-events: none;
}

.empty-icon {
  font-size: 32px;
  opacity: 0.4;
}

/* 按钮基础样式 (.btn / .btn-ghost / .btn-sm / .btn-primary) 已收敛至 global.css */
/* 依赖图工具栏紧凑,覆 padding/font-size 避免按钮变大 */
.btn { padding: 4px 12px; font-size: 12px; }

/* 添加依赖弹窗 */
.dep-graph__modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.dep-graph__modal {
  background: var(--df-bg-card);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-lg);
  padding: 20px;
  min-width: 360px;
}
.dep-graph__modal h3 { font-size: 16px; color: var(--df-text); margin-bottom: 16px; }
.dep-graph__modal-field { margin-bottom: 12px; }
.dep-graph__modal-field label {
  display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px;
}
.dep-graph__modal-input {
  width: 100%; box-sizing: border-box;
  padding: 6px 10px;
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm);
  background: var(--df-bg); color: var(--df-text);
  font-size: 13px;
}
.dep-graph__modal-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px;
}
</style>
