/**
 * Luminaires: a pendant and a recessed troffer.
 *
 * These are the two assets in the library whose datum is the ceiling rather
 * than the floor. Their origin is the **mounting plane** and all of their
 * geometry is below it (`y ≤ 0`), so a pack writes `elevation: 2.9` and gets a
 * lamp hanging at 2.9 m — rather than a lamp whose author had to know the
 * ceiling height of a room they have never seen. `footprint().height` is the
 * total drop.
 *
 * They emit no light. The office's lighting is a fixed rig owned by the scene
 * (CONTRACT.md §4), and a hundred fixtures each carrying a `PointLight` is both
 * the wrong owner and, past about four shadow-casting lights, the end of the
 * frame budget. What a fixture contributes is a glowing `lightDiffuser`, which
 * is what you actually see.
 */

import * as THREE from "three";
import { defineAsset } from "../kit.ts";
import { MeshBin } from "../parts.ts";

/**
 * A diffuser is the one thing in the office that should never cast a shadow —
 * it is the thing the light is coming out of, and a lamp that shadows the room
 * beneath it looks broken in a way nobody can name.
 */
function litGroup(name: string, body: MeshBin, glow: MeshBin): THREE.Group {
  const group = new THREE.Group();
  group.name = name;
  group.add(body.build(`${name}:body`));
  group.add(glow.build(`${name}:glow`, { castShadow: false, receiveShadow: false }));
  return group;
}

type PendantParams = {
  /** Mounting plane to the bottom of the shade. */
  drop: number;
  shadeDiameter: number;
  shadeHeight: number;
};

export const lightPendant = defineAsset<PendantParams>({
  id: "tera:light.pendant",
  label: "Pendant lamp",
  defaults: { drop: 0.9, shadeDiameter: 0.34, shadeHeight: 0.22 },

  footprint(p) {
    return { width: p.shadeDiameter, depth: p.shadeDiameter, height: p.drop };
  },

  build(p, ctx) {
    const P = ctx.parts;
    const body = new MeshBin();
    const glow = new MeshBin();
    const housing = ctx.materials.get("lightHousing");

    // A ceiling rose, then the flex, then the shade. The rose matters more than
    // it should: a cord that stops dead at the ceiling plane reads as a
    // modelling mistake from the first time anybody looks up.
    body.add(P.cylinder(12), housing, { y: -0.028, size: [0.1, 0.028, 0.1] });

    const cord = Math.max(0.02, p.drop - p.shadeHeight);
    body.add(P.rod(), housing, { y: -cord, size: [0.012, cord, 0.012] });
    // The cone's wide end is its base, so dropped to the bottom of the shade it
    // is already the right way up for a pendant.
    body.add(P.cone(20), housing, {
      y: -p.drop,
      size: [p.shadeDiameter, p.shadeHeight, p.shadeDiameter],
    });
    glow.add(P.disc(20), ctx.materials.get("lightDiffuser"), {
      y: -p.drop + 0.006,
      size: [p.shadeDiameter * 0.9, 1, p.shadeDiameter * 0.9],
      // A floor-plane disc faces up. Flipped, it faces the room.
      pitch: Math.PI,
    });

    return litGroup("light.pendant", body, glow);
  },
});

type TrofferParams = {
  length: number;
  width: number;
  /** How far the housing hangs below the mounting plane. */
  housingDepth: number;
};

export const lightTroffer = defineAsset<TrofferParams>({
  id: "tera:light.troffer",
  label: "Ceiling troffer",
  defaults: { length: 1.2, width: 0.3, housingDepth: 0.08 },

  footprint(p) {
    return { width: p.length, depth: p.width, height: p.housingDepth };
  },

  build(p, ctx) {
    const P = ctx.parts;
    const body = new MeshBin();
    const glow = new MeshBin();

    const tray = Math.max(0.02, p.housingDepth - 0.02);
    body.add(P.box(), ctx.materials.get("lightHousing"), {
      y: -tray,
      size: [p.length, tray, p.width],
    });
    // The frame is four bars rather than a slab behind the diffuser: from below
    // — the only angle a recessed fitting is ever seen from — a slab is
    // invisible and the bars are the whole of what reads as a light fitting.
    const trim = ctx.materials.get("metalTrim");
    const bar = 0.02;
    for (const sz of [-1, 1]) {
      body.add(P.box(), trim, {
        y: -p.housingDepth,
        z: (sz * (p.width - bar)) / 2,
        size: [p.length, bar, bar],
      });
      body.add(P.box(), trim, {
        x: (sz * (p.length - bar)) / 2,
        y: -p.housingDepth,
        size: [bar, bar, p.width - 2 * bar],
      });
    }
    glow.add(P.box(), ctx.materials.get("lightDiffuser"), {
      y: -p.housingDepth + 0.002,
      size: [p.length - 2 * bar, 0.018, p.width - 2 * bar],
    });

    return litGroup("light.troffer", body, glow);
  },
});
