<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import VChart from 'vue-echarts'
import PageCard from '@/components/PageCard.vue'
import ModelStatusTag from '@/components/ModelStatusTag.vue'
import TrainingTaskOverview from './training-log/TrainingTaskOverview.vue'
import { usePolling } from '@/composables/usePolling'
import '@/plugins/echarts-training-log'
import { useModelsStore } from '@/stores/models'
import { getFineTune } from '@/api/modules/fineTune'
import { getTrainingLogFiles, getTrainingLogContent } from '@/api/modules/log'
import { getDataset } from '@/api/modules/dataset'
import { getSystemInfo } from '@/api/modules/system'
import { TRAIN_TYPE_MAP, TRAIN_METHOD_MAP } from '@/constants'
import {
  buildMetricChartOption,
  parseTrainingLog,
  resolveTrainingLogFile,
} from './training-log/trainingLogModel'
import type { FineTuneTask, DatasetItem, GpuInfo } from '@/types'

interface TaskGpuItem {
  index: number
  gpu?: GpuInfo
}

const route = useRoute()
const taskId = route.params.id as string
const modelsStore = useModelsStore()

const task = ref<FineTuneTask | null>(null)
const dataset = ref<DatasetItem | null>(null)
const logContent = ref('')
const gpuPool = ref<GpuInfo[]>([])
const gpuUpdatedAt = ref<Date | null>(null)
const gpuLoadError = ref('')

/** 初始加载状态：首次数据返回前显示 loading，避免空白闪烁 */
const loading = ref(true)

// 训练指标数据（ECharts 接收 number[]，下标即 step）
const metricData = reactive({
  loss: [] as number[],
  gradNorm: [] as number[],
  lr: [] as number[],
  epoch: [] as number[],
})

const summary = reactive({
  epoch: '',
  trainLoss: '',
  runtime: '',
})

/** 训练参数折叠状态（默认收起，点击展开查看全部参数） */
const paramsExpanded = ref(false)
const GPU_PREVIEW_LIMIT = 4
const gpuExpanded = ref(false)

let refreshInFlight = false

/** 三个曲线的 ECharts 配置（响应式，数据变化自动重绘） */
const lossChartOption = computed(() => buildMetricChartOption('Loss', metricData.loss, '#4f46e5'))
const gradChartOption = computed(() => buildMetricChartOption('Grad Norm', metricData.gradNorm, '#3b82f6'))
const lrChartOption = computed(() => buildMetricChartOption('Learning Rate', metricData.lr, '#14b8a6', true))
const baseModelName = computed(() => task.value?.base_model != null
  ? modelsStore.getModelName(task.value.base_model)
  : '未配置')
const trainingTypeName = computed(() => task.value?.train_type
  ? (TRAIN_TYPE_MAP[task.value.train_type] || task.value.train_type)
  : '未配置')
const trainingMethodName = computed(() => task.value?.train_method
  ? (TRAIN_METHOD_MAP[task.value.train_method] || task.value.train_method)
  : '未配置')
const taskGpuLabel = computed(() => task.value?.gpus?.length
  ? task.value.gpus.map((gpuId) => `GPU ${gpuId}`).join('、')
  : '未配置')
const latestLoss = computed(() => metricData.loss[metricData.loss.length - 1])
const latestGradNorm = computed(() => metricData.gradNorm[metricData.gradNorm.length - 1])
const latestLearningRate = computed(() => metricData.lr[metricData.lr.length - 1])
const latestEpoch = computed(() => metricData.epoch[metricData.epoch.length - 1])
const logLineCount = computed(() => logContent.value ? logContent.value.split(/\r?\n/).length : 0)
const taskGpuItems = computed<TaskGpuItem[]>(() => (task.value?.gpus ?? []).map((gpuId) => {
  const index = Number(gpuId)
  const gpu = gpuPool.value.find((item) => item.id != null && Number(item.id) === index)
    ?? gpuPool.value[index]
  return { index, gpu }
}))
const gpuSummaryText = computed(() => {
  const items = taskGpuItems.value
  if (!items.length) return '当前任务未配置 GPU'

  const availableItems = items.filter((item) => item.gpu)
  const averageUsage = availableItems.length
    ? Math.round(availableItems.reduce((sum, item) => sum + safePercent(item.gpu?.gpu_percent), 0) / availableItems.length)
    : 0
  const states = items.map((item) => gpuRuntimeState(item.gpu).className)
  const busyCount = states.filter((state) => state === 'is-busy').length
  const attentionCount = states.filter((state) => ['is-danger', 'is-warning', 'is-unavailable'].includes(state)).length
  const attentionText = attentionCount ? ` · ${attentionCount} 张需关注` : ''
  return `${items.length} 张 GPU · ${busyCount} 张运行中${attentionText} · 平均利用率 ${averageUsage}%`
})
const visibleGpuItems = computed(() => {
  const items = taskGpuItems.value
  if (gpuExpanded.value || items.length <= GPU_PREVIEW_LIMIT) return items

  // 折叠状态优先暴露异常、不可用和运行中的设备，避免关键状态被隐藏。
  return [...items]
    .sort((left, right) => gpuRuntimePriority(left.gpu) - gpuRuntimePriority(right.gpu) || left.index - right.index)
    .slice(0, GPU_PREVIEW_LIMIT)
    .sort((left, right) => left.index - right.index)
})
const hiddenGpuCount = computed(() => Math.max(0, taskGpuItems.value.length - GPU_PREVIEW_LIMIT))
const gpuRefreshState = computed(() => {
  if (!gpuUpdatedAt.value) return gpuLoadError.value || '正在获取 GPU 状态'
  const updateTime = gpuUpdatedAt.value.toLocaleTimeString('zh-CN', {
    hour12: false,
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  })
  return gpuLoadError.value
    ? `更新失败 · 最后更新 ${updateTime}`
    : `${updateTime} 更新 · 每 5 秒刷新`
})

function formatMetric(value?: number, scientific = false) {
  if (value == null || !Number.isFinite(value)) return '-'
  return scientific ? value.toExponential(2) : value.toFixed(4).replace(/0+$/, '').replace(/\.$/, '')
}

function safePercent(value?: number) {
  return Math.round(Math.min(100, Math.max(0, Number(value || 0))))
}

function formatResourceNumber(value?: number) {
  if (value == null || !Number.isFinite(Number(value))) return '-'
  return Number(value).toLocaleString('zh-CN', { maximumFractionDigits: 1 })
}

function gpuMemoryPercent(gpu?: GpuInfo) {
  if (!gpu) return 0
  if (gpu.memory_percent != null) return safePercent(gpu.memory_percent)
  if (!gpu.memory_total_gb) return 0
  return safePercent((gpu.memory_used_gb / gpu.memory_total_gb) * 100)
}

function gpuRuntimeState(gpu?: GpuInfo) {
  if (!gpu) return { label: '数据不可用', className: 'is-unavailable' }
  if (gpu.status === 'offline') return { label: '离线', className: 'is-danger' }
  if (gpu.status === 'warning' || gpu.temperature >= 80 || gpuMemoryPercent(gpu) >= 90) {
    return { label: '需关注', className: 'is-warning' }
  }
  if (gpu.status === 'busy' || gpu.gpu_percent >= 10) {
    return { label: '运行中', className: 'is-busy' }
  }
  return { label: '空闲', className: 'is-idle' }
}

function gpuRuntimePriority(gpu?: GpuInfo) {
  const priorityMap: Record<string, number> = {
    'is-danger': 0,
    'is-warning': 1,
    'is-unavailable': 2,
    'is-busy': 3,
    'is-idle': 4,
  }
  return priorityMap[gpuRuntimeState(gpu).className] ?? 5
}

function gpuProgressColor(value?: number) {
  const percent = safePercent(value)
  if (percent >= 90) return 'var(--el-color-danger)'
  if (percent >= 75) return 'var(--el-color-warning)'
  return 'var(--el-color-primary)'
}

async function loadTask() {
  try {
    const t = await getFineTune(taskId)
    task.value = t
    return t
  } catch {
    task.value = null
    return null
  }
}

async function loadDataset(datasetId: string | number) {
  try {
    dataset.value = (await getDataset(datasetId)) || null
  } catch {
    dataset.value = null
  }
}

async function loadGpuStatus() {
  try {
    const systemInfo = await getSystemInfo()
    gpuPool.value = systemInfo.gpu ?? []
    gpuUpdatedAt.value = new Date()
    gpuLoadError.value = ''
  } catch {
    gpuLoadError.value = 'GPU 监控数据暂时不可用'
    if (!gpuUpdatedAt.value) gpuPool.value = []
  }
}

/** 当前任务是否为 LoRA 系列训练方法（lora/qlora/adalora/longlora 等） */
const isLoraMethod = computed(() =>
  String(task.value?.train_method || '').toLowerCase().includes('lora'),
)

function applyLogContent(content: string) {
  const parsed = parseTrainingLog(content)
  logContent.value = content
  metricData.loss = parsed.metrics.loss
  metricData.gradNorm = parsed.metrics.gradNorm
  metricData.lr = parsed.metrics.lr
  metricData.epoch = parsed.metrics.epoch
  Object.assign(summary, parsed.summary)
}

async function loadLog(currentTask: FineTuneTask) {
  try {
    const files = (await getTrainingLogFiles()) || []
    const matched = resolveTrainingLogFile(files, currentTask)
    if (matched) {
      const res = await getTrainingLogContent(matched.file)
      applyLogContent(res.content || '')
    } else {
      applyLogContent('')
    }
  } catch {
    // ignore
  }
}

async function refreshAll() {
  if (refreshInFlight) return
  refreshInFlight = true
  try {
    const currentTask = await loadTask()
    if (!currentTask) {
      dataset.value = null
      applyLogContent('')
      return
    }

    if (!currentTask.train_dataset_id) dataset.value = null
    const datasetPromise = currentTask.train_dataset_id
      ? loadDataset(currentTask.train_dataset_id)
      : Promise.resolve()
    await Promise.all([datasetPromise, loadLog(currentTask), loadGpuStatus()])
  } finally {
    loading.value = false
    refreshInFlight = false
  }
}

const isTerminalTask = () => ['completed', 'failed', 'stopped', 'cancelled'].includes(task.value?.status || '')
const { start: startPolling, stop: stopPolling } = usePolling(async () => {
  await refreshAll()
  if (isTerminalTask()) stopPolling()
}, 5000, { immediate: false })

onMounted(async () => {
  void modelsStore.load()
  await refreshAll()
  if (!isTerminalTask()) startPolling()
})
</script>

<template>
  <div class="training-log">
    <!-- 初始加载骨架 -->
    <div v-if="loading" v-loading="true" element-loading-text="正在加载训练详情…" class="loading-wrap" />

    <!-- 详情内容 -->
    <template v-else>
    <!-- 页面身份与任务上下文 -->
    <header class="task-page-header" aria-labelledby="task-page-title">
      <span class="page-eyebrow">模型微调 · 训练任务详情</span>
      <div class="task-page-title-row">
        <h1 id="task-page-title">{{ task?.name || '训练任务详情' }}</h1>
        <ModelStatusTag :status="task?.status" />
      </div>
      <p class="task-page-description">
        {{ task?.description || '查看模型微调任务的运行状态、训练指标与日志' }}
      </p>
      <div class="task-context-row" aria-label="训练任务关键上下文">
        <span class="task-context-item"><span>任务 ID</span><strong>#{{ task?.id ?? '-' }}</strong></span>
        <i class="task-context-divider" aria-hidden="true" />
        <span class="task-context-item"><span>基座模型</span><strong>{{ baseModelName }}</strong></span>
        <i class="task-context-divider" aria-hidden="true" />
        <span class="task-context-item"><span>训练配置</span><strong>{{ trainingTypeName }} · {{ trainingMethodName }}</strong></span>
        <i class="task-context-divider" aria-hidden="true" />
        <span class="task-context-item"><span>计算资源</span><strong>{{ taskGpuLabel }}</strong></span>
      </div>
    </header>

    <!-- 核心指标摘要 -->
    <PageCard class="summary-card">
      <template #header>
        <div class="overview-card-header">
          <div class="overview-card-title">
            <h2 id="training-overview-title">训练概览</h2>
            <p>实时监控训练进度、收敛指标与 GPU 资源占用</p>
          </div>
          <div class="overview-monitor-meta">
            <ModelStatusTag :status="task?.status" />
            <span class="overview-refresh-state">{{ gpuRefreshState }}</span>
          </div>
        </div>
      </template>

      <section class="training-progress-panel" aria-labelledby="training-progress-title">
        <div class="training-progress-heading">
          <div>
            <span id="training-progress-title">整体训练进度</span>
            <small>根据训练任务当前执行进度实时更新</small>
          </div>
          <strong>{{ safePercent(task?.progress) }}<small>%</small></strong>
        </div>
        <el-progress
          :percentage="safePercent(task?.progress)"
          :show-text="false"
          :stroke-width="8"
          aria-label="整体训练进度"
        />
      </section>

      <section class="training-metric-grid" aria-label="训练关键指标">
        <article class="training-metric-cell">
          <span>当前 Epoch</span>
          <strong>{{ summary.epoch || formatMetric(latestEpoch) }}</strong>
          <small>训练轮次</small>
        </article>
        <article class="training-metric-cell">
          <span>当前 Loss</span>
          <strong>{{ formatMetric(latestLoss) }}</strong>
          <small>{{ metricData.loss.length }} 个采样点</small>
        </article>
        <article class="training-metric-cell">
          <span>运行时长</span>
          <strong>{{ summary.runtime ? `${summary.runtime}s` : (task?.train_duration || '-') }}</strong>
          <small>任务累计运行时间</small>
        </article>
      </section>

      <section class="gpu-monitor" aria-labelledby="gpu-monitor-title">
        <div class="gpu-monitor-heading">
          <div>
            <h3 id="gpu-monitor-title">GPU 运行状态</h3>
            <p>{{ gpuSummaryText }}</p>
          </div>
        </div>

        <div
          v-if="taskGpuItems.length"
          id="gpu-device-list"
          class="gpu-device-list"
          role="table"
          aria-label="GPU 设备实时运行状态"
        >
          <div class="gpu-list-header" role="row">
            <span role="columnheader">设备</span>
            <span role="columnheader">状态</span>
            <span role="columnheader">计算利用率</span>
            <span role="columnheader">显存占用</span>
            <span role="columnheader">温度</span>
            <span role="columnheader">功耗</span>
          </div>

          <article v-for="item in visibleGpuItems" :key="item.index" class="gpu-device-row" role="row">
            <div class="gpu-device-identity" role="cell">
              <strong>GPU {{ item.index }}</strong>
              <span :title="item.gpu?.name || '未检测到设备信息'">
                {{ item.gpu?.name || '未检测到设备信息' }}
              </span>
            </div>

            <span class="gpu-status" :class="gpuRuntimeState(item.gpu).className" role="cell">
              <i aria-hidden="true" />
              {{ gpuRuntimeState(item.gpu).label }}
            </span>

            <div class="gpu-utilization-cell" role="cell">
              <div class="gpu-cell-value">
                <span class="gpu-mobile-label">计算利用率</span>
                <strong>{{ safePercent(item.gpu?.gpu_percent) }}%</strong>
              </div>
              <el-progress
                :percentage="safePercent(item.gpu?.gpu_percent)"
                :show-text="false"
                :stroke-width="6"
                :color="gpuProgressColor(item.gpu?.gpu_percent)"
                :aria-label="`GPU ${item.index} 计算利用率 ${safePercent(item.gpu?.gpu_percent)}%`"
              />
            </div>

            <div class="gpu-memory-cell" role="cell">
              <span class="gpu-mobile-label">显存占用</span>
              <strong>{{ gpuMemoryPercent(item.gpu) }}%</strong>
              <small>{{ formatResourceNumber(item.gpu?.memory_used_gb) }} / {{ formatResourceNumber(item.gpu?.memory_total_gb) }} GB</small>
            </div>

            <div class="gpu-resource-cell gpu-temperature-cell" role="cell">
              <span class="gpu-mobile-label">温度</span>
              <strong>{{ item.gpu ? `${item.gpu.temperature}°C` : '-' }}</strong>
            </div>

            <div class="gpu-resource-cell gpu-power-cell" role="cell">
              <span class="gpu-mobile-label">功耗</span>
              <strong>{{ item.gpu ? `${formatResourceNumber(item.gpu.power_w)} W` : '-' }}</strong>
            </div>
          </article>
        </div>
        <div v-else class="gpu-empty-state">当前训练任务未配置 GPU</div>

        <div v-if="taskGpuItems.length > GPU_PREVIEW_LIMIT" class="gpu-list-footer">
          <span class="gpu-list-note">
            {{ gpuExpanded ? `已展示全部 ${taskGpuItems.length} 张 GPU` : `已展示 4 张，另有 ${hiddenGpuCount} 张；异常和运行中设备优先` }}
          </span>
          <el-button
            type="primary"
            link
            class="gpu-toggle-button"
            :aria-expanded="gpuExpanded"
            aria-controls="gpu-device-list"
            @click="gpuExpanded = !gpuExpanded"
          >
            {{ gpuExpanded ? '收起 GPU 列表' : `展开全部 ${taskGpuItems.length} 张 GPU` }}
          </el-button>
        </div>
      </section>
    </PageCard>

    <!-- 任务档案概览 -->
    <TrainingTaskOverview
      :task="task"
      :dataset="dataset"
      :base-model-name="baseModelName"
    />

    <!-- 训练参数 & LoRA 参数（合并卡片，默认折叠） -->
    <PageCard
      class="parameters-card"
      title="训练参数"
      subtitle="查看本次模型微调使用的基础参数和 LoRA 配置"
      :class="{ 'is-collapsed': !paramsExpanded }"
    >
      <template #extra>
        <el-button
          type="primary"
          link
          class="params-toggle-button"
          :aria-expanded="paramsExpanded"
          aria-controls="training-parameter-content"
          @click="paramsExpanded = !paramsExpanded"
        >
          {{ paramsExpanded ? '收起参数' : '展开参数' }}
        </el-button>
      </template>

      <el-collapse-transition>
        <div v-show="paramsExpanded" id="training-parameter-content" class="params-body">
          <div class="parameter-groups">
            <section class="parameter-group base-parameter-group" aria-label="基础训练参数">
              <el-descriptions class="parameter-descriptions" :column="2" border>
                <el-descriptions-item label="训练方式">{{ task?.train_type ? (TRAIN_TYPE_MAP[task.train_type] || task.train_type) : '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="训练方法">{{ task?.train_method ? (TRAIN_METHOD_MAP[task.train_method] || task.train_method) : '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="Batch Size">{{ task?.batch_size ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="Learning Rate">{{ task?.learning_rate ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="训练轮数">{{ task?.n_epochs ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="保存间隔">{{ task?.save_steps ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="学习率调度器">{{ task?.lr_scheduler_type || '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="最大序列长度">{{ task?.max_length ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="权重衰减">{{ task?.weight_decay ?? '未配置' }}</el-descriptions-item>
              </el-descriptions>
            </section>

            <section v-if="isLoraMethod" class="parameter-group lora-parameter-group" aria-labelledby="lora-params-title">
              <div class="parameter-section-heading">
                <h3 id="lora-params-title">LoRA 参数</h3>
                <el-tag size="small" type="primary" effect="plain">LoRA</el-tag>
              </div>
              <el-descriptions class="parameter-descriptions" :column="2" border>
                <el-descriptions-item label="LoRA Rank">{{ task?.lora_rank ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="LoRA Alpha">{{ task?.lora_alpha ?? '未配置' }}</el-descriptions-item>
                <el-descriptions-item label="LoRA Dropout">{{ task?.lora_dropout ?? '未配置' }}</el-descriptions-item>
              </el-descriptions>
            </section>
          </div>
        </div>
      </el-collapse-transition>
    </PageCard>

    <!-- 训练曲线 -->
    <PageCard class="metrics-panel" title="训练曲线" subtitle="持续监控模型收敛情况与学习率变化">
      <template #extra><span class="refresh-state">每 5 秒刷新</span></template>
      <div class="chart-list" aria-label="训练指标曲线">
        <section class="chart-section">
          <div class="chart-section-header">
            <span class="chart-section-title">Loss</span>
            <span class="chart-latest">最新 {{ formatMetric(latestLoss) }}</span>
          </div>
          <VChart class="chart-canvas" :option="lossChartOption" autoresize />
        </section>
        <section class="chart-section">
          <div class="chart-section-header">
            <span class="chart-section-title">梯度范数</span>
            <span class="chart-latest">最新 {{ formatMetric(latestGradNorm) }}</span>
          </div>
          <VChart class="chart-canvas" :option="gradChartOption" autoresize />
        </section>
        <section class="chart-section">
          <div class="chart-section-header">
            <span class="chart-section-title">学习率</span>
            <span class="chart-latest">最新 {{ formatMetric(latestLearningRate, true) }}</span>
          </div>
          <VChart class="chart-canvas" :option="lrChartOption" autoresize />
        </section>
      </div>
    </PageCard>

    <!-- 原始日志 -->
    <PageCard class="log-card" title="训练日志" subtitle="查看训练任务的原始运行输出">
      <template #extra><span class="log-meta">{{ logLineCount }} 行 · 每 5 秒刷新</span></template>
      <pre class="log-pre">{{ logContent || '暂无日志' }}</pre>
    </PageCard>
    </template>
  </div>
</template>

<style scoped lang="scss">
.training-log {
  display: grid;
  gap: 16px;
  min-width: 0;
}

.loading-wrap {
  min-height: 320px;
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  background: var(--app-surface-bg);
}

.task-page-header {
  min-width: 0;
  padding: 2px 0 20px;
  border-bottom: 1px solid #e4e7ed;
}

.page-eyebrow {
  display: block;
  color: var(--el-color-primary);
  font-size: 12px;
  line-height: 1.5;
  font-weight: 600;
}

.task-page-title-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 6px;
}

.task-page-title-row h1 {
  min-width: 0;
  margin: 0;
  color: #303133;
  font-size: 24px;
  line-height: 1.35;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.task-page-description {
  max-width: 760px;
  margin: 6px 0 0;
  color: #606266;
  font-size: 13px;
  line-height: 1.6;
}

.task-context-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
}

.task-context-item {
  display: inline-flex;
  align-items: baseline;
  min-width: 0;
  gap: 6px;
  color: #909399;
  font-size: 12px;
  line-height: 1.5;
}

.task-context-item strong {
  overflow: hidden;
  max-width: 260px;
  color: #606266;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.task-context-divider {
  width: 1px;
  height: 12px;
  background: #dcdfe6;
}

.summary-card,
.parameters-card,
.metrics-panel,
.log-card {
  margin-bottom: 0;
  border: 1px solid #e4e7ed !important;
  border-radius: 8px !important;
  box-shadow: none !important;
}

.overview-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  width: 100%;
  gap: 24px;
}

.overview-card-title h2 {
  margin: 0;
  color: #303133;
  font-size: 16px;
  font-weight: 600;
}

.overview-card-title p {
  margin: 5px 0 0;
  color: #909399;
  font-size: 13px;
  line-height: 1.5;
}

.overview-monitor-meta {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 10px 14px;
}

.overview-refresh-state {
  color: #909399;
  font-size: 12px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.training-progress-panel {
  padding: 18px 20px;
  border: 1px solid var(--el-color-primary-light-7);
  border-radius: 6px;
  background: var(--el-color-primary-light-9);
}

.training-progress-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 14px;
}

.training-progress-heading > div > span {
  display: block;
  color: #303133;
  font-size: 14px;
  font-weight: 600;
}

.training-progress-heading > div > small {
  display: block;
  margin-top: 4px;
  color: #909399;
  font-size: 12px;
}

.training-progress-heading > strong {
  flex: 0 0 auto;
  color: var(--el-color-primary);
  font-size: 30px;
  line-height: 1;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.training-progress-heading > strong small {
  margin-left: 2px;
  font-size: 14px;
  font-weight: 500;
}

.training-metric-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 14px;
  overflow: hidden;
  border: 1px solid #ebeef5;
  border-radius: 6px;
  background: #fafafa;
}

.training-metric-cell {
  min-width: 0;
  padding: 14px 18px;
}

.training-metric-cell + .training-metric-cell { border-left: 1px solid #ebeef5; }
.training-metric-cell > span { display: block; color: #606266; font-size: 12px; }
.training-metric-cell > strong {
  display: block;
  margin-top: 6px;
  color: #303133;
  font-size: 20px;
  line-height: 1.25;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.training-metric-cell > small { display: block; margin-top: 4px; color: #909399; font-size: 11px; }

.gpu-monitor {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid #ebeef5;
}

.gpu-monitor-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.gpu-monitor-heading { align-items: flex-start; margin-bottom: 12px; }
.gpu-monitor-heading h3 { margin: 0; color: #303133; font-size: 14px; font-weight: 600; }
.gpu-monitor-heading p { margin: 4px 0 0; color: #909399; font-size: 12px; }
.gpu-device-list {
  overflow: hidden;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  background: #fff;
}

.gpu-list-header,
.gpu-device-row {
  display: grid;
  grid-template-columns: minmax(150px, 1.25fr) 84px minmax(180px, 1.35fr) minmax(140px, 1fr) 72px 76px;
  align-items: center;
  column-gap: 18px;
}

.gpu-list-header {
  min-height: 38px;
  padding: 0 16px;
  color: #909399;
  font-size: 11px;
  font-weight: 500;
  background: #f7f8fa;
}

.gpu-device-row {
  min-width: 0;
  min-height: 70px;
  padding: 10px 16px;
  box-sizing: border-box;
}

.gpu-device-row + .gpu-device-row { border-top: 1px solid #ebeef5; }
.gpu-device-identity { display: flex; flex-direction: column; min-width: 0; gap: 4px; }
.gpu-device-identity strong { color: #303133; font-size: 13px; font-weight: 600; }
.gpu-device-identity span { overflow: hidden; color: #909399; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.gpu-status { display: inline-flex; align-items: center; flex: 0 0 auto; gap: 6px; color: #606266; font-size: 12px; font-weight: 500; }
.gpu-status i { width: 7px; height: 7px; border-radius: 50%; background: #909399; }
.gpu-status.is-busy { color: var(--el-color-primary); }
.gpu-status.is-busy i { background: var(--el-color-primary); }
.gpu-status.is-idle { color: var(--el-color-success); }
.gpu-status.is-idle i { background: var(--el-color-success); }
.gpu-status.is-warning { color: var(--el-color-warning); }
.gpu-status.is-warning i { background: var(--el-color-warning); }
.gpu-status.is-danger { color: var(--el-color-danger); }
.gpu-status.is-danger i { background: var(--el-color-danger); }
.gpu-status.is-unavailable { color: #909399; }
.gpu-utilization-cell { min-width: 0; }
.gpu-cell-value { display: flex; align-items: center; justify-content: flex-end; margin-bottom: 7px; }
.gpu-cell-value strong,
.gpu-memory-cell strong,
.gpu-resource-cell strong {
  color: #303133;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.gpu-memory-cell { min-width: 0; }
.gpu-memory-cell strong { display: block; }
.gpu-memory-cell small { display: block; overflow: hidden; margin-top: 3px; color: #909399; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
.gpu-resource-cell { min-width: 0; }
.gpu-mobile-label { display: none; color: #909399; font-size: 10px; font-weight: 400; }
.gpu-empty-state { padding: 20px; border: 1px dashed #dcdfe6; border-radius: 6px; color: #909399; font-size: 13px; text-align: center; background: #fafafa; }
.gpu-list-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #ebeef5; }
.gpu-list-note { color: #909399; font-size: 12px; line-height: 1.5; }
.gpu-toggle-button { flex: 0 0 auto; min-height: 40px; padding: 0 8px; font-size: 13px; }

.parameters-card.is-collapsed :deep(.page-card-header) { margin-bottom: 0; }
.params-toggle-button { min-height: 36px; padding: 0 8px; font-size: 13px; }
.params-body { padding-top: 2px; }
.parameter-groups { display: flex; flex-direction: column; gap: 20px; }
.parameter-group + .parameter-group { padding-top: 20px; border-top: 1px solid #e4e7ed; }
.parameter-section-heading { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.parameter-section-heading h3 { margin: 0; color: #303133; font-size: 14px; font-weight: 600; }
.parameter-descriptions :deep(.el-descriptions__label) {
  width: 150px;
  color: #606266;
  font-weight: 500;
  background: #f7f8fa !important;
}
.parameter-descriptions :deep(.el-descriptions__content) { color: #303133; font-weight: 500; font-variant-numeric: tabular-nums; }
.parameter-descriptions :deep(.el-descriptions__cell) { padding: 11px 14px !important; }

.refresh-state,
.log-meta { color: #909399; font-size: 12px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.chart-list { border-top: 1px solid #ebeef5; }
.chart-section { padding: 18px 0 10px; }
.chart-section + .chart-section { border-top: 1px solid #ebeef5; }
.chart-section-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.chart-section-title { color: #303133; font-size: 14px; font-weight: 600; }
.chart-latest { color: #909399; font-size: 12px; font-variant-numeric: tabular-nums; }

.chart-canvas {
  width: 100%;
  height: 260px;
}

.log-pre {
  margin: 0;
  padding: 18px 20px;
  background: #111827;
  color: #d1d5db;
  font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
  font-size: 12px;
  line-height: 1.7;
  max-height: 520px;
  overflow: auto;
  white-space: pre-wrap;
  word-wrap: break-word;
  border-radius: 4px;
  scrollbar-color: #4b5563 #111827;
}

@media (max-width: 1100px) {
  .gpu-list-header { display: none; }
  .gpu-device-row {
    grid-template-columns: minmax(0, 1fr) repeat(3, minmax(88px, 0.5fr));
    grid-template-areas:
      "identity status status status"
      "utilization memory temperature power";
    row-gap: 14px;
    min-height: 104px;
    padding-top: 14px;
    padding-bottom: 14px;
  }
  .gpu-device-identity { grid-area: identity; }
  .gpu-status { grid-area: status; justify-self: end; }
  .gpu-utilization-cell { grid-area: utilization; }
  .gpu-memory-cell { grid-area: memory; }
  .gpu-temperature-cell { grid-area: temperature; }
  .gpu-power-cell { grid-area: power; }
  .gpu-cell-value { justify-content: space-between; }
  .gpu-mobile-label { display: block; margin-bottom: 4px; }
}

@media (max-width: 600px) {
  .task-page-header { padding-bottom: 16px; }
  .task-page-title-row { align-items: flex-start; }
  .task-page-title-row h1 { font-size: 21px; }
  .task-context-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 7px; }
  .task-context-divider { display: none; }
  .task-context-item { justify-content: space-between; gap: 12px; }
  .task-context-item strong { max-width: 65%; text-align: right; white-space: normal; }
  .overview-card-header { display: block; }
  .overview-monitor-meta { justify-content: flex-start; margin-top: 12px; }
  .overview-refresh-state { white-space: normal; }
  .training-progress-panel { padding: 16px; }
  .training-progress-heading { align-items: flex-start; gap: 16px; }
  .training-progress-heading > div > small { max-width: 210px; line-height: 1.5; }
  .training-progress-heading > strong { font-size: 26px; }
  .training-metric-grid { grid-template-columns: minmax(0, 1fr); }
  .training-metric-cell + .training-metric-cell { border-top: 1px solid #ebeef5; border-left: 0; }
  .gpu-device-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-areas:
      "identity identity status"
      "utilization utilization utilization"
      "memory temperature power";
    column-gap: 12px;
  }
  .gpu-device-identity span { white-space: normal; }
  .gpu-list-footer { display: block; }
  .gpu-toggle-button { margin-top: 6px; }
  .parameter-descriptions :deep(.el-descriptions__body),
  .parameter-descriptions :deep(.el-descriptions__table),
  .parameter-descriptions :deep(.el-descriptions__tbody),
  .parameter-descriptions :deep(.el-descriptions__row),
  .parameter-descriptions :deep(.el-descriptions__cell) { display: block; width: 100%; box-sizing: border-box; }
  .parameter-descriptions :deep(.el-descriptions__label) { width: 100%; border-bottom: 0 !important; }
  .chart-canvas { height: 220px; }
}
</style>
