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

const root = resolve(new URL('..', import.meta.url).pathname)
const sourceRoot = resolve(process.env.FREECAD_SOURCE_DIR || join(root, '.cache/freecad/FreeCAD'))
const outputPath = resolve(process.env.FREECAD_INVENTORY_FILE || join(root, 'config/freecad-source-inventory.json'))
const baselineCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
const cmakeAliases = {
  AddonManager: 'ADDONMGR',
  JtReader: 'JTREADER',
  MeshPart: 'MESH_PART',
  PartDesign: 'PART_DESIGN',
  TechDraw: 'TECHDRAW',
  ReverseEngineering: 'REVERSEENGINEERING',
  OpenSCAD: 'OPENSCAD',
}
const read = async (path) => readFile(path, 'utf8')
const exists = async (path) => access(path).then(() => true).catch(() => false)
const moduleRoot = join(sourceRoot, 'src/Mod')
if (!(await exists(moduleRoot))) throw new Error(`FreeCAD source directory is unavailable: ${moduleRoot}`)

const buildOptions = await read(join(sourceRoot, 'cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake'))
const optionDefaults = new Map([...buildOptions.matchAll(/option\(BUILD_([A-Z0-9_]+)[^\n]*?\s(ON|OFF)\)/g)].map((match) => [match[1], match[2] === 'ON']))
const moduleNames = (await readdir(moduleRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort()
const commandPattern = /(?:addCommand|addCommand\s*\(|registerCommand)\s*\(\s*["']([^"']+)["']/g
const commandIds = (text) => [...text.matchAll(commandPattern)].map((match) => match[1]).filter((value, index, values) => values.indexOf(value) === index).sort()

const modules = []
for (const name of moduleNames) {
  const modulePath = join(moduleRoot, name)
  const option = cmakeAliases[name] || name.toUpperCase()
  const cmakePath = join(modulePath, 'CMakeLists.txt')
  const appPath = join(modulePath, 'App')
  const guiPath = join(modulePath, 'Gui')
  const cmake = await exists(cmakePath) ? await read(cmakePath) : ''
  const pythonFiles = []
  const collectPython = async (directory) => {
    if (!(await exists(directory))) return
    for (const entry of await readdir(directory, { withFileTypes: true })) {
      const path = join(directory, entry.name)
      if (entry.isDirectory()) await collectPython(path)
      else if (entry.name.endsWith('.py')) pythonFiles.push(path)
    }
  }
  await collectPython(modulePath)
  const commandNames = []
  for (const path of pythonFiles) {
    const text = await read(path)
    commandNames.push(...commandIds(text))
  }
  modules.push({
    name,
    sourcePath: `src/Mod/${name}`,
    cmakeOption: `BUILD_${option}`,
    cmakeDefault: optionDefaults.has(option) ? optionDefaults.get(option) : null,
    hasCMake: Boolean(cmake),
    hasApp: await exists(appPath),
    hasGui: await exists(guiPath),
    pythonFileCount: pythonFiles.length,
    commandIds: [...new Set(commandNames)].sort(),
    runtimeStatus: 'requires-freecad-reference-probe',
  })
}

const inventory = {
  schemaVersion: 1,
  baseline: { freecadVersion: '1.1.1', commit: baselineCommit, sourcePath: '.cache/freecad/FreeCAD' },
  generatedBy: 'scripts/generate-freecad-source-inventory.mjs',
  moduleCount: modules.length,
  modules,
}
if (modules.length !== 34) throw new Error(`Expected 34 FreeCAD modules, found ${modules.length}.`)
await writeFile(outputPath, `${JSON.stringify(inventory, null, 2)}\n`, 'utf8')
console.log(JSON.stringify({ status: 'source-inventory-generated', output: outputPath, moduleCount: modules.length, commandCount: modules.reduce((total, module) => total + module.commandIds.length, 0) }, null, 2))
