<template>
  <div class="map-canvas-page" ref="canvasRef">
    <!-- 工具栏 -->
    <div class="toolbar">
      <h3>战略地图画布 — 四层泳道</h3>
      <div class="toolbar-right">
        <el-select v-model="selectedMap" placeholder="选择地图" style="width:200px;margin-right:8px;" @change="loadCanvas">
          <el-option v-for="m in maps" :key="m.id" :label="m.title" :value="m.id" />
        </el-select>
        <!-- 缩放控制 -->
        <div class="zoom-controls" title="画布缩放">
          <el-button size="small" @click="zoomOut" :disabled="zoomLevel <= 0.5">−</el-button>
          <span class="zoom-label">{{ Math.round(zoomLevel * 100) }}%</span>
          <el-button size="small" @click="zoomIn" :disabled="zoomLevel >= 1.5">+</el-button>
          <el-button size="small" @click="zoomReset">100%</el-button>
        </div>
        <el-button type="primary" @click="saveCanvas">保存</el-button>
        <el-button type="success" @click="publishMap" v-if="currentMap?.status === 'draft'">发布</el-button>
        <el-button @click="showVersions = true; loadVersions()">版本历史</el-button>
        <el-button @click="goAlignment" type="info" plain>
          <svg style="width:14px;height:14px;margin-right:4px;vertical-align:-2px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M3 12h4l2-3 3 3 4-7 3 5 2-2 4 4"/>
          </svg>
          查看执行看板
        </el-button>
      </div>
    </div>

    <!-- 连线模式提示 -->
    <div v-if="linkingFrom" class="linking-bar">
      <span>🔗 从「{{ linkingFrom.obj.name }}」连线 — 点击另一个目标完成连线</span>
      <el-button size="small" @click="cancelLink">取消</el-button>
    </div>

    <!-- 空状态提示 -->
    <div v-if="!loading && dimensions.every(d => d.objectives.length === 0)" class="empty-state">
      <el-empty description="四层泳道已就绪，点击各层的「+ 添加目标」开始填写">
        <template #image>
          <svg viewBox="0 0 120 120" width="120" height="120">
            <rect x="10" y="10" width="100" height="22" rx="4" fill="#FEF0F0" />
            <rect x="10" y="38" width="100" height="22" rx="4" fill="#ECF5FF" />
            <rect x="10" y="66" width="100" height="22" rx="4" fill="#F0F9EB" />
            <rect x="10" y="94" width="100" height="22" rx="4" fill="#FDF6EC" />
          </svg>
        </template>
      </el-empty>
    </div>

    <!-- 画布主体（纵向泳道布局，CSS zoom缩放） -->
    <div class="canvas-scroll-wrap" ref="scrollWrapRef" :style="{ zoom: zoomLevel }">
      <div class="canvas-body" ref="bodyRef">
        <!-- 四层泳道（纵向排列，固定顺序：财务→客户→流程→学习） -->
        <div
          v-for="(cfg, idx) in orderedLayerConfigs"
          :key="cfg.key"
          :ref="el => setLayerRef(cfg.key, el)"
          class="layer-swimlane-wrapper"
          :style="{ borderLeft: '4px solid ' + cfg.color }"
        >
          <StrategyLayer
            :ref="el => setLayerCompRef(cfg.key, el)"
            :layer-key="cfg.key"
            :label="cfg.label"
            :icon="cfg.icon"
            :color="cfg.color"
            :objectives="getLayerNodes(cfg.key)"
            :linking-from-key="linkingFrom?.key ?? null"
            :drag-connect-target="dragConnectTarget"
            :icon-map="iconMap"
            :kpi-name-map="kpiNameMap"
            :all-kpis="allKpis"
            :get-node-level="getNodeLevel"
            :get-node-progress="getNodeProgress"
            @add-objective="(k: string) => openAddDialog(k)"
            @edit-objective="(p: any) => openEditDialog(p.dimKey, p.idx, p.obj)"
            @delete-objective="(p: any) => deleteNode(p.dimKey, p.idx)"
            @start-link="(p: any) => { const ps = p.key.split('-'); startLink(ps[0], parseInt(ps[ps.length-1]), p.obj) }"
            @link-drag-start="(e: MouseEvent, dk: string, oi: number, obj: any) => onLinkDragStart(e, dk, oi, obj)"
            @kpi-click="(code: string) => goKPI(code)"
            @show-plan-list="(p: any) => showPlanList(p.dimKey, p.idx, p.obj)"
            @node-click="(dk: string, oi: number, obj: any) => onNodeClick(dk, oi, obj)"
            @reorder="(dk: string) => onLayerReorder(dk)"
          />

          <!-- 跨层箭头指示器（本层→下层） -->
          <div v-if="idx < orderedLayerConfigs.length - 1" class="cross-layer-arrow">
            <svg width="24" height="32" viewBox="0 0 24 32">
              <line x1="12" y1="0" x2="12" y2="24" stroke="#909399" stroke-width="2" stroke-dasharray="4,3" />
              <polygon points="4,22 12,30 20,22" fill="#909399" />
            </svg>
          </div>
        </div>
      </div><!-- end canvas-body -->
    </div><!-- end canvas-scroll-wrap -->

    <!-- SVG 连线层 （覆盖整个画布，绘制跨层+同层箭头） -->
    <ConnectionLines
      ref="connectionLinesRef"
      :layer-refs="layerRefs"
      :node-refs="nodeRefs"
      :layer-keys="layerKeysOrdered"
      :connections="connections"
      :temp-line="tempLine"
      :container-key="recalcTrigger"
      @select-connection="onSelectConnection"
      @delete-connection="onDeleteConnection"
    />

    <!-- 节点编辑弹窗 -->
    <NodeEditDialog
      :visible="showNodeDialog"
      :node-data="editingNodeData"
      :layer-key="editingLayerKey"
      @update:visible="showNodeDialog = $event"
      @save="onNodeSave"
    />

    <!-- 版本历史弹窗 -->
    <div v-if="showVersions" class="mc-dialog-overlay" @click.self="showVersions=false">
      <div class="mc-dialog-box" style="width:620px;">
        <div class="mc-dialog-header">
          <span>版本历史</span>
          <button class="mc-dialog-close" @click="showVersions=false">×</button>
        </div>
        <div class="mc-dialog-body" style="max-height:400px;overflow-y:auto;">
          <table class="mc-table" v-if="versions.length > 0">
            <thead><tr><th>说明</th><th>创建时间</th><th>操作</th></tr></thead>
            <tbody>
              <tr v-for="v in versions" :key="v.id">
                <td>{{ v.comment || '-' }}</td>
                <td>{{ v.created_at }}</td>
                <td><button class="mc-btn mc-btn-sm mc-btn-warn" @click="rollback(v.id)">回滚</button></td>
              </tr>
            </tbody>
          </table>
          <div v-else style="text-align:center;color:#999;padding:20px;">暂无版本记录</div>
        </div>
      </div>
    </div>

    <!-- KPI详情浮层 -->
    <div v-if="showKpiDetail" class="mc-dialog-overlay" @click.self="showKpiDetail=false">
      <div class="mc-dialog-box" style="width:560px;">
        <div class="mc-dialog-header">
          <span>📊 {{ kpiDetailObjName }} — KPI详情</span>
          <button class="mc-dialog-close" @click="showKpiDetail=false">×</button>
        </div>
        <div class="mc-dialog-body" style="max-height:360px;overflow-y:auto;">
          <div v-if="kpiDetailList.length === 0" style="text-align:center;color:#999;padding:20px;">
            该目标暂未关联KPI
          </div>
          <div v-for="(item, idx) in kpiDetailList" :key="idx" class="kpi-detail-card" :class="'kpi-detail-' + getKpiLevel(item)">
            <div class="kpi-detail-top">
              <span class="kpi-detail-badge">{{ badgeIcon(getKpiLevel(item)) }}</span>
              <span class="kpi-detail-code">{{ item.code }}</span>
              <span class="kpi-detail-name">{{ item.name }}</span>
            </div>
            <div class="kpi-detail-values">
              <div class="kdv-item">
                <span class="kdv-label">实际值</span>
                <span class="kdv-val">{{ item.actual != null ? fmtKpiVal(item.actual) : '—' }}</span>
              </div>
              <div class="kdv-item">
                <span class="kdv-label">目标值</span>
                <span class="kdv-val">{{ item.target != null ? fmtKpiVal(item.target) : '—' }}</span>
              </div>
              <div v-if="item.actual != null && item.target" class="kdv-item">
                <span class="kdv-label">达成率</span>
                <span class="kdv-val" :style="{ color: getKpiLevel(item) === 'red' ? '#f56c6c' : getKpiLevel(item) === 'yellow' ? '#e6a23c' : '#67c23a' }">
                  {{ (item.actual / item.target * 100).toFixed(1) }}%
                </span>
              </div>
              <div v-if="item.unit" class="kdv-item">
                <span class="kdv-label">单位</span>
                <span class="kdv-val">{{ item.unit }}</span>
              </div>
            </div>
          </div>
        </div>
        <div class="mc-dialog-footer">
          <el-button size="small" @click="showKpiDetail=false">关闭</el-button>
          <el-button size="small" type="primary" @click="editKpiDetailObj">编辑目标</el-button>
        </div>
      </div>
    </div>

    <!-- 行动方案列表弹窗 -->
    <div v-if="showPlanPopup" class="mc-dialog-overlay" @click.self="showPlanPopup=false">
      <div class="mc-dialog-box" style="width:600px;">
        <div class="mc-dialog-header">
          <span>📋 {{ planPopupObjName }} — 关联行动方案</span>
          <button class="mc-dialog-close" @click="showPlanPopup=false">×</button>
        </div>
        <div class="mc-dialog-body" style="max-height:400px;overflow-y:auto;">
          <div v-if="planPopupList.length === 0" style="text-align:center;color:#999;padding:20px;">
            暂无关联的行动方案
          </div>
          <div v-for="p in planPopupList" :key="p.id" class="plan-popup-card">
            <div class="plan-popup-top">
              <span class="plan-popup-status" :class="'pps-' + p.status">{{ planStatusLabel(p.status) }}</span>
              <span class="plan-popup-title">{{ p.title }}</span>
            </div>
            <div class="plan-popup-meta">
              <span v-if="p.assignee">👤 {{ p.assignee }}</span>
              <span v-if="p.due_date">📅 {{ p.due_date.slice(0, 10) }}</span>
              <span v-if="p.progress > 0">进度 {{ p.progress }}%</span>
            </div>
          </div>
        </div>
        <div class="mc-dialog-footer">
          <el-button size="small" @click="showPlanPopup=false">关闭</el-button>
          <el-button size="small" type="primary" @click="quickCreatePlan">+ 新建行动方案</el-button>
        </div>
      </div>
    </div>
  </div>
  <!-- P1-3: 关联知识点 -->
  <div style="margin-top:12px;">
    <KnowledgePanel related-page="/maps" />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick, onUnmounted } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"
import { mapApi, kpiApi } from "../api/index"
import KnowledgePanel from '../components/KnowledgePanel.vue'
import api from "../api/index"
import { LAYER_CONFIG, LAYER_KEYS, getLayerList } from "../config/layers"
import StrategyLayer from "../components/strategy-map/StrategyLayer.vue"
import NodeEditDialog from "../components/strategy-map/NodeEditDialog.vue"
import ConnectionLines from "../components/strategy/ConnectionLines.vue"

// ── 四层泳道配置 ──
const layerKeysOrdered = [...LAYER_KEYS]
const orderedLayerConfigs = getLayerList()

// ── 状态 ──
const maps = ref<any[]>([])
const selectedMap = ref<number | null>(null)
const currentMap = ref<any>(null)
const allKpis = ref<any[]>([])
const kpiNameMap = reactive<Record<string, string>>({})

// 维度数据（对应backend的dimensions）
const dimensions = reactive<any[]>([])
const loading = ref(true)

// 连线
const connections = ref<any[]>([])
const linkingFrom = ref<{ key: string; obj: any } | null>(null)
const tempLine = ref<{ x1: number; y1: number; x2: number; y2: number } | null>(null)
const dragConnectTarget = ref<string | null>(null)
const selectedConnIdx = ref<number | null>(null)

// 缩放
const zoomLevel = ref(1)
const scrollWrapRef = ref<HTMLElement | null>(null)

// 节点状态（红黄绿灯）
const objectiveLevels = reactive<Record<string, string>>({})

interface KpiProgressItem {
  ratio: number
  level: string
  kpiList: { code: string; name: string; actual: number|null; target: number|null; unit: string|null }[]
  planSummary: { total: number; pending: number; in_progress: number; completed: number; overdue: number } | null
}
const kpiProgressMap = reactive<Record<string, KpiProgressItem>>({})

// 弹窗
const showNodeDialog = ref(false)
const editingNodeData = ref<any>(null)
const editingLayerKey = ref('')
const showVersions = ref(false)
const versions = ref<any[]>([])

// DOM引用
const bodyRef = ref<HTMLElement | null>(null)
const layerRefs: Record<string, HTMLElement> = {}
const nodeRefs: Record<string, HTMLElement> = {}
const connectionLinesRef = ref<any>(null)
const recalcTrigger = ref(0)

// 泳道组件实例引用（用于汇聚各层内部的nodeRefs给ConnectionLines）
const layerCompRefs: Record<string, any> = {}
function setLayerCompRef(key: string, el: any) {
  if (el) layerCompRefs[key] = el
}

/** 从四个泳道组件汇聚所有节点DOM引用用于连线渲染，同步到nodeRefs */
function collectAllNodeRefs(): Record<string, any> {
  void (0) // keep as side-effect function
  // 清空旧refs
  Object.keys(nodeRefs).forEach(k => delete (nodeRefs as any)[k])
  // 从各泳道组件汇聚
  for (const key of layerKeysOrdered) {
    const comp = layerCompRefs[key]
    if (comp?.nodeRefs) {
      Object.assign(nodeRefs, comp.nodeRefs)
    }
  }
  return nodeRefs
}

// 拖拽引用（由 vuedraggable 接管，内部不维护）

// KPI详情浮层
const showKpiDetail = ref(false)
const kpiDetailObjName = ref("")
const kpiDetailDimKey = ref("")
const kpiDetailObjIdx = ref(-1)
const kpiDetailList = ref<any[]>([])

// 行动方案弹窗
const showPlanPopup = ref(false)
const planPopupObjName = ref("")
const planPopupDimKey = ref("")
const planPopupObjIdx = ref(-1)
const planPopupList = ref<any[]>([])

// ── 图标映射（从旧MapCanvas复用） ──
const iconMap: Record<string, string> = {
  target: "Star", star: "StarFilled", rocket: "Top", chart: "DataLine",
  team: "UserFilled", light: "Lightning", shield: "Shield", gear: "Setting",
  handshake: "Handshake", medal: "Trophy",
}

// ── 辅助方法 ──
function getLayerNodes(key: string): any[] {
  const dim = dimensions.find((d: any) => d.key === key)
  return dim?.objectives || []
}

function setLayerRef(key: string, el: any) {
  if (el) layerRefs[key] = el
}

function getNodeLevel(key: string): string {
  return objectiveLevels[key] || 'gray'
}
function getNodeProgress(key: string): any {
  return kpiProgressMap[key] || null
}

/** 确保dimensions中存在所有4层 */
function ensureFourLayers() {
  for (const cfg of orderedLayerConfigs) {
    if (!dimensions.find((d: any) => d.key === cfg.key)) {
      dimensions.push({
        key: cfg.key,
        name: cfg.label,
        label: cfg.label,
        icon: cfg.icon,
        color: cfg.color,
        objectives: [],
      })
    }
  }
}

// ── 缩放 ──
function zoomIn() { zoomLevel.value = Math.min(1.5, Math.round((zoomLevel.value + 0.1) * 10) / 10) }
function zoomOut() { zoomLevel.value = Math.max(0.5, Math.round((zoomLevel.value - 0.1) * 10) / 10) }
function zoomReset() { zoomLevel.value = 1 }

// ── 画布加载 ──
function loadCanvas() {
  if (!selectedMap.value) return
  dimensions.splice(0, dimensions.length)
  connections.value = []
  linkingFrom.value = null
  selectedConnIdx.value = null
  loading.value = true

  api.get("/maps").then((r: any) => {
    const data = r.data || []
    const map = data.find((m: any) => m.id === selectedMap.value)
    currentMap.value = map
    if (map?.dimensions) {
      for (const dim of map.dimensions) {
        dimensions.push({
          key: dim.key,
          label: dim.name || dim.label,
          icon: dim.icon || '📋',
          color: dim.color || LAYER_CONFIG[dim.key]?.color || '#409eff',
          objectives: dim.objectives || [],
        })
      }
    }
    // 确保四层都存在
    ensureFourLayers()
    connections.value = map?.canvas_data?.connections || []
    loading.value = false
    nextTick(() => {
      triggerRecalc()
      loadObjectiveLevels()
    })
  }).catch(() => {
    ensureFourLayers()
    loading.value = false
  })
}

// ── 触发连线重算 ──
function triggerRecalc() {
  recalcTrigger.value++
  collectAllNodeRefs()
  nextTick(() => {
    connectionLinesRef.value?.recalcAll()
  })
}

// ── 连线交互 ──
function startLink(dimKey: string, oi: number, obj: any) {
  selectedConnIdx.value = null
  linkingFrom.value = { key: `${dimKey}-${oi}`, obj }
}

function cancelLink() {
  linkingFrom.value = null
  tempLine.value = null
  dragConnectTarget.value = null
}

function onNodeClick(dimKey: string, oi: number, obj: any) {
  const key = `${dimKey}-${oi}`
  // 连线模式
  if (linkingFrom.value) {
    if (linkingFrom.value.key === key) { cancelLink(); return }
    completeConnection(linkingFrom.value.key, key)
    return
  }
  // 有KPI则展示详情
  if (kpiProgressMap[key]?.kpiList?.length > 0) {
    showKpiDetailModal(dimKey, oi, obj)
  } else {
    openEditDialog(dimKey, oi, obj)
  }
}

/**
 * 层内重排完成回调 — 由 vuedraggable 在拖拽排序后触发
 * SortableJS 已通过 :list 引用直接更新了响应式数组，
 * 这里只需触发连线重算和自动保存
 */
function onLayerReorder(dimKey: string) {
  nextTick(() => {
    triggerRecalc()
    debouncedSaveCanvas()
  })
}

function completeConnection(fromKey: string, toKey: string) {
  const fromDim = fromKey.split('-')[0]
  const toDim = toKey.split('-')[0]
  if (fromDim === toDim) {
    // 同层连线 -> 同层右侧箭头
  }
  // 检查重复
  if (connections.value.some((c: any) => c.from === fromKey && c.to === toKey)) {
    ElMessage.warning("已存在相同连线"); cancelLink(); return
  }
  api.post(`/maps/${selectedMap.value}/connections`, {
    from: fromKey, to: toKey,
  }).then(() => {
    ElMessage.success("连线已添加")
    connections.value.push({ from: fromKey, to: toKey, style: "solid" })
    cancelLink()
    nextTick(() => { triggerRecalc(); saveConnections() })
  }).catch((e: any) => {
    if (e?.response?.status !== 400) {
      connections.value.push({ from: fromKey, to: toKey, style: "solid" })
      ElMessage.success("连线已添加（本地）")
    } else {
      ElMessage.warning(e?.response?.data?.detail || "连线失败")
    }
    cancelLink()
    nextTick(triggerRecalc)
  })
}

// 跳转KPI详情
function goKPI(kpiCode: string) {
  const found = allKpis.value.find((k: any) => k.kpi_code === kpiCode)
  window.location.href = found ? '/kpis/' + found.id : '/kpis'
}

// ── 拖拽连线的鼠标跟踪 ──
function onLinkDragStart(e: MouseEvent, dimKey: string, oi: number, obj: any) {
  const startX = e.clientX
  const startY = e.clientY
  startLink(dimKey, oi, obj)
  const onUp = (up: MouseEvent) => {
    document.removeEventListener('mouseup', onUp)
    const dist = Math.hypot(up.clientX - startX, up.clientY - startY)
    if (dist >= 5 && linkingFrom.value) {
      let targetKey: string | null = null
      for (const key of Object.keys(nodeRefs)) {
        if (key === linkingFrom.value.key) continue
        const el = nodeRefs[key]
        if (!el) continue
        const rect = el.getBoundingClientRect()
        if (up.clientX >= rect.left && up.clientX <= rect.right &&
            up.clientY >= rect.top && up.clientY <= rect.bottom) {
          targetKey = key; break
        }
      }
      if (targetKey) {
        const parts = targetKey.split('-')
        const tDim = parts[0]
        const tIdx = parseInt(parts[1])
        const dim = dimensions.find((d: any) => d.key === tDim)
        if (dim && dim.objectives[tIdx]) {
          completeConnection(linkingFrom.value.key, targetKey)
        }
      }
      cancelLink()
    }
    dragConnectTarget.value = null
  }
  document.addEventListener('mouseup', onUp)
}

function onSelectConnection(idx: number) {
  selectedConnIdx.value = selectedConnIdx.value === idx ? null : idx
}

function onDeleteConnection(idx: number) {
  if (selectedConnIdx.value !== idx) return
  const conn = connections.value[idx]
  if (!conn) return
  api.delete(`/maps/${selectedMap.value}/connections`, {
    data: { from: conn.from, to: conn.to },
  }).then(() => {
    ElMessage.success("连线已删除")
    connections.value.splice(idx, 1)
    selectedConnIdx.value = null
    nextTick(triggerRecalc)
  }).catch(() => {
    connections.value.splice(idx, 1)
    selectedConnIdx.value = null
    nextTick(triggerRecalc)
  })
}

// ── 节点CRUD ──
function openAddDialog(layerKey: string) {
  editingLayerKey.value = layerKey
  editingNodeData.value = null
  showNodeDialog.value = true
}

function openEditDialog(dimKey: string, oi: number, obj: any) {
  editingLayerKey.value = dimKey
  editingNodeData.value = {
    id: obj.id,
    name: obj.name,
    targetValue: obj.targetValue ?? obj.target_value ?? null,
    currentValue: obj.currentValue ?? obj.current_value ?? null,
    unit: obj.unit || '%',
    owner: obj.owner || '',
    isLeading: obj.isLeading ?? obj.is_leading ?? false,
    description: obj.description || '',
  }
  // 保存编辑索引以便更新
  editingNodeData.value._dimKey = dimKey
  editingNodeData.value._index = oi
  showNodeDialog.value = true
}

function onNodeSave(data: any) {
  const dim = dimensions.find((d: any) => d.key === data.layer)
  if (!dim) return

  // 构建节点对象
  const node = {
    name: data.name,
    targetValue: data.targetValue,
    currentValue: data.currentValue,
    unit: data.unit,
    owner: data.owner,
    isLeading: data.isLeading,
    description: data.description || '',
    layer: data.layer,
  }

  if (editingNodeData.value?._index != null && editingNodeData.value._dimKey === data.layer) {
    // 编辑已有节点
    const idx = editingNodeData.value._index
    if (dim.objectives[idx]) {
      // 保留原有kpis等额外字段
      const extra = {
        kpis: dim.objectives[idx].kpis || [],
        icon: dim.objectives[idx].icon || 'target',
      }
      dim.objectives[idx] = { ...node, ...extra }
      ElMessage.success("节点已更新")
    }
  } else {
    // 新增节点
    dim.objectives.push({
      ...node,
      kpis: [],
      icon: 'target',
    })
    ElMessage.success("节点已添加")
  }

  editingNodeData.value = null
  debouncedSaveCanvas()
  nextTick(triggerRecalc)
}

async function deleteNode(dimKey: string, oi: number) {
  try {
    await ElMessageBox.confirm("确定要删除这个节点吗？", "确认删除")
    const dim = dimensions.find((d: any) => d.key === dimKey)
    if (dim) {
      const key = `${dimKey}-${oi}`
      connections.value = connections.value.filter(c => c.from !== key && c.to !== key)
      dim.objectives.splice(oi, 1)
      ElMessage.success("节点已删除")
      nextTick(() => { triggerRecalc(); debouncedSaveCanvas() })
    }
  } catch { /* cancel */ }
}

// ── 保存 ──
let saveTimer: ReturnType<typeof setTimeout> | null = null
let isSaving = false

function debouncedSaveCanvas() {
  if (isSaving) return
  if (saveTimer) clearTimeout(saveTimer)
  saveTimer = setTimeout(() => { saveCanvas() }, 500)
}

async function saveCanvas() {
  if (!selectedMap.value) return
  if (isSaving) return
  isSaving = true
  const mapData = dimensions.map((d: any) => ({
    key: d.key, name: d.label || d.name, icon: d.icon, color: d.color,
    objectives: d.objectives,
  }))
  try {
    await mapApi.update(selectedMap.value, {
      dimensions: mapData,
      canvas_data: { connections: connections.value },
      version_num: currentMap.value?.version_num,
    })
    ElMessage.success("已保存")
  } catch (e: any) {
    if (e?.response?.status === 409) {
      ElMessage.warning("地图已被修改，刷新后重试")
    } else {
      console.error("saveCanvas error:", e)
    }
  } finally {
    isSaving = false
  }
}

async function saveConnections() {
  if (!selectedMap.value) return
  try {
    await mapApi.update(selectedMap.value, {
      canvas_data: { connections: connections.value },
    })
  } catch { /* silent */ }
}

// ── 发布/版本 ──
async function publishMap() {
  if (!selectedMap.value) return
  try {
    await mapApi.update(selectedMap.value, { status: "published" })
    ElMessage.success("已发布（自动创建版本快照）")
    currentMap.value.status = "published"
    loadCanvas()
  } catch (e) {
    ElMessage.error("发布失败")
  }
}

async function loadVersions() {
  if (!selectedMap.value) return
  try {
    const r: any = await api.get(`/maps/${selectedMap.value}/versions`)
    versions.value = r.data || []
  } catch { versions.value = [] }
}

async function rollback(verId: number) {
  try {
    await ElMessageBox.confirm("确定回滚到此版本？当前内容将被覆盖。")
    await api.post(`/maps/${selectedMap.value}/versions/${verId}/rollback`)
    ElMessage.success("回滚成功，刷新页面")
    window.location.reload()
  } catch (e: any) {
    if (e !== "cancel") ElMessage.error("回滚失败")
  }
}

// ── 加载目标状态 ──
async function loadObjectiveLevels() {
  if (!selectedMap.value) return
  try {
    const r: any = await api.get(`/maps/${selectedMap.value}/review`)
    for (const key of Object.keys(objectiveLevels)) delete objectiveLevels[key]
    for (const key of Object.keys(kpiProgressMap)) delete kpiProgressMap[key]
    const dims = r.dimensions || []
    for (const dim of dims) {
      const dimKey = dim.key
      const localDim = dimensions.find((d: any) => d.key === dimKey)
      if (!localDim) continue
      for (let i = 0; i < localDim.objectives.length; i++) {
        const key = `${dimKey}-${i}`
        kpiProgressMap[key] = { ratio: 0, level: 'gray', kpiList: [], planSummary: null }
        objectiveLevels[key] = 'gray'
      }
      dim.objectives.forEach((obj: any) => {
        const localIdx = localDim.objectives.findIndex((o: any) => o.name === obj.name)
        if (localIdx >= 0) {
          const key = `${dimKey}-${localIdx}`
          objectiveLevels[key] = obj.level || 'gray'
          const kpis = obj.kpis || []
          const kpiList: any[] = []
          let totalRatio = 0
          let count = 0
          for (const k of kpis) {
            kpiList.push({
              code: k.kpi_code, name: k.kpi_name, actual: k.actual_value, target: k.target_value, unit: k.unit,
            })
            if (k.actual_value != null && k.target_value) {
              totalRatio += k.actual_value / k.target_value; count++
            }
          }
          if (kpiList.length > 0) {
            kpiProgressMap[key] = {
              ratio: count > 0 ? Math.round((totalRatio / count) * 100) : 0,
              level: obj.level || 'gray', kpiList,
              planSummary: obj.action_plan_summary || null,
            }
          } else if (obj.action_plan_summary?.total > 0) {
            kpiProgressMap[key] = { ratio: 0, level: obj.level || 'gray', kpiList: [], planSummary: obj.action_plan_summary }
          }
        }
      })
    }
  } catch { /* silent */ }
}

// ── KPI详情浮层 ──
function showKpiDetailModal(dimKey: string, oi: number, obj: any) {
  const key = `${dimKey}-${oi}`
  kpiDetailObjName.value = obj.name
  kpiDetailDimKey.value = dimKey
  kpiDetailObjIdx.value = oi
  kpiDetailList.value = kpiProgressMap[key]?.kpiList || []
  showKpiDetail.value = true
}

function editKpiDetailObj() {
  showKpiDetail.value = false
  const dim = dimensions.find((d: any) => d.key === kpiDetailDimKey.value)
  if (dim && dim.objectives[kpiDetailObjIdx.value]) {
    openEditDialog(kpiDetailDimKey.value, kpiDetailObjIdx.value, dim.objectives[kpiDetailObjIdx.value])
  }
}

function getKpiLevel(item: any): string {
  if (item.actual != null && item.target) {
    const ratio = item.actual / item.target
    if (ratio >= 0.9) return 'green'
    if (ratio >= 0.7) return 'yellow'
    return 'red'
  }
  return 'gray'
}

function fmtKpiVal(val: any): string {
  if (val == null) return '—'
  if (typeof val === 'number') {
    if (Math.abs(val) >= 10000) return (val / 10000).toFixed(1) + '万'
    return val.toLocaleString()
  }
  return String(val)
}

function badgeIcon(level: string): string {
  return level === 'green' ? '🟢' : level === 'yellow' ? '🟡' : level === 'red' ? '🔴' : '⚪'
}

// ── 行动方案 ──
async function showPlanList(dimKey: string, oi: number, obj: any) {
  planPopupObjName.value = obj.name
  planPopupDimKey.value = dimKey
  planPopupObjIdx.value = oi
  planPopupList.value = []
  try {
    const r: any = await api.get(`/maps/${selectedMap.value}/review`)
    const dims = r.dimensions || []
    const allPlans = r.action_plans || []
    for (const dim of dims) {
      if (dim.key !== dimKey) continue
      for (const robj of dim.objectives || []) {
        if (robj.name !== obj.name) continue
        const objKpiIds = (robj.kpis || []).map((k: any) => k.kpi_id)
        planPopupList.value = allPlans.filter((p: any) => objKpiIds.includes(p.kpi_id))
        break
      }
      break
    }
  } catch { /* silent */ }
  showPlanPopup.value = true
}

function planStatusLabel(status: string): string {
  const map: Record<string, string> = { pending: '待处理', in_progress: '进行中', completed: '已完成', cancelled: '已取消' }
  return map[status] || status
}

function quickCreatePlan() {
  showPlanPopup.value = false
  window.location.href = `/action-plans?dimension=${planPopupDimKey.value}`
}

function goAlignment() {
  const url = currentMap.value ? `/alignment?map_id=${currentMap.value.id}` : '/alignment'
  window.location.href = url
}

onMounted(async () => {
  const r: any = await mapApi.list()
  maps.value = r.data || []
  if (maps.value.length > 0) {
    selectedMap.value = maps.value[0].id
    loadCanvas()
  } else {
    ensureFourLayers()
    loading.value = false
  }
  const k: any = await kpiApi.list({ page_size: 100 })
  allKpis.value = k.data || []
  for (const kpi of allKpis.value) {
    if (kpi.kpi_code) kpiNameMap[kpi.kpi_code] = kpi.kpi_name || kpi.kpi_code
  }
  window.addEventListener('resize', triggerRecalc)
})

onUnmounted(() => {
  window.removeEventListener('resize', triggerRecalc)
  if (saveTimer) clearTimeout(saveTimer)
})
</script>

<style scoped>
/* ── 页面布局 ── */
.map-canvas-page { padding: 16px; height: 100vh; display: flex; flex-direction: column; position: relative; overflow: hidden; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-shrink: 0; flex-wrap: wrap; gap: 8px; }
.toolbar h3 { margin: 0; }
.toolbar-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.zoom-controls { display: flex; align-items: center; gap: 2px; margin-right: 8px; }
.zoom-controls .el-button { padding: 4px 8px !important; font-size: 13px; min-height: auto; }
.zoom-label { display: inline-block; min-width: 32px; text-align: center; font-size: 12px; color: #666; font-variant-numeric: tabular-nums; }

.linking-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 16px; background: #fdf6ec; border: 1px solid #f5dab1;
  border-radius: 8px; margin-bottom: 8px; font-size: 14px; flex-shrink: 0;
}

.empty-state {
  flex: 1; display: flex; align-items: center; justify-content: center;
}

/* ── 画布滚动容器 ── */
.canvas-scroll-wrap {
  flex: 1; min-height: 0; overflow: auto;
  position: relative; width: 100%;
}

.canvas-body {
  display: flex; flex-direction: column; gap: 0;
  position: relative; isolation: isolate;
  width: 100%; padding: 8px 0;
}

/* ── 泳道外层包装 ── */
.layer-swimlane-wrapper {
  width: 100%;
  margin-bottom: 4px;
}

/* ── 跨层箭头指示器 ── */
.cross-layer-arrow {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 32px;
  flex-shrink: 0;
  opacity: 0.5;
  pointer-events: none;
}

/* ── 重新调用的样式（从旧MapCanvas复用） ── */
.kpi-detail-card {
  border: 1px solid #eee; border-radius: 8px; padding: 12px; margin-bottom: 8px;
}
.kpi-detail-red { border-left: 4px solid #f56c6c; }
.kpi-detail-yellow { border-left: 4px solid #e6a23c; }
.kpi-detail-green { border-left: 4px solid #67c23a; }
.kpi-detail-gray { border-left: 4px solid #ccc; }
.kpi-detail-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.kpi-detail-badge { font-size: 14px; }
.kpi-detail-code { font-size: 11px; color: #999; font-family: monospace; }
.kpi-detail-name { font-weight: 600; font-size: 14px; }
.kpi-detail-values { padding-left: 24px; display: flex; flex-direction: column; gap: 4px; }
.kdv-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.kdv-label { color: #999; width: 56px; }
.kdv-val { font-weight: 500; }

.plan-popup-card {
  border: 1px solid #eee; border-radius: 8px; padding: 10px 12px; margin-bottom: 6px;
}
.plan-popup-card:hover { background: #fafafa; }
.plan-popup-top { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.plan-popup-status {
  font-size: 10px; padding: 1px 6px; border-radius: 3px; font-weight: 500; flex-shrink: 0;
}
.pps-pending { background: #f5f7fa; color: #909399; }
.pps-in_progress { background: #fdf6ec; color: #e6a23c; }
.pps-completed { background: #f0f9eb; color: #67c23a; }
.pps-cancelled { background: #f5f7fa; color: #bbb; }
.plan-popup-title { font-weight: 500; font-size: 13px; }
.plan-popup-meta { display: flex; gap: 12px; font-size: 11px; color: #999; padding-left: 4px; }

/* ── 自定义弹窗（从旧MapCanvas复用） ── */
.mc-dialog-overlay {
  position: fixed; top:0; left:0; right:0; bottom:0;
  background:rgba(0,0,0,0.5); z-index:9999;
  display:flex; align-items:center; justify-content:center;
}
.mc-dialog-box {
  background:#fff; border-radius:12px; width:520px;
  box-shadow:0 8px 32px rgba(0,0,0,0.18); overflow:hidden;
}
.mc-dialog-header {
  display:flex; justify-content:space-between; align-items:center;
  padding:16px 20px; border-bottom:1px solid #f0f0f0;
  font-weight:600; font-size:16px;
}
.mc-dialog-close {
  background:none; border:none; font-size:22px; color:#999;
  cursor:pointer; padding:0 4px; line-height:1;
}
.mc-dialog-close:hover { color:#333; }
.mc-dialog-body { padding:20px; }
.mc-dialog-footer {
  display:flex; justify-content:flex-end; gap:10px;
  padding:12px 20px; border-top:1px solid #f0f0f0;
}
.mc-table { width:100%; border-collapse:collapse; font-size:13px; }
.mc-table th, .mc-table td { padding:10px 12px; text-align:left; border-bottom:1px solid #f0f0f0; }
.mc-table th { background:#fafafa; font-weight:600; color:#666; font-size:12px; }
.mc-table tr:hover td { background:#f5f7fa; }
.mc-btn {
  padding:8px 20px; border:1px solid #dcdfe6; border-radius:6px;
  background:#fff; color:#333; font-size:14px; cursor:pointer;
}
.mc-btn-primary { background:#409eff; color:#fff; border-color:#409eff; }
.mc-btn-sm { padding:5px 12px; font-size:12px; }
.mc-btn-warn { color:#e6a23c; border-color:#e6a23c; }
.mc-btn-warn:hover { background:#e6a23c; color:#fff; }
.mc-btn:hover { opacity:0.85; }
</style>
