import { readFile } from 'node:fs/promises'
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
import { resolve } from 'node:path'
import {
  REQUIRED_FREECAD_NAMING_CALLBACKS,
  REQUIRED_FREECAD_NAMING_DEFINITIONS,
  REQUIRED_FREECAD_NAMING_LIBRARIES,
  exists,
  inspectIncludeDirectory,
  inspectPlannedLibrary,
  inspectPlannedRuntimeAsset,
  loadSdkPlan,
  resolveFrom,
  sha256File,
} from './freecad-naming-sdk-lib.mjs'

const root = resolve(new URL('..', import.meta.url).pathname)
const execFileAsync = promisify(execFile)
const plan = await loadSdkPlan(root)
const fail = (message) => { throw new Error(`FreeCAD WASM naming SDK readiness: ${message}`) }
if (plan.schemaVersion !== 1 || plan.scope !== 'pre-production-sdk-readiness') fail('unsupported plan schema or scope.')
if (plan.baseline?.freecadVersion !== '1.1.1' || plan.baseline?.sourceCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || plan.baseline?.emscriptenVersion !== '3.1.69' || plan.baseline?.target !== 'wasm32-emscripten') fail('baseline is not locked to the required FreeCAD/Emscripten wasm target.')
if (plan.productionPublication !== false) fail('candidate SDK readiness cannot publish a production Worker.')
if (plan.boundary?.freecadNamingBuildStatus !== 'production-linked' || plan.boundary?.exTsn02 !== 'completed' || plan.boundary?.systemExact !== false) fail('plan boundary must identify the completed production linkage while retaining non-exact system status.')
if (JSON.stringify(plan.libraries?.map(({ name }) => name)) !== JSON.stringify(REQUIRED_FREECAD_NAMING_LIBRARIES)) fail('library list or order does not match the production SDK contract.')
if (!REQUIRED_FREECAD_NAMING_CALLBACKS.every((callback) => plan.namingBridge?.requiredExports?.includes(callback))) fail('naming bridge plan does not require all production callbacks.')
if (plan.compileOptions?.cxxStandard !== 'c++20' || plan.compileOptions?.pthread !== true || JSON.stringify(plan.compileOptions?.definitions) !== JSON.stringify(REQUIRED_FREECAD_NAMING_DEFINITIONS)) fail('compile options must lock C++20, pthread and the FreeCAD wasm compatibility definitions.')

const libraries = []
for (const library of plan.libraries) libraries.push(await inspectPlannedLibrary(root, library))
const linkDependencies = []
for (const library of plan.linkDependencies ?? []) linkDependencies.push(await inspectPlannedLibrary(root, library))
const includeDirs = []
for (const includeDir of plan.includeDirs ?? []) includeDirs.push(await inspectIncludeDirectory(root, includeDir))
const runtimeAssets = []
for (const asset of plan.runtimeAssets ?? []) runtimeAssets.push(await inspectPlannedRuntimeAsset(root, asset))
const freeCadInclude = plan.includeDirs.find(({ name }) => name === 'FreeCAD')
let sourceCommit = null
if (freeCadInclude && await exists(resolveFrom(root, freeCadInclude.path))) {
  try {
    sourceCommit = (await execFileAsync('git', ['-C', resolveFrom(root, freeCadInclude.path), 'rev-parse', 'HEAD'])).stdout.trim()
  } catch (error) {
    fail(`cannot inspect FreeCAD source commit: ${error instanceof Error ? error.message : String(error)}`)
  }
  if (sourceCommit !== plan.baseline.sourceCommit) fail(`FreeCAD source commit mismatch: expected ${plan.baseline.sourceCommit}, got ${sourceCommit}.`)
}
const bridgePath = resolveFrom(root, plan.namingBridge.path)
const hostAdapterPath = resolveFrom(root, plan.namingBridge.hostAdapter)
const forceIncludePath = resolveFrom(root, plan.compileOptions.forceInclude)
const bridgePresent = await exists(bridgePath)
const hostAdapterPresent = await exists(hostAdapterPath)
const forceIncludePresent = await exists(forceIncludePath)
let bridgeExports = []
if (bridgePresent) {
  const source = await readFile(bridgePath, 'utf8')
  bridgeExports = REQUIRED_FREECAD_NAMING_CALLBACKS.filter((callback) => source.includes(callback))
  if (bridgeExports.length !== REQUIRED_FREECAD_NAMING_CALLBACKS.length) fail('present naming bridge source omits required callback names.')
}
if (hostAdapterPresent) {
  const source = await readFile(hostAdapterPath, 'utf8')
  if (!source.includes('Module.preRun') || !source.includes('FREECAD_USER_HOME')) fail('host adapter does not initialize the FreeCAD virtual user environment.')
}
const missingLibraries = libraries.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingLinkDependencies = linkDependencies.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingIncludes = includeDirs.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const missingRuntimeAssets = runtimeAssets.filter(({ status }) => status !== 'verified').map(({ name }) => name)
const blockers = [
  ...missingLibraries.map((name) => `${name} wasm static library`),
  ...missingLinkDependencies.map((name) => `${name} wasm link dependency`),
  ...missingIncludes.map((name) => `${name} headers`),
  ...missingRuntimeAssets.map((name) => `${name} runtime asset`),
  ...(!bridgePresent ? ['FreeCAD private naming Worker bridge'] : []),
  ...(!hostAdapterPresent ? ['FreeCAD naming host adapter'] : []),
  ...(!forceIncludePresent ? ['FreeCAD wasm force-include header'] : []),
]
console.log(JSON.stringify({
  status: blockers.length === 0 ? 'sdk-readiness-complete' : 'sdk-readiness-incomplete',
  availability: blockers.length === 0 ? 'candidate-complete' : 'unavailable',
  baseline: plan.baseline,
  libraries,
  linkDependencies,
  includeDirs,
  runtimeAssets,
  sourceCommit,
  namingBridge: {
    path: plan.namingBridge.path,
    status: bridgePresent ? 'verified' : 'missing',
    sha256: bridgePresent ? await sha256File(bridgePath) : null,
    hostAdapter: plan.namingBridge.hostAdapter,
    hostAdapterSha256: hostAdapterPresent ? await sha256File(hostAdapterPath) : null,
    exports: bridgeExports,
  },
  compileOptions: {
    ...plan.compileOptions,
    forceIncludeSha256: forceIncludePresent ? await sha256File(forceIncludePath) : null,
  },
  blockers,
  readyLibraries: libraries.length - missingLibraries.length,
  requiredLibraries: libraries.length,
  publishToWorker: false,
  boundary: plan.boundary,
}, null, 2))
