/**
 * Headless audit of the four farm GLBs in assets/meshes/. Run:
 *   node --import ./scripts/ts-resolve.mjs --experimental-strip-types scripts/farm-assets.check.ts
 *
 * This is the closest thing to browser verification the asset runtime has: it
 * parses each real GLB, runs the same paintability check the game runs, and —
 * for the blob body — normalises the geometry and fires the same
 * outside-in raycast PaintSkin uses to turn a world point into a UV. If that
 * raycast returns a UV, a splat lands; if it returns null, splats silently do
 * nothing, which is the failure mode this file exists to catch.
 *
 * `THREE.GLTFLoader: Couldn't load texture` warnings are expected here — node
 * has no ImageBitmap. Geometry, UVs and materials all still parse.
 */
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import * as THREE from 'three'
import { GLTFLoader, type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { paintableInfo, normalizeToRadius } from '../src/assets/registry'

// GLTFLoader reaches for `self` when wiring its texture loaders.
;(globalThis as unknown as { self: unknown }).self = globalThis

const MESH_DIR = fileURLToPath(new URL('../assets/meshes/', import.meta.url))
const FILES = ['blobbo-base', 'prop-spring-boot', 'prop-paint-bucket', 'prop-toaster-launcher']

const loader = new GLTFLoader()
let failures = 0

function parse(file: string): Promise<GLTF | null> {
  const buf = readFileSync(MESH_DIR + file + '.glb')
  const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer
  return new Promise((res) => loader.parse(ab, '', res, () => res(null)))
}

for (const file of FILES) {
  const gltf = await parse(file)
  if (!gltf) {
    console.log(`${file}: PARSE FAILED`)
    failures++
    continue
  }
  const info = paintableInfo(gltf.scene)
  console.log(
    `${file.padEnd(22)} meshes=${info.meshCount} materials=${info.materialCount} ` +
    `tris=${info.triCount} uvOk=${info.uvOk} skinned=${info.skinned} paintSafe=${info.ok}` +
    (gltf.animations.length ? ` clips=${gltf.animations.map((a) => a.name).join(',')}` : ''),
  )
  for (const p of info.problems) console.log(`    ! ${p}`)
}

// ---- the blob body, end to end ---------------------------------------------
const gltf = await parse('blobbo-base')
if (!gltf) {
  failures++
} else {
  let mesh: THREE.Mesh | null = null
  gltf.scene.traverse((o) => { if (!mesh && (o as THREE.Mesh).isMesh) mesh = o as THREE.Mesh })
  const body = mesh as unknown as THREE.Mesh
  const geo = body.geometry.clone()
  normalizeToRadius(geo, 0.5)
  const fitted = new THREE.Mesh(geo, new THREE.MeshStandardMaterial())
  fitted.updateWorldMatrix(true, false)

  const r = geo.boundingSphere!.radius
  const c = geo.boundingSphere!.center
  console.log(`\nblob.body fitted: boundingRadius=${r.toFixed(6)} centre=(${c.x.toFixed(6)}, ${c.y.toFixed(6)}, ${c.z.toFixed(6)})`)
  if (Math.abs(r - 0.5) > 1e-5) { console.log('  ! radius is not 0.5 — puddle belly contact would drift'); failures++ }
  if (c.length() > 1e-5) { console.log('  ! not centred on the origin — PaintSkin raycasts would miss'); failures++ }

  // PaintSkin.uvAtWorldPoint, reproduced: cast from outside, inward through the
  // centre, and take the UV of the first hit.
  const raycaster = new THREE.Raycaster()
  let hits = 0
  const dirs = [
    new THREE.Vector3(1, 0, 0), new THREE.Vector3(-1, 0, 0),
    new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, -1, 0),
    new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, -1),
    new THREE.Vector3(1, 1, 1).normalize(),
  ]
  for (const dir of dirs) {
    const origin = dir.clone().multiplyScalar(r * 2.2)
    raycaster.set(origin, dir.clone().negate())
    raycaster.near = 0
    raycaster.far = r * 4.4
    const hit = raycaster.intersectObject(fitted, false).find((h) => h.uv)
    if (hit?.uv) hits++
    else console.log(`  ! no UV hit casting along ${dir.toArray().map((n) => n.toFixed(2)).join(',')} — a splat there would vanish`)
  }
  console.log(`splat raycast: ${hits}/${dirs.length} directions returned a UV`)
  if (hits !== dirs.length) failures++
}

console.log(failures === 0 ? '\nfarm-assets.check: OK' : `\nfarm-assets.check: ${failures} FAILURE(S)`)
if (failures > 0) process.exitCode = 1
