<!--
  组件名称：BatchMonitor.vue
  组件描述：批量执行监控页面 - 实时查看所有任务状态 + 当前任务性能指标

  @author czj
  @date 2026-08-21
-->

<template>
  <div class="batch-monitor">
    <!-- 顶部：项目名称 + 返回 -->
    <div class="batch-header">
      <div class="header-left">
        <h2 class="page-title">批量执行监控 - {{ projectName }}</h2>
      </div>
      <div class="header-right">
        <el-button size="small" @click="goBack">返回项目</el-button>
      </div>
    </div>

    <!-- 进度条 -->
    <div class="progress-section">
      <div class="progress-info">
        <span>进度：{{ doneCount }}/{{ totalCount }}</span>
        <el-tag type="success" size="small" style="margin-left: 8px;">成功 {{ successCount }}</el-tag>
        <el-tag type="danger" size="small" style="margin-left: 4px;">失败 {{ failCount }}</el-tag>
        <el-tag
          v-if="allCompleted"
          :type="batchStatus === 'completed' ? 'success' : 'warning'"
          size="small"
          effect="dark"
          style="margin-left: 8px;"
        >
          {{ batchStatus === 'completed' ? '全部完成' : '部分失败' }}
        </el-tag>
        <el-tag v-else type="primary" size="small" effect="dark" style="margin-left: 8px;">执行中</el-tag>
      </div>
      <el-progress
        :percentage="progressPercent"
        :status="progressStatus"
        :stroke-width="16"
        :text-inside="true"
      />
    </div>

    <!-- 任务列表表格 -->
    <el-card shadow="never" class="section-card">
      <template #header>
        <div class="task-header">
          <span>任务列表 <span style="color: #c0c4cc; font-size: 12px;">（{{ filteredTasks.length }}/{{ tasks.length }}）</span></span>
          <div class="task-filters">
            <el-select
              v-model="taskStatusFilter"
              placeholder="全部状态"
              clearable
              size="small"
              style="width: 110px"
            >
              <el-option label="全部状态" value="" />
              <el-option label="等待中" value="pending" />
              <el-option label="运行中" value="running" />
              <el-option label="已完成" value="completed" />
              <el-option label="失败" value="failed" />
            </el-select>
            <el-input
              v-model="taskKeyword"
              placeholder="搜索任务名称"
              clearable
              size="small"
              style="width: 180px"
            >
              <template #prefix><el-icon><Search /></el-icon></template>
            </el-input>
          </div>
        </div>
      </template>
      <el-table :data="filteredTasks" size="small" max-height="300" stripe style="width: 100%">
        <el-table-column label="#" type="index" width="50" />
        <el-table-column label="任务名称" prop="name" min-width="180" />
        <el-table-column label="状态" width="110">
          <template #default="{ row }">
            <el-tag :type="taskStatusTag(row.status)" size="small" effect="plain">
              {{ taskStatusLabel(row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="TPS（吞吐量）" width="100" align="right" sortable prop="actualTps">
          <template #default="{ row }">
            {{ row.actualTps != null ? row.actualTps.toFixed(1) : '-' }}
          </template>
        </el-table-column>
        <el-table-column label="响应时间 Response Time" width="110" align="right" sortable prop="avgResponseTime">
          <template #default="{ row }">
            {{ row.avgResponseTime != null ? row.avgResponseTime.toFixed(0) + 'ms' : '-' }}
          </template>
        </el-table-column>
        <el-table-column label="错误率 Error Rate" width="90" align="right" sortable prop="errorRate">
          <template #default="{ row }">
            {{ row.errorRate != null ? (row.errorRate * 100).toFixed(1) + '%' : '-' }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120" fixed="right">
          <template #default="{ row }">
            <el-button
              v-if="row.status === 'completed' || row.status === 'failed'"
              type="primary"
              link
              size="small"
              @click="viewReport(row.taskId)"
            >查看报告</el-button>
            <el-button
              v-else-if="row.status === 'running'"
              type="primary"
              link
              size="small"
              @click="switchToTask(row.taskId)"
            >查看监控</el-button>
            <span v-else style="color: #c0c4cc;">-</span>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

    <!-- 实时监控区域（当前任务） -->
    <template v-if="currentTaskId && !allCompleted">
      <div class="current-task-bar">
        <span class="label">当前监控：</span>
        <el-tag type="primary" effect="dark">{{ currentTaskName }}</el-tag>
        <span class="ws-status" :class="wsConnected ? 'connected' : 'disconnected'">
          {{ wsConnected ? '已连接' : '连接中...' }}
        </span>
      </div>

      <!-- 实时指标卡片 -->
      <el-row :gutter="12" class="metrics-row">
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentTps.toFixed(1) }}</div>
            <div class="metric-label">TPS（当前吞吐量）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentAvgRt.toFixed(0) }}ms</div>
            <div class="metric-label">Avg Response Time（平均响应时间）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ totalRequests }}</div>
            <div class="metric-label">Total Requests（总请求数）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value" :style="{ color: errorRate > 5 ? '#f56c6c' : '#67c23a' }">
              {{ errorRate.toFixed(1) }}%
            </div>
            <div class="metric-label">Error Rate（错误率）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentPeakTps.toFixed(1) }}</div>
            <div class="metric-label">Peak TPS（峰值吞吐量）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentInflight }}</div>
            <div class="metric-label">Concurrent Users（实时并发数）</div>
          </el-card>
        </el-col>
      </el-row>

      <!-- TPS 趋势图 -->
      <el-card shadow="never" class="chart-card">
        <template #header><span>TPS 趋势（吞吐量趋势）</span></template>
        <div ref="tpsChartRef" class="chart" />
      </el-card>

      <!-- 响应时间图 + 状态码分布 -->
      <el-row :gutter="12">
        <el-col :span="16">
          <el-card shadow="never" class="chart-card">
            <template #header><span>响应时间趋势 Response Time（含 P95）</span></template>
            <div ref="rtChartRef" class="chart" />
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="never" class="chart-card">
            <template #header><span>状态码分布 Status Code</span></template>
            <div ref="statusChartRef" class="chart" />
          </el-card>
        </el-col>
      </el-row>

      <!-- 延迟 + 并发 -->
      <el-row :gutter="12">
        <el-col :span="16">
          <el-card shadow="never" class="chart-card">
            <template #header><span>延迟与连接时间趋势 Latency &amp; Connect Time</span></template>
            <div ref="latencyChartRef" class="chart" />
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="never" class="chart-card">
            <template #header><span>并发数趋势 Concurrency</span></template>
            <div ref="inflightChartRef" class="chart" />
          </el-card>
        </el-col>
      </el-row>

      <!-- 执行机资源区块（默认折叠） -->
      <el-collapse v-model="resourceExpanded" style="margin-bottom: 12px;">
        <el-collapse-item title="执行机资源 Resource Monitor" name="resource">
          <!-- 资源指标卡片 -->
          <el-row :gutter="12" class="metrics-row">
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value" :style="{ color: currentCpu > 85 ? '#f56c6c' : '#409eff' }">
                  {{ currentCpu.toFixed(1) }}%
                </div>
                <div class="metric-label">CPU Usage（CPU 使用率）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value" :style="{ color: currentMem > 85 ? '#f56c6c' : '#409eff' }">
                  {{ currentMem.toFixed(1) }}%
                </div>
                <div class="metric-label">Memory Usage（内存使用率）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value">{{ currentNetSent.toFixed(1) }}</div>
                <div class="metric-label">Network Sent（发送 KB/s）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value">{{ currentNetRecv.toFixed(1) }}</div>
                <div class="metric-label">Network Received（接收 KB/s）</div>
              </el-card>
            </el-col>
          </el-row>
          <!-- 资源趋势图（全宽堆叠） -->
          <el-card shadow="never" class="chart-card">
            <template #header><span>CPU &amp; 内存趋势 CPU / Memory</span></template>
            <div ref="resourceChartRef" class="chart" />
          </el-card>
          <el-card shadow="never" class="chart-card">
            <template #header><span>网络趋势 Network（发送 / 接收 KB/s）</span></template>
            <div ref="networkChartRef" class="chart" />
          </el-card>
        </el-collapse-item>
      </el-collapse>

      <!-- 目标机资源区块（默认折叠） -->
      <el-collapse v-model="targetResourceExpanded" style="margin-bottom: 12px;">
        <el-collapse-item title="目标机资源 Target Resource Monitor" name="targetResource">
          <div v-if="targetAvailable" class="target-resource-wrap">
            <!-- 目标机信息 + 资源指标卡片 -->
            <el-row :gutter="12" class="metrics-row">
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value" :style="{ color: targetCpu > 85 ? '#f56c6c' : '#409eff' }">
                    {{ targetCpu.toFixed(1) }}%
                  </div>
                  <div class="metric-label">CPU Usage（目标机 CPU 使用率）</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value" :style="{ color: targetMem > 85 ? '#f56c6c' : '#409eff' }">
                    {{ targetMem.toFixed(1) }}%
                  </div>
                  <div class="metric-label">Memory Usage（目标机内存使用率）</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value">{{ targetLoadAvg1.toFixed(2) }}</div>
                  <div class="metric-label">Load Average 1m（1 分钟平均负载）</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value">{{ targetMemUsedMb.toFixed(0) }}MB</div>
                  <div class="metric-label">Memory Used（已用内存 {{ targetMemTotalMb.toFixed(0) }}MB）</div>
                </el-card>
              </el-col>
            </el-row>
            <div class="target-host-info">
              <el-tag size="small" type="info" effect="plain">SSH {{ targetHost }}</el-tag>
              <span class="target-host-hint">目标机资源监控（SSH 连接 {{ targetHost }}）</span>
            </div>
            <!-- 目标机资源趋势图 -->
            <el-card shadow="never" class="chart-card">
              <template #header><span>目标机 CPU &amp; 内存趋势 CPU / Memory / Load</span></template>
              <div ref="targetResourceChartRef" class="chart" />
            </el-card>
          </div>
          <el-empty
            v-else
            description="目标机资源监控数据不可用（SSH 连接失败或未开启）"
            :image-size="80"
          />
        </el-collapse-item>
      </el-collapse>
    </template>

    <!-- 全部完成 -->
    <div v-else-if="allCompleted" class="empty-state">
      <el-icon :size="48" color="#67c23a"><Monitor /></el-icon>
      <h3>批量执行已完成</h3>
      <p>成功 {{ successCount }} 项，失败 {{ failCount }} 项</p>
      <el-button v-if="projectId" type="primary" size="large" @click="viewMergedReport">
        查看合并报告
      </el-button>
    </div>

    <!-- 等待中 -->
    <div v-else class="empty-state">
      <el-icon :size="48" class="waiting-icon"><Monitor /></el-icon>
      <p>等待第一个任务开始...</p>
    </div>
  </div>
</template>

<script setup lang="ts">
/**
 * 批量执行监控页面
 *
 * 功能：
 * 1. 加载批量执行详情，展示所有任务状态
 * 2. 连接批量进度 WebSocket 更新进度
 * 3. 自动连接当前运行任务的单任务监控 WebSocket
 * 4. 任务完成后自动切换到下一个任务
 * 5. 全部完成后展示「查看合并报告」入口
 */

import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Monitor, Search } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { getBatchDetails, createPerfMonitorStream } from '@/api/performance'
import type { EChartsOption } from 'echarts'
import type { BatchTaskItem } from '@/types/performance'

const route = useRoute()
const router = useRouter()

// ==================== 路由参数 ====================

const batchId = computed(() => (route.query.batchId as string) || '')
const projectId = computed(() => (route.query.projectId as string) || '')

// ==================== 批次状态 ====================

const projectName = ref('')
const batchStatus = ref('running')
const totalCount = ref(0)
const successCount = ref(0)
const failCount = ref(0)
const tasks = ref<BatchTaskItem[]>([])

// 任务列表筛选
const taskStatusFilter = ref('')
const taskKeyword = ref('')

const filteredTasks = computed(() => {
  return tasks.value.filter(t => {
    if (taskStatusFilter.value && t.status !== taskStatusFilter.value) return false
    if (taskKeyword.value && !t.name.toLowerCase().includes(taskKeyword.value.trim().toLowerCase())) return false
    return true
  })
})

const doneCount = computed(() => successCount.value + failCount.value)

const progressPercent = computed(() => {
  if (totalCount.value === 0) return 0
  return Math.round((doneCount.value / totalCount.value) * 100)
})

const progressStatus = computed(() => {
  if (batchStatus.value === 'completed') return 'success'
  if (batchStatus.value === 'partially_failed') return 'exception'
  return '' as '' | 'success' | 'exception' | 'warning'
})

const allCompleted = computed(() => {
  return batchStatus.value === 'completed' || batchStatus.value === 'partially_failed'
})

// ==================== 当前任务监控状态 ====================

const currentTaskId = ref('')
const currentTaskName = ref('')

// 实时指标
const currentTps = ref(0)
const currentAvgRt = ref(0)
const currentPeakTps = ref(0)
const currentInflight = ref(0)
const totalRequests = ref(0)
const errorRate = ref(0)

// 资源指标
const currentCpu = ref(0)
const currentMem = ref(0)
const currentNetSent = ref(0)
const currentNetRecv = ref(0)

// 目标机资源指标
const targetCpu = ref(0)
const targetMem = ref(0)
const targetMemUsedMb = ref(0)
const targetMemTotalMb = ref(0)
const targetLoadAvg1 = ref(0)
const targetAvailable = ref(false)
const targetHost = ref('')

// 图表数据
const timeLabels = ref<string[]>([])
const tpsData = ref<number[]>([])
const avgRtData = ref<number[]>([])
const p50Data = ref<number[]>([])
const p90Data = ref<number[]>([])
const p95Data = ref<number[]>([])
const p99Data = ref<number[]>([])
const latencyAvgData = ref<number[]>([])
const connectTimeAvgData = ref<number[]>([])
const inflightData = ref<number[]>([])
const status2xx = ref(0)
const status4xx = ref(0)
const status5xx = ref(0)

// 资源图表数据
const resourceTimeLabels = ref<string[]>([])
const cpuData = ref<number[]>([])
const memData = ref<number[]>([])
const netSentData = ref<number[]>([])
const netRecvData = ref<number[]>([])

// 目标机资源图表数据
const targetResourceTimeLabels = ref<string[]>([])
const targetCpuData = ref<number[]>([])
const targetMemData = ref<number[]>([])
const targetLoadData = ref<number[]>([])

// WebSocket 连接状态
const wsConnected = ref(false)

// 图表引用
const tpsChartRef = ref<HTMLElement>()
const rtChartRef = ref<HTMLElement>()
const statusChartRef = ref<HTMLElement>()
const latencyChartRef = ref<HTMLElement>()
const inflightChartRef = ref<HTMLElement>()

// 资源图表引用
const resourceChartRef = ref<HTMLElement>()
const networkChartRef = ref<HTMLElement>()
const targetResourceChartRef = ref<HTMLElement>()

// 资源折叠面板状态
const resourceExpanded = ref<string[]>([])
const targetResourceExpanded = ref<string[]>([])

let tpsChart: echarts.ECharts | null = null
let rtChart: echarts.ECharts | null = null
let statusChart: echarts.ECharts | null = null
let latencyChart: echarts.ECharts | null = null
let inflightChart: echarts.ECharts | null = null
let resourceChart: echarts.ECharts | null = null
let networkChart: echarts.ECharts | null = null
let targetResourceChart: echarts.ECharts | null = null
let taskWs: WebSocket | null = null
let batchWs: WebSocket | null = null

// ==================== 工具函数 ====================

function taskStatusTag(status: string) {
  const map: Record<string, string> = {
    completed: 'success',
    running: 'primary',
    pending: 'info',
    failed: 'danger',
    cancelled: 'warning',
  }
  return map[status] || 'info'
}

function taskStatusLabel(status: string) {
  const map: Record<string, string> = {
    completed: '已完成',
    running: '运行中',
    pending: '等待中',
    failed: '失败',
    cancelled: '已取消',
  }
  return map[status] || status
}

// ==================== 页面导航 ====================

function goBack() {
  if (projectId.value) {
    router.push(`/performance/projects/${projectId.value}`)
  } else {
    router.push('/performance/projects')
  }
}

function viewReport(taskId: string) {
  router.push({ path: '/performance/report', query: { taskId } })
}

function viewMergedReport() {
  router.push({
    path: '/performance/project-report',
    query: { projectId: projectId.value, batchId: batchId.value },
  })
}

function switchToTask(taskId: string) {
  disconnectTaskWs()
  resetChartData()
  currentTaskId.value = taskId
  // 在任务列表中查找名称
  const found = tasks.value.find(t => t.taskId === taskId)
  currentTaskName.value = found?.name || taskId
  nextTick(() => {
    initCharts()
    connectTaskWs()
  })
}

// ==================== 批次详情加载 ====================

async function loadBatchDetails() {
  if (!batchId.value) {
    ElMessage.error('缺少批次 ID')
    return
  }
  try {
    const res = await getBatchDetails(batchId.value)
    projectName.value = res.projectName || '项目'
    batchStatus.value = res.status
    totalCount.value = res.totalCount
    successCount.value = res.successCount
    failCount.value = res.failCount
    tasks.value = res.tasks || []

    // 找第一个 running 或 pending 任务
    if (!allCompleted.value) {
      const next = findNextTaskId()
      if (next) {
        switchToTask(next)
      }
    }
  } catch (e: any) {
    ElMessage.error('加载批次详情失败: ' + (e.message || ''))
  }
}

function findNextTaskId(): string | null {
  // 优先找 running 任务，其次找 pending 任务
  const running = tasks.value.find(t => t.status === 'running')
  if (running) return running.taskId
  const pending = tasks.value.find(t => t.status === 'pending')
  if (pending) return pending.taskId
  return null
}

// ==================== 批量进度 WebSocket ====================

function connectBatchWs() {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
  const host = window.location.host
  const ws = new WebSocket(`${protocol}//${host}/api/performance/ws/batch-progress`)

  ws.onmessage = (event) => {
    try {
      const msg = JSON.parse(event.data)
      if (msg.type === 'perf_batch_progress') {
        // 更新进度
        successCount.value = msg.successCount || 0
        failCount.value = msg.failCount || 0
        batchStatus.value = msg.currentStatus || 'running'

        // 如果当前任务完成，切换下一个
        if (msg.currentStatus === 'completed' || msg.currentStatus === 'partially_failed') {
          // 全部完成，刷新批次详情获取最终数据
          loadBatchDetails()
        } else if (
          msg.currentStatus === 'running' &&
          msg.currentTaskId &&
          msg.currentTaskId !== currentTaskId.value
        ) {
          // 新任务开始了，切换
          loadBatchDetails()
        }
      }
    } catch { /* ignore parse errors */ }
  }

  ws.onopen = () => {
    // 发送 ping 保持连接
    setInterval(() => {
      try { ws.send(JSON.stringify({ type: 'ping' })) } catch { /* ignore */ }
    }, 30000)
  }

  batchWs = ws
}

// ==================== 单任务监控 WebSocket ====================

function connectTaskWs() {
  if (!currentTaskId.value) return

  disconnectTaskWs()
  wsConnected.value = false

  taskWs = createPerfMonitorStream(
    currentTaskId.value,
    handleSnapshot,
    handleTaskComplete,
    () => { wsConnected.value = false },
    () => { wsConnected.value = false },
  )

  taskWs.onopen = () => {
    wsConnected.value = true
  }
}

function disconnectTaskWs() {
  if (taskWs) {
    taskWs.close()
    taskWs = null
  }
  wsConnected.value = false
}

function handleSnapshot(data: any) {
  const d = data.data || data
  currentTps.value = d.tps || 0
  currentAvgRt.value = d.avgResponseTime || d.avg_response_time || 0
  currentPeakTps.value = d.peakTps || d.peak_tps || 0
  currentInflight.value = d.concurrentUsers || d.concurrent_users || 0
  totalRequests.value = d.totalRequests || d.total_requests || 0
  const fail = d.failCount || d.fail_count || 0
  const total = totalRequests.value
  errorRate.value = total > 0 ? (fail / total) * 100 : 0

  // 图表数据
  const elapsed = d.elapsed != null
    ? `${d.elapsed.toFixed(0)}s`
    : `${timeLabels.value.length + 1}s`
  timeLabels.value.push(elapsed)
  tpsData.value.push(d.tps || 0)
  avgRtData.value.push(d.avgResponseTime || d.avg_response_time || 0)
  p50Data.value.push(d.p50 || 0)
  p90Data.value.push(d.p90 || 0)
  p95Data.value.push(d.p95 || 0)
  p99Data.value.push(d.p99 || 0)
  latencyAvgData.value.push(d.latencyAvg || d.latency_avg || 0)
  connectTimeAvgData.value.push(d.connectTimeAvg || d.connect_time_avg || 0)
  inflightData.value.push(d.concurrentUsers || d.concurrent_users || 0)

  status2xx.value += d.status2xx || d.status_2xx || 0
  status4xx.value += d.status4xx || d.status_4xx || 0
  status5xx.value += d.status5xx || d.status_5xx || 0

  // 执行机资源监控数据
  const r = d.resource
  if (r) {
    currentCpu.value = r.cpuPercent != null ? r.cpuPercent : (r.cpu_percent || 0)
    currentMem.value = r.memPercent != null ? r.memPercent : (r.mem_percent || 0)
    currentNetSent.value = r.netSentKbS != null ? r.netSentKbS : (r.net_sent_kb_s || 0)
    currentNetRecv.value = r.netRecvKbS != null ? r.netRecvKbS : (r.net_recv_kb_s || 0)

    const relapsed = d.elapsed != null
      ? `${d.elapsed.toFixed(0)}s`
      : `${resourceTimeLabels.value.length + 1}s`
    resourceTimeLabels.value.push(relapsed)
    cpuData.value.push(currentCpu.value)
    memData.value.push(currentMem.value)
    netSentData.value.push(currentNetSent.value)
    netRecvData.value.push(currentNetRecv.value)

    if (resourceTimeLabels.value.length > 300) {
      resourceTimeLabels.value.shift()
      cpuData.value.shift()
      memData.value.shift()
      netSentData.value.shift()
      netRecvData.value.shift()
    }

    updateResourceChart()
    updateNetworkChart()
  }

  // 目标机资源监控数据
  const tr = d.targetResource
  if (tr) {
    targetCpu.value = tr.cpuPercent != null ? tr.cpuPercent : 0
    targetMem.value = tr.memPercent != null ? tr.memPercent : 0
    targetMemUsedMb.value = tr.memUsedMb != null ? tr.memUsedMb : 0
    targetMemTotalMb.value = tr.memTotalMb != null ? tr.memTotalMb : 0
    targetLoadAvg1.value = tr.loadAvg1 != null ? tr.loadAvg1 : 0
    targetAvailable.value = tr.available !== false
    targetHost.value = tr.host || ''

    const relapsed = d.elapsed != null
      ? `${d.elapsed.toFixed(0)}s`
      : `${targetResourceTimeLabels.value.length + 1}s`
    targetResourceTimeLabels.value.push(relapsed)
    targetCpuData.value.push(targetCpu.value)
    targetMemData.value.push(targetMem.value)
    targetLoadData.value.push(targetLoadAvg1.value)

    if (targetResourceTimeLabels.value.length > 300) {
      targetResourceTimeLabels.value.shift()
      targetCpuData.value.shift()
      targetMemData.value.shift()
      targetLoadData.value.shift()
    }

    updateTargetResourceChart()
  }

  // 限制显示点数
  if (timeLabels.value.length > 300) {
    timeLabels.value.shift()
    tpsData.value.shift()
    avgRtData.value.shift()
    p50Data.value.shift()
    p90Data.value.shift()
    p95Data.value.shift()
    p99Data.value.shift()
    latencyAvgData.value.shift()
    connectTimeAvgData.value.shift()
    inflightData.value.shift()
  }

  // 更新图表
  updateTpsChart()
  updateRtChart()
  updateStatusChart()
  updateLatencyChart()
  updateInflightChart()
  // 资源图表已在 resource 分支中更新，这里不再重复调用
}

function handleTaskComplete(_data: any) {
  wsConnected.value = false
  // 重新加载批次详情获取最新状态
  loadBatchDetails()
}

// ==================== 图表初始化 ====================

function initCharts() {
  if (tpsChartRef.value) {
    tpsChart = echarts.init(tpsChartRef.value)
  }
  if (rtChartRef.value) {
    rtChart = echarts.init(rtChartRef.value)
  }
  if (statusChartRef.value) {
    statusChart = echarts.init(statusChartRef.value)
  }
  if (latencyChartRef.value) {
    latencyChart = echarts.init(latencyChartRef.value)
  }
  if (inflightChartRef.value) {
    inflightChart = echarts.init(inflightChartRef.value)
  }
  if (resourceChartRef.value) {
    resourceChart = echarts.init(resourceChartRef.value)
  }
  if (networkChartRef.value) {
    networkChart = echarts.init(networkChartRef.value)
  }
  if (targetResourceChartRef.value) {
    targetResourceChart = echarts.init(targetResourceChartRef.value)
  }
}

function resetChartData() {
  timeLabels.value = []
  tpsData.value = []
  avgRtData.value = []
  p50Data.value = []
  p90Data.value = []
  p95Data.value = []
  p99Data.value = []
  latencyAvgData.value = []
  connectTimeAvgData.value = []
  inflightData.value = []
  status2xx.value = 0
  status4xx.value = 0
  status5xx.value = 0
  currentTps.value = 0
  currentAvgRt.value = 0
  currentPeakTps.value = 0
  currentInflight.value = 0
  totalRequests.value = 0
  errorRate.value = 0
  resourceTimeLabels.value = []
  cpuData.value = []
  memData.value = []
  netSentData.value = []
  netRecvData.value = []
  currentCpu.value = 0
  currentMem.value = 0
  currentNetSent.value = 0
  currentNetRecv.value = 0
  targetResourceTimeLabels.value = []
  targetCpuData.value = []
  targetMemData.value = []
  targetLoadData.value = []
  targetCpu.value = 0
  targetMem.value = 0
  targetMemUsedMb.value = 0
  targetMemTotalMb.value = 0
  targetLoadAvg1.value = 0
  targetAvailable.value = false
  targetHost.value = ''
}

// ==================== 图表更新 ====================

function updateTpsChart() {
  if (!tpsChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    grid: { left: 50, right: 20, bottom: 30, top: 10 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'TPS' },
    series: [{
      type: 'line',
      data: tpsData.value,
      smooth: true,
      showSymbol: false,
      lineStyle: { width: 2 },
      areaStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: 'rgba(64,158,255,0.3)' },
          { offset: 1, color: 'rgba(64,158,255,0.05)' },
        ]),
      },
    }],
  }
  tpsChart.setOption(option, true)
}

function updateRtChart() {
  if (!rtChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['平均 Avg', 'P50', 'P90', 'P95', 'P99'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'ms' },
    series: [
      { name: '平均 Avg', type: 'line', data: avgRtData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5 } },
      { name: 'P50', type: 'line', data: p50Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P90', type: 'line', data: p90Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P95', type: 'line', data: p95Data.value, smooth: true, showSymbol: false, lineStyle: { width: 2, type: 'dashed', color: '#e6a23c' } },
      { name: 'P99', type: 'line', data: p99Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dotted' } },
    ],
  }
  rtChart.setOption(option, true)
}

function updateLatencyChart() {
  if (!latencyChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['Latency（首字节延迟）', 'Connect（连接时间）'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'ms' },
    series: [
      { name: 'Latency（首字节延迟）', type: 'line', data: latencyAvgData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5 }, areaStyle: { opacity: 0.08 } },
      { name: 'Connect（连接时间）', type: 'line', data: connectTimeAvgData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed', color: '#e6a23c' } },
    ],
  }
  latencyChart.setOption(option, true)
}

function updateInflightChart() {
  if (!inflightChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    grid: { left: 50, right: 20, bottom: 30, top: 10 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'Concurrency（并发数）', minInterval: 1 },
    series: [{
      type: 'line',
      data: inflightData.value,
      smooth: true,
      showSymbol: false,
      step: 'end',
      lineStyle: { width: 2, color: '#9b59b6' },
      areaStyle: { opacity: 0.12 },
    }],
  }
  inflightChart.setOption(option, true)
}

function updateStatusChart() {
  if (!statusChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
    series: [{
      type: 'pie',
      radius: ['40%', '70%'],
      center: ['50%', '55%'],
      data: [
        { name: '2xx', value: status2xx.value, itemStyle: { color: '#67c23a' } },
        { name: '4xx', value: status4xx.value, itemStyle: { color: '#e6a23c' } },
        { name: '5xx', value: status5xx.value, itemStyle: { color: '#f56c6c' } },
      ],
      label: { show: true, formatter: '{b}\n{d}%' },
    }],
  }
  statusChart.setOption(option, true)
}

function updateResourceChart() {
  if (!resourceChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['CPU %', 'Memory %'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: resourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: '%', max: 100 },
    series: [
      { name: 'CPU %', type: 'line', data: cpuData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#f56c6c' }, areaStyle: { opacity: 0.08 } },
      { name: 'Memory %', type: 'line', data: memData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#409eff' }, areaStyle: { opacity: 0.08 } },
    ],
  }
  resourceChart.setOption(option, true)
}

function updateNetworkChart() {
  if (!networkChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['发送 Sent', '接收 Recv'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: resourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'KB/s' },
    series: [
      { name: '发送 Sent', type: 'line', data: netSentData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#67c23a' }, areaStyle: { opacity: 0.08 } },
      { name: '接收 Recv', type: 'line', data: netRecvData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#e6a23c' }, areaStyle: { opacity: 0.08 } },
    ],
  }
  networkChart.setOption(option, true)
}

function updateTargetResourceChart() {
  if (!targetResourceChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['CPU %', 'Memory %', 'Load 1m'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: targetResourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: [
      { type: 'value', name: '%', max: 100 },
      { type: 'value', name: 'Load', splitLine: { show: false } },
    ],
    series: [
      { name: 'CPU %', type: 'line', data: targetCpuData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#f56c6c' }, areaStyle: { opacity: 0.08 } },
      { name: 'Memory %', type: 'line', data: targetMemData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#409eff' }, areaStyle: { opacity: 0.08 } },
      { name: 'Load 1m', type: 'line', data: targetLoadData.value, smooth: true, showSymbol: false, yAxisIndex: 1, lineStyle: { width: 1.5, type: 'dashed', color: '#e6a23c' } },
    ],
  }
  targetResourceChart.setOption(option, true)
}

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

onMounted(() => {
  loadBatchDetails()
  connectBatchWs()
})

onUnmounted(() => {
  disconnectTaskWs()
  if (batchWs) {
    batchWs.close()
    batchWs = null
  }
  tpsChart?.dispose()
  rtChart?.dispose()
  statusChart?.dispose()
  latencyChart?.dispose()
  inflightChart?.dispose()
  resourceChart?.dispose()
  networkChart?.dispose()
  targetResourceChart?.dispose()
})
</script>

<style scoped>
.batch-monitor {
  padding: 0;
}

.batch-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.page-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.progress-section {
  margin-bottom: 16px;
}

.progress-info {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
  font-size: 14px;
  color: #606266;
}

.section-card {
  margin-bottom: 16px;
}

.task-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.task-filters {
  display: flex;
  gap: 8px;
  align-items: center;
}

.current-task-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  font-size: 14px;
}

.current-task-bar .label {
  color: #909399;
}

.ws-status {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
}

.ws-status.connected {
  color: #67c23a;
  background: #f0f9eb;
}

.ws-status.disconnected {
  color: #e6a23c;
  background: #fdf6ec;
}

.metrics-row {
  margin-bottom: 12px;
}

.metric-card {
  text-align: center;
}

.metric-value {
  font-size: 28px;
  font-weight: 700;
  color: #409eff;
  line-height: 1.2;
}

.metric-label {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.chart-card {
  margin-bottom: 12px;
}

.chart {
  width: 100%;
  height: 280px;
}

.chart-sm {
  width: 100%;
  height: 220px;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80px 0;
  color: #909399;
}

.empty-state h3 {
  margin: 12px 0 8px;
  font-size: 18px;
  color: #303133;
}

.empty-state p {
  margin: 0 0 20px;
  font-size: 14px;
}

.waiting-icon {
  color: #909399;
}

.target-host-info {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  font-size: 12px;
  color: #909399;
}

.target-host-hint {
  color: #909399;
}

.target-resource-wrap {
  margin-top: 4px;
}
</style>