import { describe, expect, it } from "vitest";
import { AmbientLight, DirectionalLight, Light, PointLight, Scene } from "three";
import type { RoomDef } from "@psx/shared";
import { PhysicsWorld } from "../engine/PhysicsWorld.js";
import { GALLERY } from "./rooms/gallery.js";
import { RoomBuilder } from "./RoomBuilder.js";

/**
 * RoomBuilder touches no WebGL — geometry and material construction are pure
 * data until a renderer compiles them — so the whole room can be built in Node.
 *
 * The ambient-light assertion exists because it regressed once: `RoomDef`
 * carried an `ambientLight` field that nothing ever instantiated, leaving the
 * scene lit only by point lights and pure black everywhere else.
 */

/** Gallery plus a flag-gated slab so removeOnFlag behaviour stays covered. */
const ROOM_WITH_SHUTTER: RoomDef = {
  ...GALLERY,
  geometry: [
    ...GALLERY.geometry,
    {
      id: "shutter",
      position: { x: 0, y: 1.5, z: -4 },
      visual: { kind: "box", size: { x: 2, y: 3, z: 0.2 }, color: 0x5c4a3a },
      colliders: [{ kind: "box", halfExtents: { x: 1, y: 1.5, z: 0.1 } }],
      removeOnFlag: "study-shutter-open",
    },
  ],
};

async function build(flags: string[] = [], room: RoomDef = GALLERY) {
  const scene = new Scene();
  const physics = new PhysicsWorld();
  await physics.init();
  const builder = new RoomBuilder(scene, physics);
  const built = builder.build(room, new Set(flags));
  return { scene, physics, builder, room: built };
}

const lightsOf = <T extends Light>(scene: Scene, kind: new (...args: never[]) => T): T[] => {
  const found: T[] = [];
  scene.traverse((object) => {
    if (object instanceof kind) found.push(object);
  });
  return found;
};

describe("RoomBuilder lighting", () => {
  it("instantiates the room's ambient light", async () => {
    const { scene, physics } = await build();

    const ambient = lightsOf(scene, AmbientLight);
    expect(ambient).toHaveLength(1);
    expect(ambient[0]!.intensity).toBeGreaterThan(0);
    expect(ambient[0]!.color.getHex()).toBe(GALLERY.ambientLight.color);

    physics.dispose();
  });

  it("instantiates every light declared on the room", async () => {
    const { scene, physics } = await build();

    const expectedPoints = GALLERY.lights.filter((l) => l.kind === "point").length;
    const expectedDirectional = GALLERY.lights.filter((l) => l.kind === "directional").length;

    expect(lightsOf(scene, PointLight)).toHaveLength(expectedPoints);
    expect(lightsOf(scene, DirectionalLight)).toHaveLength(expectedDirectional);

    physics.dispose();
  });

  it("gives point lights enough intensity to reach across the room", async () => {
    const { scene, physics } = await build();

    // RoomBuilder floors point intensity and uses decay 1 (game-style falloff).
    for (const light of lightsOf(scene, PointLight)) {
      expect(light.intensity).toBeGreaterThanOrEqual(8);
      expect(light.decay).toBe(1);
    }

    physics.dispose();
  });
});

describe("RoomBuilder flag-gated geometry", () => {
  it("builds the shutter when its flag is unset", async () => {
    const { scene, physics } = await build([], ROOM_WITH_SHUTTER);

    expect(scene.getObjectByName("shutter")).toBeDefined();
    expect(physics.get("geo:shutter")).toBeDefined();

    physics.dispose();
  });

  it("omits the shutter, mesh and collider, when the flag is already set", async () => {
    const { scene, physics } = await build(["study-shutter-open"], ROOM_WITH_SHUTTER);

    expect(scene.getObjectByName("shutter")).toBeUndefined();
    expect(physics.get("geo:shutter")).toBeUndefined();

    physics.dispose();
  });

  it("removes both mesh and collider when the flag fires during play", async () => {
    const { scene, physics, builder } = await build([], ROOM_WITH_SHUTTER);

    builder.removeGeometry("shutter");

    expect(scene.getObjectByName("shutter")).toBeUndefined();
    expect(physics.get("geo:shutter")).toBeUndefined();

    physics.dispose();
  });
});

describe("campaign rooms", () => {
  it("registers nine linked precinct rooms", async () => {
    const { ROOM_CATALOGUE } = await import("./rooms/index.js");
    expect(ROOM_CATALOGUE.size).toBe(9);
    expect(ROOM_CATALOGUE.has("manor-foyer")).toBe(true);
    expect(ROOM_CATALOGUE.has("manor-cellar")).toBe(true);
    expect(ROOM_CATALOGUE.has("precinct-office")).toBe(true);
    expect(ROOM_CATALOGUE.has("precinct-interview")).toBe(true);
    expect(ROOM_CATALOGUE.has("precinct-lounge")).toBe(true);
  });
});
