/**
 * Lane I demo — every slot, twice.
 *
 * Left column is built with an EMPTY registry (today's game). Right column is
 * built with a test manifest pointing at the four farm GLBs. Same factories,
 * same arguments — the only difference is which registry is installed when the
 * factory runs, which is exactly the property the asset runtime promises.
 *
 * The paint check is deliberately numeric rather than visual: it stamps a splat
 * at a known point on the swapped body and prints coverage before/after, so
 * "the paint landed" is a number you can read, not a thing you squint at.
 *
 * NOTE: this page is not in vite.config.ts's `rollupOptions.input` (frozen
 * file), so it is served by `npm run dev` but is NOT emitted by `npm run build`
 * until integration adds the entry.
 */
import * as THREE from 'three'
import { createWorld } from '../world'
import type { World } from '../contracts'
import { PaintSkin } from '../paint/skin'
import { AssetRegistry, setAssets, paintableInfo } from '../assets/registry'
import { resolveBlobBody } from '../assets/blobBody'
import { assetUrl, type Manifest } from '../assets/manifest'
import { SLOT_LABELS, type SlotId } from '../assets/slots'
import {
  createPressurePlate, createSpringBoot, createBucketDump,
  createConveyorBelt, createBubbleArch, createFan,
} from '../machine/parts'

const FARM = (name: string) => assetUrl(`assets/meshes/${name}.glb`)

/**
 * Fit transforms measured against the farm GLBs' own scale (they export large
 * and Z-up-ish); committed here so the demo is a real fitting example, not a
 * pile of identity transforms.
 */
const TEST_MANIFEST: Manifest = {
  'machine.boot': { url: FARM('prop-spring-boot'), scale: 1.2, offset: [0, 0, 0] },
  'machine.bucket': { url: FARM('prop-paint-bucket'), scale: 1.0, offset: [0, -0.6, 0] },
  'course.scenery.cereal': { url: FARM('prop-toaster-launcher'), scale: 3.0, rotationDeg: [0, 180, 0] },
  'blob.body': { url: FARM('blobbo-base') },
}

const log: string[] = []
const panel = document.createElement('div')
panel.style.cssText =
  'position:fixed;left:8px;top:8px;max-height:96vh;overflow:auto;width:400px;' +
  'font:12px/1.5 ui-monospace,monospace;background:#0b0b12ee;color:#dfe;' +
  'padding:10px 12px;border-radius:8px;white-space:pre-wrap;z-index:10'
document.body.appendChild(panel)
function say(line: string): void {
  log.push(line)
  panel.textContent = log.join('\n')
  console.log('[lane-i] ' + line)
}

/** Builds the whole slot line-up at an X offset. Identical calls both times. */
function buildColumn(world: World, x: number): void {
  createPressurePlate(world, { id: `plate${x}`, position: [x, 0.2, 6], massThreshold: 1, emits: 'sig' })
  createSpringBoot(world, { id: `boot${x}`, position: [x, 0.2, 2], impulse: 8, onSignal: 'sig' })
  createBucketDump(world, { id: `bucket${x}`, position: [x, 4, -2], color: 'blue', radius: 1.4, onSignal: 'sig' })
  createConveyorBelt(world, { id: `belt${x}`, position: [x, 0.3, -8], velocity: [2, 0, 0], size: [8, 0.5, 3] })
  createBubbleArch(world, { id: `arch${x}`, position: [x, 0.2, -14], fraction: 0.5 })
  createFan(world, { id: `fan${x}`, position: [x, 1.5, -20], force: 4, direction: [0, 0, 1] })
}

const world = await createWorld(document.getElementById('app')!)
world.camera.position.set(0, 9, 22)
world.camera.lookAt(0, 1, -6)

// ---- LEFT: empty manifest = today's game ------------------------------------
const emptyReg = new AssetRegistry({})
setAssets(emptyReg)
buildColumn(world, -9)
const stockBody = resolveBlobBody(world, 0.5, () => makeStockSphere())
stockBody.position.set(-9, 2, 12)
world.scene.add(stockBody)
say('LEFT  (x=-9): empty manifest — procedural, unchanged.')

// ---- RIGHT: test manifest ---------------------------------------------------
const testReg = new AssetRegistry(TEST_MANIFEST)
say('\nRIGHT (x=+9): loading test manifest…')
for (const [slot, entry] of Object.entries(TEST_MANIFEST) as [SlotId, { url: string }][]) {
  say(`  ${SLOT_LABELS[slot]} <- ${entry.url.split('/').pop()}`)
}
await testReg.preload()
setAssets(testReg)
buildColumn(world, 9)

const customBody = resolveBlobBody(world, 0.5, () => makeStockSphere())
customBody.position.set(9, 2, 12)
world.scene.add(customBody)

// ---- per-slot status --------------------------------------------------------
say('\n--- slot status ---')
for (const slot of Object.keys(TEST_MANIFEST) as SlotId[]) {
  const inst = testReg.instanceSync(slot)
  say(`  ${slot.padEnd(24)} ${inst ? 'LOADED' : 'FAILED -> fallback kept'}`)
}

// ---- paintability report for blob.body --------------------------------------
say('\n--- blob.body paintability ---')
const info = testReg.paintability('blob.body')
if (!info) {
  say('  no asset resolved; the built-in sphere is in use.')
} else {
  say(`  meshes ${info.meshCount}   materials ${info.materialCount}   triangles ${info.triCount}`)
  say(`  UVs inside 0..1: ${info.uvOk ? 'yes' : 'NO'}${info.skinned ? '   (rigged)' : ''}`)
  say(`  paint-safe: ${info.ok ? 'YES' : 'NO'}`)
  for (const p of info.problems) say(`   ! ${p}`)
}
const swapped = customBody !== stockBody && customBody.geometry !== stockBody.geometry
say(`  body actually swapped: ${swapped ? 'YES' : 'no — using the built-in sphere'}`)
say(`  bounding radius: ${customBody.geometry.boundingSphere?.radius.toFixed(4)} (must be 0.5000 — it is a gameplay number)`)

// ---- scripted splat: does paint land on whatever body is in use? ------------
say('\n--- scripted splat ---')
for (const [label, mesh] of [['stock', stockBody], ['custom', customBody]] as const) {
  const skin = new PaintSkin(mesh, { size: 256 })
  const before = skin.coverage().total
  // A point on the +X face of the body, in world space.
  const p = mesh.getWorldPosition(new THREE.Vector3()).add(new THREE.Vector3(0.5, 0, 0))
  skin.splatAtPoint(p, 'red', 0.25)
  const after = skin.coverage()
  say(`  ${label}: coverage ${before.toFixed(4)} -> ${after.total.toFixed(4)}  ` +
      `red=${after.byColor.red.toFixed(4)}  ${after.total > before ? 'PAINT LANDED' : 'NOTHING LANDED'}`)
}

say('\nSlots with no entry above fall back silently — that is the empty-manifest path.')
world.start()

function makeStockSphere(): THREE.Mesh {
  const m = new THREE.Mesh(
    new THREE.SphereGeometry(0.5, 48, 36),
    new THREE.MeshStandardMaterial({ color: '#F5F5F7', roughness: 0.5, emissive: new THREE.Color('#88e0ff'), emissiveIntensity: 0 }),
  )
  m.castShadow = true
  return m
}
