// flight/ship.js (Lane B) — ENDO-1, the probe you fly, and the reticle you aim with.
//
// Procedural primitives merged into ONE geometry: assets are optional by law (TECH §manifest),
// so this is the shipping look until Lane D's GLB lands, and the permanent fallback after.
// Convention: the hull is built nose-toward −Z so Object3D.lookAt() aims it with no fudge.

import * as THREE from 'three';
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
import { emissiveMat, EMISSIVE } from './emissive.js';

// D's GLB swaps in here when it exists; logic above never learns which one it got.
export function buildShip(assets = null) {
  const group = new THREE.Group();
  group.name = 'endo1';

  const glb = assets?.get?.('models', 'endo1') ?? null;
  const hull = glb ? glb.scene ?? glb : proceduralHull();
  group.add(hull);

  // engine bloom at the tail: additive, unlit, reads as thrust without a light
  const glowGeo = new THREE.SphereGeometry(0.34, 12, 8);
  const glowMat = emissiveMat(EMISSIVE.neutral, { additive: true, opacity: 0.9 });
  const glow = new THREE.Mesh(glowGeo, glowMat);
  glow.position.z = 0.95;
  group.add(glow);

  const disposables = [glowGeo, glowMat];
  if (!glb) { disposables.push(hull.geometry, hull.material); }

  return {
    group,
    glow,
    setThrust(t) { glow.scale.setScalar(0.7 + t * 0.9); glowMat.uniforms.uOpacity.value = 0.5 + t * 0.5; },
    setFlash(v) { if (hull.material?.uniforms?.uFlash) hull.material.uniforms.uFlash.value = v; },
    dispose() { for (const d of disposables) d?.dispose?.(); },
  };
}

function proceduralHull() {
  const parts = [];

  const body = new THREE.CapsuleGeometry(0.4, 0.9, 4, 12);
  body.rotateX(Math.PI / 2);                 // capsule is +Y by default; we fly down −Z
  parts.push(body);

  const nose = new THREE.ConeGeometry(0.4, 0.85, 12);
  nose.rotateX(-Math.PI / 2);
  nose.translate(0, 0, -1.08);
  parts.push(nose);

  for (const side of [-1, 1]) {
    const fin = new THREE.BoxGeometry(0.62, 0.07, 0.44);
    fin.translate(side * 0.52, -0.04, 0.36);
    fin.rotateZ(side * -0.22);
    parts.push(fin);
  }

  const collar = new THREE.TorusGeometry(0.42, 0.06, 6, 14);
  collar.translate(0, 0, -0.28);
  parts.push(collar);

  const geo = mergeGeometries(parts, false);
  for (const p of parts) p.dispose();
  return new THREE.Mesh(geo, emissiveMat(EMISSIVE.neutral));
}

// The reticle floats TUNING.aim.distance ahead in the disc plane. Depth-test off: it is an
// instrument reading, not an object in the gut — it must never be swallowed by a fold.
export function buildReticle() {
  const geo = new THREE.RingGeometry(0.55, 0.72, 20);
  const mat = emissiveMat(EMISSIVE.neutral, { additive: true, opacity: 0.85 });
  mat.depthTest = false;
  const mesh = new THREE.Mesh(geo, mat);
  mesh.renderOrder = 10;

  const dotGeo = new THREE.CircleGeometry(0.12, 10);
  const dot = new THREE.Mesh(dotGeo, mat);
  dot.renderOrder = 10;
  mesh.add(dot);

  return {
    mesh,
    setHot(v) { mat.uniforms.uFlash.value = v; },       // hot while the cannon is firing
    dispose() { geo.dispose(); dotGeo.dispose(); mat.dispose(); },
  };
}
