import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { isDeepStrictEqual } from 'node:util'

const root = resolve(new URL('..', import.meta.url).pathname)
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-area-mutation.json'), 'utf8'))
const fail = (message) => { throw new Error(`FreeCAD PropertyArea mutation check failed: ${message}`) }
const same = isDeepStrictEqual
const near = (left, right, tolerance = 2e-6) => Math.abs(left - right) <= tolerance

if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-area-mutation' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline is invalid')
if (report.object?.name !== 'AreaProbe' || report.object.typeId !== 'Measure::MeasureArea' || report.property?.name !== 'Area' || report.property.typeId !== 'App::PropertyArea' || report.input?.name !== 'Elements' || report.input.typeId !== 'App::PropertyLinkSubList') fail('native object/property identity changed')
if (!(report.recompute?.initial > 0) || !(report.recompute.edit > 0) || !(report.recompute.restore > 0)) fail('initial, edit and restore were not recomputed')

const nominalElements = [{ object: 'AreaBox', typeId: 'Part::Box', subElements: ['Face1'] }]
const editedElements = [
  { object: 'AreaBox', typeId: 'Part::Box', subElements: ['Face1', 'Face2'] },
  { object: 'AreaCylinder', typeId: 'Part::Cylinder', subElements: ['Face1'] },
]
for (const [phase, expectedValue, expectedElements] of [
  ['before', 10, nominalElements],
  ['edited', 95.398223877, editedElements],
  ['restored', 10, nominalElements],
]) {
  const snapshot = report[phase]
  if (!near(snapshot?.value?.numeric, expectedValue) || typeof snapshot.value.unit !== 'string' || !snapshot.value.unit.includes('mm^2') || !same(snapshot.elements, expectedElements)) fail(`${phase} value or Elements changed`)
  if (!same(snapshot.propertyStatus, ['24', '27']) || !same(snapshot.editorMode, ['ReadOnly']) || snapshot.shape?.applicable !== false || !same(snapshot.objectState, ['Up-to-date']) || snapshot.statusString !== 'Valid' || snapshot.mustExecute !== false) fail(`${phase} native semantic state is invalid`)
}

for (const [phase, expectedElements] of [['touchedAfterEdit', editedElements], ['touchedAfterRestore', nominalElements]]) {
  const snapshot = report[phase]
  if (!same(snapshot?.elements, expectedElements) || !same(snapshot.objectState, ['Touched']) || snapshot.mustExecute !== false) fail(`${phase} did not preserve the native dirty state before recompute`)
}
if (!near(report.touchedAfterEdit.value?.numeric, 95.398223877) || report.touchedAfterRestore.value?.numeric !== 10) fail('derived Area was not updated synchronously with Elements')

if (report.sources?.preserved !== true || !same(report.sources.before, report.sources.edited) || !same(report.sources.before, report.sources.restored) || report.sources.before?.length !== 2) fail('source geometry changed during Elements mutation')
for (const source of report.sources.before) if (source.valid !== true || source.shapeType !== 'Solid' || source.solids !== 1 || !(source.faces > 0) || !(source.area > 0) || !(source.volume > 0)) fail(`${source.name} source Shape evidence is invalid`)
if (report.document?.objectsPreserved !== true || !same(report.document.objectsBefore, report.document.objectsRestored) || report.document.objectsBefore?.length !== 3) fail('mutation polluted the document object set')
if (report.classification?.inputChanged !== true || report.classification.derivedValueChanged !== true || report.classification.inputRestored !== true || report.classification.derivedValueRestored !== true || report.classification.semanticStateRestored !== true || report.classification.sourceGeometryPreserved !== true || report.classification.geometry !== 'source-shapes-preserved-derived-measurement-has-no-shape') fail('mutation/restore classification is incomplete')

console.log(JSON.stringify({
  status: 'freecad-property-area-mutation-pass',
  recompute: report.recompute,
  values: { before: report.before.value.numeric, edited: report.edited.value.numeric, restored: report.restored.value.numeric },
  elements: { before: report.before.elements, edited: report.edited.elements, restored: report.restored.elements },
  sourceGeometryPreserved: report.sources.preserved,
  semanticStateRestored: report.classification.semanticStateRestored,
  geometry: report.classification.geometry,
}, null, 2))
