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

const root = resolve(new URL('..', import.meta.url).pathname)
const oracle = JSON.parse(await readFile(resolve(root, 'config/freecad-partdesign-structure-oracle.json'), 'utf8'))
const fail = (message) => { throw new Error(`FreeCAD PartDesign structure oracle: ${message}`) }
const near = (actual, expected) => typeof actual === 'number' && Math.abs(actual - expected) <= oracle.tolerance
const expectedCommit = '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d'
if (oracle.schemaVersion !== 1 || oracle.baselineId !== 'freecad-1.1.1-partdesign-structure-oracle' || oracle.freecadVersion !== '1.1.1' || oracle.gitCommit !== expectedCommit || oracle.status !== 'pass' || oracle.tolerance !== 1e-7) fail('baseline is invalid.')

const expectedInitialGroups = {
  SourceBody: ['SourceBox'],
  DatumBody: ['DatumPlane', 'DatumLine', 'DatumPoint'],
  BinderBody: ['ShapeBinder'],
  SubBinderBody: ['SubShapeBinder'],
}
const expectedInitialTips = { SourceBody: 'SourceBox', DatumBody: null, BinderBody: null, SubBinderBody: null }
for (const [body, group] of Object.entries(expectedInitialGroups)) {
  if (JSON.stringify(oracle.initial?.bodyGroups?.[body]) !== JSON.stringify(group) || oracle.initial?.bodyTips?.[body] !== expectedInitialTips[body]) fail(`initial ${body} grouping or Tip state is invalid.`)
}
if (!near(oracle.initial.sourceVolume, 480) || !near(oracle.initial.shapeBinderVolume, 480) || !near(oracle.initial.subShapeBinderLength, 28)) fail('initial source or binder shape evidence is invalid.')
if (oracle.initial.datumPlane?.mapMode !== 'FlatFace' || JSON.stringify(oracle.initial.datumPlane.support) !== JSON.stringify([{ object: 'XY_Plane', subElements: [] }]) || !near(oracle.initial.datumPlane.attachmentOffset.position?.[2], 5) || !near(oracle.initial.datumPlane.attachmentOffset.angleDegrees, 30) || !near(oracle.initial.datumPlane.placement.position?.[2], 5) || !near(oracle.initial.datumPlane.placement.angleDegrees, 30) || JSON.stringify(oracle.initial.datumPlane.state) !== JSON.stringify(['Up-to-date'])) fail('DatumPlane support, offset or placement evidence is invalid.')
if (oracle.initial.datumLine?.mapMode !== 'ObjectX' || JSON.stringify(oracle.initial.datumLine.state) !== JSON.stringify(['Up-to-date']) || oracle.initial.datumPoint?.mapMode !== 'ObjectOrigin' || JSON.stringify(oracle.initial.datumPoint.attachmentOffset.position) !== JSON.stringify([1, 2, 3]) || JSON.stringify(oracle.initial.datumPoint.state) !== JSON.stringify(['Up-to-date'])) fail('DatumLine or DatumPoint evidence is invalid.')
if (JSON.stringify(oracle.initial.shapeBinder?.support) !== JSON.stringify([{ object: 'SourceBox', subElements: [] }]) || oracle.initial.shapeBinder.traceSupport !== true || JSON.stringify(oracle.initial.shapeBinder.state) !== JSON.stringify(['Up-to-date'])) fail('ShapeBinder support evidence is invalid.')
const expectedSubSupport = [{ object: 'SourceBox', subElements: ['Edge1', 'Edge2', 'Edge3', 'Edge4'] }]
if (JSON.stringify(oracle.initial.subShapeBinder?.support) !== JSON.stringify(expectedSubSupport) || JSON.stringify(oracle.initial.subShapeBinder.state) !== JSON.stringify(['Up-to-date'])) fail('SubShapeBinder support evidence is invalid.')

if (!near(oracle.edited?.sourceVolume, 672) || !near(oracle.edited?.shapeBinderVolume, 672) || !near(oracle.edited?.subShapeBinderLength, 28) || !near(oracle.edited?.datumPlaneAttachmentOffset?.position?.[2], 7) || !near(oracle.edited?.datumPlaneAttachmentOffset?.angleDegrees, 45) || !near(oracle.edited?.datumPlanePlacement?.position?.[2], 7) || !near(oracle.edited?.datumPlanePlacement?.angleDegrees, 45) || Object.entries(expectedInitialTips).some(([body, tip]) => oracle.edited?.bodyTips?.[body] !== tip)) fail('edited recompute evidence is invalid.')
if (oracle.invalidSupport?.typeId !== 'PartDesign::Plane' || JSON.stringify(oracle.invalidSupport.support) !== JSON.stringify([{ object: 'SourceBox', subElements: ['Face999'] }]) || JSON.stringify(oracle.invalidSupport.state) !== JSON.stringify(['Touched', 'Invalid']) || oracle.invalidSupport.status !== 'AttachEngine3D: subshape not found SourceBox.Face999') fail('invalid attachment support evidence is invalid.')

const expectedRoundtripGroups = { ...expectedInitialGroups, DatumBody: [...expectedInitialGroups.DatumBody, 'InvalidDatumPlane'] }
for (const [body, group] of Object.entries(expectedRoundtripGroups)) {
  if (JSON.stringify(oracle.roundtrip?.bodyGroups?.[body]) !== JSON.stringify(group) || oracle.roundtrip?.bodyTips?.[body] !== expectedInitialTips[body]) fail(`FCStd round-trip ${body} grouping or Tip state is invalid.`)
}
if (!near(oracle.roundtrip.sourceVolume, 672) || !near(oracle.roundtrip.shapeBinderVolume, 672) || !near(oracle.roundtrip.subShapeBinderLength, 28)) fail('FCStd round-trip shape evidence is invalid.')
if (JSON.stringify(oracle.roundtrip.shapeBinderSupport) !== JSON.stringify([{ object: 'SourceBox', subElements: [] }]) || JSON.stringify(oracle.roundtrip.subShapeBinderSupport) !== JSON.stringify(expectedSubSupport) || JSON.stringify(oracle.roundtrip.datumPlaneSupport) !== JSON.stringify([{ object: 'XY_Plane', subElements: [] }]) || oracle.roundtrip.datumPlaneMapMode !== 'FlatFace' || !near(oracle.roundtrip.datumPlaneAttachmentOffset.position?.[2], 7) || !near(oracle.roundtrip.datumPlaneAttachmentOffset.angleDegrees, 45) || JSON.stringify(oracle.roundtrip.invalidDatumState) !== JSON.stringify(['Touched', 'Invalid']) || oracle.roundtrip.invalidDatumStatus !== oracle.invalidSupport.status) fail('FCStd round-trip reference or failure evidence is invalid.')

const externalSupport = [{ object: 'ExternalBox', subElements: ['Face1'] }]
const crossInitial = oracle.crossDocument?.initial
if (crossInitial?.shapeBinderExternalLink?.accepted !== false || crossInitial.shapeBinderExternalLink.errorType !== 'ValueError' || crossInitial.shapeBinderExternalLink.error !== 'PropertyLinkSubList does not support external object' || crossInitial.shapeBinderSupportType !== 'App::PropertyLinkSubListGlobal' || crossInitial.subShapeBinderSupportType !== 'App::PropertyXLinkSubList') fail('cross-document binder property boundary is invalid.')
if (!near(crossInitial.sourceVolume, 120) || !near(crossInitial.sourceFaceArea, 30) || crossInitial.shapeBinderShapeNull !== true || !near(crossInitial.subShapeBinderArea, 30) || JSON.stringify(crossInitial.shapeBinderSupport) !== '[]' || JSON.stringify(crossInitial.subShapeBinderSupport) !== JSON.stringify(externalSupport) || crossInitial.subShapeBinderSourceDocument !== 'PartDesignExternalSource' || crossInitial.shapeBodyTip !== null || crossInitial.subBodyTip !== null) fail('cross-document initial shape, support or Tip evidence is invalid.')

const crossRoundtrip = oracle.crossDocument?.roundtrip
if (!near(crossRoundtrip?.sourceVolume, 120) || !near(crossRoundtrip?.sourceFaceArea, 30) || crossRoundtrip?.shapeBinderShapeNull !== true || !near(crossRoundtrip?.subShapeBinderArea, 30) || JSON.stringify(crossRoundtrip?.shapeBinderSupport) !== '[]' || JSON.stringify(crossRoundtrip?.subShapeBinderSupport) !== JSON.stringify(externalSupport) || crossRoundtrip?.subShapeBinderSourceDocument !== 'PartDesignExternalSource' || crossRoundtrip?.shapeBodyTip !== null || crossRoundtrip?.subBodyTip !== null) fail('cross-document FCStd round-trip evidence is invalid.')

const crossEdited = oracle.crossDocument?.edited
if (!near(crossEdited?.sourceVolume, 192) || !near(crossEdited?.sourceFaceArea, 48) || crossEdited?.shapeBinderShapeNull !== true || !near(crossEdited?.subShapeBinderArea, 48) || JSON.stringify(crossEdited?.shapeBinderState) !== JSON.stringify(['Up-to-date']) || JSON.stringify(crossEdited?.subShapeBinderState) !== JSON.stringify(['Up-to-date'])) fail('cross-document source mutation evidence is invalid.')

const crossDeleted = oracle.crossDocument?.deleted
if (JSON.stringify(crossDeleted?.shapeBinderSupport) !== '[]' || JSON.stringify(crossDeleted?.subShapeBinderSupport) !== '[]' || crossDeleted?.shapeBinderShapeNull !== true || crossDeleted?.subShapeBinderShapeNull !== false || !near(crossDeleted?.subShapeBinderCachedArea, 48) || JSON.stringify(crossDeleted?.shapeBinderState) !== JSON.stringify(['Up-to-date']) || JSON.stringify(crossDeleted?.subShapeBinderState) !== JSON.stringify(['Up-to-date']) || crossDeleted?.shapeBinderStatus !== 'Valid' || crossDeleted?.subShapeBinderStatus !== 'Valid') fail('cross-document source deletion evidence is invalid.')

console.log(JSON.stringify({ status: 'freecad-partdesign-structure-oracle-pass', baselineId: oracle.baselineId, bodies: Object.keys(oracle.initial.bodyGroups).length, datumTypes: ['PartDesign::Plane', 'PartDesign::Line', 'PartDesign::Point'], binderTypes: ['PartDesign::ShapeBinder', 'PartDesign::SubShapeBinder'], mutation: { sourceVolume: [oracle.initial.sourceVolume, oracle.edited.sourceVolume], shapeBinderVolume: [oracle.initial.shapeBinderVolume, oracle.edited.shapeBinderVolume], datumPlaneZ: [oracle.initial.datumPlane.attachmentOffset.position[2], oracle.edited.datumPlaneAttachmentOffset.position[2]] }, crossDocument: { shapeBinder: 'external-link-rejected', subShapeBinderArea: [crossInitial.subShapeBinderArea, crossEdited.subShapeBinderArea], deletion: 'support-cleared-shape-cache-retained' }, invalidSupport: oracle.invalidSupport.status, fcstdRoundtrip: true }, null, 2))
