/**
 * LANE-RENDER — tile/world coordinate math. The single place the CONTRACTS.md
 * conventions are encoded: tile grid, origin at world centre, +x east, +y south,
 * Dir 0..3 = N,E,S,W clockwise, machine `pos` = origin (min) corner.
 *
 * Mapping: tile (x, y) -> three.js world (x, 0, y). Tile (x,y) spans [x,x+1]x[y,y+1].
 */
import * as THREE from 'three';
import type { Dir, MachineDef, Vec2 } from '../contracts';

export const DIR_VEC: Record<Dir, Vec2> = {
  0: { x: 0, y: -1 }, // N
  1: { x: 1, y: 0 }, // E
  2: { x: 0, y: 1 }, // S
  3: { x: -1, y: 0 }, // W
};

/** Placeholders are authored facing N (-z). Yaw -90° per dir step: N->E->S->W. */
export function yawFor(dir: Dir): number {
  return (-dir * Math.PI) / 2;
}

/** E/W rotation swaps the footprint's width and depth. */
export function rotatedFootprint(def: MachineDef, dir: Dir): Vec2 {
  return dir === 1 || dir === 3
    ? { x: def.footprint.y, y: def.footprint.x }
    : { x: def.footprint.x, y: def.footprint.y };
}

/** Centre of a machine's rotated footprint, in world units, on the ground plane. */
export function centerOf(pos: Vec2, def: MachineDef, dir: Dir, out = new THREE.Vector3()): THREE.Vector3 {
  const f = rotatedFootprint(def, dir);
  return out.set(pos.x + f.x / 2, 0, pos.y + f.y / 2);
}

/** Every tile a machine occupies. Used for ghost occupancy checks. */
export function tilesOf(pos: Vec2, def: MachineDef, dir: Dir): Vec2[] {
  const f = rotatedFootprint(def, dir);
  const out: Vec2[] = [];
  for (let dx = 0; dx < f.x; dx++) {
    for (let dy = 0; dy < f.y; dy++) out.push({ x: pos.x + dx, y: pos.y + dy });
  }
  return out;
}

export const tileKey = (x: number, y: number): number => (x + 512) * 4096 + (y + 512);

/** Free all geometry/material owned by a subtree before dropping it. */
export function disposeObject(root: THREE.Object3D): void {
  root.traverse((o) => {
    const m = o as THREE.Mesh;
    if (!m.isMesh) return;
    m.geometry?.dispose();
    const mat = m.material;
    if (Array.isArray(mat)) mat.forEach((x) => x.dispose());
    else mat?.dispose();
  });
}
