import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'

const root = resolve(new URL('..', import.meta.url).pathname)
const runtimePath = resolve(root, '.cache/freecad/reference-desktop.json')
const reportPath = resolve(root, 'config/freecad-property-color-inventory.json')
const fail = (message) => { throw new Error(`FreeCAD PropertyColor inventory check failed: ${message}`) }
const [runtimeContent, reportContent] = await Promise.all([readFile(runtimePath), readFile(reportPath)])
const runtime = JSON.parse(runtimeContent)
const report = JSON.parse(reportContent)

if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyColor' || report.classification !== 'opaque-fcstd-proxy') fail('report boundary is invalid')
if (report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline is not locked to FreeCAD 1.1.1')
if (report.recordCount !== 8 || !Array.isArray(report.records) || report.records.length !== 8) fail('record count is not exactly eight')
if (report.provenance?.runtime?.path !== '.cache/freecad/reference-desktop.json' || report.provenance.runtime.bytes !== runtimeContent.length || report.provenance.runtime.sha256 !== createHash('sha256').update(runtimeContent).digest('hex')) fail('runtime provenance is stale')
if (report.storage?.xmlElement !== 'PropertyColor' || report.storage.attribute !== 'value' || report.storage.encoding !== 'unsigned-decimal-packed-0xRRGGBBAA' || report.storage.legacyBeforeVersion !== '1.1' || report.storage.legacyConversion !== 'invert-alpha-byte') fail('native storage contract is incomplete')

const expected = [
  ['App::FeatureTest', 'Colour', '', [0, 0, 0, 1]],
  ['App::FeatureTestException', 'Colour', '', [0, 0, 0, 1]],
  ['App::Part', 'Color', '', [1, 1, 1, 0]],
  ['Assembly::AssemblyLink', 'Color', '', [1, 1, 1, 0]],
  ['Assembly::AssemblyObject', 'Color', '', [1, 1, 1, 0]],
  ['TechDraw::DrawViewAnnotation', 'TextColor', 'Annotation', [0, 0, 0, 1]],
  ['TechDraw::DrawViewDraft', 'Color', 'Draft view', [0, 0, 0, 1]],
  ['TechDraw::DrawViewSpreadsheet', 'TextColor', 'Spreadsheet', [0, 0, 0, 1]],
]
for (const [objectTypeId, propertyName, group, defaultValue] of expected) {
  const record = report.records.find((candidate) => candidate.objectTypeId === objectTypeId && candidate.propertyName === propertyName)
  if (!record || record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== group || record.status?.length !== 0 || JSON.stringify(record.defaultValue) !== JSON.stringify(defaultValue)) fail(`native inventory changed for ${objectTypeId}.${propertyName}`)
  const model = record.valueModel
  if (model?.kind !== 'rgba-color' || model.channels?.join(',') !== 'red,green,blue,alpha' || model.canonicalRepresentation !== 'four-channel-float-tuple' || model.packedRepresentation !== '0xRRGGBBAA' || model.alphaSemantics !== '1-opaque-0-transparent' || model.writable !== true) fail(`value model changed for ${objectTypeId}.${propertyName}`)
  const [floats, integers, packed] = record.inputs ?? []
  if (floats?.kind !== 'float-tuple' || JSON.stringify(floats.cardinality) !== '[3,4]' || floats.componentsMustHaveSameType !== true || floats.omittedAlpha !== 1 || integers?.kind !== 'integer-tuple' || JSON.stringify(integers.cardinality) !== '[3,4]' || integers.componentsMustHaveSameType !== true || integers.normalizationDivisor !== 255 || integers.omittedAlpha !== 1 || packed?.kind !== 'packed-integer' || packed.encoding !== '0xRRGGBBAA') fail(`setter inputs changed for ${objectTypeId}.${propertyName}`)
  if (!Array.isArray(record.dependencies) || record.dependencies.length !== 0 || record.applicability?.requiredObjectTypeId !== objectTypeId || record.applicability.source !== 'Document.supportedTypes runtime inventory' || record.applicability.shapeRequired !== false) fail(`dependencies or applicability changed for ${objectTypeId}.${propertyName}`)
}

const nativeMatches = []
for (const objectType of runtime.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) if (property.typeId === 'App::PropertyColor') nativeMatches.push({ objectTypeId: objectType.typeId, propertyName: property.name, group: property.group, status: property.status, defaultValue: property.default })
if (nativeMatches.length !== 8) fail('locked runtime oracle no longer contains eight PropertyColor records')
for (const locked of report.provenance?.sources ?? []) {
  const content = await readFile(resolve(root, locked?.path ?? ''))
  if (content.length !== locked.bytes || createHash('sha256').update(content).digest('hex') !== locked.sha256) fail(`source provenance is stale for ${locked.path}`)
}
const propertySource = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/App/PropertyStandard.cpp'), 'utf8')
const colorHeader = await readFile(resolve(root, '.cache/freecad/FreeCAD/src/Base/Color.h'), 'utf8')
if (!propertySource.includes('PyTuple_Size(value) == 3 || PyTuple_Size(value) == 4') || !propertySource.includes('cCol.r = PyLong_AsLong(item) / 255.0') || !propertySource.includes('cCol.setPackedValue(PyLong_AsUnsignedLong(value))') || !propertySource.includes('<PropertyColor value=') || !propertySource.includes('alphaMax - (rgba & alphaMax)')) fail('locked PropertyColor source no longer exposes the recorded setter/storage contract')
if (!colorHeader.includes('0xRRGGBBAA') || !colorHeader.includes('A defines the alpha value, 1 means fully opaque and 0 transparent')) fail('locked Base::Color source no longer exposes the recorded channel contract')
console.log(JSON.stringify({ status: 'freecad-property-color-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, writableRecords: report.records.filter(({ valueModel }) => valueModel.writable).length, defaults: [...new Set(report.records.map(({ defaultValue }) => JSON.stringify(defaultValue)))], classification: report.classification }, null, 2))
