/**
 * Screens: the one on a desk and the one on a wall.
 *
 * Both have their glass at +Z, facing the person, and both are authored with
 * their origin on the floor. The monitor's foot sits at `y = 0` and a pack
 * raises it onto a desk with `Prop.elevation`; the wall display carries its own
 * `mount` height, because how high a display hangs is a property of the display
 * and not of the room it is in.
 *
 * ### A screen is three surfaces, not one
 *
 * Both of these used to be a bezel with a flat `screenDisplay` panel glued to
 * it, and on the live site every display in the building was one uniform glowing
 * rectangle. Under the tone curve `stage.ts` now runs it was worse than uniform:
 * a 40%-emissive white panel clips, so a monitor, a wall display and a whiteboard
 * were all the same shade of blown-out white.
 *
 * The fix is to separate the three things a display physically is:
 *
 *  - **`screenBezel`** — the moulded surround, a lit surface like any other;
 *  - **`screenDisplay`** — the *dark* panel, the black border of glass around the
 *    active area and what an off screen looks like;
 *  - **`screenContent`** — the active area, carrying the `screenUI` drawing as
 *    both `map` and `emissiveMap`. That second binding is the whole point: with
 *    a flat glow the drawn interface is a pattern printed on a lamp, and with the
 *    map on `emissiveMap` the lit pixels emit and the dark chrome between them
 *    does not. It is the difference between a monitor and a light box.
 *
 * ### Where the layout comes from, and why it cannot be random
 *
 * `screenUI` draws `SCREEN_UI_VARIANTS` different layouts, and a wall of screens
 * showing the same one is the same defect one level down. But an asset cannot
 * roll for a layout per instance: `furnish.ts` batches props **per kind** and
 * draws `ctx.rand` once for the whole batch, so twelve monitors in one batch
 * would roll once between them and get one layout anyway.
 *
 * So the layout arrives as *authored data*: it is derived from the prop's
 * `colorKey`, which is already part of the batch key. A pack that writes
 * `colorKey: "ui-b"` on half its monitors gets two batches, two materials and
 * two layouts, and gets them deterministically — the same pack renders the same
 * wall of screens on every reload. `colorKey` is opaque here in exactly the way
 * `ARCHITECTURE.md` §3.3 requires: this file will never learn that `"ui-b"` means
 * anything, it only hashes it.
 *
 * Neither asset tints. A screen is bezel and glass, and there is no part of it
 * anybody wants to be the colour of a team.
 */

import { defineAsset, type AssetContext } from "../kit.ts";
import { MeshBin } from "../parts.ts";
import { alongFacing } from "./common.ts";

/**
 * The layout this instance's batch shows, from its `colorKey`.
 *
 * FNV-1a, the same four lines `furnish.ts` uses to seed a batch, and for the
 * same reason: any stable string-to-number would do and this one needs no
 * dependency. An absent key gives layout 0 rather than a random one, so a pack
 * that says nothing gets the plainest screen rather than an arbitrary one.
 */
function layoutFor(ctx: AssetContext): number {
  const key = ctx.colorKey;
  if (!key) return 0;
  let h = 0x811c9dc5;
  for (let i = 0; i < key.length; i++) {
    h ^= key.charCodeAt(i);
    h = Math.imul(h, 0x01000193);
  }
  return h >>> 0;
}

/**
 * The dark panel and the lit content face, as two quads a fraction of a
 * millimetre apart.
 *
 * The dark one is the full active-area rectangle and the lit one is inset by the
 * black border every panel has, so the content never runs to the edge of the
 * glass — which is the single cue that separates a screen from a sheet of paper
 * with a picture on it. `screenContent` is `DoubleSide`-free and faces +Z, so
 * both quads are drawn only from the front.
 */
function displayFace(
  bin: MeshBin,
  ctx: AssetContext,
  face: {
    y: number;
    z: number;
    width: number;
    height: number;
    pitch?: number;
    /** Black border between the glass edge and the drawn content, metres. */
    border: number;
  },
): void {
  const pitch = face.pitch ?? 0;
  const dark = ctx.materials.get("screenDisplay");
  const lit = ctx.materials.variant("screenContent", layoutFor(ctx));

  bin.add(ctx.parts.panel(), dark, {
    y: face.y,
    z: face.z,
    size: [face.width, face.height, 1],
    pitch,
  });
  const proud = alongFacing(pitch, 0.0015);
  bin.add(ctx.parts.panel(), lit, {
    y: face.y + face.border + proud.y,
    z: face.z + proud.z,
    size: [face.width - face.border * 2, face.height - face.border * 2, 1],
    pitch,
  });
}

type MonitorParams = {
  /** Bezel width, metres. 0.56 is a 24-inch panel. */
  width: number;
  height: number;
  /** Floor of the stand to the bottom of the bezel. */
  standHeight: number;
  /** Radians the panel leans back. */
  tilt: number;
};

export const screenMonitor = defineAsset<MonitorParams>({
  id: "tera:screen.monitor",
  label: "Monitor",
  defaults: { width: 0.56, height: 0.34, standHeight: 0.14, tilt: 0.07 },

  footprint(p) {
    return { width: p.width, depth: 0.19, height: p.standHeight + p.height + 0.02 };
  },

  build(p, ctx) {
    const P = ctx.parts;
    const bin = new MeshBin();
    const trim = ctx.materials.get("metalTrim");
    const bezel = ctx.materials.get("screenBezel");

    // `metalTrim` here is all `roundedBoxOf` and `screenBezel` is all
    // `roundedBoxOf` too — see the indexed/non-indexed rule in `common.ts`. A
    // 4 mm chamfer on the foot is what makes it catch a highlight along its edge
    // instead of reading as a printed rectangle on the desk.
    bin.add(P.roundedBoxOf(p.width * 0.42, 0.014, 0.16, 0.004), trim, {
      size: 1,
      z: -0.008,
    });
    // The neck runs a few centimetres past the bottom of the bezel, so the
    // joint is hidden behind the panel however far it is tilted.
    bin.add(P.roundedBoxOf(0.052, p.standHeight + 0.07, 0.042, 0.008), trim, {
      y: 0.01,
      z: -0.02,
      size: 1,
    });

    const baseY = p.standHeight + 0.02;
    const pitch = -p.tilt;
    const front = alongFacing(pitch, 0.013);

    // The shell, and a shallower housing behind it. A monitor is not a slab: it
    // is a thin panel with the electronics in a bulge behind the middle, and
    // that bulge is what its silhouette from three-quarters is made of.
    bin.add(P.roundedBoxOf(p.width, p.height, 0.022, 0.006), bezel, {
      y: baseY,
      size: 1,
      pitch,
    });
    const back = alongFacing(pitch, -0.02);
    bin.add(P.roundedBoxOf(p.width * 0.6, p.height * 0.55, 0.026, 0.01), bezel, {
      y: baseY + p.height * 0.22 + back.y,
      z: back.z,
      size: 1,
      pitch,
    });

    displayFace(bin, ctx, {
      y: baseY + 0.011 + front.y,
      z: front.z,
      width: p.width - 0.016,
      height: p.height - 0.024,
      pitch,
      border: 0.008,
    });

    // Standby light, bottom-right of the chin as it is on almost every panel.
    bin.add(P.box(), ctx.materials.get("deviceIndicator"), {
      x: p.width * 0.36,
      y: baseY + 0.005 + front.y,
      z: front.z + 0.002,
      size: [0.012, 0.004, 0.004],
      pitch,
    });

    return bin.build("screen.monitor");
  },
});

type WallDisplayParams = {
  width: number;
  height: number;
  /** Floor to the bottom edge of the screen. */
  mount: number;
};

export const screenWallDisplay = defineAsset<WallDisplayParams>({
  id: "tera:screen.wall-display",
  label: "Wall display",
  defaults: { width: 1.62, height: 0.94, mount: 0.86 },

  footprint(p) {
    // The depth is the whole assembly off the wall face, bracket included, so a
    // pack can push the prop `depth / 2` off the wall and have it sit flush.
    return { width: p.width, depth: 0.12, height: p.mount + p.height };
  },

  build(p, ctx) {
    const P = ctx.parts;
    const bin = new MeshBin();
    const bezel = ctx.materials.get("screenBezel");

    // The bracket. Boxes, and `metalTrim` uses nothing else in this asset.
    bin.add(P.box(), ctx.materials.get("metalTrim"), {
      y: p.mount + p.height / 2 - 0.16,
      z: -0.05,
      size: [0.44, 0.32, 0.03],
    });
    for (const sx of [-1, 1]) {
      bin.add(P.box(), ctx.materials.get("metalTrim"), {
        x: sx * 0.19,
        y: p.mount + p.height / 2 - 0.16,
        z: -0.028,
        size: [0.05, 0.3, 0.026],
      });
    }

    bin.add(P.roundedBoxOf(p.width, p.height, 0.042, 0.008), bezel, {
      y: p.mount,
      size: 1,
    });
    displayFace(bin, ctx, {
      y: p.mount + 0.012,
      z: 0.023,
      width: p.width - 0.022,
      height: p.height - 0.024,
      border: 0.01,
    });

    return bin.build("screen.wall-display");
  },
});
