/**
 * Headless check of the paint report, run against REAL parsed GLB data rather
 * than a hand-built THREE object — GLTFLoader.parse needs no DOM for an
 * untextured mesh, so the whole "bytes in, verdict out" path is testable:
 *   node --experimental-strip-types --import ./src/editor/node-ts-resolve.mjs \
 *        src/editor/paintability.test.ts
 */
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { buildTinyGlb } from './tiny-glb'
import { firstMesh, inspectPaintability } from './paintability'

let passed = 0
function ok(cond: boolean, msg: string): void {
  if (!cond) throw new Error('FAIL: ' + msg)
  passed++
}

const parse = (bytes: ArrayBuffer): Promise<THREE.Group> =>
  new Promise((resolve, reject) => {
    new GLTFLoader().parse(bytes, '', (gltf) => resolve(gltf.scene), reject)
  })

// ---- a well-formed asset passes -------------------------------------------
{
  const scene = await parse(buildTinyGlb())
  const mesh = firstMesh(scene)
  ok(mesh !== null, 'the parsed GLB yields a mesh')
  ok((mesh as THREE.Mesh).isMesh === true, 'what PaintSkin would bind to really is a Mesh')

  const report = inspectPaintability(scene)
  ok(report.hasMesh, 'the report found a mesh')
  ok(report.uvOk, 'a 0-1 UV map passes the paint check')
  ok(report.triCount === 2, `the quad reports 2 triangles (got ${report.triCount})`)
  ok(report.materialCount === 1, 'a single-material asset reports 1 material')
  ok(report.problems.length === 0, `a good asset raises no problems (got ${JSON.stringify(report.problems)})`)
}

// ---- no UVs is rejected with a plain-language reason ------------------------
{
  const scene = await parse(buildTinyGlb({ withUv: false }))
  const report = inspectPaintability(scene)
  ok(report.hasMesh, 'the UV-less asset still parses to a mesh')
  ok(!report.uvOk, 'an asset with no UV map fails the paint check')
  ok(report.problems.length > 0, 'the failure comes with a reason')
  ok(/unwrap/i.test(report.problems[0]!), 'the reason tells you to unwrap it in Blender')
  ok(!/uv attribute|BufferAttribute|undefined/i.test(report.problems[0]!),
    'the reason is written for an artist, not a programmer')
}

// ---- an empty group is reported, not crashed on ----------------------------
{
  const report = inspectPaintability(new THREE.Group())
  ok(!report.hasMesh, 'an empty asset reports no mesh')
  ok(!report.uvOk, 'an empty asset cannot be painted')
  ok(report.problems.length === 1, 'an empty asset gets exactly one complaint')
}

// ---- UVs outside 0..1 are caught (the silent wrap-around bug) --------------
{
  const scene = await parse(buildTinyGlb())
  const mesh = firstMesh(scene)!
  const uv = mesh.geometry.getAttribute('uv') as THREE.BufferAttribute
  uv.setXY(2, 2.5, 1) // push one corner into the next UV tile
  uv.needsUpdate = true
  const report = inspectPaintability(scene)
  ok(!report.uvOk, 'UVs running past 1 fail the paint check')
  ok(/0.{0,3}1|square/i.test(report.problems[0]!), 'the reason names the 0-1 square')
}

// ---- more than one material is caught --------------------------------------
{
  const scene = await parse(buildTinyGlb())
  const mesh = firstMesh(scene)!
  mesh.material = [mesh.material as THREE.Material, new THREE.MeshStandardMaterial()]
  const report = inspectPaintability(scene)
  ok(report.materialCount === 2, 'a material array is counted')
  ok(report.problems.some((p) => /single material/i.test(p)),
    'multi-material assets are told to join down to one')
}

console.log(`editor paintability.test: ${passed} assertions passed ✓`)
