import { mkdir, writeFile } from 'node:fs/promises'
import { resolve, relative } from 'node:path'
import {
  REQUIRED_FREECAD_NAMING_CALLBACKS,
  REQUIRED_FREECAD_NAMING_DEFINITIONS,
  inspectIncludeDirectory,
  inspectPlannedLibrary,
  inspectPlannedRuntimeAsset,
  loadSdkPlan,
  resolveFrom,
  exists,
  sha256File,
} from './freecad-naming-sdk-lib.mjs'

const root = resolve(new URL('..', import.meta.url).pathname)
const outputRoot = process.env.FREECAD_WASM_SDK_OUTPUT_DIR
  ? resolve(root, process.env.FREECAD_WASM_SDK_OUTPUT_DIR)
  : resolve(root, '.cache/toolchains/freecad-naming-sdk')
const plan = await loadSdkPlan(root)
const fail = (message) => { throw new Error(`FreeCAD WASM SDK manifest generation: ${message}`) }

const libraries = []
for (const library of [...(plan.libraries ?? []), ...(plan.linkDependencies ?? [])]) {
  const inspected = await inspectPlannedLibrary(root, library)
  if (inspected.status !== 'verified') fail(`missing ${library.name} wasm static library: ${resolveFrom(root, library.path)}`)
  libraries.push({
    name: library.name,
    path: relative(outputRoot, resolveFrom(root, library.path)),
    sha256: inspected.sha256,
  })
}
const includeDirs = []
for (const includeDir of plan.includeDirs ?? []) {
  const inspected = await inspectIncludeDirectory(root, includeDir)
  if (inspected.status !== 'verified') fail(`include directory ${includeDir.name} is incomplete: ${inspected.missingHeaders.join(', ')}`)
  includeDirs.push(relative(outputRoot, resolveFrom(root, includeDir.path)))
}
const bridge = resolveFrom(root, plan.namingBridge.path)
if (!await exists(bridge)) fail(`missing naming bridge source: ${bridge}`)
const hostAdapter = resolveFrom(root, plan.namingBridge.hostAdapter)
if (!await exists(hostAdapter)) fail(`missing naming host adapter: ${hostAdapter}`)
const forceInclude = resolveFrom(root, plan.compileOptions.forceInclude)
if (!await exists(forceInclude)) fail(`missing force-include header: ${forceInclude}`)
if (plan.compileOptions.cxxStandard !== 'c++20' || plan.compileOptions.pthread !== true || JSON.stringify(plan.compileOptions.definitions) !== JSON.stringify(REQUIRED_FREECAD_NAMING_DEFINITIONS)) fail('compile options do not match the locked SDK contract')
const bridgeSha256 = await sha256File(bridge)
const runtimeAssets = []
for (const asset of plan.runtimeAssets ?? []) {
  const inspected = await inspectPlannedRuntimeAsset(root, asset)
  if (inspected.status !== 'verified') fail(`runtime asset ${asset.name} is incomplete: ${inspected.missingFiles.join(', ')}`)
  runtimeAssets.push({
    name: asset.name,
    path: relative(outputRoot, resolveFrom(root, asset.path)),
    preloadTo: asset.preloadTo,
    files: inspected.files,
  })
}
const manifest = {
  schemaVersion: 1,
  freecadVersion: plan.baseline.freecadVersion,
  sourceCommit: plan.baseline.sourceCommit,
  emscriptenVersion: plan.baseline.emscriptenVersion,
  qtTarget: plan.baseline.target,
  pythonTarget: plan.baseline.target,
  includeDirs,
  libraries,
  namingBridge: {
    source: relative(outputRoot, bridge),
    sha256: bridgeSha256,
    hostAdapter: relative(outputRoot, hostAdapter),
    hostAdapterSha256: await sha256File(hostAdapter),
    exports: [...REQUIRED_FREECAD_NAMING_CALLBACKS],
  },
  compileOptions: {
    cxxStandard: plan.compileOptions.cxxStandard,
    pthread: plan.compileOptions.pthread,
    forceInclude: relative(outputRoot, forceInclude),
    forceIncludeSha256: await sha256File(forceInclude),
    definitions: [...plan.compileOptions.definitions],
  },
  runtimeAssets,
  productionPublication: false,
  boundary: { ...plan.boundary },
}
await mkdir(outputRoot, { recursive: true })
const output = resolve(outputRoot, 'manifest.json')
await writeFile(output, `${JSON.stringify(manifest, null, 2)}\n`)
console.log(JSON.stringify({
  status: 'freecad-naming-sdk-manifest-generated',
  output,
  productionPublished: false,
  systemExact: false,
}, null, 2))
