/**
 * The two flat things: a rug on the floor and a board on the wall.
 *
 * Neither is part of the shell. A room's floor finish and its walls come from
 * the `Floorplan` (CONTRACT.md §2); a rug is a prop laid on top of whatever the
 * room's floor already is, which is exactly how a rug works.
 */

import { defineAsset } from "../kit.ts";
import { MeshBin } from "../parts.ts";
import { jitter, panelSlab, tintable } from "./common.ts";

type RugParams = {
  width: number;
  depth: number;
  /** Pile thickness. Small, but not zero — a rug at zero z-fights the floor. */
  pile: number;
  /** A plain band of the base carpet around the tinted field. */
  border: boolean;
};

/** The pile is the tintable part; the border, when there is one, is not. */
export const rug = defineAsset<RugParams>({
  id: "tera:rug",
  label: "Rug",
  defaults: { width: 2.4, depth: 1.7, pile: 0.014, border: true },

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

  build(p, ctx) {
    const bin = new MeshBin();
    const field = tintable(ctx, "carpetAccent");
    const edge = ctx.materials.get("carpet");

    bin.add(ctx.parts.box(), field, { size: [p.width, p.pile, p.depth] });

    if (p.border) {
      const band = Math.min(0.12, Math.min(p.width, p.depth) * 0.08);
      bin.add(ctx.parts.metricQuad(p.width, p.depth), edge, { y: p.pile + 0.0006 });
      bin.add(ctx.parts.metricQuad(p.width - band * 2, p.depth - band * 2), field, {
        y: p.pile + 0.0012,
      });
    } else {
      bin.add(ctx.parts.metricQuad(p.width, p.depth), field, { y: p.pile + 0.0006 });
    }

    return bin.build("rug", { castShadow: false });
  },
});

type WhiteboardParams = {
  width: number;
  height: number;
  /** Floor to the bottom edge of the writing surface. */
  mount: number;
  tray: boolean;
  /** Sticky notes and abstract marker strokes on the face. */
  worked: boolean;
};

/**
 * A wall-hung board. Authored standing on the floor with the writing surface
 * from `mount` up, so a pack places it against the wall and never has to work
 * out how high a whiteboard goes.
 *
 * The −Z face is skipped: it is against a wall, and drawing it would put a
 * second sheet of whiteboard texture into the merge for a surface nobody can
 * ever see.
 *
 * ### Why there is anything on it
 *
 * The live site shows this as "a large blank white rectangle", and it is the
 * biggest single flat surface in a meeting room. The `whiteboard` texture draws
 * faint ghosting from previous wipes, which is right and is not enough: what
 * makes a board read as *used* is objects on it that catch their own light —
 * sticky notes standing a millimetre proud, and strokes with a shadow under
 * them.
 *
 * So `worked` adds relief rather than more texture. Two millimetres is enough to
 * cast a hairline shadow under the rig's key light, which is what separates a
 * note stuck to a board from a coloured rectangle printed on one.
 *
 * Everything on it is **abstract**: rectangles and strokes, no glyphs, no words,
 * no diagrams of anything in particular. That is `ARCHITECTURE.md` §3.1 —
 * legible content on a board is either somebody's real work or a convincing
 * imitation of it, and neither belongs in an Apache-2.0 repo. Read from two
 * metres it says "a team used this room", which is the whole job.
 *
 * It is seeded from `ctx.rand`, so every board in one batch carries the same
 * notes in the same places. That is the price `furnish.ts` documents and it is
 * paid knowingly; a pack that wants two different boards authors two batches
 * with different `colorKey`s, which is the same seam the screens use.
 */
export const whiteboard = defineAsset<WhiteboardParams>({
  id: "tera:whiteboard",
  label: "Whiteboard",
  defaults: { width: 1.8, height: 1.2, mount: 0.9, tray: true, worked: true },

  footprint(p) {
    return { width: p.width, depth: 0.1, height: p.mount + p.height };
  },

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

    panelSlab(bin, ctx, ctx.materials.get("whiteboard"), {
      y: p.mount,
      width: p.width - 0.05,
      height: p.height - 0.05,
      thickness: 0.022,
      faces: "front",
    });

    const bar = 0.025;
    for (const s of [-1, 1]) {
      bin.add(P.box(), trim, {
        y: p.mount + (s < 0 ? 0 : p.height - bar),
        size: [p.width, bar, 0.03],
      });
      bin.add(P.box(), trim, {
        x: (s * (p.width - bar)) / 2,
        y: p.mount,
        size: [bar, p.height, 0.03],
      });
    }

    if (p.worked) {
      // The writing surface, inset from the frame — the same rectangle the
      // panel above occupies, minus its border.
      const faceX = p.width - 0.14;
      const faceY = p.height - 0.14;
      const originX = -faceX / 2;
      const originY = p.mount + 0.06;
      const face = 0.012;

      // Two columns of sticky notes. A grid rather than a scatter, because a
      // board that has been worked on has structure on it and a scatter reads as
      // confetti.
      const notes = ctx.materials.get("accent");
      const pale = ctx.materials.get("paper");
      for (let column = 0; column < 3; column++) {
        const rows = 2 + Math.floor(ctx.rand() * 2);
        for (let row = 0; row < rows; row++) {
          const size = 0.07 + ctx.rand() * 0.02;
          bin.add(P.box(), ctx.rand() < 0.55 ? notes : pale, {
            x: originX + faceX * (0.62 + column * 0.13) + jitter(ctx.rand, 0.008),
            y: originY + faceY * (0.62 - row * 0.19) + jitter(ctx.rand, 0.008),
            z: face,
            size: [size, size, 0.002],
            roll: jitter(ctx.rand, 0.06),
          });
        }
      }

      // Marker work on the left two thirds: a few boxes and the strokes joining
      // them. Thin dark slabs, standing proud enough to catch an edge highlight.
      const ink = ctx.materials.get("screenBezel");
      const boxes: [number, number][] = [];
      for (let i = 0; i < 3; i++) {
        const bx = originX + faceX * (0.1 + i * 0.16);
        const by = originY + faceY * (0.28 + (i % 2) * 0.3);
        boxes.push([bx, by]);
        const w = 0.16 + ctx.rand() * 0.06;
        const h = 0.09 + ctx.rand() * 0.03;
        // Four strokes rather than a filled rectangle: a drawn box is an
        // outline, and a solid one reads as a sticker.
        for (const sy of [-1, 1]) {
          bin.add(P.box(), ink, { x: bx, y: by + (sy * h) / 2, z: face, size: [w, 0.006, 0.002] });
        }
        for (const sx of [-1, 1]) {
          bin.add(P.box(), ink, { x: bx + (sx * w) / 2, y: by, z: face, size: [0.006, h, 0.002] });
        }
      }
      for (let i = 0; i < boxes.length - 1; i++) {
        const from = boxes[i];
        const to = boxes[i + 1];
        if (!from || !to) continue;
        const dx = to[0] - from[0];
        const dy = to[1] - from[1];
        bin.add(P.box(), ink, {
          x: from[0] + dx / 2,
          y: from[1] + dy / 2,
          z: face,
          size: [Math.hypot(dx, dy), 0.005, 0.002],
          roll: Math.atan2(dy, dx),
        });
      }
      // One underlined heading bar across the top, which is what the eye reads
      // as "this board has a subject" without anything being legible.
      bin.add(P.box(), ink, {
        x: originX + faceX * 0.26,
        y: originY + faceY * 0.88,
        z: face,
        size: [faceX * 0.4, 0.008, 0.002],
      });
    }

    if (p.tray) {
      bin.add(P.box(), trim, {
        y: p.mount - 0.03,
        z: 0.035,
        size: [p.width * 0.55, 0.016, 0.07],
      });
      const pens = ctx.materials.get("accent");
      for (let i = 0; i < 3; i++) {
        // Rolled a quarter turn, a rod lies along −X from where it is placed.
        bin.add(P.rod(), pens, {
          x: 0.02 + i * 0.045,
          y: p.mount - 0.014,
          z: 0.04,
          size: [0.014, 0.13, 0.014],
          roll: Math.PI / 2,
        });
      }
    }

    return bin.build("whiteboard");
  },
});
