/**
 * The small-space habitat kit.
 *
 * Offices prove the scene format with desks and meeting rooms. A studio home
 * needs a different handful of silhouettes before it reads honestly: a bed is
 * not a wide desk, a kitchen is not a row of lockers, and two lounge chairs do
 * not become a sofa because they touch. These assets stay inside the same
 * primitive grammar and material palette as the office kit, so they add no
 * binary art, texture download, or second visual language.
 *
 * As with every asset in this directory, source is Apache-2.0 and the generated
 * artistic output is dedicated under CC0-1.0 by `src/assets/LICENSE-ART`.
 */

import * as THREE from "three";
import { defineAsset } from "../kit.ts";
import { MeshBin } from "../parts.ts";
import { panelSlab, slab, tintable } from "./common.ts";

type BedParams = {
  width: number;
  length: number;
  platformHeight: number;
  headboardHeight: number;
};

/** A low platform bed. The headboard is at local -Z and the foot faces +Z. */
export const bedPlatform = defineAsset<BedParams>({
  id: "tera:bed.platform",
  label: "Platform bed",
  defaults: { width: 1.52, length: 2.03, platformHeight: 0.28, headboardHeight: 0.92 },

  footprint(p) {
    return { width: p.width, depth: p.length, height: p.headboardHeight, clearance: 0.7 };
  },

  build(p, ctx) {
    const bin = new MeshBin();
    const wood = ctx.materials.get("shelf");
    const textile = tintable(ctx, "upholstery");
    const linen = ctx.materials.get("paper");
    const frameH = Math.max(0.12, p.platformHeight);
    const mattressH = 0.2;

    // A recessed plinth gives the platform a shadow line instead of a box-on-floor read.
    bin.add(ctx.parts.box(), ctx.materials.get("chairBase"), {
      y: 0.035,
      size: [p.width - 0.22, 0.07, p.length - 0.18],
    });
    slab(bin, ctx, wood, {
      y: 0.09,
      width: p.width,
      depth: p.length,
      thickness: frameH - 0.09,
    });
    bin.add(ctx.parts.roundedBox(0.09), textile, {
      y: frameH,
      z: 0.03,
      size: [p.width - 0.12, mattressH, p.length - 0.12],
    });
    panelSlab(bin, ctx, wood, {
      y: 0,
      z: -p.length / 2 + 0.035,
      width: p.width,
      height: p.headboardHeight,
      thickness: 0.07,
      faces: "front",
    });

    // Two pillows, separated enough that the silhouette survives the dollhouse view.
    for (const sx of [-1, 1]) {
      bin.add(ctx.parts.roundedBox(0.12), linen, {
        x: sx * p.width * 0.23,
        y: frameH + mattressH + 0.015,
        z: -p.length / 2 + 0.38,
        size: [p.width * 0.38, 0.11, 0.48],
        yaw: sx * 0.035,
      });
    }

    return bin.build("bed.platform");
  },
});

type SofaParams = {
  width: number;
  depth: number;
  seatHeight: number;
  backHeight: number;
  modules: number;
};

/** A low modular sofa with individually readable seat and back cushions. */
export const sofaModular = defineAsset<SofaParams>({
  id: "tera:sofa.modular",
  label: "Modular sofa",
  defaults: { width: 2.2, depth: 0.9, seatHeight: 0.42, backHeight: 0.82, modules: 3 },

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

  build(p, ctx) {
    const bin = new MeshBin();
    const P = ctx.parts;
    const base = ctx.materials.get("chairShell");
    const textile = tintable(ctx, "upholstery");
    const modules = Math.max(2, Math.min(4, Math.round(p.modules)));
    const innerW = p.width - 0.28;
    const moduleW = innerW / modules;

    for (const sx of [-1, 1]) {
      for (const sz of [-1, 1]) {
        bin.add(P.rod(), ctx.materials.get("chairBase"), {
          x: sx * (p.width / 2 - 0.12),
          z: sz * (p.depth / 2 - 0.12),
          size: [0.04, 0.08, 0.04],
        });
      }
    }
    bin.add(P.box(), base, { y: 0.08, size: [innerW, p.seatHeight - 0.15, p.depth - 0.14] });

    for (let i = 0; i < modules; i += 1) {
      const x = -innerW / 2 + moduleW * (i + 0.5);
      bin.add(P.roundedBox(0.07), textile, {
        x,
        y: p.seatHeight - 0.12,
        z: -0.04,
        size: [moduleW - 0.025, 0.14, p.depth - 0.25],
      });
      bin.add(P.roundedBox(0.09), textile, {
        x,
        y: p.seatHeight + 0.015,
        z: p.depth / 2 - 0.15,
        size: [moduleW - 0.035, p.backHeight - p.seatHeight, 0.17],
        pitch: 0.1,
      });
    }
    for (const sx of [-1, 1]) {
      bin.add(P.roundedBox(0.09), textile, {
        x: sx * (p.width / 2 - 0.07),
        y: 0.08,
        size: [0.14, p.seatHeight + 0.18, p.depth - 0.1],
      });
    }

    return bin.build("sofa.modular");
  },
});

type KitchenRunParams = {
  width: number;
  depth: number;
  counterHeight: number;
  height: number;
  bays: number;
};

/** A complete compact kitchen wall: base units, worktop, sink, backsplash and uppers. */
export const kitchenRun = defineAsset<KitchenRunParams>({
  id: "tera:kitchen.run",
  label: "Kitchen wall",
  defaults: { width: 3.6, depth: 0.62, counterHeight: 0.91, height: 2.16, bays: 5 },

  footprint(p) {
    // The worktop oversails the carcass by 50 mm at the front, which is what a
    // worktop does and what the stated depth was missing.
    return { width: p.width, depth: p.depth + 0.05, height: p.height, clearance: 1 };
  },

  build(p, ctx) {
    const bin = new MeshBin();
    const P = ctx.parts;
    const cabinet = tintable(ctx, "cabinet");
    const top = ctx.materials.get("tableTop");
    const metal = ctx.materials.get("metalTrim");
    const bays = Math.max(3, Math.min(7, Math.round(p.bays)));
    const bayW = p.width / bays;

    bin.add(P.box(), ctx.materials.get("chairBase"), {
      y: 0.02,
      z: -0.035,
      size: [p.width - 0.08, 0.08, p.depth - 0.12],
    });
    for (let i = 0; i < bays; i += 1) {
      const x = -p.width / 2 + bayW * (i + 0.5);
      bin.add(P.box(), cabinet, {
        x,
        y: 0.1,
        z: -0.03,
        size: [bayW - 0.018, p.counterHeight - 0.15, p.depth - 0.06],
      });
      bin.add(P.box(), metal, {
        x: x + bayW * 0.34,
        y: p.counterHeight * 0.55,
        z: p.depth / 2 + 0.006,
        size: [0.012, 0.12, 0.012],
      });
    }
    // The worktop's front edge is at hand height and is the one edge in a
    // kitchen you actually touch, so it gets the full 6 mm.
    slab(bin, ctx, top, {
      y: p.counterHeight - 0.045,
      width: p.width,
      depth: p.depth + 0.05,
      thickness: 0.045,
      chamfer: 0.006,
    });

    // Backsplash and upper units stop short over the sink to create a focal bay.
    panelSlab(bin, ctx, ctx.materials.get("tile"), {
      y: p.counterHeight,
      z: -p.depth / 2 + 0.012,
      width: p.width,
      height: 0.62,
      thickness: 0.024,
      faces: "front",
    });
    const upperY = 1.48;
    for (let i = 0; i < bays; i += 1) {
      if (i === Math.floor(bays / 2)) continue;
      const x = -p.width / 2 + bayW * (i + 0.5);
      bin.add(P.box(), cabinet, {
        x,
        y: upperY,
        z: -p.depth / 2 + 0.17,
        size: [bayW - 0.018, p.height - upperY, 0.34],
      });
      bin.add(P.box(), metal, {
        x: x + bayW * 0.34,
        y: upperY + 0.2,
        z: -p.depth / 2 + 0.345,
        size: [0.012, 0.13, 0.012],
      });
    }

    const sinkX = -p.width / 2 + bayW * (Math.floor(bays / 2) + 0.5);
    bin.add(P.box(), metal, {
      x: sinkX,
      y: p.counterHeight + 0.003,
      size: [bayW * 0.62, 0.018, p.depth * 0.5],
    });
    bin.add(P.rod(), metal, {
      x: sinkX,
      y: p.counterHeight + 0.02,
      z: -0.12,
      size: [0.025, 0.24, 0.025],
    });
    bin.add(P.rod(), metal, {
      x: sinkX,
      y: p.counterHeight + 0.24,
      z: -0.04,
      size: [0.025, 0.025, 0.16],
    });

    return bin.build("kitchen.run");
  },
});

type IslandParams = {
  length: number;
  depth: number;
  height: number;
  overhang: number;
};

/** A storage island with a real seating overhang on its +Z side. */
export const kitchenIsland = defineAsset<IslandParams>({
  id: "tera:kitchen.island",
  label: "Kitchen island",
  defaults: { length: 2.2, depth: 0.9, height: 0.92, overhang: 0.24 },

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

  build(p, ctx) {
    const bin = new MeshBin();
    const cabinetDepth = Math.max(0.42, p.depth - p.overhang);
    bin.add(ctx.parts.box(), tintable(ctx, "cabinet"), {
      y: 0.06,
      z: -p.overhang / 2,
      size: [p.length - 0.12, p.height - 0.12, cabinetDepth],
    });
    slab(bin, ctx, ctx.materials.get("tableTop"), {
      y: p.height - 0.05,
      width: p.length,
      depth: p.depth,
      thickness: 0.05,
      chamfer: 0.006,
    });
    for (const sx of [-1, 1]) {
      bin.add(ctx.parts.rod(), ctx.materials.get("metalTrim"), {
        x: sx * (p.length / 2 - 0.12),
        y: 0.08,
        z: p.depth / 2 - 0.08,
        size: [0.035, p.height - 0.13, 0.035],
      });
    }
    return bin.build("kitchen.island");
  },
});

type WardrobeParams = { width: number; depth: number; height: number; doors: number };

/** Full-height closed storage with a recessed plinth and quiet vertical pulls. */
export const storageWardrobe = defineAsset<WardrobeParams>({
  id: "tera:storage.wardrobe",
  label: "Wardrobe",
  defaults: { width: 1.8, depth: 0.58, height: 2.18, doors: 3 },

  footprint(p) {
    // Doors and pulls stand proud of the carcass at +Z, and the stated depth has
    // to include them or a wardrobe pushed flush to a wall puts its handles
    // through the plaster.
    return { width: p.width, depth: p.depth + 0.03, height: p.height, clearance: 0.75 };
  },

  build(p, ctx) {
    const bin = new MeshBin();
    const body = ctx.materials.get("shelf");
    const doors = tintable(ctx, "cabinet");
    const metal = ctx.materials.get("metalTrim");
    const count = Math.max(2, Math.min(5, Math.round(p.doors)));
    const doorW = (p.width - 0.05) / count;
    bin.add(ctx.parts.box(), ctx.materials.get("chairBase"), {
      x: 0,
      y: 0.02,
      z: -0.02,
      size: [p.width - 0.1, 0.08, p.depth - 0.1],
    });
    bin.add(ctx.parts.box(), body, { y: 0.08, size: [p.width, p.height - 0.08, p.depth] });
    for (let i = 0; i < count; i += 1) {
      const x = -p.width / 2 + 0.025 + doorW * (i + 0.5);
      bin.add(ctx.parts.box(), doors, {
        x,
        y: 0.11,
        z: p.depth / 2 + 0.006,
        size: [doorW - 0.014, p.height - 0.16, 0.018],
      });
      bin.add(ctx.parts.box(), metal, {
        x: x + doorW * 0.34,
        y: p.height * 0.46,
        z: p.depth / 2 + 0.02,
        size: [0.014, 0.28, 0.014],
      });
    }
    return bin.build("storage.wardrobe");
  },
});

type StoolParams = { diameter: number; seatHeight: number; back: boolean };

/** A kitchen-height stool. It faces -Z so a seat address can share its yaw. */
export const seatStool = defineAsset<StoolParams>({
  id: "tera:seat.stool",
  label: "Counter stool",
  defaults: { diameter: 0.4, seatHeight: 0.68, back: true },

  footprint(p) {
    // The back reaches 380 mm above the seat pan, and the height has to be
    // derived from `seatHeight` rather than hard-coded: it was 0.96, which was
    // right for the default 0.68 seat and 100 mm short of the geometry, and
    // wrong by an arbitrary amount for any other seat height a pack asked for.
    return {
      width: p.diameter + 0.1,
      depth: p.diameter + 0.12,
      height: p.back ? p.seatHeight + 0.38 : p.seatHeight,
      clearance: 0.35,
    };
  },

  build(p, ctx) {
    const bin = new MeshBin();
    const metal = ctx.materials.get("chairBase");
    const textile = tintable(ctx, "chairFabric");
    for (let i = 0; i < 4; i += 1) {
      const yaw = Math.PI / 4 + i * Math.PI / 2;
      bin.add(ctx.parts.rod(), metal, {
        x: Math.sin(yaw) * p.diameter * 0.34,
        z: Math.cos(yaw) * p.diameter * 0.34,
        size: [0.025, p.seatHeight - 0.06, 0.025],
      });
    }
    bin.add(ctx.parts.cylinder(20), textile, {
      y: p.seatHeight - 0.06,
      size: [p.diameter, 0.1, p.diameter],
    });
    if (p.back) {
      bin.add(ctx.parts.rod(), metal, {
        x: -p.diameter * 0.34,
        y: p.seatHeight - 0.02,
        z: p.diameter * 0.34,
        size: [0.022, 0.28, 0.022],
      });
      bin.add(ctx.parts.rod(), metal, {
        x: p.diameter * 0.34,
        y: p.seatHeight - 0.02,
        z: p.diameter * 0.34,
        size: [0.022, 0.28, 0.022],
      });
      // Keep the back and circular seat in the same indexed primitive family.
      // `roundedBox` is non-indexed; mixing it with the indexed cylinder would
      // make BufferGeometryUtils reject the material bin and drop every fabric
      // part from the finished stool.
      bin.add(ctx.parts.box(), textile, {
        y: p.seatHeight + 0.18,
        z: p.diameter * 0.34,
        size: [p.diameter * 0.82, 0.2, 0.06],
      });
    }
    return bin.build("seat.stool");
  },
});

type FloorLightParams = { height: number; reach: number; shadeDiameter: number };

/** An arced floor lamp. Geometry glows; the office lighting rig remains the only light owner. */
export const lightFloor = defineAsset<FloorLightParams>({
  id: "tera:light.floor",
  label: "Floor lamp",
  defaults: { height: 1.55, reach: 0.42, shadeDiameter: 0.32 },

  footprint(p) {
    // The one asset in the kit that is genuinely not centred on its own origin:
    // the base is under the column and the shade cantilevers out to −Z over it.
    // The footprint has to enclose both, so it is the reach plus half a shade in
    // front and the base's own radius behind — which is 180 mm the stated depth
    // used to be missing, and 180 mm is enough to push a floor lamp through a
    // wall.
    const base = 0.18;
    return {
      width: Math.max(0.42, p.shadeDiameter),
      depth: p.reach + p.shadeDiameter / 2 + base,
      height: p.height,
    };
  },

  build(p, ctx) {
    const body = new MeshBin();
    const glow = new MeshBin();
    const metal = ctx.materials.get("lightHousing");
    body.add(ctx.parts.cylinder(20), ctx.materials.get("chairBase"), {
      size: [0.36, 0.035, 0.36],
    });
    body.add(ctx.parts.rod(), metal, { y: 0.035, size: [0.025, p.height - 0.18, 0.025] });
    body.add(ctx.parts.rod(), metal, {
      y: p.height - 0.18,
      z: -p.reach / 2,
      size: [0.025, 0.025, p.reach],
    });
    body.add(ctx.parts.cone(20), metal, {
      y: p.height - 0.4,
      z: -p.reach,
      size: [p.shadeDiameter, 0.24, p.shadeDiameter],
      pitch: Math.PI,
    });
    glow.add(ctx.parts.disc(20), ctx.materials.get("lightDiffuser"), {
      y: p.height - 0.395,
      z: -p.reach,
      size: [p.shadeDiameter * 0.86, 1, p.shadeDiameter * 0.86],
      pitch: Math.PI,
    });
    const group = new THREE.Group();
    group.name = "light.floor";
    group.add(body.build("light.floor:body"));
    group.add(glow.build("light.floor:glow", { castShadow: false, receiveShadow: false }));
    return group;
  },
});
