// tests/audit-product-code-r14-r19-r9.test.ts
//
// gate 覆盖率第三档 · consumer 侧（INFRA-F68 §L7 follow-up, 2026-07-20）：
// 把 code-conventions.md 的 3 条 Class-A R-rule 机械化进 audit-product-code.mjs：
//   R14 — inline-style position:absolute/fixed 无相邻注释（M31 code mirror；acceptance §916）
//   R19 — selected 指示反模式：box-shadow outline / rail 写死 px（M38 code mirror）
//   R9  — 交互态补全 hover/focus/disabled（M?; 高 FP → report-only advisory）
//
// 纯函数 findR14Violations / findR19Violations / findR9Violations，无 fs 副作用，
// 便于 planted-drift 单测（必须命中 + 不可误报，镜像 audit-deliverable-open-questions 范式）。
import { describe, it, expect } from 'vitest'
import {
  findR14Violations,
  findR19Violations,
  findR9Violations,
} from '../scripts/audit-product-code.mjs'

// ---------------------------------------------------------------------------
// R14 — inline-style position:absolute/fixed 无相邻注释
// ---------------------------------------------------------------------------
describe('R14 findR14Violations — 必须命中', () => {
  it('Vue inline style="position: absolute" 无注释', () => {
    const v = findR14Violations('<div style="position: absolute; top: 0"></div>')
    expect(v).toHaveLength(1)
    expect(v[0].line).toBe(1)
  })
  it('JSX style={{ position: "fixed" }} 无注释', () => {
    const v = findR14Violations('<div style={{ position: "fixed", top: 0 }} />')
    expect(v).toHaveLength(1)
  })
  it('Vue :style 对象绑定 position absolute 无注释', () => {
    const v = findR14Violations("<span :style=\"{ position: 'absolute' }\"></span>")
    expect(v).toHaveLength(1)
  })
})

describe('R14 findR14Violations — 不可误报', () => {
  it('同行相邻注释豁免', () => {
    expect(findR14Violations('<div style="position:absolute"></div> <!-- portal mount point -->')).toHaveLength(0)
  })
  it('前一行注释豁免', () => {
    const md = '<!-- overlay anchored to backdrop -->\n<div style="position:absolute"></div>'
    expect(findR14Violations(md)).toHaveLength(0)
  })
  it('AUDIT-IGNORE-R14 行标记豁免', () => {
    expect(findR14Violations('<div style="position:fixed"></div> // AUDIT-IGNORE-R14: sticky header')).toHaveLength(0)
  })
  it('LIBRARY GAP 相邻注释豁免', () => {
    expect(findR14Violations('<!-- LIBRARY GAP: no DS overlay -->\n<div style="position:absolute"></div>')).toHaveLength(0)
  })
  it('<style> block 内 position:absolute 不算 inline → 不误报', () => {
    const md = '<style>\n.tooltip { position: absolute; top: 0; }\n</style>'
    expect(findR14Violations(md)).toHaveLength(0)
  })
  it('inline style 无 position → 不误报', () => {
    expect(findR14Violations('<div style="color: red; display: flex"></div>')).toHaveLength(0)
  })
  it('position: relative/sticky inline → 不误报（只管 absolute/fixed）', () => {
    expect(findR14Violations('<div style="position: relative"></div>')).toHaveLength(0)
  })
})

// ---------------------------------------------------------------------------
// R19 — selected 指示反模式
// ---------------------------------------------------------------------------
describe('R19 findR19Violations — 必须命中', () => {
  it('selected 块内 box-shadow outline 用 brand', () => {
    const md = '.list-item--selected {\n  box-shadow: 0 0 0 2px var(--brand);\n}'
    const v = findR19Violations(md)
    expect(v.length).toBeGreaterThanOrEqual(1)
    expect(v.some((x) => x.kind === 'box-shadow-outline')).toBe(true)
  })
  it('selected 块内 rail 写死 px（应 var(--sp-*)）', () => {
    const md = '.tab.is-selected {\n  border-left: 3px solid var(--brand);\n}'
    const v = findR19Violations(md)
    expect(v.some((x) => x.kind === 'rail-hardcoded-px')).toBe(true)
  })
  it('aria-selected 属性选择器块也命中', () => {
    const md = '[aria-selected="true"] {\n  border-left: 5px solid var(--brand);\n}'
    expect(findR19Violations(md).some((x) => x.kind === 'rail-hardcoded-px')).toBe(true)
  })
})

describe('R19 findR19Violations — 不可误报', () => {
  it('正确 spec：rail 用 var(--sp-xxs) → 不误报', () => {
    const md = '.list-item--selected {\n  border-left: var(--sp-xxs) solid var(--brand);\n  background: var(--layer-3);\n}'
    expect(findR19Violations(md)).toHaveLength(0)
  })
  it(':focus 环 box-shadow brand（非 selected 上下文）→ 不误报', () => {
    const md = '.input:focus {\n  box-shadow: 0 0 0 2px var(--brand);\n}'
    expect(findR19Violations(md)).toHaveLength(0)
  })
  it('非 selected 块 border px brand → 不误报（作用域限 selected 指示）', () => {
    const md = '.divider {\n  border-left: 3px solid var(--brand);\n}'
    expect(findR19Violations(md)).toHaveLength(0)
  })
  it('AUDIT-IGNORE-R19 行标记豁免', () => {
    const md = '.row--selected {\n  box-shadow: 0 0 0 2px var(--brand); // AUDIT-IGNORE-R19: intentional\n}'
    expect(findR19Violations(md)).toHaveLength(0)
  })
})

// ---------------------------------------------------------------------------
// R9 — 交互态补全（advisory / report-only）
// ---------------------------------------------------------------------------
describe('R9 findR9Violations — 必须命中', () => {
  it('interactive class 无 :hover 规则', () => {
    const md = '<style>\n.menu-item { color: #333; }\n.menu-item.active { color: red; }\n</style>'
    const v = findR9Violations(md, 'Nav.vue')
    expect(v.some((x) => x.cls === 'menu-item')).toBe(true)
  })
  it('.btn 无 :hover', () => {
    const md = '<style>\n.btn { padding: 8px; }\n</style>'
    expect(findR9Violations(md, 'x.vue').some((x) => x.cls === 'btn')).toBe(true)
  })
})

describe('R9 findR9Violations — 不可误报', () => {
  it('有 :hover 规则 → 不报', () => {
    const md = '<style>\n.menu-item { color: #333; }\n.menu-item:hover { color: blue; }\n</style>'
    expect(findR9Violations(md, 'x.vue')).toHaveLength(0)
  })
  it('Tailwind hover: 工具类 → 整文件豁免', () => {
    const md = '<template>\n<button class="px-2 hover:bg-gray-100">Go</button>\n</template>\n<style>\n.btn { padding: 8px; }\n</style>'
    expect(findR9Violations(md, 'x.vue')).toHaveLength(0)
  })
  it('无 <style> 块的纯工具类组件 → 豁免', () => {
    const md = '<template>\n<button class="btn">Go</button>\n</template>'
    expect(findR9Violations(md, 'x.vue')).toHaveLength(0)
  })
  it('非交互白名单 class（.card-header / .button-group）→ 不报', () => {
    const md = '<style>\n.card-header { color: #333; }\n.button-group { display: flex; }\n</style>'
    expect(findR9Violations(md, 'x.vue')).toHaveLength(0)
  })
  it('AUDIT-IGNORE-R9 文件级豁免', () => {
    const md = '<style>\n/* AUDIT-IGNORE-R9: static demo */\n.menu-item { color: #333; }\n</style>'
    expect(findR9Violations(md, 'x.vue')).toHaveLength(0)
  })
})
