<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { RouterLink } from 'vue-router'
import {
  CalendarRange,
  FileStack,
  MessagesSquare,
  RefreshCw,
  Scale,
  Sparkles,
  Telescope,
  X,
} from 'lucide-vue-next'

import BrainGraphCanvas from './BrainGraphCanvas.vue'

import { graphApi } from '@/api/graph'
import type { KGEdge, KGNode } from '@/api/graph'

type BrainNodeType = 'knowledge' | 'chat' | 'forum' | 'schedule'
type ImportanceFilter = 'all' | 'high' | 'medium' | 'low'

interface BrainNode extends KGNode {
  type: BrainNodeType | string
  ingestedAt?: string
  tags?: string[]
  summary?: string
  sourceLabel?: string
}

interface BrainGraphCanvasExpose {
  resetViewport: () => void
  zoomBy: (multiplier: number) => void
}

withDefaults(defineProps<{
  fullscreen?: boolean
  showOpenFullView?: boolean
}>(), {
  fullscreen: false,
  showOpenFullView: false,
})

const importanceOptions = [
  { key: 'all', label: 'ALL' },
  { key: 'high', label: 'HIGH' },
  { key: 'medium', label: 'MEDIUM' },
  { key: 'low', label: 'LOW' },
] as const

const typeLabels: Record<string, string> = {
  knowledge: '知识库',
  chat: '对话记录',
  forum: '论坛',
  schedule: '日程任务',
}

const typeColors: Record<string, string> = {
  knowledge: '#eef7ff',
  chat: '#91d7ff',
  forum: '#4ea1ff',
  schedule: '#c2e6ff',
  default: '#8faecc',
}

const fallbackNodes: BrainNode[] = [
  {
    id: 'kb-architecture',
    name: 'Jarvis 架构手册',
    type: 'knowledge',
    description: '系统架构文档。',
    importance: 0.95,
    created_at: '2026-03-21T09:30:00Z',
    ingestedAt: '2026-03-21T09:30:00Z',
    tags: ['系统设计', '知识库', '核心'],
    sourceLabel: '知识库文档',
    summary: '汇总 Jarvis 的模块边界、调用链和前后端职责，是多个讨论与任务的事实依据。',
  },
  {
    id: 'chat-memory-strategy',
    name: '记忆策略讨论',
    type: 'chat',
    description: '关于知识记忆与标签机制的对话',
    importance: 0.88,
    created_at: '2026-03-24T13:45:00Z',
    ingestedAt: '2026-03-24T13:45:00Z',
    tags: ['对话', '记忆系统', '标签'],
    sourceLabel: '历史对话',
    summary: '围绕知识聚合、标签分类与可视化表达形成了一轮关键共识。',
  },
  {
    id: 'forum-ux-trend',
    name: '图谱产品体验趋势',
    type: 'forum',
    description: '来自社区的图谱设计讨论',
    importance: 0.74,
    created_at: '2026-03-20T07:20:00Z',
    ingestedAt: '2026-03-20T07:20:00Z',
    tags: ['论坛', 'UX', '图谱'],
    sourceLabel: '论坛讨论',
    summary: '案例显示，高科技感必须和信息可读性同时成立。',
  },
  {
    id: 'schedule-brain-redesign',
    name: '知识大脑改版排期',
    type: 'schedule',
    description: '知识大脑第一版重构安排',
    importance: 0.83,
    created_at: '2026-03-26T02:00:00Z',
    ingestedAt: '2026-03-26T02:00:00Z',
    tags: ['日程', '重构', '优先级高'],
    sourceLabel: '项目日程',
    summary: '围绕 UI 改版拆成图谱重构、详情侧栏、筛选控制和验收演示四个阶段。',
  },
]

const fallbackEdges: KGEdge[] = [
  { id: 'e1', source: 'kb-architecture', target: 'chat-memory-strategy', relation: '支持讨论' },
  { id: 'e2', source: 'chat-memory-strategy', target: 'forum-ux-trend', relation: '验证趋势' },
  { id: 'e3', source: 'chat-memory-strategy', target: 'schedule-brain-redesign', relation: '驱动排期' },
]

function createDemoGraph() {
  const nodes: BrainNode[] = [
    {
      id: 'demo-core-orchestrator',
      name: 'Orchestrator Prime',
      type: 'knowledge',
      description: '指挥中枢',
      importance: 0.98,
      created_at: '2026-03-26T08:30:00Z',
      ingestedAt: '2026-03-26T08:30:00Z',
      tags: ['指挥中枢', '核心链路', '决策层'],
      sourceLabel: '模拟核心层',
      summary: '负责汇总知识、会话、论坛与排程信号，形成整个知识大脑的稳定核心。',
    },
    {
      id: 'demo-memory-vault',
      name: 'Memory Vault',
      type: 'knowledge',
      description: '知识存储层',
      importance: 0.91,
      created_at: '2026-03-26T08:00:00Z',
      ingestedAt: '2026-03-26T08:00:00Z',
      tags: ['知识存储', '文档资产', '高可信'],
      sourceLabel: '模拟知识层',
      summary: '沉淀结构手册、规范与结论，为上层推理提供长期记忆支撑。',
    },
    {
      id: 'demo-session-cluster',
      name: 'Session Cluster 07',
      type: 'chat',
      description: '实时会话汇聚节点',
      importance: 0.89,
      created_at: '2026-03-26T09:10:00Z',
      ingestedAt: '2026-03-26T09:10:00Z',
      tags: ['会话流', '需求协同', '即时信号'],
      sourceLabel: '模拟对话层',
      summary: '承接多轮需求讨论与决策反馈，是最活跃的输入流之一。',
    },
    {
      id: 'demo-operator-feed',
      name: 'Operator Feed',
      type: 'chat',
      description: '操作台输入流',
      importance: 0.76,
      created_at: '2026-03-26T09:22:00Z',
      ingestedAt: '2026-03-26T09:22:00Z',
      tags: ['操作指令', '前线反馈', '热区'],
      sourceLabel: '模拟对话层',
      summary: '持续注入人工指令与反馈，用于驱动图谱重排与局部锁定。',
    },
    {
      id: 'demo-research-swarm',
      name: 'Research Swarm',
      type: 'forum',
      description: '外部案例趋势池',
      importance: 0.81,
      created_at: '2026-03-25T22:10:00Z',
      ingestedAt: '2026-03-25T22:10:00Z',
      tags: ['趋势池', '案例信号', '外部观察'],
      sourceLabel: '模拟论坛层',
      summary: '聚合外部产品案例与设计趋势，帮助评估当前方案方向。',
    },
    {
      id: 'demo-threat-monitor',
      name: 'Threat Monitor',
      type: 'forum',
      description: '异常风险观察哨',
      importance: 0.72,
      created_at: '2026-03-25T20:40:00Z',
      ingestedAt: '2026-03-25T20:40:00Z',
      tags: ['风险侦测', '异常链路', '监控'],
      sourceLabel: '模拟论坛层',
      summary: '监视潜在风险、断链与质量漂移，提醒编排层及时修正。',
    },
    {
      id: 'demo-sprint-grid',
      name: 'Sprint Grid',
      type: 'schedule',
      description: '执行排期矩阵',
      importance: 0.87,
      created_at: '2026-03-26T10:00:00Z',
      ingestedAt: '2026-03-26T10:00:00Z',
      tags: ['执行矩阵', '排期', '高优先级'],
      sourceLabel: '模拟日程层',
      summary: '把上层判断拆成可执行时间片，连接洞察和交付节奏。',
    },
    {
      id: 'demo-review-window',
      name: 'Review Window',
      type: 'schedule',
      description: '评审与验收窗口',
      importance: 0.74,
      created_at: '2026-03-27T11:30:00Z',
      ingestedAt: '2026-03-27T11:30:00Z',
      tags: ['评审窗口', '验收', '同步'],
      sourceLabel: '模拟日程层',
      summary: '负责汇总阶段性成果，形成清晰的验收节奏。',
    },
    {
      id: 'demo-signal-bridge',
      name: 'Signal Bridge',
      type: 'knowledge',
      description: '跨域桥接节点',
      importance: 0.84,
      created_at: '2026-03-26T09:45:00Z',
      ingestedAt: '2026-03-26T09:45:00Z',
      tags: ['桥接器', '跨域同步', '知识通道'],
      sourceLabel: '模拟桥接层',
      summary: '把知识、会话、排程和外部趋势连成完整闭环，避免分区孤立。',
    },
    {
      id: 'demo-low-trace-1',
      name: 'Dormant Trace A',
      type: 'forum',
      description: '低优先级外围线索',
      importance: 0.55,
      created_at: '2026-03-24T18:00:00Z',
      ingestedAt: '2026-03-24T18:00:00Z',
      tags: ['外围线索', '低优先级'],
      sourceLabel: '模拟外围层',
      summary: '保留的低频外部信号，用于模拟更真实的外部扰动与上下文。',
    },
  ]

  for (let index = 1; index <= 10; index += 1) {
    nodes.push({
      id: `demo-knowledge-archive-${index}`,
      name: `Knowledge Archive ${String(index).padStart(2, '0')}`,
      type: 'knowledge',
      description: '扩展知识存档',
      importance: 0.66 + (index % 4) * 0.06,
      created_at: `2026-03-${String(10 + index).padStart(2, '0')}T08:15:00Z`,
      ingestedAt: `2026-03-${String(10 + index).padStart(2, '0')}T08:15:00Z`,
      tags: ['知识存档', '文档群', index % 2 === 0 ? '事实层' : '参考层'],
      sourceLabel: '模拟知识簇',
      summary: `第 ${index} 组知识存档，围绕规范、经验库与历史决策进行补充。`,
    })
    nodes.push({
      id: `demo-chat-thread-${index}`,
      name: `Conversation Thread ${String(index).padStart(2, '0')}`,
      type: 'chat',
      description: '扩展对话线程',
      importance: 0.58 + (index % 5) * 0.06,
      created_at: `2026-03-${String(11 + index).padStart(2, '0')}T09:20:00Z`,
      ingestedAt: `2026-03-${String(11 + index).padStart(2, '0')}T09:20:00Z`,
      tags: ['会话线程', index % 2 === 0 ? '需求同步' : '执行沟通', '上下文流'],
      sourceLabel: '模拟会话簇',
      summary: `第 ${index} 条对话线程，承载局部需求确认、方案评估与反馈。`,
    })
    nodes.push({
      id: `demo-forum-signal-${index}`,
      name: `Forum Signal ${String(index).padStart(2, '0')}`,
      type: 'forum',
      description: '扩展论坛信号',
      importance: 0.52 + (index % 4) * 0.07,
      created_at: `2026-03-${String(12 + index).padStart(2, '0')}T06:40:00Z`,
      ingestedAt: `2026-03-${String(12 + index).padStart(2, '0')}T06:40:00Z`,
      tags: ['外部趋势', index % 2 === 0 ? '案例池' : '风险池', '观察哨'],
      sourceLabel: '模拟论坛簇',
      summary: `第 ${index} 条论坛信号，提供外部案例趋势、异常观察与竞争参考。`,
    })
    nodes.push({
      id: `demo-schedule-lane-${index}`,
      name: `Schedule Lane ${String(index).padStart(2, '0')}`,
      type: 'schedule',
      description: '扩展执行排程支线',
      importance: 0.57 + (index % 5) * 0.06,
      created_at: `2026-03-${String(13 + index).padStart(2, '0')}T11:05:00Z`,
      ingestedAt: `2026-03-${String(13 + index).padStart(2, '0')}T11:05:00Z`,
      tags: ['排程支线', index % 2 === 0 ? '执行中' : '待评审', '任务波段'],
      sourceLabel: '模拟日程簇',
      summary: `第 ${index} 条排程支线，把洞察转成任务队列与评审窗口。`,
    })
  }

  const edges: KGEdge[] = [
    { id: 'de1', source: 'demo-core-orchestrator', target: 'demo-memory-vault', relation: '读取知识' },
    { id: 'de2', source: 'demo-core-orchestrator', target: 'demo-session-cluster', relation: '接收会话' },
    { id: 'de3', source: 'demo-core-orchestrator', target: 'demo-sprint-grid', relation: '下发编排' },
    { id: 'de4', source: 'demo-core-orchestrator', target: 'demo-signal-bridge', relation: '稳定桥接' },
    { id: 'de5', source: 'demo-memory-vault', target: 'demo-signal-bridge', relation: '提供事实' },
    { id: 'de6', source: 'demo-session-cluster', target: 'demo-operator-feed', relation: '扩展输入' },
    { id: 'de7', source: 'demo-session-cluster', target: 'demo-signal-bridge', relation: '触发同步' },
    { id: 'de8', source: 'demo-research-swarm', target: 'demo-signal-bridge', relation: '补充趋势' },
    { id: 'de9', source: 'demo-threat-monitor', target: 'demo-signal-bridge', relation: '反馈风险' },
    { id: 'de10', source: 'demo-signal-bridge', target: 'demo-sprint-grid', relation: '投递任务' },
    { id: 'de11', source: 'demo-sprint-grid', target: 'demo-review-window', relation: '进入评审' },
    { id: 'de12', source: 'demo-research-swarm', target: 'demo-review-window', relation: '支持验证' },
    { id: 'de13', source: 'demo-low-trace-1', target: 'demo-threat-monitor', relation: '弱风险' },
    { id: 'de14', source: 'demo-low-trace-1', target: 'demo-review-window', relation: '外围参考' },
    { id: 'de15', source: 'demo-signal-bridge', target: 'demo-memory-vault', relation: '回写经验' },
    { id: 'de16', source: 'demo-operator-feed', target: 'demo-sprint-grid', relation: '推动执行' },
  ]

  for (let index = 1; index <= 10; index += 1) {
    edges.push({ id: `dk-${index}`, source: index <= 5 ? 'demo-memory-vault' : 'demo-signal-bridge', target: `demo-knowledge-archive-${index}`, relation: '归档事实' })
    edges.push({ id: `dc-${index}`, source: index <= 5 ? 'demo-session-cluster' : 'demo-operator-feed', target: `demo-chat-thread-${index}`, relation: '拆分会话' })
    edges.push({ id: `df-${index}`, source: index <= 5 ? 'demo-research-swarm' : 'demo-threat-monitor', target: `demo-forum-signal-${index}`, relation: '吸收信号' })
    edges.push({ id: `ds-${index}`, source: index <= 5 ? 'demo-sprint-grid' : 'demo-review-window', target: `demo-schedule-lane-${index}`, relation: '拆分排期' })
    edges.push({ id: `dxc-${index}`, source: `demo-knowledge-archive-${index}`, target: `demo-chat-thread-${index}`, relation: '支持讨论' })
    edges.push({ id: `dxf-${index}`, source: `demo-chat-thread-${index}`, target: `demo-forum-signal-${index}`, relation: '验证外部信号' })
  }

  return { nodes, edges }
}

const demoGraph = createDemoGraph()

const nodes = ref<BrainNode[]>([])
const edges = ref<KGEdge[]>([])
const isLoading = ref(false)
const selectedEntity = ref<BrainNode | null>(null)
const entityContext = ref('')
const activeEntityRequestKey = ref<string | null>(null)
const isBuilding = ref(false)
const buildStatus = ref<'idle' | 'running' | 'success' | 'error'>('idle')
const buildTitle = ref('')
const buildMessage = ref('')
const graphCanvasRef = ref<BrainGraphCanvasExpose | null>(null)
const importanceFilter = ref<ImportanceFilter>('all')
const previewMode = ref<'live' | 'demo'>('live')
const systemTime = ref('--:--:--')

let buildPollTimer: ReturnType<typeof setTimeout> | null = null
let systemTimeTimer: ReturnType<typeof window.setInterval> | null = null

function getColor(type: string) {
  return typeColors[type] || typeColors.default
}

function getNodeWeight(node: BrainNode) {
  return node.importance || 0.5
}

function getNodeTier(node: BrainNode) {
  const weight = getNodeWeight(node)
  if (weight >= 0.86) return '核心'
  if (weight >= 0.7) return '连接'
  return '线索'
}

function getImportanceLabel(importance: number) {
  if (importance >= 0.86) return '高'
  if (importance >= 0.7) return '中'
  return '低'
}

function normalizeNode(node: KGNode): BrainNode {
  const importance = typeof node.importance === 'number' ? node.importance : 0.5
  const sourceType = node.type
  const sourceLabel = typeLabels[sourceType] || sourceType || '未知来源'
  return {
    ...node,
    importance,
    type: sourceType,
    ingestedAt: node.created_at,
    tags: [sourceLabel, getNodeTier({ ...node, importance } as BrainNode)],
    sourceLabel: `${sourceLabel}来源`,
    summary: node.description || `${node.name} 已纳入知识大脑，可在右侧查看更完整的上下文摘要。`,
  }
}

function applyGraphData(nextNodes: BrainNode[], nextEdges: KGEdge[]) {
  nodes.value = [...nextNodes]
  edges.value = [...nextEdges]
}

function clearBuildPoll() {
  if (buildPollTimer) {
    clearTimeout(buildPollTimer)
    buildPollTimer = null
  }
}

function getErrorMessage(error: unknown) {
  const detail = (error as { response?: { data?: { detail?: string } } })?.response?.data?.detail
  if (typeof detail === 'string' && detail) return detail
  return '暂时无法重建知识图谱，请稍后再试。'
}

function matchesImportance(node: BrainNode) {
  const weight = getNodeWeight(node)
  if (importanceFilter.value === 'high') return weight >= 0.86
  if (importanceFilter.value === 'medium') return weight >= 0.7 && weight < 0.86
  if (importanceFilter.value === 'low') return weight < 0.7
  return true
}

const filteredNodes = computed(() => nodes.value.filter(matchesImportance))
const filteredNodeIds = computed(() => new Set(filteredNodes.value.map((node) => node.id)))
const filteredEdges = computed(() => edges.value.filter((edge) => filteredNodeIds.value.has(edge.source) && filteredNodeIds.value.has(edge.target)))
const selectedEntityVisible = computed(() => filteredNodes.value.find((node) => node.id === selectedEntity.value?.id) || null)

const graphDegrees = computed(() => {
  const map = new Map<string, number>()
  for (const node of filteredNodes.value) map.set(node.id, 0)
  for (const edge of filteredEdges.value) {
    map.set(edge.source, (map.get(edge.source) || 0) + 1)
    map.set(edge.target, (map.get(edge.target) || 0) + 1)
  }
  return map
})

const highBridgeNodes = computed(() => filteredNodes.value
  .filter((node) => (graphDegrees.value.get(node.id) || 0) >= 2)
  .sort((left, right) => (graphDegrees.value.get(right.id) || 0) - (graphDegrees.value.get(left.id) || 0))
  .slice(0, 3))

const clusterCount = computed(() => {
  const visited = new Set<string>()
  let count = 0

  function walk(startId: string) {
    const queue = [startId]
    while (queue.length > 0) {
      const current = queue.shift()
      if (!current || visited.has(current)) continue
      visited.add(current)
      for (const edge of filteredEdges.value) {
        if (edge.source === current && !visited.has(edge.target)) queue.push(edge.target)
        if (edge.target === current && !visited.has(edge.source)) queue.push(edge.source)
      }
    }
  }

  for (const node of filteredNodes.value) {
    if (!visited.has(node.id)) {
      count += 1
      walk(node.id)
    }
  }

  return count
})

const recentIngestedAt = computed(() => filteredNodes.value
  .map((node) => node.ingestedAt || node.created_at)
  .filter((value): value is string => Boolean(value))
  .sort((a, b) => new Date(b).getTime() - new Date(a).getTime())[0] || '')

const prominentTags = computed(() => {
  const frequency = new Map<string, number>()
  for (const node of filteredNodes.value) {
    for (const tag of node.tags || []) frequency.set(tag, (frequency.get(tag) || 0) + 1)
  }
  return Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([tag]) => tag)
})

const selectedRelations = computed(() => {
  if (!selectedEntityVisible.value) return []
  const selectedId = selectedEntityVisible.value.id
  return filteredEdges.value
    .filter((edge) => edge.source === selectedId || edge.target === selectedId)
    .slice(0, 6)
    .map((edge) => {
      const relatedId = edge.source === selectedId ? edge.target : edge.source
      return {
        id: edge.id,
        relation: edge.relation,
        targetName: filteredNodes.value.find((node) => node.id === relatedId)?.name || '未知节点',
      }
    })
})

const telemetryItems = computed(() => [
  { id: 'clusters', label: 'ACTIVE CLUSTERS', value: String(clusterCount.value).padStart(2, '0') },
  { id: 'links', label: 'LIVE LINKS', value: String(filteredEdges.value.length).padStart(2, '0') },
  { id: 'bridges', label: 'BRIDGE NODES', value: String(highBridgeNodes.value.length).padStart(2, '0') },
  { id: 'lock', label: 'TARGET LOCK', value: selectedEntityVisible.value?.name || 'STANDBY' },
])

const previewModeLabel = computed(() => previewMode.value === 'demo' ? 'DEMO GRID' : 'LIVE GRID')
const previewModeActionLabel = computed(() => previewMode.value === 'demo' ? 'RETURN LIVE' : 'SIMULATE GRID')
const brainStatusLabel = computed(() => {
  if (isBuilding.value) return 'REBUILDING'
  if (isLoading.value) return 'SYNCING'
  return previewMode.value === 'demo' ? 'SIMULATION' : 'ONLINE'
})
const nodeRegistry = computed(() => filteredNodes.value.slice().sort((a, b) => getNodeWeight(b) - getNodeWeight(a)).slice(0, 7))
const connectionDensity = computed(() => {
  const nodeCount = filteredNodes.value.length
  if (nodeCount <= 1) return 0
  const maxLinks = (nodeCount * (nodeCount - 1)) / 2
  return Math.min(100, Math.round((filteredEdges.value.length / maxLinks) * 100))
})
const coreNodeCount = computed(() => filteredNodes.value.filter((node) => getNodeWeight(node) >= 0.86).length)
const activityBars = computed(() => Array.from({ length: 12 }, (_, index) => 28 + ((filteredEdges.value.length * 7 + filteredNodes.value.length * 11 + index * 13) % 68)))

function formatDateTime(value?: string, options: { short?: boolean } = {}) {
  if (!value) return '--'
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return value
  return new Intl.DateTimeFormat('zh-CN', {
    year: options.short ? undefined : 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: options.short ? '2-digit' : undefined,
    minute: options.short ? '2-digit' : undefined,
    hour12: false,
  }).format(date)
}

async function loadGraph() {
  if (previewMode.value === 'demo') {
    isLoading.value = false
    applyGraphData(demoGraph.nodes, demoGraph.edges)
    return
  }

  isLoading.value = true
  try {
    const response = await graphApi.get()
    const remoteNodes = response.data.nodes.map((node) => normalizeNode(node))
    applyGraphData(remoteNodes.length === 0 ? fallbackNodes : remoteNodes, remoteNodes.length === 0 ? fallbackEdges : response.data.edges)
  } catch {
    applyGraphData(fallbackNodes, fallbackEdges)
  } finally {
    isLoading.value = false
    await nextTick()
  }
}

function pollGraphBuild() {
  clearBuildPoll()
  buildPollTimer = setTimeout(async () => {
    try {
      await loadGraph()
      isBuilding.value = false
      buildStatus.value = 'success'
      buildTitle.value = 'PROJECTION SYNCHRONIZED'
      buildMessage.value = '图谱重建完成，最新关系已同步。'
    } catch (error) {
      isBuilding.value = false
      buildStatus.value = 'error'
      buildTitle.value = 'REBUILD FAILURE'
      buildMessage.value = getErrorMessage(error)
    }
  }, 1200)
}

async function buildGraph() {
  if (isBuilding.value) return
  isBuilding.value = true
  buildStatus.value = 'running'
  buildTitle.value = 'REBUILD REQUEST ACCEPTED'
  buildMessage.value = '正在整理最新记忆、标签与知识关系。'

  try {
    const response = await graphApi.build()
    buildMessage.value = response.data?.message || buildMessage.value
    pollGraphBuild()
  } catch (error) {
    isBuilding.value = false
    buildStatus.value = 'error'
    buildTitle.value = 'REBUILD FAILURE'
    buildMessage.value = getErrorMessage(error)
  }
}

async function selectEntity(node: BrainNode) {
  selectedEntity.value = node
  entityContext.value = node.summary || '正在加载上下文...'
  activeEntityRequestKey.value = node.id
  const requestKey = node.id

  if (previewMode.value === 'demo') return

  try {
    const response = await graphApi.getEntityContext(node.name)
    if (activeEntityRequestKey.value !== requestKey) return
    entityContext.value = response.data.context || node.summary || '暂无额外上下文。'
  } catch {
    if (activeEntityRequestKey.value !== requestKey) return
    entityContext.value = node.summary || '暂无额外上下文。'
  }
}

function clearSelection() {
  selectedEntity.value = null
  entityContext.value = ''
  activeEntityRequestKey.value = null
}

function togglePreviewMode() {
  previewMode.value = previewMode.value === 'live' ? 'demo' : 'live'
  clearSelection()
  clearBuildPoll()
  buildStatus.value = 'idle'
  void loadGraph()
}

function handleSelectNode(nodeId: string) {
  const node = filteredNodes.value.find((item) => item.id === nodeId)
  if (node) void selectEntity(node)
}

function zoomIn() {
  graphCanvasRef.value?.zoomBy(1.15)
}

function zoomOut() {
  graphCanvasRef.value?.zoomBy(0.87)
}

function resetChartView() {
  graphCanvasRef.value?.resetViewport()
}

function syncSystemTime() {
  systemTime.value = new Intl.DateTimeFormat('en-GB', {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  }).format(new Date())
}

watch(filteredNodeIds, () => {
  if (selectedEntity.value && !filteredNodeIds.value.has(selectedEntity.value.id)) clearSelection()
})

onMounted(() => {
  syncSystemTime()
  systemTimeTimer = window.setInterval(syncSystemTime, 1000)
  void loadGraph()
})

onBeforeUnmount(() => {
  clearBuildPoll()
  if (systemTimeTimer) clearInterval(systemTimeTimer)
})
</script>

<template>
  <div class="graph-projection" :class="{ fullscreen }" data-testid="brain-projection-shell">
    <div class="projection-scanlines"></div>
    <div class="projection-vignette"></div>
    <div class="projection-gridlines"></div>
    <div class="projection-pulse projection-pulse-a"></div>
    <div class="projection-pulse projection-pulse-b"></div>
    <div class="projection-starfield"></div>
    <div class="projection-noise"></div>

    <div class="projection-shell">
      <header class="command-strip">
        <div class="brand-cluster">
          <div class="brand-sigil"><span></span><span></span></div>
          <div class="strip-brand">
            <span class="strip-kicker">J.A.R.V.I.S. / KNOWLEDGE BRAIN COMMAND</span>
            <h1>知识大脑</h1>
            <p>中间聚焦全息 3D 图，左右拆成情报舱和锁定详情，整体风格向 demo 的中控台靠拢。</p>
          </div>
        </div>

        <div class="status-cluster">
          <div class="system-readout">
            <span class="telemetry-label">BRAIN STATUS</span>
            <strong class="status-pill">{{ brainStatusLabel }}</strong>
          </div>
          <div class="system-readout">
            <span class="telemetry-label">SYS_TIME</span>
            <strong class="time-pill">{{ systemTime }}</strong>
          </div>
        </div>
      </header>

      <div class="top-actions" data-testid="brain-telemetry-rail">
        <div class="preview-badge" :class="`is-${previewMode}`" data-testid="brain-preview-badge">{{ previewModeLabel }}</div>
        <button class="command-btn ghost" type="button" data-testid="brain-preview-toggle" @click="togglePreviewMode">{{ previewModeActionLabel }}</button>
        <RouterLink v-if="showOpenFullView" class="command-btn ghost" to="/graph">全屏查看</RouterLink>
        <button class="command-btn" @click="buildGraph" :disabled="isBuilding || previewMode === 'demo'" data-testid="brain-rebuild-button">
          <RefreshCw :size="14" :class="{ spin: isBuilding }" />
          {{ isBuilding ? 'REBUILDING' : 'REBUILD GRAPH' }}
        </button>
      </div>

      <div v-if="buildStatus !== 'idle'" class="status-ribbon" :class="`is-${buildStatus}`" data-testid="brain-build-banner">
        <span class="status-ribbon-title">{{ buildTitle }}</span>
        <span class="status-ribbon-copy">{{ buildMessage }}</span>
      </div>

      <section class="brain-deck" data-testid="brain-graph-card">
        <aside class="side-column">
          <section class="tech-panel">
            <div class="panel-head">
              <span class="panel-kicker">NODE REGISTRY</span>
              <span class="panel-caption">{{ filteredNodes.length }} ACTIVE</span>
            </div>
            <div class="registry-list">
              <button
                v-for="node in nodeRegistry"
                :key="node.id"
                type="button"
                class="registry-item"
                :class="{ active: selectedEntityVisible?.id === node.id }"
                @click="selectEntity(node)"
              >
                <span class="registry-dot" :style="{ background: getColor(node.type) }"></span>
                <span class="registry-main">
                  <strong>{{ node.name }}</strong>
                  <span>{{ typeLabels[node.type] || node.type }} / {{ Math.round(getNodeWeight(node) * 100) }}%</span>
                </span>
              </button>
            </div>
          </section>

          <section class="tech-panel">
            <div class="panel-head">
              <span class="panel-kicker">BRAIN METRICS</span>
              <span class="panel-caption">REALTIME</span>
            </div>
            <div class="metric-stack">
              <div class="metric-row" data-testid="brain-telemetry-clusters">
                <div class="metric-copy"><span>ACTIVE CLUSTERS</span><strong>{{ String(clusterCount).padStart(2, '0') }}</strong></div>
                <div class="metric-bar"><span :style="{ width: `${Math.min(clusterCount * 18, 100)}%` }"></span></div>
              </div>
              <div class="metric-row">
                <div class="metric-copy"><span>LINK DENSITY</span><strong>{{ connectionDensity }}%</strong></div>
                <div class="metric-bar"><span :style="{ width: `${connectionDensity}%` }"></span></div>
              </div>
              <div class="metric-row">
                <div class="metric-copy"><span>CORE NODES</span><strong>{{ String(coreNodeCount).padStart(2, '0') }}</strong></div>
                <div class="metric-bar"><span :style="{ width: `${Math.min(coreNodeCount * 20, 100)}%` }"></span></div>
              </div>
            </div>
          </section>
        </aside>

        <div class="center-column">
          <div class="stage-header">
            <div>
              <div class="panel-kicker">NEURAL BRAIN MATRIX</div>
              <h2>INTERACTIVE 3D GRAPH</h2>
            </div>
            <div class="stage-subcopy">DRAG TO ROTATE / SCROLL TO ZOOM / CLICK TO LOCK</div>
          </div>

          <div class="whiteboard-stage" data-testid="brain-whiteboard-stage">
            <div class="whiteboard-grid"></div>
            <div class="whiteboard-core-ring ring-a"></div>
            <div class="whiteboard-core-ring ring-b"></div>
            <div class="whiteboard-core-ring ring-c"></div>
            <div class="whiteboard-beam whiteboard-beam-a"></div>
            <div class="whiteboard-beam whiteboard-beam-b"></div>
            <div class="whiteboard-scan"></div>

            <div class="overlay-controls">
              <div class="cluster-chip-row">
                <button
                  v-for="option in importanceOptions"
                  :key="option.key"
                  class="signal-chip"
                  :class="{ active: importanceFilter === option.key }"
                  :data-testid="`brain-filter-importance-${option.key}`"
                  @click="importanceFilter = option.key"
                >
                  {{ option.label }}
                </button>
              </div>
              <div class="cluster-chip-row">
                <button class="signal-chip tool" @click="zoomOut">-</button>
                <button class="signal-chip tool" @click="zoomIn">+</button>
                <button class="signal-chip tool wide" @click="resetChartView">RESET</button>
              </div>
            </div>

            <div class="hud-column hud-left">
              <div
                v-for="item in telemetryItems"
                :key="item.id"
                class="telemetry-cell"
                :data-testid="`brain-telemetry-${item.id}`"
              >
                <span class="telemetry-label">{{ item.label }}</span>
                <strong class="telemetry-value">{{ item.value }}</strong>
              </div>
            </div>

            <div class="hud-column hud-right">
              <div class="telemetry-cell spotlight-cell">
                <span class="telemetry-label">SIGNAL TAGS</span>
                <strong class="telemetry-value multi">{{ prominentTags.join(' / ') || 'NO TAG SIGNAL' }}</strong>
              </div>
              <div class="telemetry-cell spotlight-cell">
                <span class="telemetry-label">CORE HUBS</span>
                <strong class="telemetry-value multi">{{ highBridgeNodes.map((node) => node.name).join(' / ') || 'STANDBY' }}</strong>
              </div>
              <div class="telemetry-cell spotlight-cell">
                <span class="telemetry-label">RECENT INGEST</span>
                <strong class="telemetry-value">{{ recentIngestedAt ? formatDateTime(recentIngestedAt, { short: true }) : '--' }}</strong>
              </div>
            </div>

            <div v-if="filteredNodes.length === 0 && !isLoading" class="empty-state" data-testid="brain-empty-state">
              <div class="empty-core"></div>
              <div class="empty-title">NO ACTIVE SIGNAL</div>
              <div class="empty-sub">放宽过滤范围，重新唤醒外围链路。</div>
            </div>

            <div v-else class="graph-canvas-shell">
              <BrainGraphCanvas
                ref="graphCanvasRef"
                :nodes="filteredNodes"
                :edges="filteredEdges"
                :selected-node-id="selectedEntityVisible?.id || null"
                :get-color="getColor"
                @select-node="handleSelectNode"
                @clear-selection="clearSelection"
              />
            </div>
          </div>

          <div class="brain-console">
            <div class="console-head">
              <span>BRAIN_CONSOLE // SYSTEM LOG</span>
              <div class="console-values">
                <span>ACTIVE: {{ filteredNodes.length }}</span>
                <span>SELECTED: {{ selectedEntityVisible?.name || 'NONE' }}</span>
              </div>
            </div>
            <div class="console-body">
              <div class="console-line"><Sparkles :size="14" /><span>主舞台专门承载 3D 关联网络，视觉重心完全落在中间全息图上。</span></div>
              <div class="console-line"><Telescope :size="14" /><span>左右两侧改为数据舱，避免详情继续遮挡画布。</span></div>
              <div class="console-line"><MessagesSquare :size="14" /><span>{{ selectedEntityVisible ? `已锁定 ${selectedEntityVisible.name}` : 'WAITING FOR TARGET LOCK' }}</span></div>
            </div>
          </div>
        </div>

        <aside class="side-column">
          <section v-if="selectedEntityVisible" class="tech-panel selection-inspector" data-testid="brain-detail-panel">
            <div class="inspector-header">
              <div>
                <div class="panel-kicker">TARGET INSPECTOR</div>
                <h2 :style="{ color: getColor(selectedEntityVisible.type) }">{{ selectedEntityVisible.name }}</h2>
              </div>
              <button class="inspector-close" type="button" @click="clearSelection" data-testid="brain-inspector-close">
                <X :size="16" />
              </button>
            </div>

            <div class="target-meta">
              <span class="meta-chip">{{ typeLabels[selectedEntityVisible.type] || selectedEntityVisible.type }}</span>
              <span class="meta-chip">{{ getNodeTier(selectedEntityVisible) }}</span>
              <span class="meta-chip">{{ getImportanceLabel(selectedEntityVisible.importance) }}</span>
              <span class="meta-chip">{{ Math.round(getNodeWeight(selectedEntityVisible) * 100) }}%</span>
            </div>

            <section class="intel-module">
              <div class="intel-module-title">INGEST SIGNAL</div>
              <div class="info-stack">
                <div class="info-row">
                  <CalendarRange :size="14" />
                  <span>入脑时间</span>
                  <strong>{{ formatDateTime(selectedEntityVisible.ingestedAt || selectedEntityVisible.created_at) }}</strong>
                </div>
                <div class="info-row">
                  <FileStack :size="14" />
                  <span>来源</span>
                  <strong>{{ selectedEntityVisible.sourceLabel || '知识来源' }}</strong>
                </div>
                <div class="info-row">
                  <Scale :size="14" />
                  <span>权重等级</span>
                  <strong>{{ getImportanceLabel(selectedEntityVisible.importance) }}</strong>
                </div>
              </div>
            </section>

            <section class="intel-module">
              <div class="intel-module-title">TAG ARRAY</div>
              <div class="tag-array">
                <span v-for="tag in selectedEntityVisible.tags || []" :key="tag" class="tag-signal">{{ tag }}</span>
              </div>
            </section>

            <section class="intel-module">
              <div class="intel-module-title">ENTITY BRIEF</div>
              <p class="entity-context">{{ entityContext }}</p>
            </section>
          </section>

          <section v-else class="tech-panel selection-idle" data-testid="brain-detail-empty">
            <div class="idle-icon"><MessagesSquare :size="16" /></div>
            <div>
              <strong>WAITING FOR TARGET LOCK</strong>
              <span>点击任意节点，展开局部星链。</span>
            </div>
          </section>

          <section class="tech-panel">
            <div class="panel-head">
              <span class="panel-kicker">OUTGOING CONNECTIONS</span>
              <span class="panel-caption">{{ selectedRelations.length }} ROUTES</span>
            </div>
            <div class="route-list">
              <div v-for="relation in selectedRelations" :key="relation.id" class="route-item">
                <span class="route-type">{{ relation.relation }}</span>
                <span class="route-line"></span>
                <span class="route-target">{{ relation.targetName }}</span>
              </div>
              <div v-if="selectedRelations.length === 0" class="panel-empty">SELECT A NODE</div>
            </div>
          </section>

          <section class="tech-panel">
            <div class="panel-head">
              <span class="panel-kicker">BRAIN ACTIVITY</span>
              <span class="panel-caption">PULSE TRACE</span>
            </div>
            <div class="activity-graph">
              <span v-for="(height, index) in activityBars" :key="index" class="activity-bar" :style="{ height: `${height}%` }"></span>
            </div>
          </section>
        </aside>
      </section>
    </div>
  </div>
</template>

<style scoped>
.graph-projection {
  position: relative;
  min-height: 100%;
  padding: 14px;
  overflow: hidden;
  color: #d9f6ff;
  background:
    radial-gradient(circle at 50% 48%, rgba(0, 243, 255, 0.12), transparent 22%),
    radial-gradient(circle at 18% 18%, rgba(0, 243, 255, 0.08), transparent 20%),
    radial-gradient(circle at 82% 20%, rgba(79, 123, 255, 0.1), transparent 22%),
    linear-gradient(180deg, #02050a 0%, #050b13 42%, #020408 100%);
}

.graph-projection.fullscreen { min-height: calc(100vh - 48px); }

.projection-scanlines,
.projection-vignette,
.projection-pulse,
.projection-noise,
.projection-starfield,
.projection-gridlines,
.whiteboard-grid,
.whiteboard-scan,
.whiteboard-beam,
.whiteboard-core-ring,
.overlay-controls,
.hud-column,
.graph-canvas-shell,
.empty-state {
  position: absolute;
}

.projection-scanlines,
.projection-vignette,
.projection-pulse,
.projection-noise,
.projection-starfield,
.projection-gridlines {
  inset: 0;
  pointer-events: none;
}

.projection-scanlines {
  opacity: 0.12;
  background: linear-gradient(to bottom, transparent 0, transparent 50%, rgba(0, 0, 0, 0.24) 50%, rgba(0, 0, 0, 0.24) 100%);
  background-size: 100% 4px;
}

.projection-vignette { background: radial-gradient(circle, transparent 42%, rgba(0, 0, 0, 0.82) 100%); }
.projection-pulse { filter: blur(42px); opacity: 0.56; }
.projection-pulse-a { background: radial-gradient(circle at 24% 36%, rgba(0, 243, 255, 0.12), transparent 28%); animation: shell-pulse 10s ease-in-out infinite; }
.projection-pulse-b { background: radial-gradient(circle at 78% 24%, rgba(68, 122, 255, 0.16), transparent 24%); animation: shell-pulse 14s ease-in-out infinite reverse; }
.projection-gridlines { background-image: linear-gradient(rgba(0, 243, 255, 0.05) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 243, 255, 0.05) 1px, transparent 1px); background-size: 76px 76px; opacity: 0.36; }
.projection-starfield { opacity: 0.4; background-image: radial-gradient(circle at 12% 18%, rgba(192, 249, 255, 0.54) 0 1px, transparent 1.5px), radial-gradient(circle at 76% 28%, rgba(0, 243, 255, 0.44) 0 1px, transparent 1.5px), radial-gradient(circle at 64% 78%, rgba(113, 148, 255, 0.32) 0 1px, transparent 1.5px); }
.projection-noise { opacity: 0.05; mix-blend-mode: screen; background-image: linear-gradient(rgba(198, 247, 255, 0.14) 1px, transparent 1px), linear-gradient(90deg, rgba(198, 247, 255, 0.1) 1px, transparent 1px); background-size: 3px 3px, 4px 4px; }

.projection-shell {
  position: relative;
  z-index: 1;
  min-height: calc(100vh - 76px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  border: 1px solid rgba(0, 243, 255, 0.16);
  border-radius: 24px;
  background: rgba(1, 10, 20, 0.72);
  box-shadow: inset 0 1px 0 rgba(219, 251, 255, 0.04), 0 24px 80px rgba(0, 0, 0, 0.58), 0 0 50px rgba(0, 243, 255, 0.08);
  backdrop-filter: blur(10px);
  overflow: hidden;
}

.projection-shell::before {
  content: '';
  position: absolute;
  inset: 8px;
  border: 1px solid rgba(0, 243, 255, 0.09);
  border-radius: 18px;
  pointer-events: none;
}

.command-strip,
.top-actions,
.status-ribbon,
.brain-deck { position: relative; z-index: 1; }

.command-strip,
.panel-head,
.stage-header,
.inspector-header,
.console-head,
.route-item,
.info-row,
.metric-copy {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.brand-cluster,
.status-cluster,
.top-actions,
.cluster-chip-row,
.registry-item,
.console-values,
.console-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

.command-strip { align-items: flex-start; padding-bottom: 10px; border-bottom: 1px solid rgba(0, 243, 255, 0.16); }
.brand-sigil { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.brand-sigil span:first-child { width: 10px; height: 10px; border-radius: 50%; background: #00f3ff; box-shadow: 0 0 16px rgba(0, 243, 255, 0.9); }
.brand-sigil span:last-child { width: 1px; height: 42px; background: linear-gradient(180deg, rgba(0, 243, 255, 0.7), transparent); }
.strip-brand { display: flex; flex-direction: column; gap: 8px; }

.strip-kicker,
.telemetry-label,
.panel-kicker,
.intel-module-title {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(145, 235, 255, 0.72);
}

.strip-brand h1,
.stage-header h2,
.inspector-header h2,
.telemetry-value,
.empty-title,
.status-pill,
.time-pill,
.preview-badge {
  font-family: var(--font-display);
}

.strip-brand h1,
.stage-header h2,
.inspector-header h2 { margin: 0; letter-spacing: 0.16em; text-transform: uppercase; }
.strip-brand h1 { font-size: 34px; color: #ecfeff; text-shadow: 0 0 12px rgba(0, 243, 255, 0.28); }
.strip-brand p,
.empty-sub,
.console-line span,
.panel-empty { margin: 0; color: rgba(196, 239, 255, 0.76); line-height: 1.6; }
.status-cluster { flex-wrap: wrap; justify-content: flex-end; }
.system-readout { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }

.status-pill,
.time-pill,
.preview-badge,
.command-btn,
.signal-chip,
.meta-chip,
.tag-signal,
.route-type {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  padding: 0 14px;
  border: 1px solid rgba(0, 243, 255, 0.18);
  border-radius: 999px;
  background: rgba(0, 18, 36, 0.72);
  color: #ecfeff;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.preview-badge.is-demo { border-color: rgba(255, 190, 70, 0.35); color: #ffd483; }
.command-btn, .signal-chip, .inspector-close, .registry-item { cursor: pointer; }
.command-btn:hover, .signal-chip:hover, .signal-chip.active, .inspector-close:hover, .registry-item:hover, .registry-item.active { border-color: rgba(0, 243, 255, 0.38); background: rgba(0, 27, 46, 0.94); }
.command-btn:disabled { opacity: 0.55; cursor: not-allowed; }

.status-ribbon,
.tech-panel,
.brain-console {
  border: 1px solid rgba(0, 243, 255, 0.14);
  border-radius: 18px;
  background: rgba(0, 13, 28, 0.62);
  box-shadow: inset 0 1px 0 rgba(228, 253, 255, 0.03), 0 0 20px rgba(0, 243, 255, 0.04);
  backdrop-filter: blur(10px);
}

.status-ribbon { display: flex; flex-wrap: wrap; gap: 10px; padding: 10px 14px; }
.brain-deck { display: grid; grid-template-columns: 300px minmax(0, 1fr) 320px; gap: 14px; flex: 1; min-height: 0; }
.side-column, .center-column { display: flex; flex-direction: column; gap: 14px; min-height: 0; }
.tech-panel { padding: 16px; }
.panel-caption, .registry-main span, .metric-copy span, .console-values, .info-row span { font-family: var(--font-mono); font-size: 11px; color: rgba(145, 235, 255, 0.6); }
.registry-list, .metric-stack, .route-list { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.registry-item { width: 100%; padding: 10px 12px; border: 1px solid rgba(0, 243, 255, 0.12); border-radius: 14px; background: rgba(0, 21, 38, 0.6); text-align: left; }
.registry-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.registry-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.metric-row { display: flex; flex-direction: column; gap: 8px; }
.metric-bar { height: 5px; background: rgba(0, 243, 255, 0.12); border-radius: 999px; overflow: hidden; }
.metric-bar span { display: block; height: 100%; background: linear-gradient(90deg, #00f3ff, #77b7ff); }

.stage-header h2 { font-size: 24px; color: #effeff; }
.stage-subcopy { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.14em; color: rgba(145, 235, 255, 0.62); }

.whiteboard-stage {
  position: relative;
  min-height: 620px;
  flex: 1;
  overflow: hidden;
  border: 1px solid rgba(0, 243, 255, 0.16);
  border-radius: 26px;
  background: radial-gradient(circle at 50% 50%, rgba(0, 243, 255, 0.08), transparent 22%), linear-gradient(180deg, rgba(1, 11, 21, 0.88), rgba(0, 7, 16, 0.96));
}

.whiteboard-grid, .graph-canvas-shell, .empty-state { inset: 0; }
.whiteboard-grid { background-image: linear-gradient(rgba(0, 243, 255, 0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 243, 255, 0.04) 1px, transparent 1px); background-size: 48px 48px; }
.whiteboard-core-ring { top: 50%; left: 50%; border-radius: 50%; border: 1px solid rgba(0, 243, 255, 0.18); transform: translate(-50%, -50%); }
.ring-a { width: 300px; height: 300px; animation: spin 20s linear infinite; }
.ring-b { width: 420px; height: 420px; border-style: dashed; animation: spin 32s linear infinite reverse; }
.ring-c { width: 540px; height: 540px; opacity: 0.45; }
.whiteboard-scan { left: 0; right: 0; top: -20%; height: 32%; background: linear-gradient(180deg, transparent, rgba(0, 243, 255, 0.12), transparent); animation: stage-sweep 10s linear infinite; }
.whiteboard-beam { top: 50%; left: 50%; width: 56%; height: 84%; border-radius: 50%; filter: blur(38px); transform: translate(-50%, -50%); }
.whiteboard-beam-a { background: radial-gradient(circle, rgba(0, 243, 255, 0.18), transparent 62%); animation: beam-pulse 8s ease-in-out infinite; }
.whiteboard-beam-b { background: radial-gradient(circle, rgba(97, 139, 255, 0.12), transparent 58%); animation: beam-pulse-alt 11s ease-in-out infinite; }
.overlay-controls { top: 18px; left: 18px; display: flex; flex-direction: column; gap: 10px; z-index: 3; }
.signal-chip { min-height: 32px; font-size: 10px; }
.signal-chip.tool { min-width: 42px; padding-inline: 0; }
.signal-chip.wide { min-width: 76px; }
.hud-column { display: flex; flex-direction: column; gap: 10px; z-index: 2; }
.hud-left { top: 126px; left: 18px; width: 180px; }
.hud-right { top: 18px; right: 18px; width: 220px; }
.telemetry-cell { padding: 12px 14px; border: 1px solid rgba(0, 243, 255, 0.1); border-radius: 14px; background: rgba(0, 15, 30, 0.68); }
.telemetry-value { display: block; margin-top: 8px; letter-spacing: 0.08em; }
.telemetry-value.multi { line-height: 1.5; white-space: normal; }

.empty-state { z-index: 2; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; text-align: center; }
.empty-core { width: 72px; height: 72px; border-radius: 50%; border: 1px solid rgba(0, 243, 255, 0.28); box-shadow: 0 0 0 10px rgba(0, 243, 255, 0.04), 0 0 28px rgba(0, 243, 255, 0.14); }
.empty-title { font-size: 16px; letter-spacing: 0.14em; }

.brain-console { overflow: hidden; }
.console-head { padding: 10px 14px; border-bottom: 1px solid rgba(0, 243, 255, 0.14); font-family: var(--font-mono); font-size: 11px; color: rgba(145, 235, 255, 0.72); }
.console-body { display: flex; flex-direction: column; gap: 10px; padding: 14px; }

.selection-idle { display: flex; align-items: center; gap: 12px; }
.idle-icon { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 14px; background: rgba(0, 243, 255, 0.08); }
.selection-idle strong { display: block; font-family: var(--font-display); color: #effeff; }
.selection-idle span { display: block; margin-top: 4px; color: rgba(196, 239, 255, 0.72); }
.selection-inspector { overflow: auto; }
.inspector-header h2 { font-size: 24px; }
.inspector-close { width: 36px; height: 36px; border: 1px solid rgba(0, 243, 255, 0.18); border-radius: 999px; background: rgba(0, 20, 38, 0.72); color: #ecfeff; }
.target-meta, .tag-array { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.intel-module { margin-top: 14px; padding: 14px; border: 1px solid rgba(0, 243, 255, 0.1); border-radius: 14px; background: rgba(0, 19, 34, 0.5); }
.info-stack, .route-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.info-row, .route-item { padding: 10px 0; border-bottom: 1px solid rgba(0, 243, 255, 0.08); }
.info-row:last-child, .route-item:last-child { border-bottom: 0; padding-bottom: 0; }
.entity-context { margin: 10px 0 0; line-height: 1.8; color: rgba(232, 248, 255, 0.84); white-space: pre-wrap; }
.route-line { flex: 1; height: 1px; background: linear-gradient(90deg, rgba(0, 243, 255, 0.08), rgba(0, 243, 255, 0.56), rgba(0, 243, 255, 0.08)); }
.activity-graph { display: flex; align-items: flex-end; gap: 8px; height: 140px; margin-top: 16px; }
.activity-bar { flex: 1; min-width: 8px; border-radius: 999px 999px 0 0; background: linear-gradient(180deg, #dffcff 0%, #00f3ff 48%, rgba(0, 243, 255, 0.24) 100%); }

.spin { animation: spin 1s linear infinite; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes stage-sweep { 0% { transform: translateY(-110%); opacity: 0; } 12% { opacity: 1; } 88% { opacity: 1; } 100% { transform: translateY(260%); opacity: 0; } }
@keyframes beam-pulse { 0%, 100% { opacity: 0.34; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 0.9; transform: translate(-50%, -50%) scale(1.06); } }
@keyframes beam-pulse-alt { 0%, 100% { opacity: 0.22; transform: translate(-50%, -50%) scale(1.04); } 50% { opacity: 0.58; transform: translate(-50%, -50%) scale(0.98); } }
@keyframes shell-pulse { 0%, 100% { opacity: 0.36; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.04); } }

@media (max-width: 1200px) {
  .brain-deck { grid-template-columns: 1fr; }
  .whiteboard-stage { min-height: 560px; }
}

@media (max-width: 780px) {
  .graph-projection { padding: 10px; }
  .projection-shell { padding: 12px; min-height: calc(100vh - 64px); }
  .command-strip,
  .panel-head,
  .stage-header,
  .console-head,
  .route-item,
  .info-row { flex-direction: column; align-items: flex-start; }
  .hud-column { position: static; width: auto; margin: 18px 18px 0; }
  .overlay-controls { position: static; margin: 18px 18px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .spin,
  .whiteboard-scan,
  .whiteboard-beam-a,
  .whiteboard-beam-b,
  .ring-a,
  .ring-b,
  .projection-pulse-a,
  .projection-pulse-b { animation: none; }
}
</style>
