import { readdir, readFile } from 'node:fs/promises'
import { join } from 'node:path'

const sourceRoot = new URL('../src/', import.meta.url)
const forbidden = [
  /from\s+["']three(?:\/|["'])/, /from\s+["']@types\/three/, /from\s+["']sqlite3?/, /from\s+["']@sqlite/, /from\s+["']opfs/, /SharedArrayBuffer/, /\bpostMessage\s*\(/,
]
const allowDirectRuntime = new Set(['facade/threeViewport.ts', 'facade/threeViewport.tsx', 'facade/persistenceWorker.ts', 'facade/projectStore.ts', 'facade/projectMigrationSeedWorker.ts', 'facade/geometryWorker.ts', 'facade/nativeHistoryWorkerClient.ts', 'facade/nativeHistoryWorkerEntry.ts', 'facade/planegcsWorkerClient.ts', 'facade/planegcsWorkerEntry.ts'])
const allowWorkerMessaging = new Set([
  'assemblySolverWorker.ts',
  'meshLodWorker.ts',
  'chromeAssemblyHarness.ts',
  'chromeMeshHarness.ts',
  'chromePropertyAccelerationHarness.tsx',
  'chromePropertyAreaHarness.tsx',
  'chromePropertyBoolListHarness.tsx',
  'chromePropertyColorHarness.tsx',
  'chromePropertyColorListHarness.tsx',
  'chromePropertyDirectionHarness.tsx',
  'chromePropertyFileHarness.tsx',
  'chromePropertyFileIncludedHarness.tsx',
  'chromePropertyFontHarness.tsx',
  'chromePropertyForceHarness.tsx',
  'chromePropertyHeatFluxHarness.tsx',
  'chromePropertyIntegerSetHarness.tsx',
  'facade/camPipeline.ts',
])

async function walk(relative = '') {
  const directory = new URL(relative, sourceRoot)
  const entries = await readdir(directory, { withFileTypes: true })
  const files = []
  for (const entry of entries) {
    const next = join(relative, entry.name)
    if (entry.isDirectory()) files.push(...await walk(next))
    else if (/\.(ts|tsx)$/.test(entry.name)) files.push(next)
  }
  return files
}

const violations = []
const sourceFiles = await walk()
for (const file of sourceFiles) {
  if (allowDirectRuntime.has(file)) continue
  const content = await readFile(new URL(file, sourceRoot), 'utf8')
  for (const pattern of forbidden) {
    if (pattern.source === '\\bpostMessage\\s*\\(' && allowWorkerMessaging.has(file)) continue
    if (pattern.test(content)) violations.push(`${file}: ${pattern}`)
  }
}

const productionFacade = await readFile(new URL('facade/productionFacade.ts', sourceRoot), 'utf8')
const application = await readFile(new URL('App.tsx', sourceRoot), 'utf8')
const runtimeProfile = await readFile(new URL('facade/runtimeProfile.ts', sourceRoot), 'utf8')
if (/from\s+["']\.\/mockFacade["']/.test(productionFacade)) violations.push('facade/productionFacade.ts: production entry imports mockFacade directly')
if (/createMockFacade|facade\/mockFacade/.test(application)) violations.push('App.tsx: production application references the mock Facade')
for (const declaration of ['bitbybit-occt', 'optional-worker', 'freecad-private-v1-optional-worker', 'not-exposed', 'sqlite-opfs-with-memory-fallback', 'three-webgl2']) {
  if (!runtimeProfile.includes(declaration)) violations.push(`facade/runtimeProfile.ts: missing explicit runtime boundary '${declaration}'`)
}

if (violations.length) {
  console.error('Facade-only boundary failed:')
  violations.forEach((violation) => console.error(`- ${violation}`))
  process.exit(1)
}
console.log(`Facade-only boundary passed (${sourceFiles.length} TypeScript source files checked).`)
