/**
 * Desks: the workstation, the pedestal that lives under it, and the screen that
 * separates it from the next one.
 *
 * `desk.workstation` is the anchor of the library. It is the asset a `DeskBank`
 * repeats, it is the one every self-hoster will override first, and its
 * dimensions are what the rest of the furniture is sized against: a 730 mm
 * working height, a 1.6 × 0.8 m desktop, and a person sitting at +Z facing −Z.
 */

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

type WorkstationParams = {
  width: number;
  depth: number;
  /** Working height of the desktop, metres. 1.05 or so for a standing desk. */
  height: number;
  /**
   * `"loop"` is the cantilever frame most office desks actually have — a foot
   * bar, two uprights and a top rail at each end. `"post"` is four legs, which
   * reads as a table and is here for the rooms where that is wanted.
   */
  legs: "loop" | "post";
  /** The panel across the far edge. Tintable; this is the desk's colour key. */
  modesty: boolean;
};

const TOP_THICKNESS = 0.03;

export const deskWorkstation = defineAsset<WorkstationParams>({
  id: "tera:desk.workstation",
  label: "Workstation",
  defaults: { width: 1.6, depth: 0.8, height: 0.73, legs: "loop", modesty: true },

  footprint(p) {
    // The clearance is a chair pulled out, not a chair tucked in: 900 mm is
    // what a person needs to stand up and leave without moving the desk.
    return { width: p.width, depth: p.depth, height: p.height, clearance: 0.9 };
  },

  build(p, ctx) {
    const P = ctx.parts;
    const bin = new MeshBin();
    const frame = ctx.materials.get("deskFrame");
    const deckY = p.height - TOP_THICKNESS;

    // 4 mm on the edge of the desktop. `deskSurface` is used by nothing else in
    // this asset, which is what makes the chamfer affordable here — see the
    // primitive-class note on `slab`.
    slab(bin, ctx, ctx.materials.get("deskSurface"), {
      y: deckY,
      width: p.width,
      depth: p.depth,
      thickness: TOP_THICKNESS,
      chamfer: 0.004,
    });

    const legX = p.width / 2 - 0.09;
    const legZ = p.depth / 2 - 0.11;

    if (p.legs === "post") {
      for (const sx of [-1, 1]) {
        for (const sz of [-1, 1]) {
          bin.add(P.rod(), frame, {
            x: sx * legX,
            z: sz * legZ,
            size: [0.055, deckY, 0.055],
          });
        }
      }
    } else {
      const barDepth = p.depth - 0.18;
      for (const sx of [-1, 1]) {
        const x = sx * legX;
        bin.add(P.box(), frame, { x, size: [0.07, 0.045, barDepth] });
        bin.add(P.box(), frame, { x, y: deckY - 0.05, size: [0.07, 0.05, barDepth] });
        for (const sz of [-1, 1]) {
          bin.add(P.box(), frame, {
            x,
            z: sz * legZ,
            size: [0.05, deckY - 0.05, 0.05],
          });
        }
      }
      // The spine between the two end frames. Without it a cantilever desk
      // looks like two separate trestles that happen to be under one board.
      bin.add(P.box(), frame, {
        y: deckY - 0.19,
        size: [Math.max(0.2, p.width - 0.28), 0.055, 0.055],
      });
    }

    if (p.modesty) {
      const height = clamp(deckY - 0.3, 0.14, 0.4);
      panelSlab(bin, ctx, tintable(ctx, "partitionFabric"), {
        y: deckY - 0.05 - height,
        z: -(p.depth / 2 - 0.08),
        width: p.width - 0.24,
        height,
        thickness: 0.018,
      });
    }

    return bin.build("desk.workstation");
  },
});

type PedestalParams = {
  width: number;
  depth: number;
  height: number;
  drawers: number;
  /** Castors, for the pedestal that gets rolled out and sat on. */
  mobile: boolean;
};

/**
 * The under-desk drawer unit. Its fronts are at +Z — the same side the person
 * is on — so a pack gives it the same rotation as the desk it belongs to and
 * the drawers open toward the chair.
 */
export const deskPedestal = defineAsset<PedestalParams>({
  id: "tera:desk.pedestal",
  label: "Desk pedestal",
  defaults: { width: 0.42, depth: 0.6, height: 0.6, drawers: 3, mobile: true },

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

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

    const lift = p.mobile ? 0.05 : 0.02;
    const bodyH = p.height - lift;
    bin.add(P.box(), carcass, { y: lift, size: [p.width, bodyH, p.depth] });

    if (p.mobile) {
      for (const sx of [-1, 1]) {
        for (const sz of [-1, 1]) {
          bin.add(P.cylinder(8), trim, {
            x: sx * (p.width / 2 - 0.07),
            z: sz * (p.depth / 2 - 0.07),
            size: [0.05, lift, 0.05],
          });
        }
      }
    }

    const count = Math.max(1, Math.round(p.drawers));
    const pitch = (bodyH - 0.03) / count;
    const face = tintable(ctx, "cabinet");
    for (let i = 0; i < count; i++) {
      const y = lift + 0.015 + i * pitch;
      bin.add(P.box(), face, {
        y,
        z: p.depth / 2,
        size: [p.width - 0.03, pitch - 0.012, 0.02],
      });
      // A recessed pull rather than a handle: a D-handle at this scale is four
      // more parts and reads as a smudge from any distance you see a pedestal.
      bin.add(P.box(), trim, {
        y: y + pitch - 0.05,
        z: p.depth / 2 + 0.012,
        size: [p.width * 0.42, 0.014, 0.012],
      });
    }

    return bin.build("desk.pedestal");
  },
});

type PartitionParams = {
  width: number;
  height: number;
  thickness: number;
  /** Floor-standing feet. Off by default: most of these clamp to a desk. */
  feet: boolean;
};

/**
 * A fabric screen. Authored standing on the floor like everything else, so the
 * desk-mounted case is `elevation: 0.73` in the pack rather than a `mount`
 * parameter here — the same screen clamps to a desk, stands on the floor and
 * caps a bench run, and only the pack knows which.
 *
 * The fabric is the tintable part.
 */
export const deskPartition = defineAsset<PartitionParams>({
  id: "tera:desk.partition",
  label: "Desk partition",
  defaults: { width: 1.4, height: 0.45, thickness: 0.04, feet: false },

  footprint(p) {
    return { width: p.width, depth: p.feet ? 0.34 : p.thickness + 0.02, height: p.height };
  },

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

    panelSlab(bin, ctx, tintable(ctx, "partitionFabric"), {
      y: 0,
      width: p.width - 0.03,
      height: p.height,
      thickness: p.thickness,
    });

    bin.add(P.box(), frame, {
      y: p.height - 0.018,
      size: [p.width, 0.018, p.thickness + 0.012],
    });
    for (const sx of [-1, 1]) {
      bin.add(P.box(), frame, {
        x: sx * (p.width / 2 - 0.008),
        size: [0.016, p.height, p.thickness + 0.012],
      });
    }

    if (p.feet) {
      for (const sx of [-1, 1]) {
        bin.add(P.box(), frame, {
          x: sx * (p.width / 2 - 0.1),
          size: [0.05, 0.02, 0.32],
        });
      }
    }

    return bin.build("desk.partition");
  },
});
