import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import { parse as parseTemplate } from '@vue/compiler-dom'
import { parse as parseSfc } from '@vue/compiler-sfc'

const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const viewPath = path.resolve(scriptDir, '../src/views/system/TrainingLogView.vue')
const source = await readFile(viewPath, 'utf8')
const { descriptor } = parseSfc(source, { filename: viewPath })
const template = descriptor.template?.content || ''
const style = descriptor.styles.map((item) => item.content).join('\n')
const templateAst = parseTemplate(template)

function findElements(node, predicate, result = []) {
  if (node?.type === 1 && predicate(node)) result.push(node)
  for (const child of node?.children || []) findElements(child, predicate, result)
  return result
}

function staticAttribute(node, name) {
  const prop = node.props.find((item) => item.type === 6 && item.name === name)
  return prop?.value?.content
}

function boundExpression(node, name) {
  const prop = node.props.find(
    (item) => item.type === 7
      && item.name === 'bind'
      && item.arg?.type === 4
      && item.arg.content === name,
  )
  return prop?.exp?.type === 4 ? prop.exp.content : undefined
}

function extractCssBlock(css, marker) {
  const markerIndex = css.indexOf(marker)
  assert.notEqual(markerIndex, -1, `未找到样式规则：${marker}`)
  const openBrace = css.indexOf('{', markerIndex)
  assert.notEqual(openBrace, -1, `样式规则缺少左花括号：${marker}`)

  let depth = 0
  for (let index = openBrace; index < css.length; index += 1) {
    if (css[index] === '{') depth += 1
    if (css[index] === '}') depth -= 1
    if (depth === 0) return css.slice(openBrace + 1, index)
  }

  assert.fail(`样式规则缺少右花括号：${marker}`)
}

function relativeLuminance(hex) {
  const channels = hex
    .replace('#', '')
    .match(/.{2}/g)
    .map((channel) => Number.parseInt(channel, 16) / 255)
    .map((value) => (value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4))
  return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
}

function contrastRatio(foreground, background) {
  const lighter = Math.max(relativeLuminance(foreground), relativeLuminance(background))
  const darker = Math.min(relativeLuminance(foreground), relativeLuminance(background))
  return (lighter + 0.05) / (darker + 0.05)
}

const overview = findElements(
  templateAst,
  (node) => staticAttribute(node, 'class')?.split(/\s+/).includes('overview-layout'),
)
assert.equal(overview.length, 1, '双栏任务档案容器必须且只能存在一个')

for (const expectedClass of ['task-profile', 'dataset-profile', 'runtime-panel', 'parameter-groups']) {
  const matched = findElements(
    templateAst,
    (node) => staticAttribute(node, 'class')?.split(/\s+/).includes(expectedClass),
  )
  assert.equal(matched.length, 1, `缺少或重复布局结构：${expectedClass}`)
}

const toggleButtons = findElements(
  templateAst,
  (node) => node.tag === 'button'
    && staticAttribute(node, 'class')?.split(/\s+/).includes('params-toggle-button'),
)
assert.equal(toggleButtons.length, 1, '参数折叠必须使用唯一的原生 button')

const toggleButton = toggleButtons[0]
assert.equal(boundExpression(toggleButton, 'aria-expanded'), 'paramsExpanded', '折叠按钮未绑定 aria-expanded')
assert.equal(staticAttribute(toggleButton, 'aria-controls'), 'training-parameter-content', '折叠按钮缺少正确的 aria-controls')

const controlledRegions = findElements(
  templateAst,
  (node) => staticAttribute(node, 'id') === 'training-parameter-content',
)
assert.equal(controlledRegions.length, 1, 'aria-controls 指向的参数内容区域不存在或重复')

const firstChartIndex = template.indexOf('<!-- 训练曲线 -->')
assert.notEqual(firstChartIndex, -1, '未找到训练曲线边界，无法限定首屏检查范围')
assert.equal(
  template.slice(0, firstChartIndex).includes('<el-descriptions'),
  false,
  '任务概览、数据集和训练参数仍使用带表格感的 el-descriptions',
)

assert.ok(template.includes("task?.output_model_name || '暂未生成'"), '输出模型缺失值文案不正确')
assert.ok(template.includes("task?.batch_size ?? '未配置'"), '训练参数缺失值文案不正确')

const media1100 = extractCssBlock(style, '@media (max-width: 1100px)')
const overviewAt1100 = extractCssBlock(media1100, '.overview-layout')
assert.match(overviewAt1100, /grid-template-columns:\s*minmax\(0,\s*1fr\)/, '1100px 断点未将双栏改为单栏')

const media700 = extractCssBlock(style, '@media (max-width: 700px)')
for (const selector of ['.dataset-metrics', '.runtime-list', '.parameter-grid']) {
  assert.ok(media700.includes(selector), `700px 断点缺少单列规则：${selector}`)
}
assert.match(media700, /grid-template-columns:\s*minmax\(0,\s*1fr\)/, '700px 断点未设置单列网格')

const mutedBlock = extractCssBlock(style, '.is-muted')
const mutedColor = mutedBlock.match(/color:\s*(#[0-9a-f]{6})/i)?.[1]
assert.ok(mutedColor, '未配置文本缺少明确颜色')
assert.ok(
  contrastRatio(mutedColor, '#ffffff') >= 4.5,
  `未配置文本颜色 ${mutedColor} 与白色背景对比度不足 4.5:1`,
)

console.log('训练日志详情布局回归检查通过')
