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

const root = resolve(new URL('..', import.meta.url).pathname)
const inventory = JSON.parse(await readFile(resolve(root, 'config/freecad-type-property-inventory.json'), 'utf8'))
const fail = (message) => { throw new Error(`FreeCAD TypeId/property inventory: ${message}`) }
if (inventory.schemaVersion !== 1) fail('unsupported schemaVersion.')
if (inventory.baseline?.freecadVersion !== '1.1.1' || inventory.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline mismatch.')
if (inventory.moduleCount !== 34 || !Array.isArray(inventory.modules) || inventory.modules.length !== 34) fail('expected exactly 34 modules.')
const names = new Set()
for (const module of inventory.modules) {
  if (!module.name || names.has(module.name)) fail(`duplicate or empty module '${module.name}'.`)
  names.add(module.name)
  if (!Array.isArray(module.objects)) fail(`${module.name}.objects must be an array.`)
  for (const object of module.objects) {
    if (typeof object.sourceFile !== 'string' || !object.sourceFile.startsWith('src/Mod/')) fail(`${module.name} has an invalid sourceFile.`)
    for (const field of ['classes', 'typeIdSymbols', 'propertyRegistrations', 'propertyMembers', 'pythonProperties']) if (!Array.isArray(object[field])) fail(`${module.name}/${object.sourceFile}.${field} must be an array.`)
    for (const field of ['typeIdDeclarations', 'documentObjectUsages']) if (object[field] !== undefined && !Array.isArray(object[field])) fail(`${module.name}/${object.sourceFile}.${field} must be an array when present.`)
    const declaredTypeIds = object.typeIdDeclarations ?? []
    const documentObjectUsages = object.documentObjectUsages ?? []
    for (const value of [...object.classes, ...object.typeIdSymbols, ...object.propertyRegistrations, ...declaredTypeIds, ...documentObjectUsages]) if (typeof value !== 'string' || !value) fail(`${module.name}/${object.sourceFile} contains an invalid symbol.`)
    for (const value of [...object.propertyMembers, ...object.pythonProperties]) if (!value || typeof value.name !== 'string' || !value.name) fail(`${module.name}/${object.sourceFile} contains an invalid property.`)
  }
}
const validateTypeIdIndex = (records, field, expectedCount) => {
  if (!Array.isArray(records) || records.length !== expectedCount) fail(`${field} count does not match totals.`)
  const typeIds = new Set()
  for (const record of records) {
    if (!record?.typeId || typeIds.has(record.typeId) || !/^[A-Za-z_][A-Za-z0-9_]*::[A-Za-z_][A-Za-z0-9_]*$/.test(record.typeId)) fail(`${field} contains an invalid or duplicate TypeId.`)
    typeIds.add(record.typeId)
    if (!Array.isArray(record.sourceModules) || record.sourceModules.length === 0 || record.sourceModules.some((name) => !names.has(name))) fail(`${field}/${record.typeId} has invalid source modules.`)
    if (!Array.isArray(record.sourceFiles) || record.sourceFiles.length === 0 || record.sourceFiles.some((path) => !path.startsWith('src/Mod/'))) fail(`${field}/${record.typeId} has invalid source files.`)
  }
}
validateTypeIdIndex(inventory.typeIdDeclarations, 'typeIdDeclarations', inventory.totals?.typeIdDeclarationCount)
validateTypeIdIndex(inventory.documentObjectUsages, 'documentObjectUsages', inventory.totals?.documentObjectUsageCount)
const declarationEvidenceCount = inventory.modules.reduce((total, module) => total + module.objects.reduce((sum, object) => sum + (object.typeIdDeclarations?.length ?? 0), 0), 0)
const usageEvidenceCount = inventory.modules.reduce((total, module) => total + module.objects.reduce((sum, object) => sum + (object.documentObjectUsages?.length ?? 0), 0), 0)
if (declarationEvidenceCount !== inventory.totals.typeIdDeclarationEvidenceCount || usageEvidenceCount !== inventory.totals.documentObjectUsageEvidenceCount) fail('TypeId source evidence totals are inconsistent.')
const sourceRoot = process.env.FREECAD_SOURCE_DIR ? resolve(process.env.FREECAD_SOURCE_DIR) : resolve(root, inventory.baseline.sourcePath || '.cache/freecad/FreeCAD')
let sourceAvailable = false
try {
  const entries = await readdir(resolve(sourceRoot, 'src/Mod'), { withFileTypes: true })
  const sourceNames = new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name))
  sourceAvailable = true
  if (sourceNames.size !== names.size || [...sourceNames].some((name) => !names.has(name))) fail('tracked inventory differs from the available source checkout.')
} catch (error) {
  if (error?.code !== 'ENOENT') throw error
}
console.log(JSON.stringify({ status: 'type-property-inventory-pass', moduleCount: inventory.moduleCount, totals: inventory.totals, sourceAvailable }, null, 2))
