/**
 * SF HQ · Studio — a compact live/work apartment in San Francisco.
 *
 * This is deliberately not a speculative corporate floor. It is a twelve by
 * nine metre studio used as a small AI and robotics headquarters: two serious
 * workstations, a demo wall, a kitchen that can host a conversation, a place to
 * sleep, and a front door a possessed actor actually arrives beside.
 *
 * The stable `lumbridge-hq` id is retained because it is an address used by
 * journey snapshots, presence, media grants and self-hosted deployments. The
 * public name and the authored environment are allowed to become more honest
 * without invalidating those addresses.
 */

import type { DeviceCapability, DeviceDeclaration } from "../devices/types.ts";
import type {
  AssetId,
  DeskBank,
  Level,
  Office,
  Opening,
  Outline,
  Prop,
  Room,
  Seat,
  Viewpoint,
  Wall,
  Yaw,
  Zone,
} from "../interiors/types.ts";
import { LUMBRIDGE_HQ_SITE } from "./sites.ts";

// ---- Frame ----------------------------------------------------------------

const WIDTH = 12;
const DEPTH = 9;
const FACE = 0.2;
const CEILING = 2.85;
const EXT = 0.24;
const INT = 0.12;

const NORTH: Yaw = 0;
const EAST: Yaw = -Math.PI / 2;
const SOUTH: Yaw = Math.PI;
const WEST: Yaw = Math.PI / 2;

// ---- Assets and finishes --------------------------------------------------

const DESK: AssetId = "tera:desk.workstation";
const TASK_CHAIR: AssetId = "tera:seat.task-chair";
const BED: AssetId = "tera:bed.platform";
const SOFA: AssetId = "tera:sofa.modular";
const KITCHEN: AssetId = "tera:kitchen.run";
const ISLAND: AssetId = "tera:kitchen.island";
const WARDROBE: AssetId = "tera:storage.wardrobe";
const STOOL: AssetId = "tera:seat.stool";
const FLOOR_LIGHT: AssetId = "tera:light.floor";
const SIDE_TABLE: AssetId = "tera:table.side";
const SHELF: AssetId = "tera:storage.shelf";
const MONITOR: AssetId = "tera:screen.monitor";
const DISPLAY: AssetId = "tera:screen.wall-display";
const PLANT: AssetId = "tera:plant.potted";
const TREE: AssetId = "tera:plant.tall";
const PENDANT: AssetId = "tera:light.pendant";
const TROFFER: AssetId = "tera:light.troffer";
const RUG: AssetId = "tera:rug";
const WHITEBOARD: AssetId = "tera:whiteboard";
const MIC: AssetId = "tera:device.mic.desk";
const SPEAKER: AssetId = "tera:device.speaker.desk";

const CONCRETE = "tera:concrete.polished";
const WOOD = "tera:wood.plank";
const TILE = "tera:tile.terrazzo";
const PAINT = "tera:paint.matt";
const ACCENT = "tera:plasterAccent.deep";
const GLASS = "tera:glass.curtain";

function rect(x1: number, z1: number, x2: number, z2: number): Outline {
  return [
    { x: x1, z: z1 },
    { x: x1, z: z2 },
    { x: x2, z: z2 },
    { x: x2, z: z1 },
  ];
}

function doorway(start: number, width = 1.0, head = 2.2): Opening {
  return { kind: "door", start, width, sill: 0, head };
}

function windowAt(start: number, width: number): Opening {
  return { kind: "window", start, width, sill: 0.78, head: 2.5 };
}

function prop(
  id: string,
  kind: AssetId,
  x: number,
  z: number,
  rotation: Yaw = NORTH,
  extra: Omit<Prop, "id" | "kind" | "position" | "rotation"> = {},
): Prop {
  return { id, kind, position: { x, z }, rotation, ...extra };
}

// ---- Plan -----------------------------------------------------------------

const ROOMS: Room[] = [
  {
    id: "kitchen",
    name: "Kitchen",
    outline: rect(FACE, FACE, 4.55, 3.15),
    floor: TILE,
    ceiling: null,
  },
  {
    id: "sleeping-alcove",
    name: "Sleeping Alcove",
    outline: rect(4.55, FACE, 8.55, 3.15),
    floor: WOOD,
    ceiling: null,
  },
  {
    id: "bath-storage",
    name: "Bath / Storage",
    outline: rect(8.55, FACE, WIDTH - FACE, 3.15),
    floor: TILE,
    ceiling: { height: CEILING, surface: PAINT },
  },
  {
    id: "live-work",
    name: "Live / Work Studio",
    outline: rect(FACE, 3.15, WIDTH - FACE, DEPTH - FACE),
    floor: CONCRETE,
    ceiling: null,
  },
];

const WALLS: Wall[] = [
  {
    id: "ext-north",
    from: { x: FACE, z: FACE },
    to: { x: WIDTH - FACE, z: FACE },
    thickness: EXT,
    openings: [windowAt(0.8, 2.2), windowAt(4.0, 2.4), windowAt(8.0, 2.5)],
  },
  {
    id: "ext-east",
    from: { x: WIDTH - FACE, z: FACE },
    to: { x: WIDTH - FACE, z: DEPTH - FACE },
    thickness: EXT,
    openings: [windowAt(3.7, 2.2)],
  },
  {
    id: "ext-south",
    from: { x: FACE, z: DEPTH - FACE },
    to: { x: WIDTH - FACE, z: DEPTH - FACE },
    thickness: EXT,
    openings: [windowAt(6.8, 3.4)],
  },
  {
    id: "ext-west",
    from: { x: FACE, z: FACE },
    to: { x: FACE, z: DEPTH - FACE },
    thickness: EXT,
    openings: [windowAt(1.0, 2.0), doorway(6.8, 1.15, 2.35)],
  },

  // The bath is the only fully enclosed room. Its door opens directly into the
  // studio and the clear approach to it is asserted in `office.test.ts`.
  {
    id: "bath-west",
    from: { x: 8.55, z: FACE },
    to: { x: 8.55, z: 3.15 },
    openings: [doorway(1.72, 0.92)],
  },
  {
    id: "bath-south",
    from: { x: 8.55, z: 3.15 },
    to: { x: WIDTH - FACE, z: 3.15 },
  },
  // A transparent shower screen gives the bath an interior silhouette without
  // adding a decorative prop that the walk collider would ignore.
  {
    id: "bath-screen",
    from: { x: 10.25, z: 0.48 },
    to: { x: 10.25, z: 2.05 },
    thickness: 0.06,
    height: 2.05,
    surface: GLASS,
  },
  // A short felt-backed divider turns the bed into an alcove while preserving
  // the open plan and a 1.35 m route around its foot.
  {
    id: "sleep-divider",
    from: { x: 4.55, z: FACE },
    to: { x: 4.55, z: 1.9 },
    thickness: INT,
    height: 1.45,
    surface: ACCENT,
  },
];

const DESK_BANKS: DeskBank[] = [
  {
    id: "sf-agent",
    desk: DESK,
    chair: TASK_CHAIR,
    origin: { x: 2.15, z: 4.45 },
    rotation: NORTH,
    columns: 2,
    rows: 1,
    pitch: 1.82,
    rowPitch: 1,
    seatOffset: 0.62,
    pose: "sit",
  },
];

const SEATS: Seat[] = [
  { id: "sf-island-01", position: { x: 1.9, z: 3.35 }, facing: NORTH, pose: "sit" },
  { id: "sf-island-02", position: { x: 3.0, z: 3.35 }, facing: NORTH, pose: "sit" },
  { id: "sf-lounge-01", position: { x: 8.15, z: 7.55 }, facing: NORTH, pose: "sit" },
];

const PROPS: Prop[] = [
  // Entry: clear 1.2 m turning circle, one landing shelf and a plant. The first
  // viewpoint focuses at x=1.0,z=7.75, so nothing is allowed to drift into it.
  prop("entry-shelf", SHELF, 0.65, 5.75, WEST),
  prop("entry-plant", PLANT, 1.25, 8.2),

  // Two-place agent bench. Media keeps the legacy `lobby-monitor` id so hosted
  // screen grants survive this content rewrite.
  prop("lobby-monitor", MONITOR, 2.15, 4.45, NORTH, { elevation: 0.73 }),
  prop("agent-monitor-b", MONITOR, 3.97, 4.45, NORTH, { elevation: 0.73 }),
  // The two pieces of hardware the studio operates, and the only two props in
  // this pack that a `DeviceDeclaration` points at: a desk condenser on the
  // left-hand workstation and a monitor speaker beside its screen. Both stand on
  // the 0.73 m desktop, so both carry the elevation rather than assuming one.
  // The declarations are in `DEVICES` at the foot of this file and carry no
  // coordinate — they name these ids and take the transform from here.
  prop("agent-mic", MIC, 1.62, 4.72, NORTH, { elevation: 0.73, seat: "sf-agent-01" }),
  prop("agent-speaker", SPEAKER, 2.72, 4.5, NORTH, { elevation: 0.73 }),
  prop("agent-tree", TREE, 5.15, 4.15),
  prop("agent-light-a", TROFFER, 2.2, 4.6, NORTH, { elevation: CEILING }),
  prop("agent-light-b", TROFFER, 4.0, 4.6, NORTH, { elevation: CEILING }),

  // Real kitchen silhouettes, not office lockers standing in for cabinetry.
  prop("kitchen-run", KITCHEN, 2.35, 0.65, NORTH),
  prop("kitchen-island", ISLAND, 2.45, 2.55, NORTH),
  prop("kitchen-stool-a", STOOL, 1.9, 3.35, NORTH, { seat: "sf-island-01" }),
  prop("kitchen-stool-b", STOOL, 3.0, 3.35, NORTH, { seat: "sf-island-02" }),
  prop("kitchen-pendant-a", PENDANT, 1.95, 2.55, NORTH, { elevation: CEILING }),
  prop("kitchen-pendant-b", PENDANT, 2.95, 2.55, NORTH, { elevation: CEILING }),
  prop("kitchen-plant", PLANT, 4.05, 2.25),

  // Sleeping alcove and closed storage. Both face the studio so the domestic
  // half stays legible from the arrival view rather than hiding behind walls.
  prop("studio-bed", BED, 6.45, 1.35, NORTH),
  prop("bed-side", SIDE_TABLE, 7.75, 1.2),
  prop("bed-light", FLOOR_LIGHT, 7.95, 2.15, SOUTH),
  prop("studio-wardrobe", WARDROBE, 8.22, 1.3, WEST),
  prop("bath-shelf", SHELF, 11.1, 2.55, EAST),
  prop("bath-plant", PLANT, 9.25, 2.55),

  // Lounge: one properly articulated sofa, a low table and a demo display on
  // the north-facing wall. `open-display` is the other stable media address.
  prop("lounge-rug", RUG, 8.15, 6.75, NORTH, { scale: [1.35, 1, 1.25], colorKey: "social" }),
  prop("lounge-sofa", SOFA, 8.15, 7.72, NORTH, { seat: "sf-lounge-01", colorKey: "social" }),
  prop("lounge-table", SIDE_TABLE, 8.15, 6.45),
  prop("open-display", DISPLAY, 9.65, 3.23, SOUTH),
  prop("demo-whiteboard", WHITEBOARD, 11.65, 5.6, WEST),
  prop("lounge-floor-light", FLOOR_LIGHT, 10.55, 7.65, EAST),
  prop("lounge-tree", TREE, 11.05, 7.9),
  prop("lounge-plant", PLANT, 6.4, 7.95),
];

/**
 * One sentence, shown to a viewer beside every reading this building publishes.
 *
 * Mandatory and checked: `validateDeviceDeclaration` refuses a `simulated`
 * declaration whose disclosure does not contain the word, exactly as
 * `resolveRobotOperations` refuses a robot definition that does not say its
 * activity is simulated. A live-looking level meter with no provenance beside it
 * is a claim about a real room, and this is the field that stops the pack making
 * one by omission.
 */
const DISCLOSURE =
  "Simulated studio hardware in an authored room. Lumbridge's San Francisco " +
  "studio is a place this pack describes, not a room anybody is standing in: " +
  "there is no hardware in San Francisco to read. These readings are " +
  "demonstration data, never live presence data.";

/**
 * Two devices: the mic on the desk and the speaker on the computer.
 *
 * Both anchored to a prop and neither carrying a coordinate — see `DeviceAnchor`
 * in `src/devices/types.ts` for why that is the whole design of the type. Move
 * `agent-mic` 200 mm and the mic moves with it, because there was never a second
 * number to forget.
 *
 * ### This studio stays simulated, and says so louder
 *
 * `mateo-court`'s two studio instruments read a real desk in the LA Studio. This
 * one does not, and it is not going to: every machine in that inventory is
 * physically in that one room, and there is no sensor, camera or microphone
 * anywhere in the fleet at this address. Inventing an SF feed — or, worse,
 * quietly relabelling LA's readings — would be exactly the
 * `first-party-sensor` / `simulated` confusion `DeviceProvenance` exists to
 * prevent, in the direction that matters. So the answer is the honest one and it
 * is written into the sentence a viewer actually reads: *this is a room nobody
 * is standing in*. The day an SF room exists, `provenance` and `ranges` change
 * and nothing else here moves.
 *
 * ### The capability list matches LA's exactly, including the omission
 *
 * `capabilities` used to be `CANONICAL_CAPABILITIES[kind]`. It is now the same
 * hand-written pair `mateo-court` declares, which differs from canonical in two
 * ways, and both are deliberate:
 *
 * - **no `level`.** LA's real microphone has no passive level to read — getting
 *   one records the room — so it declares none. This one *could* invent one, and
 *   choosing not to is what makes the two studios the same instrument with two
 *   provenances rather than two different products. A viewer comparing them is
 *   then comparing where the numbers come from, which is the only interesting
 *   difference, instead of counting rows.
 * - **`mute` on the speaker**, which canonical omits and both real and simulated
 *   speakers plainly have.
 *
 * `ranges` restates the global default rather than overriding it, which looks
 * redundant and is not: stating the unit is now part of how a studio describes
 * itself, and the interesting thing about the pair is that this one really is
 * decibels and LA's really is a percentage of a mixer's travel.
 */
const STUDIO_MIC_CAPABILITIES: readonly DeviceCapability[] = ["power", "mute", "gain"];
const STUDIO_SPEAKER_CAPABILITIES: readonly DeviceCapability[] = [
  "power",
  "mute",
  "volume",
  "playback",
];

const DEVICES: DeviceDeclaration[] = [
  {
    id: "sf-desk-mic",
    kind: "mic",
    label: "Desk mic",
    assetId: MIC,
    anchor: {
      levelId: "level-1",
      propId: "agent-mic",
      roomId: "live-work",
      // The seat this mic is in front of. An address, like every other seat
      // reference in a pack — it is what lets a consumer ask whether anybody is
      // sitting where the mic is pointed without being told a coordinate.
      seatId: "sf-agent-01",
    },
    capabilities: STUDIO_MIC_CAPABILITIES,
    // A desk condenser's preamp, in decibels, because that is what it would be.
    ranges: { gain: { min: -12, max: 36, initial: 12, unit: "dB" } },
    provenance: "simulated",
    disclosure: DISCLOSURE,
  },
  {
    id: "sf-desk-speaker",
    kind: "speaker",
    label: "Desk speaker",
    assetId: SPEAKER,
    anchor: {
      levelId: "level-1",
      propId: "agent-speaker",
      roomId: "live-work",
      seatId: "sf-agent-01",
    },
    capabilities: STUDIO_SPEAKER_CAPABILITIES,
    provenance: "simulated",
    disclosure: DISCLOSURE,
  },
];

const ZONES: Zone[] = [
  { id: "zone-entry", name: "Entry", outline: rect(FACE, 6.7, 1.75, DEPTH - FACE), colorKey: "social" },
  { id: "zone-agent-bench", name: "Agent Bench", outline: rect(1.1, 3.5, 5.15, 5.6), colorKey: "focus" },
  { id: "zone-lounge", name: "Demo Lounge", outline: rect(6.1, 3.4, 11.35, 8.45), colorKey: "social" },
  { id: "zone-home", name: "Living Space", outline: rect(FACE, FACE, 8.55, 3.15), colorKey: "team-a" },
];

const VIEWPOINTS: Viewpoint[] = [
  {
    id: "arrival",
    number: "01",
    label: "The Front Door",
    shortLabel: "Front Door",
    levelId: "level-1",
    focus: { at: { x: 1.02, z: 7.68 }, distance: 11.8, height: 6.2, rotation: 2.35 },
    description:
      "The way in: a clear threshold with the two-place agent bench ahead, the demo lounge across the room, and the domestic half visible beyond it.",
  },
  {
    id: "studio",
    number: "02",
    label: "The Whole Studio",
    shortLabel: "Studio",
    levelId: "level-1",
    focus: { at: { x: 6.0, z: 4.5 }, distance: 16.5, height: 9.2, rotation: 0.65 },
    description:
      "Twelve metres by nine: one compact live/work headquarters, not a speculative corporate floor. Work, demos, cooking and sleep all fit in the same honest frame.",
  },
  {
    id: "agent-bench",
    number: "03",
    label: "The Agent Bench",
    shortLabel: "Agent Bench",
    levelId: "level-1",
    focus: { at: { x: 3.05, z: 4.7 }, distance: 6.4, height: 2.5, rotation: 0.25 },
    description:
      "Two workstations, two media-capable monitors and a clear aisle behind the chairs: enough space to build agents without pretending a hundred people sit here.",
  },
  {
    id: "demo-lounge",
    number: "04",
    label: "The Demo Lounge",
    shortLabel: "Demo Lounge",
    levelId: "level-1",
    focus: { at: { x: 8.2, z: 6.2 }, distance: 7.2, height: 2.65, rotation: 2.9 },
    description:
      "A real sofa facing the shared demo wall. This is where a screen session becomes something people can sit together and inspect.",
  },
  {
    id: "kitchen-home",
    number: "05",
    label: "The Kitchen and Alcove",
    shortLabel: "Home",
    levelId: "level-1",
    focus: { at: { x: 4.4, z: 1.75 }, distance: 7.5, height: 3.0, rotation: 3.5 },
    description:
      "A complete kitchen wall and island beside the sleeping alcove. The headquarters is also a home, and the environment says so without euphemism.",
  },
];

const LEVEL: Level = {
  id: "level-1",
  name: "Studio",
  elevation: 0,
  wallHeight: CEILING,
  wallThickness: INT,
  wallSurface: PAINT,
  floorplan: {
    rooms: ROOMS,
    walls: WALLS,
    props: PROPS,
    deskBanks: DESK_BANKS,
    seats: SEATS,
    zones: ZONES,
    devices: DEVICES,
  },
};

export const LUMBRIDGE_HQ: Office = {
  id: "lumbridge-hq",
  name: "SF HQ · Studio",
  levels: [LEVEL],
  viewpoints: VIEWPOINTS,
  // Coordinates remain the existing hand-authored approximation. No street
  // address is claimed or inferred by this environment rewrite.
  site: LUMBRIDGE_HQ_SITE,
  meta: {
    description:
      "A compact 12 × 9 metre San Francisco live/work studio: two agent workstations, a demo lounge, kitchen, sleeping alcove and bath/storage.",
    author: "Lumbridge",
    license: "CC0-1.0",
    version: "2.1.0",
    updated: "2026-08-21",
  },
};

export default LUMBRIDGE_HQ;
