// THE SACRED PROPERTY: empty manifest + empty IndexedDB must construct exactly
// what the game constructed before the workshop existed. Fingerprints the whole
// scene graph the course + zones + a cannon build, so any drift shows up as a
// changed hash. Run against main and against the branch; the numbers must match.
//
// It has to be BUNDLED rather than strip-typed: the game modules use TypeScript
// parameter properties, which node's --experimental-strip-types refuses.
//
//   ./node_modules/.bin/esbuild scripts/sacred-parity.check.ts --bundle \
//     --platform=node --format=esm --outfile=/tmp/sacred.mjs && node /tmp/sacred.mjs
//
// Measured on fix-runtime and on main at f827f6f, both:
//   nodes=49 colliders=15 bodies=3 greyboxMeshes=11 blobRadius=0.500000
//   sceneFingerprint=49df4f20
import * as THREE from 'three'
import RAPIER from '@dimforge/rapier3d-compat'
import { buildGreybox } from '../src/course/greybox.ts'
import { installZones } from '../src/course/zones.ts'
import { PaintCannon } from '../src/paint/cannon.ts'
import { AssetRegistry, setAssets } from '../src/assets/registry.ts'
import { createBlob } from '../src/blob/createBlob.ts'

setAssets(new AssetRegistry({}))   // empty manifest, no IndexedDB
await RAPIER.init()

const scene = new THREE.Scene()
const physics = new RAPIER.World({ x: 0, y: -9.81, z: 0 })
const listeners: any = {}
const world: any = {
  scene, physics, rapier: RAPIER,
  events: { on: (n: string, f: any) => { (listeners[n] ||= []).push(f) }, emit: () => {} },
  addSystem: () => {}, onFrame: () => {}, tick: () => {}, renderOnce: () => {}, start: () => {},
}

const gb = buildGreybox(world)
const blob = createBlob(world, {})
world.blob = blob
const skin: any = { boundingRadius: 0.5, splat: () => {}, splatAtWorldPoint: () => {}, coverage: () => ({ total: 0 }) }
installZones(world, blob, skin)
new PaintCannon({ world, color: 'red', position: new THREE.Vector3(0, 2, 0), target: blob.mesh, paint: skin, targetRadius: 0.5 })

await new Promise((r) => setTimeout(r, 50)) // let any async swap settle

function fp(root: THREE.Object3D) {
  const parts: string[] = []
  root.traverse((o) => {
    const m = o as THREE.Mesh
    const mat: any = Array.isArray(m.material) ? m.material[0] : m.material
    parts.push([
      o.type, o.name, o.visible ? 1 : 0,
      o.position.toArray().map((n) => n.toFixed(4)).join(','),
      o.scale.toArray().map((n) => n.toFixed(4)).join(','),
      m.isMesh ? (m.geometry.getAttribute('position')?.count ?? 0) : '-',
      mat ? `${mat.type}:${mat.color?.getHexString?.() ?? ''}:${mat.visible ? 1 : 0}` : '-',
    ].join('|'))
  })
  return parts
}
const lines = fp(scene)
let h = 0
for (const s of lines) for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0
console.log(`nodes=${lines.length} colliders=${physics.colliders.len()} bodies=${physics.bodies.len()} greyboxMeshes=${gb.meshes.length} blobRadius=${blob.mesh.geometry.boundingSphere?.radius?.toFixed(6) ?? (blob.mesh.geometry.computeBoundingSphere(), blob.mesh.geometry.boundingSphere!.radius.toFixed(6))}`)
console.log(`sceneFingerprint=${(h >>> 0).toString(16)}`)
