import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { COMMANDER_SKILLS, DEFAULT_AGENTS, MAIN_AGENT_ORDER, RELATION_LABELS, SUB_COMMANDERS } from '@/data/agents'
import type { Agent, CommanderSkill, MainAgentId, SubCommander } from '@/data/agents'
import {
  agentApi,
  type AgentHierarchyStats,
  type AgentStats,
  type AgentVisibilityCostSummary,
  type AgentVisibilityRuntimeSummary,
  type AgentVisibilityToolGovernance,
  type AgentVisibilityTopology,
  type AgentVisibilityVerifier,
} from '@/api/agent'
import { skillApi, type Skill } from '@/api/skill'
import { useConversationStore } from '@/stores/conversation'

export function useAgentsPage() {
const conversationStore = useConversationStore()
const { currentConversationId } = storeToRefs(conversationStore)

const NODE_W = 200
const NODE_H = 170
const CHILD_W = 140
const CHILD_H = 150
const MASTER_TOP = 48
const MAIN_TOP = 350
const CHILD_TOP = 640
const MAIN_XS: Record<Exclude<MainAgentId, 'master'>, number> = {
  schedule_planner: 12.5,
  executor: 37.5,
  librarian: 62.5,
  analyst: 87.5,
}
const CHILD_LANE_OFFSET = 6
const motionEnabled = typeof window !== 'undefined' && typeof window.matchMedia === 'function'
  ? window.matchMedia('(prefers-reduced-motion: no-preference)').matches
  : false
const MIN_ZOOM = 0.8
const MAX_ZOOM = 1.6
const ZOOM_STEP = 0.1
const CRISP_ZOOM_THRESHOLD = 1.12
const OFFLINE_ROUTE_INTERVAL = 1700

type PlaybackHandle = ReturnType<typeof window.setTimeout>
type PollHandle = ReturnType<typeof setInterval>
interface AgentRuntimeState {
  callCount: number
  currentTask: string | null
  status: string
}

interface AgentDraft {
  name: string
  role: string
  description: string
  systemPrompt: string
  enabled: boolean
  selectedSkillIds: string[]
}

interface RuntimeSummaryCard {
  executionMode: string
  currentPhase: string
  currentCheckpoint: string
  verifierStatus: string
  verifierSummary: string
  verifierEvidence: Array<{
    label: string
    status: string
  }>
  isolationMode: string
  workspacePath: string | null
  totalTokens: number
  estimatedCost: string
  budgetWarning: boolean
  activeTaskCount: number
  completedTaskCount: number
  topologyNodeCount: number
  topologyNodes: Array<{
    agentId: string
    role: string
    taskCount: number
    completedTaskCount: number
  }>
  costByAgent: Array<{
    agentId: string
    totalTokens: number
    estimatedCost: string
    budgetWarning: boolean
  }>
  costThresholds: {
    totalTokens: number
    estimatedCost: string
  }
  toolGovernance: Array<{
    toolName: string
    permissionClass: string
    sideEffectScope: string
    usageCount: number
  }>
  toolGovernanceTotals: {
    totalTools: number
    usedTools: number
  }
  upgradeCandidates: string[]
  recentEvents: Array<{
    eventId: string
    eventType: string
    timestamp: string
    severity: string
  }>
}

const mainAgents = computed(() => MAIN_AGENT_ORDER.map(id => localAgents[id]))
const childAgents = SUB_COMMANDERS
const relationLabels = RELATION_LABELS
const childLabelMap = Object.fromEntries(childAgents.map(child => [child.id, child.name])) as Record<string, string>
const childMetaMap = Object.fromEntries(childAgents.map(child => [child.id, child])) as Record<string, SubCommander>

const canvasRef = ref<HTMLElement | null>(null)
const svgRef = ref<SVGElement | null>(null)
const masterCardRef = ref<HTMLElement | null>(null)
const nodeRefs: Record<string, HTMLElement> = {}
const cleanupFns: Array<() => void> = []
const hoverResetTimers: Record<string, PlaybackHandle | null> = {}
const bgParticles = Array.from({ length: 60 }, (_, i) => {
  const d = 3 + Math.random() * 5
  const delay = Math.random() * 4
  const o = 0.25 + Math.random() * 0.5
  const size = 1 + Math.random() * 2.5
  return {
    id: i,
    style: {
      left: `${Math.random() * 98}%`,
      top: `${Math.random() * 95}%`,
      width: `${size}px`,
      height: `${size}px`,
      '--d': `${d}s`,
      '--delay': `${delay}s`,
      '--o': String(o),
      opacity: o,
    },
  }
})

let resizeObserver: ResizeObserver | null = null
let pollInterval: PollHandle | null = null
let demoInterval: PollHandle | null = null

const selectedAgentId = ref<string | null>(null)
const drawerOpen = ref(false)
const addModalOpen = ref(false)
const editAgent = ref<AgentDraft | null>(null)
const newAgent = reactive({ name: '', roleKey: '', role: '', description: '', systemPrompt: '' })
const saving = ref(false)
const loading = ref(false)
const skillsLoading = ref(false)
const skillsError = ref('')
const saveError = ref('')
const availableSkillsByNode = reactive<Record<string, Skill[]>>({})
const agentSkillSelections = reactive<Record<string, string[]>>({})
const activeSkillRequestId = ref(0)
const zoom = ref(1)
const pan = reactive({ x: 0, y: 0 })
const basePan = reactive({ x: 0, y: 0 })
const isPanning = ref(false)
const panStart = reactive({ x: 0, y: 0 })
const panOrigin = reactive({ x: 0, y: 0 })
const connectionStatus = ref<'connected' | 'disconnected'>('disconnected')
const connectionLabel = computed(() => connectionStatus.value === 'connected' ? '实时同步' : '离线模式')
const zoomPercent = computed(() => `${Math.round(zoom.value * 100)}%`)
const activeMainId = ref<string | null>(null)
const activeChildId = ref<string | null>(null)
const agentData = reactive<Record<string, AgentRuntimeState>>({})
const runtimeSummary = ref<RuntimeSummaryCard>({
  executionMode: 'direct',
  currentPhase: 'phase_0_bootstrap',
  currentCheckpoint: 'bootstrap.initialized',
  verifierStatus: 'unknown',
  verifierSummary: '暂无运行时摘要',
  verifierEvidence: [],
  isolationMode: 'none',
  workspacePath: null,
  totalTokens: 0,
  estimatedCost: '$0.000000',
  budgetWarning: false,
  activeTaskCount: 0,
  completedTaskCount: 0,
  topologyNodeCount: 0,
  topologyNodes: [],
  costByAgent: [],
  costThresholds: {
    totalTokens: 0,
    estimatedCost: '$0.000000',
  },
  toolGovernance: [],
  toolGovernanceTotals: {
    totalTools: 0,
    usedTools: 0,
  },
  upgradeCandidates: [],
  recentEvents: [],
})
const runtimeConversationId = computed(() => currentConversationId.value)

const localAgents = reactive<Record<string, Agent>>(
  Object.fromEntries([
    ...DEFAULT_AGENTS,
    ...SUB_COMMANDERS.map((child) => ({
      id: child.id,
      name: child.name,
      role: child.role,
      roleKey: child.id,
      description: child.description,
      systemPrompt: `${child.role}: ${child.description}`,
      enabled: true,
    })),
  ].map(agent => [agent.id, { ...agent }]))
)

const nodePackagesMap = COMMANDER_SKILLS.reduce<Record<string, CommanderSkill[]>>((acc, skillPkg) => {
  skillPkg.relatedAgentIds.forEach((nodeId) => {
    acc[nodeId] = [...(acc[nodeId] || []), skillPkg]
  })
  return acc
}, {})

const layoutZoom = computed(() => Math.min(zoom.value, CRISP_ZOOM_THRESHOLD))
const stageScale = computed(() => zoom.value / layoutZoom.value)

const viewportStyle = computed(() => ({
  transform: `translate(${roundPx(basePan.x + pan.x)}px, ${roundPx(basePan.y + pan.y)}px)`,
}))

const stageStyle = computed(() => ({
  width: '100%',
  minHeight: `${roundPx((CHILD_TOP + CHILD_H + 120) * layoutZoom.value)}px`,
  left: '0px',
  transform: `scale(${stageScale.value})`,
  transformOrigin: '50% 0%',
  '--node-scale': String(layoutZoom.value),
}))

const activeMainAgents = computed(() => mainAgents.value.filter(agent => agent.id === activeMainId.value))
const activeChildAgents = computed(() => childAgents.filter(child => child.id === activeChildId.value))
const selectedNodePackages = computed(() => selectedAgentId.value ? nodePackagesMap[selectedAgentId.value] || [] : [])
const selectedNodeSkills = computed(() => {
  if (!selectedAgentId.value) return []
  return (availableSkillsByNode[selectedAgentId.value] || []).filter(skill => matchesNodeSkill(selectedAgentId.value as string, skill))
})
const activeMainRouteLabel = computed(() => {
  if (!activeMainId.value) return 'STANDBY'
  return getAgentName(activeMainId.value)
})
const activeChildRouteLabel = computed(() => activeChildId.value ? childLabelMap[activeChildId.value] || 'STANDBY' : 'STANDBY')

function roundPx(value: number) {
  return Math.round(value)
}

function getCanvasMetrics() {
  const canvas = canvasRef.value
  if (!canvas) return { width: 0, height: 0 }
  return { width: canvas.clientWidth, height: canvas.clientHeight }
}

function pxToSvg(pctX: number) {
  const canvas = canvasRef.value
  if (!canvas) return 0
  return (pctX / 100) * canvas.clientWidth
}

function updateBasePan() {
  const { width, height } = getCanvasMetrics()
  const scaledWidth = width * zoom.value
  const scaledHeight = height * zoom.value
  basePan.x = width ? roundPx((width - scaledWidth) / 2) : 0
  basePan.y = height ? roundPx((height - scaledHeight) / 2) : 0
}

function getNodeMetrics(width = NODE_W, height = NODE_H) {
  return {
    width: roundPx(width * layoutZoom.value),
    height: roundPx(height * layoutZoom.value),
    paddingX: roundPx(16 * layoutZoom.value),
    paddingY: roundPx(14 * layoutZoom.value),
    corner: Math.max(6, roundPx(10 * layoutZoom.value)),
    status: Math.max(8, roundPx(10 * layoutZoom.value)),
    statusRing: Math.max(6, roundPx(8 * layoutZoom.value)),
    relOffset: roundPx(-20 * layoutZoom.value),
  }
}

function buildNodeStyle(centerPct: number, top: number, width = NODE_W, height = NODE_H) {
  const x = pxToSvg(centerPct)
  const metrics = getNodeMetrics(width, height)
  return {
    left: `${roundPx(x - metrics.width / 2)}px`,
    top: `${roundPx(top)}px`,
    width: `${metrics.width}px`,
    height: `${metrics.height}px`,
    '--node-padding-x': `${metrics.paddingX}px`,
    '--node-padding-y': `${metrics.paddingY}px`,
    '--node-corner-size': `${metrics.corner}px`,
    '--node-status-size': `${metrics.status}px`,
    '--node-status-ring-size': `${metrics.statusRing}px`,
    '--node-rel-offset': `${metrics.relOffset}px`,
  }
}

const masterNodeStyle = computed(() => buildNodeStyle(50, MASTER_TOP, NODE_W, NODE_H))
function getMainNodeStyle(id: string) {
  return buildNodeStyle(getMainLaneX(id as Exclude<MainAgentId, 'master'>), MAIN_TOP, NODE_W, NODE_H)
}
function getChildNodeStyle(id: string) {
  const child = childMetaMap[id]
  const parentX = getMainLaneX(child.parentId as Exclude<MainAgentId, 'master'>)
  const siblingIndex = childAgents.filter(item => item.parentId === child.parentId).findIndex(item => item.id === id)
  const offset = siblingIndex === 0 ? -CHILD_LANE_OFFSET : CHILD_LANE_OFFSET
  return buildNodeStyle(parentX + offset, CHILD_TOP, CHILD_W, CHILD_H)
}

function getCurvePath(fromX: number, fromY: number, toX: number, toY: number) {
  const midY = (fromY + toY) / 2
  return `M ${roundPx(fromX)},${roundPx(fromY)} C ${roundPx(fromX)},${roundPx(midY)} ${roundPx(toX)},${roundPx(midY)} ${roundPx(toX)},${roundPx(toY)}`
}

function getBusLinePath(mainId: string) {
  const metrics = getNodeMetrics()
  return getCurvePath(pxToSvg(50), MASTER_TOP + metrics.height / 2, pxToSvg(getMainLaneX(mainId as Exclude<MainAgentId, 'master'>)), MAIN_TOP + metrics.height / 2)
}

function getSubLinePath(childId: string) {
  const child = childMetaMap[childId]
  const mainMetrics = getNodeMetrics()
  const childMetrics = getNodeMetrics(CHILD_W, CHILD_H)
  const parentX = pxToSvg(getMainLaneX(child.parentId as Exclude<MainAgentId, 'master'>))
  const siblingIndex = childAgents.filter(item => item.parentId === child.parentId).findIndex(item => item.id === childId)
  const childX = pxToSvg(getMainLaneX(child.parentId as Exclude<MainAgentId, 'master'>) + (siblingIndex === 0 ? -CHILD_LANE_OFFSET : CHILD_LANE_OFFSET))
  return getCurvePath(parentX, MAIN_TOP + mainMetrics.height / 2, childX, CHILD_TOP + childMetrics.height / 2)
}

function updateSvgSize() {
  const canvas = canvasRef.value
  const svg = svgRef.value
  if (!canvas || !svg) return
  svg.setAttribute('width', String(canvas.clientWidth))
  svg.setAttribute('height', String(Math.max(canvas.clientHeight, CHILD_TOP + CHILD_H + 120)))
}

function setNodeRef(id: string, el: HTMLElement | null) {
  if (el) nodeRefs[id] = el
}

function getStatusClass(agentId: string) {
  const data = agentData[agentId]
  const agent = localAgents[agentId]
  if (!agent?.enabled) return 'disabled'
  if (!data) return 'idle'
  return data.status === 'active' ? 'active' : 'idle'
}
function getAgentName(id: string) { return localAgents[id]?.name || id.toUpperCase() }
function getAgentRole(id: string) { return localAgents[id]?.role || '' }
function getAgentDesc(id: string) { return localAgents[id]?.description || '' }
function getMainLaneX(agentId: Exclude<MainAgentId, 'master'>) {
  return MAIN_XS[agentId]
}
function getNodeRootAgentType(nodeId: string) {
  if (nodeId === 'master') return 'general'
  return childMetaMap[nodeId]?.parentId || nodeId
}
function matchesNodeSkill(nodeId: string, skill: Skill) {
  return skill.is_active && skill.agent_type === getNodeRootAgentType(nodeId)
}
function ensureSkillSelections(nodeId: string, fallbackSkillIds?: string[]) {
  if (agentSkillSelections[nodeId]) return
  agentSkillSelections[nodeId] = fallbackSkillIds ? [...fallbackSkillIds] : selectedNodeSkills.value.map(skill => skill.id)
}
async function loadSkillsForNode(nodeId: string) {
  const requestId = activeSkillRequestId.value + 1
  activeSkillRequestId.value = requestId
  skillsLoading.value = true
  skillsError.value = ''
  try {
    const response = await skillApi.list({ agent_type: getNodeRootAgentType(nodeId) })
    if (activeSkillRequestId.value !== requestId || selectedAgentId.value !== nodeId) return
    availableSkillsByNode[nodeId] = response.data
    if (!agentSkillSelections[nodeId]) {
      agentSkillSelections[nodeId] = response.data.filter(skill => matchesNodeSkill(nodeId, skill)).map(skill => skill.id)
    }
  } catch {
    if (activeSkillRequestId.value !== requestId || selectedAgentId.value !== nodeId) return
    availableSkillsByNode[nodeId] = []
    skillsError.value = '加载技能失败，请稍后重试。'
  } finally {
    if (activeSkillRequestId.value === requestId && selectedAgentId.value === nodeId) {
      skillsLoading.value = false
    }
  }
}
function toggleSkillSelection(skillId: string, checked: boolean) {
  if (!editAgent.value) return
  editAgent.value = {
    ...editAgent.value,
    selectedSkillIds: checked
      ? [...editAgent.value.selectedSkillIds, skillId]
      : editAgent.value.selectedSkillIds.filter(id => id !== skillId),
  }
}
function clampZoom(value: number) {
  return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Number(value.toFixed(2))))
}

function applyZoom(nextZoom: number, anchorX?: number, anchorY?: number) {
  const clamped = clampZoom(nextZoom)
  const canvas = canvasRef.value
  const previousZoom = zoom.value

  if (!canvas || clamped === previousZoom) {
    zoom.value = clamped
    updateBasePan()
    return
  }

  const rect = canvas.getBoundingClientRect()
  const localX = anchorX ?? rect.width / 2
  const localY = anchorY ?? rect.height / 2
  const contentX = (localX - (basePan.x + pan.x)) / previousZoom
  const contentY = (localY - (basePan.y + pan.y)) / previousZoom

  zoom.value = clamped
  updateBasePan()

  pan.x = roundPx(localX - basePan.x - contentX * zoom.value)
  pan.y = roundPx(localY - basePan.y - contentY * zoom.value)
}

function zoomIn() {
  applyZoom(zoom.value + ZOOM_STEP)
}
function zoomOut() {
  applyZoom(zoom.value - ZOOM_STEP)
}
function resetView() {
  zoom.value = 1
  pan.x = 0
  pan.y = 0
  updateBasePan()
}
function handleWheel(event: WheelEvent) {
  const delta = event.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP
  const canvas = canvasRef.value
  if (!canvas) {
    applyZoom(zoom.value + delta)
    return
  }
  const rect = canvas.getBoundingClientRect()
  applyZoom(zoom.value + delta, event.clientX - rect.left, event.clientY - rect.top)
}
function startPan(event: MouseEvent) {
  const target = event.target as HTMLElement | null
  if (!target || target.closest('.node-card') || target.closest('.canvas-controls') || target.closest('.hud-panel')) return
  isPanning.value = true
  panStart.x = event.clientX
  panStart.y = event.clientY
  panOrigin.x = pan.x
  panOrigin.y = pan.y
  window.addEventListener('mousemove', movePan)
  window.addEventListener('mouseup', endPan, { once: true })
}
function movePan(event: MouseEvent) {
  if (!isPanning.value) return
  pan.x = panOrigin.x + event.clientX - panStart.x
  pan.y = panOrigin.y + event.clientY - panStart.y
}
function endPan() {
  isPanning.value = false
  window.removeEventListener('mousemove', movePan)
}

async function selectAgent(id: string) {
  const agent = localAgents[id]
  if (!agent) return
  selectedAgentId.value = id
  saveError.value = ''
  let persistedSkillIds: string[] | undefined
  try {
    const config = await agentApi.getConfig(id)
    if (localAgents[id]) {
      Object.assign(localAgents[id], {
        name: config.name,
        description: config.description,
        systemPrompt: config.system_prompt,
        enabled: config.enabled,
      })
    }
    persistedSkillIds = config.selected_skill_ids || []
    agentSkillSelections[id] = [...persistedSkillIds]
  } catch {
    persistedSkillIds = agentSkillSelections[id] || []
  }
  await loadSkillsForNode(id)
  ensureSkillSelections(id, persistedSkillIds)
  editAgent.value = {
    name: localAgents[id].name,
    role: localAgents[id].role,
    description: localAgents[id].description,
    systemPrompt: localAgents[id].systemPrompt,
    enabled: localAgents[id].enabled,
    selectedSkillIds: [...(agentSkillSelections[id] || [])],
  }
  drawerOpen.value = true
}

function resetConfig() {
  const currentId = selectedAgentId.value || ''
  const original = localAgents[currentId]
  if (original && editAgent.value) {
    editAgent.value = {
      name: original.name,
      role: original.role,
      description: original.description,
      systemPrompt: original.systemPrompt,
      enabled: original.enabled,
      selectedSkillIds: [...(agentSkillSelections[currentId] || [])],
    }
  }
}

async function saveConfig() {
  if (!editAgent.value || !selectedAgentId.value) return
  saving.value = true
  saveError.value = ''
  try {
    const nextLocalState = {
      name: editAgent.value.name,
      role: editAgent.value.role,
      description: editAgent.value.description,
      systemPrompt: editAgent.value.systemPrompt,
      enabled: editAgent.value.enabled,
    }
    try {
      const response = await agentApi.updateConfig(selectedAgentId.value, {
        name: editAgent.value.name,
        description: editAgent.value.description,
        system_prompt: editAgent.value.systemPrompt,
        enabled: editAgent.value.enabled,
        selected_skill_ids: [...editAgent.value.selectedSkillIds],
      })
      if (localAgents[selectedAgentId.value]) {
        Object.assign(localAgents[selectedAgentId.value], nextLocalState)
      }
      agentSkillSelections[selectedAgentId.value] = [...(response.selected_skill_ids || editAgent.value.selectedSkillIds)]
      drawerOpen.value = false
    } catch {
      saveError.value = '保存失败，请稍后重试。'
    }
  } finally {
    saving.value = false
  }
}

function addAgent() {
  if (!newAgent.name || !newAgent.roleKey) return
  const id = newAgent.roleKey.toLowerCase().replace(/\s+/g, '_')
  if (localAgents[id]) return
  localAgents[id] = { id, name: newAgent.name.toUpperCase(), role: newAgent.role, roleKey: id, description: newAgent.description, systemPrompt: newAgent.systemPrompt, enabled: true }
  addModalOpen.value = false
}

function setRuntimeState(agentId: string, state: AgentStats) {
  agentData[agentId] = {
    callCount: state.call_count,
    currentTask: state.current_task,
    status: state.status,
  }
}

function applyHierarchyStats(stats: AgentHierarchyStats) {
  agentData.master = { callCount: 47, currentTask: '协调协作链路', status: 'active' }
  let nextMain: string | null = null
  let nextChild: string | null = null

  for (const main of stats.main_agents) {
    setRuntimeState(main.agent_id, main)
    if (main.status === 'active') nextMain = main.agent_id

    for (const child of main.sub_commanders) {
      setRuntimeState(child.agent_id, child)
      if (child.status === 'active') {
        nextMain = main.agent_id
        nextChild = child.agent_id
      }
    }
  }

  activeMainId.value = nextMain
  activeChildId.value = nextChild
}

function stopDemoRouteCycle() {
  if (demoInterval) {
    clearInterval(demoInterval)
    demoInterval = null
  }
}

function startDemoRouteCycle() {
  stopDemoRouteCycle()
  const demoRoutes = childAgents.map(child => ({ mainId: child.parentId, childId: child.id }))
  let index = 0
  const applyRoute = () => {
    const route = demoRoutes[index]
    activeMainId.value = route.mainId
    activeChildId.value = route.childId
    index = (index + 1) % demoRoutes.length
  }
  applyRoute()
  demoInterval = setInterval(applyRoute, OFFLINE_ROUTE_INTERVAL)
}

function buildOfflineStats() {
  return {
    main_agents: [
      {
        agent_id: 'schedule_planner',
        call_count: 12,
        current_task: null,
        status: 'active',
        sub_commanders: [
          { agent_id: 'schedule_analysis', call_count: 4, current_task: '梳理今日重点', status: 'active' },
          { agent_id: 'schedule_planning', call_count: 9, current_task: null, status: 'idle' },
        ],
      },
      {
        agent_id: 'executor',
        call_count: 8,
        current_task: '创建文档',
        status: 'idle',
        sub_commanders: [
          { agent_id: 'executor_tasks', call_count: 8, current_task: null, status: 'idle' },
          { agent_id: 'executor_forum', call_count: 4, current_task: null, status: 'idle' },
        ],
      },
      {
        agent_id: 'librarian',
        call_count: 5,
        current_task: null,
        status: 'idle',
        sub_commanders: [
          { agent_id: 'librarian_retrieval', call_count: 5, current_task: null, status: 'idle' },
          { agent_id: 'librarian_graph', call_count: 2, current_task: null, status: 'idle' },
        ],
      },
      {
        agent_id: 'analyst',
        call_count: 3,
        current_task: null,
        status: 'idle',
        sub_commanders: [
          { agent_id: 'analyst_progress', call_count: 2, current_task: null, status: 'idle' },
          { agent_id: 'analyst_insights', call_count: 3, current_task: null, status: 'idle' },
        ],
      },
    ],
  } satisfies AgentHierarchyStats
}

function applyRuntimeSummary(
  summary: AgentVisibilityRuntimeSummary,
  topology?: AgentVisibilityTopology,
  verifier?: AgentVisibilityVerifier,
  costSummary?: AgentVisibilityCostSummary,
  toolGovernance?: AgentVisibilityToolGovernance,
) {
  const verifierPayload = verifier || summary.verifier
  const costPayload = costSummary || {
    conversation_id: summary.conversation_id,
    total: summary.cost,
    thresholds: {},
    by_agent: [],
  }
  runtimeSummary.value = {
    executionMode: summary.execution_mode || 'direct',
    currentPhase: summary.current_phase || 'phase_0_bootstrap',
    currentCheckpoint: summary.current_checkpoint || 'bootstrap.initialized',
    verifierStatus: verifierPayload.status || 'unknown',
    verifierSummary: verifierPayload.summary || '暂无 verifier 结论',
    verifierEvidence: (verifierPayload.evidence || []).slice(0, 4).map((entry, index) => ({
      label: String(entry.task_id || entry.event_type || `evidence-${index}`),
      status: String(entry.status || entry.summary || 'available'),
    })),
    isolationMode: summary.isolation.mode || 'none',
    workspacePath: summary.isolation.workspace_path || null,
    totalTokens: summary.cost.total_tokens,
    estimatedCost: `$${(summary.cost.estimated_cost || 0).toFixed(6)}`,
    budgetWarning: Boolean(summary.cost.budget_warning),
    activeTaskCount: summary.active_task_count,
    completedTaskCount: summary.completed_task_count,
    topologyNodeCount: summary.topology_node_count,
    topologyNodes: (topology?.nodes || []).slice(0, 5).map(node => ({
      agentId: node.agent_id,
      role: node.role || 'unknown',
      taskCount: node.task_count,
      completedTaskCount: node.completed_task_count,
    })),
    costByAgent: (costPayload.by_agent || []).slice(0, 4).map(item => ({
      agentId: item.agent_id,
      totalTokens: item.total_tokens,
      estimatedCost: `$${(item.estimated_cost || 0).toFixed(6)}`,
      budgetWarning: Boolean(item.budget_warning),
    })),
    costThresholds: {
      totalTokens: Number(costPayload.thresholds.total_tokens || 0),
      estimatedCost: `$${Number(costPayload.thresholds.estimated_cost || 0).toFixed(6)}`,
    },
    toolGovernance: (toolGovernance?.items || []).slice(0, 6).map(item => ({
      toolName: item.tool_name,
      permissionClass: item.permission_class,
      sideEffectScope: item.side_effect_scope,
      usageCount: item.usage_count,
    })),
    toolGovernanceTotals: {
      totalTools: Number(toolGovernance?.total_tools || 0),
      usedTools: Number(toolGovernance?.used_tools || 0),
    },
    upgradeCandidates: [...(toolGovernance?.upgrade_candidates || [])],
    recentEvents: summary.recent_events.map(event => ({
      eventId: event.event_id,
      eventType: event.event_type,
      timestamp: event.timestamp,
      severity: event.severity,
    })),
  }
}

async function refreshStats() {
  loading.value = true
  try {
    const stats = await agentApi.getHierarchyStats()
    applyHierarchyStats(stats)
    if (runtimeConversationId.value) {
      try {
        const [summary, topology, verifier, costSummary, tools] = await Promise.all([
          agentApi.getRuntimeSummary(runtimeConversationId.value),
          agentApi.getVisibilityTopology(runtimeConversationId.value),
          agentApi.getVisibilityVerifier(runtimeConversationId.value),
          agentApi.getVisibilityCost(runtimeConversationId.value),
          agentApi.getVisibilityTools(runtimeConversationId.value),
        ])
        applyRuntimeSummary(summary, topology, verifier, costSummary, tools)
      } catch {
        runtimeSummary.value = {
          ...runtimeSummary.value,
          verifierSummary: '运行时摘要暂不可用',
        }
      }
    } else {
      runtimeSummary.value = {
        ...runtimeSummary.value,
        verifierSummary: '请选择一条会话以查看运行时摘要',
      }
    }
    connectionStatus.value = 'connected'
    stopDemoRouteCycle()
  } catch {
    connectionStatus.value = 'disconnected'
    applyHierarchyStats(buildOfflineStats())
    runtimeSummary.value = {
      executionMode: 'direct',
      currentPhase: 'offline_demo',
      currentCheckpoint: 'offline.demo_mode',
      verifierStatus: 'offline',
      verifierSummary: '当前使用离线演示数据',
      verifierEvidence: [],
      isolationMode: 'none',
      workspacePath: null,
      totalTokens: 0,
      estimatedCost: '$0.000000',
      budgetWarning: false,
      activeTaskCount: 0,
      completedTaskCount: 0,
      topologyNodeCount: 0,
      topologyNodes: [],
      costByAgent: [],
      costThresholds: {
        totalTokens: 0,
        estimatedCost: '$0.000000',
      },
      toolGovernance: [],
      toolGovernanceTotals: {
        totalTools: 0,
        usedTools: 0,
      },
      upgradeCandidates: [],
      recentEvents: [],
    }
    startDemoRouteCycle()
  } finally {
    loading.value = false
  }
}

function stopTimer(timer: PlaybackHandle | null) {
  if (timer) window.clearTimeout(timer)
}

function runTransition(el: Element, keyframes: Keyframe[], options: KeyframeAnimationOptions, done?: () => void) {
  const target = el as HTMLElement
  if (typeof target.animate !== 'function') {
    done?.()
    return null
  }
  const animation = target.animate(keyframes, { fill: 'forwards', ...options })
  const finish = () => done?.()
  animation.addEventListener('finish', finish, { once: true })
  cleanupFns.push(() => animation.cancel())
  return animation
}

function animateIn(el: Element, done: () => void) {
  runTransition(el, [{ opacity: 0, transform: 'translateX(80px)' }, { opacity: 1, transform: 'translateX(0)' }], { duration: 350, easing: 'cubic-bezier(0.4, 0, 0.2, 1)' }, done)
}
function animateOut(el: Element, done: () => void) {
  runTransition(el, [{ opacity: 1, transform: 'translateX(0)' }, { opacity: 0, transform: 'translateX(80px)' }], { duration: 250, easing: 'cubic-bezier(0.4, 0, 1, 1)' }, done)
}
function fadeIn(el: Element, done: () => void) {
  runTransition(el, [{ opacity: 0 }, { opacity: 1 }], { duration: 250 }, done)
}
function fadeOut(el: Element, done: () => void) {
  runTransition(el, [{ opacity: 1 }, { opacity: 0 }], { duration: 200 }, done)
}

function playEntranceAnimations() {
  if (!motionEnabled) return
  if (masterCardRef.value) {
    runTransition(masterCardRef.value, [
      { opacity: 0, transform: 'translateY(14px) scale(0.99)', filter: 'brightness(0.86)' },
      { opacity: 1, transform: 'translateY(0) scale(1)', filter: 'brightness(1)' },
    ], { duration: 680, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' })
  }

  ;[...mainAgents.value.map(agent => agent.id), ...childAgents.map(child => child.id)].forEach((id, idx) => {
    const el = nodeRefs[id]
    if (!el) return
    runTransition(el, [
      { opacity: 0, transform: 'translateY(12px) scale(0.99)', filter: 'brightness(0.82)' },
      { opacity: 1, transform: 'translateY(0) scale(1)', filter: 'brightness(1)' },
    ], { duration: 500, delay: 210 + idx * 55, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' })

    const handleMouseEnter = () => {
      if (!localAgents[id]?.enabled) return
      stopTimer(hoverResetTimers[id] ?? null)
      el.style.transform = 'translateY(-2px)'
    }
    const handleMouseLeave = () => {
      stopTimer(hoverResetTimers[id] ?? null)
      hoverResetTimers[id] = window.setTimeout(() => {
        el.style.transform = ''
        hoverResetTimers[id] = null
      }, 180)
    }

    el.addEventListener('mouseenter', handleMouseEnter)
    el.addEventListener('mouseleave', handleMouseLeave)
    cleanupFns.push(() => {
      stopTimer(hoverResetTimers[id] ?? null)
      el.removeEventListener('mouseenter', handleMouseEnter)
      el.removeEventListener('mouseleave', handleMouseLeave)
    })
  })
}

onMounted(async () => {
  await refreshStats()
  pollInterval = setInterval(refreshStats, 5000)
  requestAnimationFrame(() => {
    updateBasePan()
    updateSvgSize()
    playEntranceAnimations()
  })
  resizeObserver = new ResizeObserver(() => {
    updateBasePan()
    updateSvgSize()
  })
  if (canvasRef.value) resizeObserver.observe(canvasRef.value)
})

onUnmounted(() => {
  if (pollInterval) clearInterval(pollInterval)
  stopDemoRouteCycle()
  resizeObserver?.disconnect()
  window.removeEventListener('mousemove', movePan)
  window.removeEventListener('mouseup', endPan)
  cleanupFns.forEach(cleanup => cleanup())
})
  return {
    bgParticles,
    canvasRef,
    svgRef,
    masterCardRef,
    isPanning,
    connectionStatus,
    connectionLabel,
    loading,
    drawerOpen,
    addModalOpen,
    editAgent,
    newAgent,
    skillsLoading,
    skillsError,
    saveError,
    saving,
    mainAgents,
    childAgents,
    relationLabels,
    activeMainId,
    activeChildId,
    activeMainAgents,
    activeChildAgents,
    activeMainRouteLabel,
    activeChildRouteLabel,
    selectedAgentId,
    selectedNodePackages,
    selectedNodeSkills,
    agentData,
    runtimeSummary,
    localAgents,
    viewportStyle,
    stageStyle,
    masterNodeStyle,
    zoomPercent,
    refreshStats,
    startPan,
    handleWheel,
    getBusLinePath,
    getSubLinePath,
    selectAgent,
    setNodeRef,
    getStatusClass,
    getAgentName,
    getAgentRole,
    getAgentDesc,
    getMainNodeStyle,
    getChildNodeStyle,
    zoomOut,
    zoomIn,
    resetView,
    toggleSkillSelection,
    resetConfig,
    saveConfig,
    addAgent,
    animateIn,
    animateOut,
    fadeIn,
    fadeOut,
  }
}
