// tests/audit-doc-sync.test.ts
// -----------------------------------------------------------------------------
// `audit:doc-sync`（L4 pre-commit + L5 gh-ci + L5 gate-chain 三挂）的**整脚本**回归面。
//
// 为什么是整脚本而不是 import 判据函数：该闸 138 行**零导出**、判据写在顶层，
// 结构上没有可 import 的符号 —— 量具 `pnpm report:gate-regression-face` 把它记为
// 「零判据覆盖 · P1/P2 双 PASS ⇒ 零改动 fixture-root 直接可行」的**样板**条目。
// 本文件是那条判定的兑现（[[INFRA-F138]]）。⛔ 闸本体一行没改。
//
// 覆盖：4 类判据各一条致败用例 + 绿档非空过 + 一条 must-not-hit 阴性对照 + 接线钉。
// -----------------------------------------------------------------------------
import { describe, it, expect, afterEach } from 'vitest'
import {
  createGateFixture,
  runGate,
  expectGateRed,
  expectGateGreen,
  cleanupGateFixtures,
} from './lib/gate-fixture-root'

const GATE = 'scripts/audit-doc-sync.mjs'
const MAP = 'docs/internal/doc-sync-map.json'

afterEach(cleanupGateFixtures)

type Overrides = {
  /** 覆盖整张 map */
  map?: unknown
  /** 额外/覆盖的 fixture 文件 */
  files?: Record<string, string>
  /** manifest 数组长度（json-array-length 的真值），默认 3 */
  manifestLen?: number
  /** 引用文档里写的 manifest 数字，默认与 manifestLen 一致 */
  refCount?: number
  /** L-core 份数真值，默认 4 */
  lCoreTruth?: number
  /** 引用文档里写的 L-core 份数，默认与 lCoreTruth 一致 */
  lCoreWritten?: string
}

function build(o: Overrides = {}) {
  const manifestLen = o.manifestLen ?? 3
  const refCount = o.refCount ?? manifestLen
  const lCoreTruth = o.lCoreTruth ?? 4
  const lCoreWritten = o.lCoreWritten ?? String(lCoreTruth)

  const map = o.map ?? {
    edges: [{ whenYouEdit: 'src/x.vue', recheck: ['docs/A.md'], why: 'x' }],
    crossFileFacts: [
      {
        id: 'manifestEntryCount',
        authority: { type: 'json-array-length', path: 'figma-data/m.json' },
        referencedIn: ['docs/A.md'],
        pattern: '(\\d{3,5})\\s*(?:entries|条)',
      },
      {
        id: 'lCoreCount',
        authority: { type: 'literal-count', value: lCoreTruth },
        referencedIn: ['docs/B.md'],
        pattern: 'L-?core[^\\n]{0,10}?([0-9〇零一二三四五六七八九十两]+)\\s*份',
      },
    ],
  }

  return createGateFixture({
    gate: GATE,
    prefix: 'doc-sync-fx',
    dirs: ['docs/internal', 'figma-data'],
    files: {
      [MAP]: JSON.stringify(map, null, 2),
      'figma-data/m.json': JSON.stringify(Array.from({ length: manifestLen }, (_, i) => ({ i }))),
      // 邻近守卫要求同一行出现 manifest 语境词，否则数字不被核
      'docs/A.md': `# A\n\nrender-verification manifest 共 ${refCount} 条。\n`,
      'docs/B.md': `# B\n\nL-core ${lCoreWritten} 份必读。\n`,
      ...(o.files ?? {}),
    },
  })
}

describe('audit:doc-sync — 整脚本 fixture 回归（绿档 + 自印非空过）', () => {
  it('map 自洽 → exit 0，且自印的是 fixture 自己的读数（≠ 真仓库 ⇒ 非空过）', () => {
    const run = runGate(build(), GATE)
    // fixture 的路径面 = docs/A.md + figma-data/m.json + docs/B.md（去重后 3）· 事实 2 条。
    // 真仓库那两个数不是这一对，跑错了树不可能同时对上 —— 这是本文件的非空过凭据。
    expectGateGreen(run, { contains: ['✓ doc-sync PASS', '校验 3 路径 · 2 跨文件事实'] })
  })
})

describe('audit:doc-sync — 判据 (1) 路径完整性', () => {
  it('recheck 指向不存在的文件 → doc-sync-dead-path', () => {
    const run = runGate(
      build({
        map: {
          edges: [{ whenYouEdit: 'src/x.vue', recheck: ['docs/GONE.md'], why: 'x' }],
          crossFileFacts: [],
        },
      }),
      GATE,
    )
    expectGateRed(run, {
      marker: '❌ doc-sync 跨文件一致性失败',
      checks: ['[doc-sync-dead-path]', 'docs/GONE.md'],
    })
  })

  it('同一张 map 只把 GONE.md 换成真实存在的文件 → 绿（致败源就是那一条，不是 map 形态）', () => {
    const run = runGate(
      build({
        map: {
          edges: [{ whenYouEdit: 'src/x.vue', recheck: ['docs/A.md'], why: 'x' }],
          crossFileFacts: [],
        },
      }),
      GATE,
    )
    expectGateGreen(run, { contains: ['校验 1 路径 · 0 跨文件事实'] })
  })

  it('⛔ must-not-hit：recheck 里的 prose 型条目（无扩展名）不当路径校验', () => {
    const run = runGate(
      build({
        map: {
          edges: [{ whenYouEdit: 'src/x.vue', recheck: ['随手写的一句说明，不是路径'], why: 'x' }],
          crossFileFacts: [],
        },
      }),
      GATE,
    )
    expectGateGreen(run, { contains: ['校验 0 路径'] })
  })
})

describe('audit:doc-sync — 判据 (2) json-array-length 跨文件事实', () => {
  it('引用文档写 999 条但真值 3 → doc-sync-fact-mismatch 且点名真值', () => {
    const run = runGate(build({ manifestLen: 3, refCount: 999 }), GATE)
    expectGateRed(run, {
      marker: '❌ doc-sync 跨文件一致性失败',
      checks: ['[doc-sync-fact-mismatch]', 'manifestEntryCount', '真值 = 3'],
    })
  })

  it('authority JSON 不是数组 → doc-sync-fact-authority（fail-closed，不静默跳过）', () => {
    const run = runGate(build({ files: { 'figma-data/m.json': '{"notAnArray":true}' } }), GATE)
    expectGateRed(run, {
      marker: '❌ doc-sync 跨文件一致性失败',
      checks: ['[doc-sync-fact-authority]', '不是数组'],
    })
  })

  it('⛔ must-not-hit：同一份文档里不谈 manifest 的行上的 "N 条" 不被核（邻近守卫）', () => {
    const run = runGate(
      build({ files: { 'docs/A.md': '# A\n\nmanifest 共 3 条。\n\n另有 888 条无关记录。\n' } }),
      GATE,
    )
    expectGateGreen(run)
  })
})

describe('audit:doc-sync — 判据 (3) literal-count 事实', () => {
  it('写 5 份但真值 4 → doc-sync-literal-mismatch', () => {
    const run = runGate(build({ lCoreTruth: 4, lCoreWritten: '5' }), GATE)
    expectGateRed(run, {
      marker: '❌ doc-sync 跨文件一致性失败',
      checks: ['[doc-sync-literal-mismatch]', 'lCoreCount', '真值 = 4'],
    })
  })

  it('中文数字「四份」与真值 4 一致 → 绿（parseCount 归一）', () => {
    const run = runGate(build({ lCoreTruth: 4, lCoreWritten: '四' }), GATE)
    expectGateGreen(run)
  })

  it('中文数字「五份」与真值 4 不一致 → 红（中文侧也真的在判，不是只认阿拉伯数字）', () => {
    const run = runGate(build({ lCoreTruth: 4, lCoreWritten: '五' }), GATE)
    expectGateRed(run, { checks: ['[doc-sync-literal-mismatch]', '解析为 5'] })
  })

  it('pattern 在所有 referencedIn 里零命中 → doc-sync-literal-nomatch（反 stale-map 守卫）', () => {
    const run = runGate(build({ files: { 'docs/B.md': '# B\n\n这里不再提那个词。\n' } }), GATE)
    expectGateRed(run, {
      marker: '❌ doc-sync 跨文件一致性失败',
      checks: ['[doc-sync-literal-nomatch]', 'lCoreCount'],
    })
  })
})

describe('audit:doc-sync — 接线钉（判据 → 退出码 → 点名输出）', () => {
  it('map 文件缺失 → exit 1 且报缺失，⛔ 不是静默 exit 0', () => {
    const root = createGateFixture({ gate: GATE, prefix: 'doc-sync-fx-nomap', dirs: ['docs/internal'] })
    const run = runGate(root, GATE)
    expectGateRed(run, { marker: 'doc-sync: docs/internal/doc-sync-map.json 不存在' })
  })

  it('多条判据同时违例 → findings 全部逐条印出且计数正确（接线不吞 findings）', () => {
    const run = runGate(build({ refCount: 999, lCoreWritten: '5' }), GATE)
    expect(run.status).toBe(1)
    expect(run.stderr).toContain('失败: 2 处')
    expect(run.stderr).toContain('[doc-sync-fact-mismatch]')
    expect(run.stderr).toContain('[doc-sync-literal-mismatch]')
  })
})
