// tests/audit-gate-mount-scope.test.ts
//
// `audit:gate-mount-declaration` 的**扫描面**那一半 —— 跑在**真仓库**上（⛔ 不是 fixture）。
//
// 🔴 为什么必须另起一份、不能塞进 `tests/audit-gate-mount-declaration.test.ts`：
//   那份是 fixture-root 回归面，且它的文件头有一条硬纪律 ——
//   **fixture 里不许出现任何真实闸的 `<name>.mjs`**（否则 `report:gate-regression-face`
//   的 E 类判据会把那条闸的「零接线覆盖」伪装成「已覆盖」，是高报）。
//   ⇒ `EXTRA_SCAN_FILES` 里那 3 个真实文件名结构上进不了 fixture
//   ⇒ 闸本体对「条目缺席」只能跳过（并把跳过数印出来），
//     而「这张表有没有 stale」的判定只能落在这里。
//
// 治什么（2026-09-07，队列 C1「元闸扫描面」那一条）：
//   元闸的分母是**按文件名前缀**圈的（`audit-` / `smoke-` / `check-`），
//   而**没有任何东西在守那个约定** —— 与 Q8 同族（「闸在跑，但分母无人守」）。
//   本轮的处置：3 个存量错名闸按名收进 `EXTRA_SCAN_FILES` + 新增 D3 判据守约定。
//
// ⛔ 如实登记的射程（⛔ 别读成「约定已被守住」）：
//   D3 靠 **npm key** 暴露闸身份 ⇒ 抓不到「既无 `audit:`/`smoke:`/`check:` key、
//   文件名也不合约定」的闸。实例：`scripts/a11y-contrast-inspector.mjs`
//   —— 2026-09-07 亲验它**没有任何闸类 npm key** ⇒ D3 看不见它。
import { describe, it, expect } from 'vitest'
import { readFileSync, existsSync } from 'node:fs'
import { resolve } from 'node:path'
// 判据从共用 lib 取（⛔ 不 import 那条闸本体 —— 它顶层无入口守卫，一 import 就 process.exit；
// 完整理由在 scripts/lib/gate-scan-scope.mjs 的头注释里）
import { findGatesEscapingScanScope } from '../scripts/lib/gate-scan-scope.mjs'

const ROOT = process.cwd()
const GATE_SRC = resolve(ROOT, 'scripts/audit-gate-mount-declaration.mjs')
const PKG = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8'))
const KEY_PREFIXES = ['audit:', 'smoke:', 'check:']

/**
 * 从闸源码里解析 `EXTRA_SCAN_FILES` 的**活源**（⛔ 不在测试里抄第二份 —— 抄一份就会漂，
 * 同 `tests/audit-gate-mount-declaration.test.ts` 对 `KNOWN_SILENT` 的处理）。
 * 解析不到就抛 = fail-closed。
 */
function extraScanFiles(): string[] {
  const src = readFileSync(GATE_SRC, 'utf8')
  const m = src.match(/const EXTRA_SCAN_FILES = \[([\s\S]*?)\]/)
  if (!m) throw new Error('解析不到 EXTRA_SCAN_FILES —— 闸的形态变了，本测试的判据面已失效')
  const list = [...m[1].matchAll(/'([^']+)'/g)].map((x) => x[1])
  if (!list.length) throw new Error('EXTRA_SCAN_FILES 解析出 0 条 —— 要么真空了，要么正则坏了')
  return list
}

describe('元闸扫描面 —— EXTRA_SCAN_FILES 不许 stale（真仓库）', () => {
  const extras = extraScanFiles()

  it('每个条目在仓库里都存在（改名 / 删除 ⇒ 这里红，而不是闸静默跳过）', () => {
    const missing = extras.filter((f) => !existsSync(resolve(ROOT, f)))
    expect(missing).toEqual([])
  })

  it('每个条目都确实带闸类 npm key —— ⛔ 表里不许混进非闸文件', () => {
    const notGate = extras.filter((f) => {
      return !Object.entries(PKG.scripts).some(
        ([k, v]) => KEY_PREFIXES.some((p) => k.startsWith(p)) && String(v).includes(f),
      )
    })
    expect(notGate).toEqual([])
  })

  it('表不许静默增长 —— 当前恰好 3 条，且逐条是已知的存量错名闸', () => {
    // ⚠️ 这不是「以后不许加」，是「加的时候必须同时改这条断言」= 一次显式的评审动作。
    expect(extras.sort()).toEqual([
      'scripts/a11y-token-contrast.mjs',
      'scripts/f129-rootradius-silent-gap-probe.mjs',
      'scripts/validate-upstream-gate.mjs',
    ])
  })
})

describe('D3 findGatesEscapingScanScope —— 两侧', () => {
  it('真仓库现状：零逃逸（3 个存量已按名收进扫描面）', () => {
    // 复现闸自己算扫描面的方式：前缀闭集 ∪ EXTRA_SCAN_FILES
    const scanned = currentScanSurface()
    expect(findGatesEscapingScanScope(PKG.scripts, scanned)).toEqual([])
  })

  it('造故障：给一个不合约定的文件挂上 audit: key ⇒ 必须点名它', () => {
    const scanned = currentScanSurface()
    const escaping = findGatesEscapingScanScope(
      { ...PKG.scripts, 'audit:fx-probe': 'node scripts/new-backlog.mjs' },
      scanned,
    )
    expect(escaping).toEqual(['scripts/new-backlog.mjs'])
  })

  it('阴性对照：非闸前缀的 key（report: / gen:）⛔ 不触发', () => {
    const scanned = currentScanSurface()
    const escaping = findGatesEscapingScanScope(
      { ...PKG.scripts, 'report:fx-probe': 'node scripts/new-backlog.mjs' },
      scanned,
    )
    expect(escaping).toEqual([])
  })

  it('阴性对照：已在扫描面内的文件 ⛔ 不触发', () => {
    const scanned = currentScanSurface()
    const inScope = scanned.find((f) => f.startsWith('scripts/audit-'))
    expect(inScope).toBeTruthy()
    const escaping = findGatesEscapingScanScope(
      { ...PKG.scripts, 'audit:fx-inscope': `node ${inScope}` },
      scanned,
    )
    expect(escaping).toEqual([])
  })
})

function currentScanSurface(): string[] {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const { readdirSync } = require('node:fs') as typeof import('node:fs')
  const PREFIXES = ['audit-', 'smoke-', 'check-']
  const out: string[] = []
  for (const dir of ['scripts', 'figma-sync']) {
    for (const f of readdirSync(resolve(ROOT, dir))) {
      if (f.endsWith('.mjs') && PREFIXES.some((p) => f.startsWith(p))) out.push(`${dir}/${f}`)
    }
  }
  return out.concat(extraScanFiles())
}
