/**
 * Lane B demo — the paint core, standalone. A stub white test ball (NOT Lane A's
 * blob) orbits between a RED and a GREEN cannon. Paint lands where each glob
 * hits, coverage drives buffs (speed/embers/grip/waterproof/mass/glow), and the
 * HUD reads it live. Cleanse scrubs it off.
 *
 * Everything Lane B ships is exercised here: PaintSkin, PaintCannon, BuffSystem,
 * PaintHUD, and the inbound event protocol via installPaint().
 *
 * DEBUG KEYS
 *   1 / 2  fire the RED / GREEN cannon at the ball right now
 *   B / N  emit machine:signal for the RED / GREEN cannon's trigger id
 *   9 / 0  flood ~80% RED / GREEN (fast path to the super state)
 *   G      emit paint:request-splat on the ball (simulates a Lane C bucket dump)
 *   K      emit paint:request-cleanse {0.5} (simulates a Lane C bubble arch)
 *   C      cleanse 50% (scrub holes)      X  cleanse 100% (full clean)
 *   P      pause / resume the ball's orbit (watch the paint sit)
 *   H      toggle the HUD
 * A state line prints to the console every second: coverage %s, buffs, splats/s.
 */
import * as THREE from 'three'
import { createWorld } from '../world'
import { defaultModifiers, PALETTE } from '../contracts'
import type { PaintColor } from '../contracts'
import {
  BuffSystem,
  PaintCannon,
  PaintHUD,
  PaintSkin,
  installPaint,
  COLOR_ORDER,
} from '../paint'

const RED_SIGNAL = 'sig-red'
const GREEN_SIGNAL = 'sig-green'
const ORBIT_R = 3.6
const BALL_R = 0.6

const world = await createWorld(document.getElementById('app')!)
const { scene, camera, physics, rapier, events } = world

camera.position.set(0, 7.5, 12)

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

// ---- stub test ball (our own — NOT Lane A's blob) -------------------------
const ballMesh = new THREE.Mesh(
  new THREE.SphereGeometry(BALL_R, 48, 32),
  new THREE.MeshStandardMaterial({ color: '#F5F5F7', roughness: 0.35, metalness: 0 }),
)
ballMesh.castShadow = true
scene.add(ballMesh)

const ballBody = physics.createRigidBody(
  rapier.RigidBodyDesc.dynamic().setTranslation(ORBIT_R, BALL_R + 0.2, 0).setAngularDamping(0.4),
)
physics.createCollider(
  rapier.ColliderDesc.ball(BALL_R).setRestitution(0.2).setFriction(1.4).setDensity(2),
  ballBody,
)
const baseMass = ballBody.mass()

// the blob-like target the paint systems operate on
const blob = {
  mesh: ballMesh,
  body: ballBody,
  modifiers: defaultModifiers(),
  paint: new PaintSkin(ballMesh, { size: 256 }),
}

// ---- paint systems --------------------------------------------------------
const hud = new PaintHUD()

const redCannon = new PaintCannon({
  world,
  position: new THREE.Vector3(-7, 0, -1.5),
  color: 'red',
  target: ballMesh,
  paint: blob.paint,
  targetRadius: BALL_R,
  triggerId: RED_SIGNAL,
  interval: 0.9,
  muzzleSpeed: 15,
  splatRadius: 0.3,
})
const greenCannon = new PaintCannon({
  world,
  position: new THREE.Vector3(7, 0, 1.5),
  color: 'green',
  target: ballMesh,
  paint: blob.paint,
  targetRadius: BALL_R,
  triggerId: GREEN_SIGNAL,
  interval: 1.0,
  muzzleSpeed: 15,
  splatRadius: 0.3,
})

const buffs = new BuffSystem(world, blob)

// inbound Lane C protocol → this blob (proximity-gated splats + cleanse)
installPaint(world, { mesh: ballMesh, paint: blob.paint })

world.addSystem(redCannon)
world.addSystem(greenCannon)
world.addSystem(buffs)

// ---- ball orbit controller (reads modifiers so buffs visibly change it) ---
let paused = false
world.addSystem({
  update(dt: number) {
    // paint = weight: reflect massMul in the physics body
    ballBody.setAdditionalMass(Math.max(0, baseMass * (blob.modifiers.massMul - 1)), true)

    const p = ballBody.translation()
    if (!paused) {
      // chase a point a little ahead on the ring; force-based so it rolls
      const cur = Math.atan2(p.z, p.x)
      const targetAngle = cur + 0.6
      const tx = Math.cos(targetAngle) * ORBIT_R
      const tz = Math.sin(targetAngle) * ORBIT_R
      const dx = tx - p.x
      const dz = tz - p.z
      const len = Math.hypot(dx, dz) || 1
      const push = 9 * blob.modifiers.speedMul // BURN makes it faster
      ballBody.applyImpulse({ x: (dx / len) * push * dt, y: 0, z: (dz / len) * push * dt }, true)

      // cap horizontal speed (scaled by speed buff)
      const v = ballBody.linvel()
      const sp = Math.hypot(v.x, v.z)
      const maxSp = 4.2 * blob.modifiers.speedMul
      if (sp > maxSp) {
        const k = maxSp / sp
        ballBody.setLinvel({ x: v.x * k, y: v.y, z: v.z * k }, true)
      }
    }

    // sync mesh to body (rotation included → paint rides the spinning body)
    ballMesh.position.set(p.x, p.y, p.z)
    const q = ballBody.rotation()
    ballMesh.quaternion.set(q.x, q.y, q.z, q.w)
  },
})

// ---- camera ---------------------------------------------------------------
world.onFrame(() => {
  camera.lookAt(ballMesh.position.x * 0.3, 0.5, ballMesh.position.z * 0.3)
})

// ---- debug keys -----------------------------------------------------------
function floodColor(color: PaintColor, splats = 130) {
  for (let i = 0; i < splats; i++) {
    blob.paint.applySplat(
      new THREE.Vector2(Math.random(), 0.1 + Math.random() * 0.8),
      color,
      0.55,
    )
  }
  console.log(`[flood] ${color} — ${splats} splats`)
}

addEventListener('keydown', (e) => {
  switch (e.code) {
    case 'Digit1':
      redCannon.fire()
      break
    case 'Digit2':
      greenCannon.fire()
      break
    case 'KeyB':
      events.emit('machine:signal', { id: RED_SIGNAL })
      console.log('[signal] machine:signal', RED_SIGNAL)
      break
    case 'KeyN':
      events.emit('machine:signal', { id: GREEN_SIGNAL })
      console.log('[signal] machine:signal', GREEN_SIGNAL)
      break
    case 'Digit9':
      floodColor('red')
      break
    case 'Digit0':
      floodColor('green')
      break
    case 'KeyG': {
      // simulate a Lane C bucket dump (BLUE) landing on the ball
      const pos = ballMesh.position.clone().add(new THREE.Vector3(0, BALL_R, 0))
      events.emit('paint:request-splat', { point: pos, color: 'blue' as PaintColor, radius: 0.35 })
      console.log('[request-splat] blue on ball')
      break
    }
    case 'KeyK':
      events.emit('paint:request-cleanse', { fraction: 0.5 })
      console.log('[request-cleanse] 0.5')
      break
    case 'KeyC':
      blob.paint.cleanse(0.5)
      console.log('[cleanse] 0.5 (scrub)')
      break
    case 'KeyX':
      blob.paint.cleanse(1)
      console.log('[cleanse] full')
      break
    case 'KeyP':
      paused = !paused
      console.log(`[orbit] ${paused ? 'paused' : 'running'}`)
      break
    case 'KeyH':
      hud.root.style.display = hud.root.style.display === 'none' ? '' : 'none'
      break
  }
})

// ---- HUD + console state line ---------------------------------------------
let splatCount = 0
events.on('paint:splatted', (p: { color: PaintColor; target: string }) => {
  if (p.target === 'blob') splatCount++
})

let lastLog = performance.now()
world.onFrame(() => {
  const cov = blob.paint.coverage()
  hud.update(cov, blob.modifiers)

  const now = performance.now()
  if (now - lastLog >= 1000) {
    lastLog = now
    const pcts = COLOR_ORDER.filter((c) => cov.byColor[c] > 0.005)
      .map((c) => `${c}:${(cov.byColor[c] * 100).toFixed(0)}%`)
      .join(' ')
    const m = blob.modifiers
    const buffStr = [
      m.speedMul > 1.001 ? `speed×${m.speedMul.toFixed(2)}` : '',
      m.grip > 0.001 ? `grip${m.grip.toFixed(2)}` : '',
      m.waterproof ? 'waterproof' : '',
      `mass×${m.massMul.toFixed(2)}`,
      m.glow > 0 ? 'SUPER' : '',
    ].filter(Boolean).join(' ')
    console.log(
      `[paint] total ${(cov.total * 100).toFixed(0)}% | ${pcts || '(clean)'} | ${buffStr} | +${splatCount} splats/s`,
    )
    splatCount = 0
  }
})

// friendly banner
console.log(
  '%cBLOBBO Lane B — Paint demo',
  `color:${PALETTE.red};font-weight:700`,
)
console.log('keys: 1/2 fire  B/N signal  9/0 flood  G request-splat  K request-cleanse  C/X cleanse  P pause  H hud')

world.start()
