/** Original procedural distributed-electric V-tail aircraft, authored in metres and facing -Z. */

import * as THREE from "three";

export const ELECTRIC_AIRCRAFT_METRICS = Object.freeze({
  length: 8.8,
  wingspan: 12.4,
  height: 2.55,
});

export interface ElectricAircraftMaterials {
  body: THREE.Material;
  accent: THREE.Material;
  glass: THREE.Material;
  dark: THREE.Material;
  rotor: THREE.Material;
  light: THREE.Material;
  redLight: THREE.Material;
  greenLight: THREE.Material;
}

export interface ElectricAircraftBuildOptions {
  materials?: ElectricAircraftMaterials;
  bodyColor?: THREE.ColorRepresentation;
}

export interface ElectricAircraftRig {
  root: THREE.Group;
  leftAileron: THREE.Group;
  rightAileron: THREE.Group;
  leftVTail: THREE.Group;
  rightVTail: THREE.Group;
  fans: readonly THREE.Group[];
  ownsMaterials: boolean;
}

export interface AircraftSurfacePose {
  roll: number;
  pitch: number;
  yaw: number;
}

export function createElectricAircraftMaterials(
  bodyColor: THREE.ColorRepresentation = 0xdfe5e8,
): ElectricAircraftMaterials {
  return {
    body: new THREE.MeshPhysicalMaterial({
      name: "electric-aircraft.body",
      color: bodyColor,
      metalness: 0.22,
      roughness: 0.28,
      clearcoat: 0.86,
      clearcoatRoughness: 0.22,
    }),
    accent: new THREE.MeshPhysicalMaterial({
      name: "electric-aircraft.accent",
      color: 0xc58b22,
      metalness: 0.42,
      roughness: 0.3,
      clearcoat: 0.65,
    }),
    /**
     * The canopy, and the most expensive number ever written in this repo.
     *
     * This material used to carry `transmission: 0.08`, and it cost the
     * California board **304,659 triangles and 191 draw calls** — measured, by
     * patching that one value in a copy of the built bundle and serving both:
     * 703,267 triangles / 562 calls with it, 398,608 / 371 without. That is
     * 43% of the whole frame, for a canopy that is a few dozen pixels of dark
     * glass on a glyph-scale aeroplane.
     *
     * The mechanism is not a cost per pixel, which is why it never showed up as
     * one. three.js runs a **transmission backdrop pass** whenever any rendered
     * material has `transmission > 0`: the entire opaque scene is drawn a second
     * time into a render target for the transparent surface to refract. One
     * value of 0.08 on one small mesh therefore made the board draw its terrain,
     * its blocks and every freeway piece **twice per frame**, everywhere, for
     * every viewer — including the phone.
     *
     * Nothing visible was bought with it. The material is already
     * `transparent` at `opacity: 0.86`, so the canopy reads as dark glass from
     * the alpha blend and the clearcoat; 8% refraction on top of that is below
     * the level anything in this scene resolves at. Shot before and after in
     * the chase camera on the California route — `/?city=california`, "Fly the
     * California route" — and the two frames are indistinguishable.
     *
     * `src/assets/materials.ts` keeps `transmission: 0.92` on the office
     * glazing and should: that is a wall of windows a metre from the camera at
     * 1 unit = 1 m, it is the difference between glass and a grey panel there,
     * and the office scene has four hundred thousand triangles of headroom to
     * pay the pass with. This scene did not.
     */
    glass: new THREE.MeshPhysicalMaterial({
      name: "electric-aircraft.glass",
      color: 0x10232d,
      roughness: 0.08,
      metalness: 0.08,
      transparent: true,
      opacity: 0.86,
      clearcoat: 1,
    }),
    dark: new THREE.MeshStandardMaterial({
      name: "electric-aircraft.dark",
      color: 0x151b20,
      metalness: 0.68,
      roughness: 0.3,
    }),
    rotor: new THREE.MeshBasicMaterial({
      name: "electric-aircraft.rotor-disc",
      color: 0x60717a,
      transparent: true,
      opacity: 0.15,
      depthWrite: false,
      side: THREE.DoubleSide,
    }),
    light: new THREE.MeshBasicMaterial({ name: "electric-aircraft.strobe", color: 0xf1fbff }),
    redLight: new THREE.MeshBasicMaterial({ name: "electric-aircraft.nav-red", color: 0xff3c33 }),
    greenLight: new THREE.MeshBasicMaterial({ name: "electric-aircraft.nav-green", color: 0x44ff9a }),
  };
}

function mesh(
  geometry: THREE.BufferGeometry,
  material: THREE.Material,
  name: string,
): THREE.Mesh {
  const value = new THREE.Mesh(geometry, material);
  value.name = name;
  value.castShadow = true;
  value.receiveShadow = true;
  return value;
}

/** Smooth, tapered fuselage without the toy-like capsule/cone seam of the first asset. */
function fuselageGeometry(): THREE.BufferGeometry {
  const rings = [
    { z: -4.4, y: 0.38, rx: 0.08, ry: 0.08 },
    { z: -4.05, y: 0.4, rx: 0.48, ry: 0.42 },
    { z: -3.35, y: 0.44, rx: 0.7, ry: 0.62 },
    { z: -1.5, y: 0.48, rx: 0.78, ry: 0.73 },
    { z: 0.8, y: 0.48, rx: 0.68, ry: 0.64 },
    { z: 2.7, y: 0.55, rx: 0.4, ry: 0.43 },
    { z: 4.15, y: 0.65, rx: 0.11, ry: 0.12 },
  ] as const;
  const radialSegments = 18;
  const positions: number[] = [];
  const indices: number[] = [];
  for (const ring of rings) {
    for (let segment = 0; segment < radialSegments; segment += 1) {
      const angle = segment / radialSegments * Math.PI * 2;
      positions.push(
        Math.cos(angle) * ring.rx,
        ring.y + Math.sin(angle) * ring.ry,
        ring.z,
      );
    }
  }
  for (let ring = 0; ring < rings.length - 1; ring += 1) {
    for (let segment = 0; segment < radialSegments; segment += 1) {
      const next = (segment + 1) % radialSegments;
      const a = ring * radialSegments + segment;
      const b = ring * radialSegments + next;
      const c = (ring + 1) * radialSegments + segment;
      const d = (ring + 1) * radialSegments + next;
      indices.push(a, c, b, b, c, d);
    }
  }
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
  geometry.setIndex(indices);
  geometry.computeVertexNormals();
  return geometry;
}

/** Closed, dihedral wing prism: visible from above, below, and during a steep bank. */
function wingGeometry(span: number, rootChord: number, tipChord: number): THREE.BufferGeometry {
  const half = span / 2;
  const thickness = 0.18;
  const tipRise = 0.38;
  const planform = [
    [0, -rootChord / 2], [half, -tipChord / 2], [half, tipChord / 2],
    [0, rootChord / 2], [-half, tipChord / 2], [-half, -tipChord / 2],
  ] as const;
  const positions: number[] = [];
  for (const y of [thickness / 2, -thickness / 2]) {
    for (const [x, z] of planform) positions.push(x, y + Math.abs(x / half) * tipRise, z);
  }
  const indices: number[] = [];
  indices.push(0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 5, 4);
  indices.push(6, 7, 8, 6, 8, 9, 6, 9, 10, 6, 10, 11);
  for (let edge = 0; edge < 6; edge += 1) {
    const next = (edge + 1) % 6;
    indices.push(edge, next + 6, edge + 6, edge, next, next + 6);
  }
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
  geometry.setIndex(indices);
  geometry.computeVertexNormals();
  return geometry;
}

function addPanelLine(
  root: THREE.Group,
  points: readonly THREE.Vector3[],
  name: string,
): void {
  const geometry = new THREE.BufferGeometry().setFromPoints([...points]);
  const line = new THREE.Line(
    geometry,
    new THREE.LineBasicMaterial({ color: 0x59656b, transparent: true, opacity: 0.7 }),
  );
  line.name = name;
  root.add(line);
}

const TWO_PI = Math.PI * 2;

function addFan(
  root: THREE.Group,
  x: number,
  ordinal: number,
  materials: ElectricAircraftMaterials,
): THREE.Group {
  const fan = new THREE.Group();
  const side = x < 0 ? "left" : "right";
  const sideOrdinal = ordinal < 3 ? ordinal : 5 - ordinal;
  fan.name = sideOrdinal === 0
    ? `electric-aircraft.fan-${side}`
    : `electric-aircraft.fan-${side}-${sideOrdinal === 1 ? "mid" : "outer"}`;
  fan.position.set(x, 0.5 + Math.abs(x) * 0.035, -0.52 + Math.abs(x) * 0.015);

  const nacelle = mesh(
    new THREE.CapsuleGeometry(0.18, 0.56, 4, 10),
    materials.accent,
    `${fan.name}:nacelle`,
  );
  nacelle.rotation.x = Math.PI / 2;
  nacelle.position.z = 0.16;
  fan.add(nacelle);
  const hub = mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.16, 12), materials.dark, `${fan.name}:hub`);
  hub.rotation.x = Math.PI / 2;
  fan.add(hub);
  for (let index = 0; index < 5; index += 1) {
    const angle = index * TWO_PI / 5;
    const blade = mesh(new THREE.BoxGeometry(0.055, 0.58, 0.018), materials.dark, `${fan.name}:blade-${index}`);
    blade.position.set(-Math.sin(angle) * 0.25, Math.cos(angle) * 0.25, 0);
    blade.rotation.z = angle;
    fan.add(blade);
  }
  const disc = mesh(new THREE.CircleGeometry(0.61, 24), materials.rotor, `${fan.name}:motion-disc`);
  disc.position.z = -0.018;
  fan.add(disc);
  root.add(fan);
  return fan;
}

function addLight(
  root: THREE.Group,
  name: string,
  position: THREE.Vector3Tuple,
  material: THREE.Material,
  radius = 0.075,
): void {
  const value = mesh(new THREE.SphereGeometry(radius, 10, 6), material, name);
  value.position.set(...position);
  value.castShadow = false;
  root.add(value);
}

export function buildElectricAircraft(options: ElectricAircraftBuildOptions = {}): ElectricAircraftRig {
  const ownsMaterials = options.materials === undefined;
  const materials = options.materials ?? createElectricAircraftMaterials(options.bodyColor);
  const root = new THREE.Group();
  root.name = "electric-aircraft";
  root.userData.kind = "aircraft";
  root.userData.aircraftModel = "electric-vtail";
  root.userData.forwardAxis = "-Z";
  root.userData.design = "lumbridge-de6";

  root.add(mesh(fuselageGeometry(), materials.body, "electric-aircraft:fuselage"));

  const lowerNose = mesh(new THREE.ConeGeometry(0.34, 1.25, 16), materials.accent, "electric-aircraft:nose-keel");
  lowerNose.rotation.x = -Math.PI / 2;
  lowerNose.position.set(0, 0.18, -3.9);
  lowerNose.scale.set(1, 0.48, 1);
  root.add(lowerNose);

  const canopy = mesh(new THREE.SphereGeometry(0.72, 20, 10), materials.glass, "electric-aircraft:canopy");
  canopy.scale.set(0.82, 0.55, 1.68);
  canopy.position.set(0, 1.05, -1.0);
  root.add(canopy);
  const canopySpine = mesh(new THREE.BoxGeometry(0.055, 0.07, 2.0), materials.dark, "electric-aircraft:canopy-spine");
  canopySpine.position.set(0, 1.43, -0.98);
  root.add(canopySpine);

  const wing = mesh(wingGeometry(ELECTRIC_AIRCRAFT_METRICS.wingspan, 2.45, 0.82), materials.body, "electric-aircraft:wing");
  wing.position.set(0, 0.5, 0.05);
  root.add(wing);
  addPanelLine(root, [
    new THREE.Vector3(-5.7, 0.98, 0.12),
    new THREE.Vector3(0, 0.6, 0.26),
    new THREE.Vector3(5.7, 0.98, 0.12),
  ], "electric-aircraft:wing-panel-line");

  const leftAileron = new THREE.Group();
  const rightAileron = new THREE.Group();
  leftAileron.name = "electric-aircraft.aileron-left";
  rightAileron.name = "electric-aircraft.aileron-right";
  for (const [joint, x, rise] of [
    [leftAileron, -4.45, 0.29],
    [rightAileron, 4.45, 0.29],
  ] as const) {
    joint.position.set(x, 0.62 + rise, 0.56);
    const surface = mesh(new THREE.BoxGeometry(2.15, 0.09, 0.43), materials.accent, `${joint.name}:surface`);
    surface.rotation.z = x < 0 ? -0.055 : 0.055;
    joint.add(surface);
    root.add(joint);
  }

  const leftVTail = new THREE.Group();
  const rightVTail = new THREE.Group();
  leftVTail.name = "electric-aircraft.v-tail-left";
  rightVTail.name = "electric-aircraft.v-tail-right";
  for (const [joint, x, tilt] of [
    [leftVTail, -0.28, -0.68],
    [rightVTail, 0.28, 0.68],
  ] as const) {
    joint.position.set(x, 0.68, 3.0);
    joint.rotation.z = tilt;
    const surface = mesh(new THREE.BoxGeometry(0.16, 1.88, 1.45), materials.body, `${joint.name}:surface`);
    surface.position.set(0, 0.72, 0.16);
    joint.add(surface);
    const ruddervator = mesh(new THREE.BoxGeometry(0.18, 1.5, 0.34), materials.accent, `${joint.name}:ruddervator`);
    ruddervator.position.set(0, 0.72, 0.88);
    joint.add(ruddervator);
    root.add(joint);
  }

  const belly = mesh(new THREE.BoxGeometry(0.92, 0.22, 2.65), materials.dark, "electric-aircraft:battery-belly");
  belly.position.set(0, -0.16, 0.15);
  root.add(belly);
  for (const x of [-0.56, 0.56]) {
    const door = mesh(new THREE.BoxGeometry(0.38, 0.045, 0.86), materials.accent, `electric-aircraft:gear-door-${x < 0 ? "left" : "right"}`);
    door.position.set(x, -0.29, 0.42);
    root.add(door);
  }

  const fanPositions = [-4.45, -3.05, -1.68, 1.68, 3.05, 4.45] as const;
  const fans = fanPositions.map((x, index) => addFan(root, x, index, materials));

  addLight(root, "electric-aircraft:nav-left", [-6.05, 0.96, 0.08], materials.redLight, 0.09);
  addLight(root, "electric-aircraft:nav-right", [6.05, 0.96, 0.08], materials.greenLight, 0.09);
  addLight(root, "electric-aircraft:strobe-left", [-4.9, 0.98, 0.18], materials.light);
  addLight(root, "electric-aircraft:strobe-right", [4.9, 0.98, 0.18], materials.light);
  addLight(root, "electric-aircraft:tail-light", [0, 1.35, 4.0], materials.light, 0.065);

  root.traverse((item) => {
    if (item instanceof THREE.Mesh) {
      item.geometry.computeBoundingBox();
      item.geometry.computeBoundingSphere();
    }
  });
  return { root, leftAileron, rightAileron, leftVTail, rightVTail, fans, ownsMaterials };
}

export function setAircraftControlSurfaces(
  rig: ElectricAircraftRig,
  pose: Partial<AircraftSurfacePose>,
): void {
  const roll = THREE.MathUtils.clamp(Number.isFinite(pose.roll) ? pose.roll ?? 0 : 0, -1, 1);
  const pitch = THREE.MathUtils.clamp(Number.isFinite(pose.pitch) ? pose.pitch ?? 0 : 0, -1, 1);
  const yaw = THREE.MathUtils.clamp(Number.isFinite(pose.yaw) ? pose.yaw ?? 0 : 0, -1, 1);
  rig.leftAileron.rotation.x = roll * 0.45;
  rig.rightAileron.rotation.x = -roll * 0.45;
  rig.leftVTail.rotation.x = pitch * 0.34 + yaw * 0.22;
  rig.rightVTail.rotation.x = pitch * 0.34 - yaw * 0.22;
}

export function setAircraftFanRotation(rig: ElectricAircraftRig, radians: number): void {
  const safe = Number.isFinite(radians) ? radians : 0;
  for (const fan of rig.fans) fan.rotation.z = safe;
}

export function advanceAircraftFans(rig: ElectricAircraftRig, radians: number): void {
  const delta = Number.isFinite(radians) ? radians : 0;
  for (const fan of rig.fans) fan.rotation.z = (fan.rotation.z + delta) % TWO_PI;
}

export function disposeElectricAircraft(rig: ElectricAircraftRig): void {
  const geometries = new Set<THREE.BufferGeometry>();
  const materials = new Set<THREE.Material>();
  rig.root.traverse((item) => {
    if (item instanceof THREE.Mesh) {
      geometries.add(item.geometry);
      const values = Array.isArray(item.material) ? item.material : [item.material];
      for (const material of values) materials.add(material);
    } else if (item instanceof THREE.Line) {
      geometries.add(item.geometry);
      const values = Array.isArray(item.material) ? item.material : [item.material];
      for (const material of values) materials.add(material);
    }
  });
  for (const geometry of geometries) geometry.dispose();
  if (rig.ownsMaterials) for (const material of materials) material.dispose();
  rig.root.removeFromParent();
  rig.root.clear();
}
