import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import { createBuildingGlyph, layoutBuildingGlyph } from "../engine/buildingGlyph.ts";
import type { BuildingGlyph, City } from "../engine/types.ts";
import { World } from "../engine/world.ts";
import { setReconcile } from "../cities/reconcile.ts";

setReconcile(false);

function glyph(overrides: Partial<BuildingGlyph> = {}): BuildingGlyph {
  return {
    kind: "building",
    width: 40,
    depth: 30,
    height: 100,
    storeys: 24,
    heading: 0,
    profile: "block",
    ...overrides,
  };
}

describe("map building glyph layouts", () => {
  it("steps a tower inward without changing its declared height", () => {
    const segments = layoutBuildingGlyph(glyph({ profile: "tower" }));
    assert.equal(segments.length, 3);
    assert.ok(segments[1]!.width < segments[0]!.width);
    assert.ok(segments[2]!.width < segments[1]!.width);
    assert.equal(Math.max(...segments.map((s) => s.base + s.height)), 100);
  });

  it("leaves a real hole inside a courtyard ring", () => {
    const segments = layoutBuildingGlyph(
      glyph({ profile: "courtyard", width: 36, depth: 26, height: 9 }),
    );
    assert.equal(segments.length, 4);

    const north = segments[0]!;
    const west = segments[2]!;
    const openWidth = 36 - west.width * 2;
    const openDepth = 26 - north.depth * 2;
    assert.ok(openWidth > 0, `courtyard closes across its width: ${openWidth}`);
    assert.ok(openDepth > 0, `courtyard closes across its depth: ${openDepth}`);
  });

  it("reserves the top of a hangar for its pitched roof", () => {
    const [body] = layoutBuildingGlyph(glyph({ profile: "hangar", height: 11 }));
    assert.ok(body);
    assert.equal(body.base, 0);
    assert.equal(body.height, 11 * 0.72);
  });

  it("keeps undersized authored footprints renderable", () => {
    const [segment] = layoutBuildingGlyph(
      glyph({ width: 0, depth: -2, height: 0, profile: "block" }),
    );
    assert.ok(segment);
    assert.deepEqual(
      { width: segment.width, depth: segment.depth, height: segment.height },
      { width: 4, depth: 4, height: 3 },
    );
  });
});

/** The same square of ground at two scales; see `markerScale.test.ts`. */
function board(latScale: number): City {
  return {
    id: `glyph-scale-${latScale}`,
    name: "Glyph Scale Board",
    center: { lat: 37.78, lng: -122.42 },
    bounds: { minLat: 37.7, maxLat: 37.86, minLng: -122.5, maxLng: -122.34 },
    latScale,
    verticalExaggeration: 3.6,
    cellLat: 0.01,
    cellLng: 0.01,
    coastFalloff: 0.02,
    landmasses: [
      [
        [37.71, -122.49],
        [37.85, -122.49],
        [37.85, -122.35],
        [37.71, -122.35],
      ],
    ],
    parks: [],
    inlandWater: [],
    districts: [],
    landmarks: [],
    bridges: [],
    roads: [],
    chapters: [],
    hills: [],
  };
}

const HQ: BuildingGlyph = {
  kind: "building",
  width: 48,
  depth: 42,
  height: 326,
  storeys: 61,
  heading: 205,
  profile: "tower",
  seed: 115,
  bodyColor: 0x8799a8,
};

function doorDepthM(latScale: number): number {
  const world = new World(board(latScale));
  const built = createBuildingGlyph(world, HQ, 0xf5b53f);
  let depthM = 0;
  built.group.traverse((node) => {
    if (!(node instanceof THREE.Mesh) || node.name !== "building entrance") return;
    node.updateWorldMatrix(true, false);
    depthM = Math.abs(node.scale.z) * world.metresPerUnit;
  });
  built.dispose();
  return depthM;
}

describe("a building glyph is a true-metre object", () => {
  it("draws the same door thickness on a 94 m board and a 1,919 m one", () => {
    const metro = doorDepthM(1180);
    const state = doorDepthM(58);
    assert.ok(metro > 1 && metro < 6, `a door ${metro.toFixed(2)} m thick is not a door`);
    assert.ok(
      Math.abs(metro - state) < 0.05,
      `a door is ${metro.toFixed(2)} m thick on a 94 m/unit board and ` +
        `${state.toFixed(2)} m on a 1,919 m/unit one`,
    );
  });
});
