#!/usr/bin/env node
// INFRA-F129 ② 的判据，机械化版本。只读，不是闸（always exit 0 除外文件读不到）。
//
// ── 它替掉了什么 ──────────────────────────────────────────────────────────────
// entry 原先内联的那条 `node -e` 只数「expected rootFillHex 为 null 而 code actual
// 非 null」的 entry，并写着「输出为 0 即闭合」。**那个 0 结构上到不了**：判据不减掉
// 已经分族并写进豁免表的行，而那些行是合法的（extract 侧缺口，`fixDirection:
// extract-pipeline`）⇒ 只要它们还在，候选数就恒 > 0。2026-08-18 实测：候选 28 /
// 已豁免 28 / **未判 0**，而 entry 当时写的是「28 条待逐个 get_metadata 分族」。
// 「不可满足的闸 = 没有闸」，所以判据改成量**真正没人判过的那部分**。
//
// ⚠️ 豁免判定刻意走 `tests/visual-verify/lib/render-excused-fields.ts` 的 `isExcused`
// 本体，不在这里重写一遍 glob —— 重写就会与真 matcher 分叉，而分叉的方向是假阴
// （把已豁免的算成未判，或反过来）。matcher 用 `entry.figmaName` 对 `row.component`
// 精确比，这点容易踩：manifest 的 `codeComponent` 是 `Button`，而豁免行写的是
// `Button/url link` —— 拿 codeComponent 去比会得到「未豁免」的假结论。
//
// 用法：`node scripts/f129-rootfill-candidate-triage.ts`（需 Node ≥22 的原生类型剥离，
// 与本仓另外三个 audit 脚本同前提）。
//
// 输出三个数 + 逐条列出未判项。**UNJUDGED 非空**才是 F129 ② 还有勘察工作的信号；
// 为 0 只说明「今天没有未分族的候选」，⛔ 不等于 extract 那个缺口修好了。
// ⚠️ 2026-08-20：本体修法（把满幅子节点合成进 root 视觉）**已落地** ——
// `scripts/lib/full-bleed-cover.mjs`（判据与诚实边界的唯一真源在它的头注释），
// 候选由 28 降到 15。降的那 13 条全是 `switch`；剩下的 15 条**不是**同一个机制
// （Tooltips 是整块 autolayout 丢失、Button/url link 是 code-first divergence、
// switch 还剩 1 条拍平成 VECTOR 的），⛔ 别指望这个数继续靠本次修法往下掉。

import { readFileSync } from 'node:fs'
import { isExcused, validateExcuseRows } from '../tests/visual-verify/lib/render-excused-fields.ts'

const manifest = JSON.parse(readFileSync('figma-data/render-verification-manifest.json', 'utf8'))
const report = JSON.parse(
  readFileSync('figma-data/normalized/render-verification.report.json', 'utf8'),
)
const raw = JSON.parse(readFileSync('figma-data/audit-allowlist/render-excused-fields.json', 'utf8'))

const CHECK_FIELDS = [
  ...new Set(report.entries.flatMap((e: any) => e.checks.map((c: any) => c.field))),
] as readonly string[]
const rows = validateExcuseRows(raw, CHECK_FIELDS)

const byId = new Map<string, any>(report.entries.map((e: any) => [e.manifestId, e]))

// 候选 = Figma 侧 expected 为 null，而 code 真的画了颜色（透明不算）。
const candidates = manifest
  .filter((e: any) => e.expectedFromFigma?.rootFillHex === null)
  .filter((e: any) => {
    const c = (byId.get(e.manifestId)?.checks || []).find((c: any) => c.field === 'rootFillHex')
    return c && c.actual && c.actual !== 'rgba(0, 0, 0, 0)'
  })

const unjudged: string[] = []
let excused = 0
for (const e of candidates) {
  const entry = byId.get(e.manifestId)
  if (!entry) continue
  if (isExcused(rows, entry.figmaName, entry.manifestId, 'rootFillHex')) excused++
  else unjudged.push(`${entry.figmaName} | node ${e.figmaNodeId ?? e.nodeId} | ${entry.manifestId}`)
}

// 阴性对照：同一批 entry 换一个**确实没有任何豁免行**的字段，必须全部判为「未豁免」。
// ⚠️ 对照字段是从数据里**推**出来的，不是我挑的 —— 第一版硬写 `rootWidth`，实测得 6
// （`rootWidth` 本来就有豁免行）⇒ 对照物错配，那个「必须是 0」的断言当场就是错的。
// 教训：阴性对照的前提（「这个字段没人豁免过」）本身也要现算，别凭印象填。
const excusedFields = new Set(rows.map((r) => r.field))
const controlField = CHECK_FIELDS.find((f) => !excusedFields.has(f as string)) as string | undefined
const control = controlField
  ? candidates
      .map((e: any) => byId.get(e.manifestId))
      .filter(Boolean)
      .filter((entry: any) => isExcused(rows, entry.figmaName, entry.manifestId, controlField)).length
  : -1

console.log(`[f129-triage] candidates=${candidates.length}  excused=${excused}  UNJUDGED=${unjudged.length}`)
for (const u of unjudged) console.log(`  UNJUDGED: ${u}`)
console.log(
  `[f129-triage] 阴性对照（同批 entry × 字段 "${controlField ?? '<无零豁免字段可用>'}"，` +
    `该字段在豁免表里 0 行）: ${control} —— 必须是 0，否则 matcher 恒真、上面的 excused 无意义。` +
    `${control === 0 ? '' : ' ⛔ 现在不是 0，excused 那个数不可采信。'}`,
)
console.log(
  `[f129-triage] 已分族（2026-08-20 更新）：` +
    `switch = ✅ **本体已修** —— 满幅 RECTANGLE 已由 scripts/lib/full-bleed-cover.mjs 合成进 ` +
    `rootFillHex（两链各 13 个 check 由红转绿，候选 28 → 15）；**只剩 1 条残余** = ` +
    `337:18086 那块被拍平成 VECTOR 的矩形（bbox 判据对 VECTOR 不成立，fail closed，` +
    `修法方向写在豁免表那行）· ` +
    `Tooltips = root autolayout 整块被 extract 丢掉（padding/gap/fill 一起没了，另一族，未修）· ` +
    `Button/url link status=hover = code-first divergence（Figma 那两个变体是纯文本 url 链接，无帧填充）`,
)
