/**
 * DATA-DRIVEN COURSES — a course is a plain object (boxes + machines + cannons +
 * spawn/finish), and `buildCourseFromSpec` turns it into a live course by
 * dispatching to the existing greybox box builder and the Lane C machine
 * factories. No new gameplay code: this is pure plumbing so a new level is
 * *data*, not a game.ts fork.
 *
 * The flagship "Breakfast Rush" course stays hand-built (it carries a lot of
 * integration tuning) — see game.ts. New courses live in COURSES below and load
 * with `?course=<name>`.
 */
import * as THREE from 'three'
import type { World, PaintColor } from '../contracts'
import type { PaintSkin } from '../paint/skin'
import { PaintCannon } from '../paint/index'
import {
  createPaintMine, createColorGate, createSpringBoot, createSeeSaw, createFan,
  createBucketDump, createBubbleArch, createConveyorBelt, createPressurePlate,
  createPaintBubbles, createDominoChain, applySurface,
} from '../machine/index'
import type {
  PaintMineConfig, ColorGateConfig, SpringBootConfig, SeeSawConfig, FanConfig,
  BucketDumpConfig, BubbleArchConfig, ConveyorBeltConfig, PressurePlateConfig,
  PaintBubblesConfig, DominoChainConfig, SurfaceName,
} from '../machine/index'
import { TOASTER_ALLEY } from './toaster-alley'
import { SEESAW_LAB } from './seesaw-lab'
import { BUTTER_RUN } from './butter-run'

export interface FinishBox { xMin: number; xMax: number; zMin: number; zMax: number; yMax: number }

export interface BoxSpec {
  pos: [number, number, number]
  /** half-extents [hx, hy, hz]. */
  half: [number, number, number]
  color: THREE.ColorRepresentation
  rot?: [number, number, number]
  surface?: SurfaceName
  cast?: boolean
  transparent?: number
  roughness?: number
}

/** A machine placement. `cfg` is the exact factory config; the gate's qualifies
 *  is derived from needColor/needPct so the data stays a plain object. */
export type MachineSpec =
  | { kind: 'mine'; cfg: PaintMineConfig }
  | { kind: 'gate'; cfg: Omit<ColorGateConfig, 'qualifies'>; needColor: PaintColor; needPct: number }
  | { kind: 'boot'; cfg: SpringBootConfig }
  | { kind: 'seesaw'; cfg: SeeSawConfig }
  | { kind: 'fan'; cfg: FanConfig }
  | { kind: 'bucket'; cfg: BucketDumpConfig }
  | { kind: 'bubble'; cfg: BubbleArchConfig }
  | { kind: 'plate'; cfg: PressurePlateConfig }
  | { kind: 'conveyor'; cfg: ConveyorBeltConfig }
  | { kind: 'bubbles'; cfg: PaintBubblesConfig }
  | { kind: 'dominoes'; cfg: DominoChainConfig }
  | { kind: 'oil'; pos: [number, number, number]; half: [number, number, number] }

export interface CannonSpec {
  color: PaintColor
  position: [number, number, number]
  triggerId?: string
  interval?: number
}

export interface CourseSpec {
  name: string
  spawn: [number, number, number]
  finish: FinishBox
  boxes: BoxSpec[]
  machines?: MachineSpec[]
  cannons?: CannonSpec[]
  /** Fall-respawn points, in course order (see course/checkpoints.ts). */
  checkpoints?: Array<[number, number, number]>
}

export interface BuildCtx {
  blob: { mesh: THREE.Object3D; radius: number }
  skin: PaintSkin
}

/** Static box: THREE mesh + fixed cuboid collider, optional rotation + surface
 *  tag (mirrored onto the collider so the controller can resolve it). Mirrors
 *  greybox.ts's addBox so spec courses look identical to the hand-built one. */
function addBox(world: World, b: BoxSpec): void {
  const [cx, cy, cz] = b.pos
  const [hx, hy, hz] = b.half
  const mat = new THREE.MeshStandardMaterial({
    color: b.color,
    roughness: b.roughness ?? 0.9,
    metalness: 0,
    transparent: b.transparent !== undefined,
    opacity: b.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 = b.cast ?? false
  if (b.rot) mesh.rotation.set(b.rot[0], b.rot[1], b.rot[2])
  world.scene.add(mesh)

  const desc = world.rapier.ColliderDesc.cuboid(hx, hy, hz).setTranslation(cx, cy, cz)
  if (b.rot) {
    const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(b.rot[0], b.rot[1], b.rot[2]))
    desc.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w })
  }
  const col = world.physics.createCollider(desc)
  if (b.surface) {
    applySurface(world.rapier, col, b.surface)
    ;(col as unknown as { userData?: unknown }).userData = { surface: b.surface }
  }
}

export function buildCourseFromSpec(
  world: World,
  spec: CourseSpec,
  ctx: BuildCtx,
): { spawn: THREE.Vector3; finish: FinishBox } {
  for (const b of spec.boxes) addBox(world, b)

  for (const m of spec.machines ?? []) {
    switch (m.kind) {
      case 'mine': createPaintMine(world, m.cfg); break
      case 'gate':
        createColorGate(world, {
          ...m.cfg,
          qualifies: () => ctx.skin.coverage().byColor[m.needColor] >= m.needPct,
        })
        break
      case 'boot': createSpringBoot(world, m.cfg); break
      case 'seesaw': createSeeSaw(world, m.cfg); break
      case 'fan': createFan(world, m.cfg); break
      case 'bucket': createBucketDump(world, m.cfg); break
      case 'bubble': createBubbleArch(world, m.cfg); break
      case 'plate': createPressurePlate(world, m.cfg); break
      case 'conveyor': createConveyorBelt(world, m.cfg); break
      case 'bubbles': createPaintBubbles(world, m.cfg); break
      case 'dominoes': createDominoChain(world, m.cfg); break
      case 'oil': {
        const [cx, cy, cz] = m.pos
        const [hx, hy, hz] = m.half
        const mat = new THREE.MeshStandardMaterial({ color: '#3a2f4a', roughness: 0.12, metalness: 0.35 })
        const mesh = new THREE.Mesh(new THREE.BoxGeometry(hx * 2, hy * 2, hz * 2), mat)
        mesh.position.set(cx, cy, cz)
        mesh.receiveShadow = true
        world.scene.add(mesh)
        const col = world.physics.createCollider(
          world.rapier.ColliderDesc.cuboid(hx, hy, hz).setTranslation(cx, cy, cz))
        applySurface(world.rapier, col, 'oil')
        ;(col as unknown as { userData?: unknown }).userData = { surface: 'oil' }
        break
      }
    }
  }

  for (const c of spec.cannons ?? []) {
    world.addSystem(new PaintCannon({
      world,
      position: new THREE.Vector3(...c.position),
      color: c.color,
      target: ctx.blob.mesh,
      paint: ctx.skin,
      targetRadius: ctx.blob.radius,
      triggerId: c.triggerId,
      interval: c.interval ?? 2.2,
      muzzleSpeed: 26,
      splatRadius: 0.45,
    }))
  }

  return { spawn: new THREE.Vector3(...spec.spawn), finish: spec.finish }
}

/** Selectable data-driven courses (the flagship Breakfast Rush is hand-built in
 *  game.ts and is the default). Load with `?course=<name>`. */
export const COURSES: Record<string, CourseSpec> = {
  toaster: TOASTER_ALLEY,
  butter: BUTTER_RUN,
  seesaw: SEESAW_LAB, // tuning lab — URL-only, not in the switcher rotation
}

/** The playable rotation, in order — one source of truth for the title screen's
 *  course select and game.ts's in-game switcher. `course` undefined = the
 *  hand-built default (Breakfast Rush), whose best/ghost use the legacy keys. */
export const COURSE_META: Array<{
  course?: string
  label: string
  emoji: string
  href: string
}> = [
  { label: 'Breakfast Rush', emoji: '🍳', href: 'index.html' },
  { course: 'toaster', label: 'Toaster Alley', emoji: '🍞', href: '?course=toaster' },
  { course: 'butter', label: 'Butter Run', emoji: '🧈', href: '?course=butter' },
]
