/**
 * Lane C demo — the Rube-Goldberg chain + surfaces sandbox.
 *
 * Chain (pure data wiring via `machine:signal`, no cross-instance references):
 *   heavy ball rests on PressurePlate ──emit 'sig:boot'──▶ SpringBoot
 *     SpringBoot TELEGRAPHs then kicks the ball ▶ it lands on the SeeSaw
 *     SeeSaw (real revolute joint) tips ▶ ball rolls into the BucketDump catch zone
 *     BucketDump TELEGRAPHs (teeter) then dumps ▶ emits 'paint:request-splat' (logged)
 * Side rigs: ConveyorBelt, BubbleArch, a Fan, plus ICE and HONEY patches with two
 * probe balls so surfaces measurably change rolling behaviour.
 *
 * Debug keys (browser):
 *   T — re-run the heavy chain ball from the start
 *   L — drop a LIGHT ball on the plate (must NOT trip it)
 *   S — re-run the ice/honey surface probes
 *   F — drop a light ball into the fan beam
 *   P — print a one-line machine state snapshot
 *
 * Console: every machine:signal / paint:* event is logged, and a STATE line is
 * printed once per second, so integration can verify the chain without the screen.
 */
import * as THREE from 'three'
import type { World } from '../contracts'
import {
  createPressurePlate, createSpringBoot, createSeeSaw, createBucketDump,
  createConveyorBelt, createBubbleArch, createFan,
  SurfaceRegistry, SURFACES, type SurfaceName,
} from '../machine'

// ---- tunables (kept in one place so integration can retune the layout) ----
// Values were tuned in a headless physics sweep so the ball reliably lands on
// the plank and the ice runway carries it into the bucket's catch zone.
const CFG = {
  station: [0, 0, 0] as [number, number, number],   // plate + boot
  plateThreshold: 2.5,
  bootImpulse: 36,
  bootDir: [0.5, 1.0, 0] as [number, number, number],
  seesaw: [4.5, 1.3, 0] as [number, number, number],
  plankLen: 5,
  maxTilt: 0.45,
  restTilt: -0.3,                                    // +X end starts low → delivers +X
  runway: [6, 10] as [number, number],              // ice strip seesaw → bucket
  bucket: [11, 2.4, 0] as [number, number, number],
  heavyBall: { radius: 0.7, density: 2.5, spawn: [0, 2.4, 0] as [number, number, number] },
  lightBall: { radius: 0.5, density: 0.35 },
}

interface Handles {
  world: World
  surfaces: SurfaceRegistry
  spawnHeavyBall: () => void
  spawnLightBall: () => void
  runSurfaceProbes: () => void
  spawnFanBall: () => void
  snapshot: () => Record<string, unknown>
}

export function buildLaneC(world: World): Handles {
  const { scene, physics, rapier } = world

  // -------------------------------------------------------------- lighting/floor
  const floor = new THREE.Mesh(
    new THREE.BoxGeometry(80, 1, 40),
    new THREE.MeshStandardMaterial({ color: '#cbb98a', roughness: 0.95 }),
  )
  floor.position.y = -0.5
  floor.receiveShadow = true
  scene.add(floor)
  const floorCol = physics.createCollider(
    rapier.ColliderDesc.cuboid(40, 0.5, 20).setTranslation(0, -0.5, 0).setFriction(1.0),
  )

  const surfaces = new SurfaceRegistry(rapier)
  surfaces.apply(floorCol, 'normal')

  // low containment wall past the bucket so the chain ball can't overshoot
  const wall = new THREE.Mesh(
    new THREE.BoxGeometry(1, 3, 8),
    new THREE.MeshStandardMaterial({ color: '#b0a06e', roughness: 0.9 }),
  )
  wall.position.set(CFG.bucket[0] + 2.5, 0.75, 0)
  scene.add(wall)
  physics.createCollider(
    rapier.ColliderDesc.cuboid(0.5, 1.5, 4).setTranslation(wall.position.x, 0.75, 0),
  )

  // ICE runway on the chain path: carries the ball off the seesaw into the bucket.
  const [rx0, rx1] = CFG.runway
  const rMid = (rx0 + rx1) / 2, rHalf = (rx1 - rx0) / 2
  const runwayMesh = new THREE.Mesh(
    new THREE.BoxGeometry(rHalf * 2, 0.2, 4),
    new THREE.MeshStandardMaterial({ color: SURFACES.ice.color, roughness: 0.15, metalness: 0.1 }),
  )
  runwayMesh.position.set(rMid, 0.1, 0)
  runwayMesh.receiveShadow = true
  scene.add(runwayMesh)
  const runwayCol = physics.createCollider(
    rapier.ColliderDesc.cuboid(rHalf, 0.1, 2).setTranslation(rMid, 0.1, 0),
  )
  surfaces.apply(runwayCol, 'ice')

  // ---------------------------------------------------------------- the chain
  // (1) PressurePlate — emits the boot signal only past its mass threshold.
  const plate = createPressurePlate(world, {
    id: 'plate-launch',
    position: CFG.station,
    massThreshold: CFG.plateThreshold,
    size: [3, 3],
    emits: 'sig:boot',
  })

  // (2) SpringBoot — co-mounted on the plate (no pad of its own); kicks on signal.
  createSpringBoot(world, {
    id: 'boot',
    position: CFG.station,
    impulse: CFG.bootImpulse,
    direction: CFG.bootDir,
    onSignal: 'sig:boot',
    strikeSize: [1.4, 0.9, 1.4],
    pad: false,
    emits: 'sig:boot-fired',
  })

  // (3) SeeSaw — real revolute joint; pre-biased so the ball is delivered +X.
  createSeeSaw(world, {
    id: 'seesaw',
    position: CFG.seesaw,
    plankSize: [CFG.plankLen, 0.4, 2.6],
    maxTilt: CFG.maxTilt,
    restTilt: CFG.restTilt,
  })

  // (4) BucketDump — self-contained catch zone (placed beyond the plank's reach)
  //     triggers the teeter+dump when the ball slides in off the ice runway.
  createBucketDump(world, {
    id: 'bucket',
    position: CFG.bucket,
    color: 'purple',
    radius: 2.2,
    trigger: { size: [1.4, 2.2, 4], offset: [-1.7, -2.4, 0] },
    emits: 'sig:bucket-dumped',
  })

  // ---------------------------------------------------------------- side rigs
  createConveyorBelt(world, {
    id: 'belt',
    position: [-6, 0.25, 8],
    size: [10, 0.5, 3],
    velocity: [3.5, 0, 0],
  })

  createBubbleArch(world, {
    id: 'arch',
    position: [4, 0, 8],
    fraction: 0.5,
    size: [4, 4, 2],
  })

  createFan(world, {
    id: 'fan',
    position: [-4, 1.2, -8],
    force: 9,
    direction: [1, 0.1, 0],
    range: 12,
    spread: 1.8,
  })

  // ------------------------------------------------- surface probe lanes (ice/honey)
  // Two identical lanes side by side; a probe ball on each gets the same push.
  // Ice lets it slide far, honey stops it fast — measurable in the STATE line.
  const makePatch = (name: SurfaceName, z: number) => {
    const mat = new THREE.MeshStandardMaterial({ color: SURFACES[name].color, roughness: 0.4 })
    const patch = new THREE.Mesh(new THREE.BoxGeometry(18, 0.2, 3), mat)
    patch.position.set(3, 0.1, z)
    patch.receiveShadow = true
    scene.add(patch)
    const col = physics.createCollider(
      rapier.ColliderDesc.cuboid(9, 0.1, 1.5).setTranslation(3, 0.1, z),
    )
    surfaces.apply(col, name)
    return z
  }
  const iceZ = makePatch('ice', -14)
  const honeyZ = makePatch('honey', -18)

  // ----------------------------------------------------------- test balls (stub)
  // NOTE: this is Lane C's own throwaway ball — NOT Lane A's blob (never imported).
  let chainBall: { mesh: THREE.Mesh; body: import('@dimforge/rapier3d-compat').RigidBody } | null = null

  const makeBall = (
    pos: [number, number, number], radius: number, density: number, color = '#F5F5F7',
  ) => {
    const mesh = new THREE.Mesh(
      new THREE.SphereGeometry(radius, 24, 18),
      new THREE.MeshStandardMaterial({ color, roughness: 0.4 }),
    )
    mesh.castShadow = true
    scene.add(mesh)
    const body = physics.createRigidBody(
      rapier.RigidBodyDesc.dynamic().setTranslation(pos[0], pos[1], pos[2]).setLinearDamping(0.05),
    )
    physics.createCollider(
      rapier.ColliderDesc.ball(radius).setDensity(density).setFriction(1.0).setRestitution(0.15),
      body,
    )
    world.onFrame(() => {
      const t = body.translation(), r = body.rotation()
      mesh.position.set(t.x, t.y, t.z)
      mesh.quaternion.set(r.x, r.y, r.z, r.w)
    })
    return { mesh, body }
  }

  const spawnHeavyBall = () => {
    if (chainBall) {
      scene.remove(chainBall.mesh)
      physics.removeRigidBody(chainBall.body)
    }
    const { radius, density, spawn } = CFG.heavyBall
    chainBall = makeBall(spawn, radius, density, '#F5F5F7')
    console.log(`[demo] heavy chain ball spawned (mass=${chainBall.body.mass().toFixed(2)}) — chain should run`)
  }

  const spawnLightBall = () => {
    const b = makeBall([0, 2.4, 0], CFG.lightBall.radius, CFG.lightBall.density, '#ffd1dc')
    console.log(`[demo] LIGHT ball on plate (mass=${b.body.mass().toFixed(2)} < threshold ${CFG.plateThreshold}) — plate must NOT trip`)
  }

  const spawnFanBall = () => {
    const b = makeBall([-3, 2.2, -8], 0.5, 0.35, '#c8f7c5')
    console.log(`[demo] light ball dropped in fan beam (mass=${b.body.mass().toFixed(2)}) — should be blown +X`)
  }

  // surface probes: give each an identical +X kick and log resting distance
  let iceProbe: import('@dimforge/rapier3d-compat').RigidBody | null = null
  let honeyProbe: import('@dimforge/rapier3d-compat').RigidBody | null = null
  const runSurfaceProbes = () => {
    for (const p of [iceProbe, honeyProbe]) if (p) physics.removeRigidBody(p)
    const ice = makeBall([-4, 0.7, iceZ], 0.5, 1.0, '#e6f7ff')
    const honey = makeBall([-4, 0.7, honeyZ], 0.5, 1.0, '#fff0c2')
    iceProbe = ice.body
    honeyProbe = honey.body
    ice.body.setLinvel({ x: 8, y: 0, z: 0 }, true)
    honey.body.setLinvel({ x: 8, y: 0, z: 0 }, true)
    console.log('[demo] surface probes launched (+8 m/s): ICE vs HONEY — compare rest x')
  }

  // --------------------------------------------------------------- event logging
  world.events.on('machine:signal', (p: { id: string }) =>
    console.log(`[event] machine:signal id="${p.id}"`))
  world.events.on('paint:request-splat', (p: any) =>
    console.log('[event] paint:request-splat', { color: p.color, radius: p.radius }))
  world.events.on('paint:request-cleanse', (p: any) =>
    console.log('[event] paint:request-cleanse', p))

  const snapshot = () => {
    const ball = chainBall?.body.translation()
    const surfUnderBall = ball
      ? surfaces.surfaceAt(physics, new THREE.Vector3(ball.x, ball.y, ball.z), 2.5)?.name
      : 'n/a'
    return {
      ballX: ball ? +ball.x.toFixed(2) : null,
      ballY: ball ? +ball.y.toFixed(2) : null,
      surfaceUnderBall: surfUnderBall,
      iceProbeX: iceProbe ? +iceProbe.translation().x.toFixed(2) : null,
      honeyProbeX: honeyProbe ? +honeyProbe.translation().x.toFixed(2) : null,
    }
  }

  return { world, surfaces, spawnHeavyBall, spawnLightBall, runSurfaceProbes, spawnFanBall, snapshot }
}

// --------------------------------------------------------------- browser bootstrap
if (typeof document !== 'undefined') {
  const { createWorld } = await import('../world')
  const world = await createWorld(document.getElementById('app')!)
  world.camera.position.set(2, 10, 20)
  const h = buildLaneC(world)

  // frame a nice overview
  world.onFrame(() => world.camera.lookAt(5, 1, 0))

  // start the chain, and print a STATE line once a second
  h.spawnHeavyBall()
  h.runSurfaceProbes()
  let acc = 0
  world.onFrame((dt) => {
    acc += dt
    if (acc >= 1) { acc = 0; console.log('[STATE]', JSON.stringify(h.snapshot())) }
  })

  addEventListener('keydown', (e) => {
    if (e.code === 'KeyT') h.spawnHeavyBall()
    else if (e.code === 'KeyL') h.spawnLightBall()
    else if (e.code === 'KeyS') h.runSurfaceProbes()
    else if (e.code === 'KeyF') h.spawnFanBall()
    else if (e.code === 'KeyP') console.log('[STATE]', JSON.stringify(h.snapshot()))
  })

  world.start()
  console.log('[demo] lane-c ready — keys: T re-run ball · L light ball · S surfaces · F fan · P state')
}
