import { describe, it, expect } from 'vitest'
import {
  checkConsumerContract,
  checkPackagedTargets,
  collectDeclaredTargets,
  isCoveredByFiles,
} from '../scripts/audit-consumer-contract.mjs'

const ctx = {
  exportsBySubpath: {
    '.': new Set(['Button', 'InputBoxLine']),
    './chart': new Set(['Chart']),
  },
  subpaths: new Set(['.', './chart', './style.css']),
  forbiddenPhrases: ['no TS/JSON token export'],
}

describe('consumer contract', () => {
  it('flags an import of a name not exported from root', () => {
    const doc = "```ts\nimport { Button, Input } from '@ux-team/tvu-design-system'\n```"
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/Input/)
  })
  it('flags a bad named symbol in a default+named mixed import', () => {
    const doc = "```ts\nimport TVUDesignSystem, { Button, Input } from '@ux-team/tvu-design-system'\n```"
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/Input/)
  })
  it('flags an import from an undeclared subpath', () => {
    const doc = "```ts\nimport { X } from '@ux-team/tvu-design-system/nope'\n```"
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/nope/)
  })
  it('flags a forbidden stale capability phrase', () => {
    const doc = 'Text: there is no TS/JSON token export yet.'
    expect(checkConsumerContract(doc, ctx).violations.join(' ')).toMatch(/no TS\/JSON token export/)
  })
  it('passes a clean doc', () => {
    const doc = "```ts\nimport { Button, InputBoxLine } from '@ux-team/tvu-design-system'\n```"
    expect(checkConsumerContract(doc, ctx).violations).toHaveLength(0)
  })
  it('flags Chart imported from the root entry (Chart only lives at ./chart)', () => {
    const doc = "```ts\nimport { Chart } from '@ux-team/tvu-design-system'\n```"
    const result = checkConsumerContract(doc, ctx)
    expect(result.violations.join(' ')).toMatch(/Chart/)
    expect(result.violations.join(' ')).toMatch(/src\/index\.ts exports/)
  })
  it('passes Chart imported from the ./chart subpath', () => {
    const doc = "```ts\nimport { Chart } from '@ux-team/tvu-design-system/chart'\n```"
    expect(checkConsumerContract(doc, ctx).violations).toHaveLength(0)
  })
})

// S2 — declared entry points must actually ship. Narrow denominator on purpose:
// hand-built pkg fixtures + an injected probe, never the real package.json (which
// gains exports over time and would make these assertions fail for unrelated reasons
// — the窄分母 lesson from tests/audit-page-recipes.test.ts).
describe('packaged targets (S2)', () => {
  const pkgWithWc = {
    main: './dist/tvu-design-system.umd.cjs',
    files: ['dist', 'dist-wc'],
    exports: {
      '.': { import: './dist/tvu-design-system.js' },
      './web-components': { import: './dist-wc/tvu-web-components.js' },
    },
  }

  it('passes when every declared target is covered by files[]', () => {
    expect(checkPackagedTargets(pkgWithWc, () => 'present').violations).toHaveLength(0)
  })

  it('flags a target whose directory is not in files[] (the INFRA-F69③ shape)', () => {
    const pkg = { ...pkgWithWc, files: ['dist'] }
    const { violations } = checkPackagedTargets(pkg, () => 'present')
    expect(violations).toHaveLength(1)
    expect(violations[0]).toMatch(/web-components/)
    expect(violations[0]).toMatch(/MISSING from the published tarball/)
  })

  it('flags main/module/types too, not just exports', () => {
    const pkg = { ...pkgWithWc, types: './dist-wc/index.d.ts', files: ['dist'] }
    const { violations } = checkPackagedTargets(pkg, () => 'present')
    expect(violations.some((v) => v.startsWith('types ->'))).toBe(true)
  })

  it('flags a covered target that does not exist on a built tree', () => {
    const pkg = { ...pkgWithWc, types: './dist-wc/index.d.ts' }
    const probe = (rel: string) => (rel === 'dist-wc/index.d.ts' ? 'absent' : 'present')
    const { violations } = checkPackagedTargets(pkg, probe)
    expect(violations).toHaveLength(1)
    expect(violations[0]).toMatch(/points at nothing/)
  })

  it('does NOT flag a missing file on an unbuilt tree (probe: unknown)', () => {
    const pkg = { ...pkgWithWc, types: './dist-wc/index.d.ts' }
    expect(checkPackagedTargets(pkg, () => 'unknown').violations).toHaveLength(0)
  })

  it('never existence-checks a wildcard target, but still coverage-checks it', () => {
    const withGlob = {
      files: ['dist'],
      exports: { './icons/svg/*': './dist/icons/svg/*', './nope/*': './not-shipped/*' },
    }
    const { violations } = checkPackagedTargets(withGlob, () => 'absent')
    expect(violations).toHaveLength(1)
    expect(violations[0]).toMatch(/not-shipped/)
  })

  it('treats a files[] directory as covering everything beneath it', () => {
    expect(isCoveredByFiles('dist/icons/esm/index.js', ['dist'])).toBe(true)
    expect(isCoveredByFiles('dist-wc/tvu-web-components.js', ['dist'])).toBe(false)
    // A files[] entry that is a file, not a directory, must match exactly.
    expect(isCoveredByFiles('docs/API_STABILITY.md', ['docs/API_STABILITY.md'])).toBe(true)
    expect(isCoveredByFiles('docs/RELEASING.md', ['docs/API_STABILITY.md'])).toBe(false)
    // 'dist' must not be read as a prefix of 'dist-wc'.
    expect(isCoveredByFiles('dist-wc/x.js', ['dist', 'llms.txt'])).toBe(false)
  })

  it('collects nested export conditions and skips non-path values', () => {
    const targets = collectDeclaredTargets({
      exports: { '.': { node: { import: './dist/a.js' } }, './x': 'some-bare-specifier' },
    })
    expect(targets).toEqual([
      { field: 'exports["."][node][import]', target: './dist/a.js' },
      { field: 'exports["./x"]', target: 'some-bare-specifier' },
    ])
    // The bare specifier is counted as skipped, not judged as a path.
    const { checked, skipped } = checkPackagedTargets(
      { exports: { './x': 'some-bare-specifier' }, files: [] },
      () => 'present'
    )
    expect({ checked, skipped }).toEqual({ checked: 0, skipped: 1 })
  })
})
