/**
 * Lane A — greybox course chunk ("Breakfast Rush", primitive edition).
 *
 * A big, readable, absurdly-proportioned play space built from boxes so the blob
 * has somewhere to strut, ramp, gap-jump, slide and splash. Everything sits on
 * one generous base floor so a missed jump just drops you a little — never into
 * the void. Surfaces that mean something to other lanes are tagged on the mesh
 * `userData` (e.g. `{ surface: 'water' }`), and colliders carry the same tag so
 * Lane C can look either up.
 *
 *   +Z is "behind the start"; the course runs toward -Z (into the camera view).
 */
import * as THREE from 'three'
import type { World } from '../contracts'
import { assets } from '../assets/registry'
import type { SlotId } from '../assets/slots'

export interface GreyboxHandle {
  /** Recommended blob spawn point (just above the start plateau). */
  spawn: THREE.Vector3
  /** Center of the open flat area where Lane C drops machines at integration. */
  machineArea: THREE.Vector3
  meshes: THREE.Mesh[]
}

interface BoxOpts {
  color: THREE.ColorRepresentation
  /** Euler rotation in radians (applied to both mesh and collider). */
  rot?: THREE.Euler
  /** Arbitrary surface/zone tag copied to mesh.userData AND collider.userData. */
  surface?: string
  transparent?: number // opacity 0..1 when set
  roughness?: number
  cast?: boolean
}

export function buildGreybox(world: World): GreyboxHandle {
  const { scene, physics, rapier } = world
  const meshes: THREE.Mesh[] = []

  /** Add a static box: THREE mesh + fixed cuboid collider, optionally rotated. */
  function addBox(
    cx: number, cy: number, cz: number,
    hx: number, hy: number, hz: number,
    o: BoxOpts,
  ): THREE.Mesh {
    const mat = new THREE.MeshStandardMaterial({
      color: o.color,
      roughness: o.roughness ?? 0.9,
      metalness: 0.0,
      transparent: o.transparent !== undefined,
      opacity: o.transparent ?? 1,
    })
    const mesh = new THREE.Mesh(new THREE.BoxGeometry(hx * 2, hy * 2, hz * 2), mat)
    mesh.position.set(cx, cy, cz)
    mesh.receiveShadow = true
    mesh.castShadow = o.cast ?? false
    if (o.rot) mesh.rotation.copy(o.rot)
    if (o.surface) mesh.userData.surface = o.surface
    scene.add(mesh)
    meshes.push(mesh)

    const desc = rapier.ColliderDesc.cuboid(hx, hy, hz).setTranslation(cx, cy, cz)
    if (o.rot) {
      const q = new THREE.Quaternion().setFromEuler(o.rot)
      desc.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w })
    }
    const collider = physics.createCollider(desc)
    // Mirror the tag onto the collider so Lane C can query from a physics hit.
    if (o.surface) (collider as unknown as { userData?: unknown }).userData = { surface: o.surface }
    return mesh
  }

  /**
   * Park a custom prop inside a greybox box. The box's material is switched off
   * rather than the mesh itself so the prop (a child) still renders, and the
   * mesh stays in `meshes[]` so the handle's contract is unchanged.
   */
  function decorate(slot: SlotId, mesh: THREE.Mesh): void {
    assets().attachSlot(slot, mesh, {
      onSwap: () => { (mesh.material as THREE.Material).visible = false },
    })
  }

  // ---- base floor: catches everything, spans the whole course ----
  addBox(0, -0.5, -15, 28, 0.5, 60, { color: '#efe3c6', roughness: 1 })

  // ---- start plateau (raised cream pad) ----
  addBox(0, 0.75, 30, 10, 0.75, 9, { color: '#f7edd2', cast: true })

  // ---- ramp up (orange) : lifts the -Z end so you climb toward the high shelf ----
  addBox(0, 1.7, 14, 6, 0.4, 8, {
    color: '#FF9500', rot: new THREE.Euler(0.42, 0, 0), cast: true,
  })

  // ---- gap jump: two blue shelves with a real gap over the base floor ----
  addBox(0, 3.0, 6, 7, 0.5, 4, { color: '#0A84FF', cast: true })   // high shelf, z[2..10]
  addBox(0, 3.0, -6, 7, 0.5, 4, { color: '#0A84FF', cast: true })  // landing,   z[-10..-2]

  // ---- down slope (green) : back to ground level ----
  addBox(0, 1.7, -16, 6, 0.4, 8, {
    color: '#34C759', rot: new THREE.Euler(-0.36, 0, 0), cast: true,
  })

  // ---- milk river (water zone) : thin translucent slab flush with the floor ----
  addBox(0, 0.06, -30, 14, 0.06, 7, {
    color: '#f4f6ff', surface: 'water', transparent: 0.72, roughness: 0.2,
  })

  // ---- open flat area : Lane C lands machines here at integration ----
  const machineArea = new THREE.Vector3(0, 0.12, -48)
  addBox(machineArea.x, 0.12, machineArea.z, 16, 0.12, 11, {
    color: '#d9c9a0', surface: 'machine-area',
  })

  // ---- finish pad (bright pink, raised) ----
  addBox(0, 0.6, -64, 9, 0.6, 6, { color: '#FF6EB4', surface: 'finish', cast: true })

  // ---- absurd-proportion scenery: a giant cereal box beside the start ----
  // Slot hooks go on the MESH only, after the collider exists: the box volume
  // is still solid whatever model is dropped in, so a custom prop can never
  // open a hole in the course or grow an invisible wall.
  decorate('course.scenery.cereal', addBox(-20, 7, 26, 4, 7, 3, { color: '#FFD60A', cast: true }))
  decorate('course.scenery.block', addBox(20, 5, 12, 3, 5, 3, { color: '#AF52DE', cast: true }))

  return {
    spawn: new THREE.Vector3(0, 3.5, 30),
    machineArea,
    meshes,
  }
}
