/**
 * The apron is built from the site's elevation and from nothing else.
 *
 * `officeExterior.test.ts` asserts the outcome for the three shipped sites.
 * This file asserts the *reason*, which is the part that rots: a rule that
 * happens to produce the right answer for `lumbridge-hq` because it names
 * `lumbridge-hq` passes every test in that file and is wrong the moment a
 * self-hoster hands the same tower a different id, or an office moves.
 *
 * This repo has been here before. A door marker was once gated on
 * `id === "sf"`, and had it shipped it would have pinned the Los Angeles
 * building to San Francisco. So the sites below are the shipped ones with one
 * number swapped and their identities kept: a `lumbridge-hq` that has come down
 * to street level must get its car, and a `mateo-court` that has gone up a
 * tower must lose it. Nothing but `elevation` moves between the two halves of
 * each pair.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import * as THREE from "three";
import { MaterialRegistry } from "../../assets/materials.ts";
import { kit } from "../../assets/kit.ts";
import "../../assets/office/index.ts";
import { createOfficeExterior } from "../../engine/officeExterior.ts";
import { LUMBRIDGE_HQ_SITE, MATEO_COURT_SITE } from "../../offices/sites.ts";
import type { OfficeSite } from "../../interiors/types.ts";
import {
  GROUND_ARRIVAL_MAX_ELEVATION_M,
  arrivalGroundFor,
} from "../../transport/exteriorVehicle.ts";

/** A deterministic generator, so a failure is reproducible from the seed alone. */
function seeded(seed: number): () => number {
  let a = seed >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/** How many cars a given site gets outside it. */
function carsOutside(site: OfficeSite): number {
  assert.ok(site.arrival, "the site under test authors an arrival anchor");
  const materials = new MaterialRegistry({ quality: "low" });
  const exterior = createOfficeExterior({
    site,
    arrival: site.arrival,
    assets: kit,
    materials,
    rand: seeded(4242),
    detail: "corridor",
  });
  let cars = 0;
  exterior.object.traverse((object: THREE.Object3D) => {
    if (object.userData.vehicleModel === "model-x") cars += 1;
  });
  exterior.dispose();
  return cars;
}

/** The same site, moved vertically and changed in no other way. */
function at(site: OfficeSite, elevation: number): OfficeSite {
  return { ...site, elevation };
}

describe("the apron follows the ground, not the office id", () => {
  it("gives the tower's own site a car once it is standing on a street", () => {
    // Same id, same lat/lng, same heading, same anchor — down at a kerb.
    assert.equal(carsOutside(at(LUMBRIDGE_HQ_SITE, 1.2)), 1);
  });

  it("takes the street office's car away once its site is up a tower", () => {
    assert.equal(carsOutside(at(MATEO_COURT_SITE, 188)), 0);
  });

  it("switches at the documented threshold and nowhere else", () => {
    // Both sides of the constant, from both packs, so neither the number nor
    // the comparison can drift without this failing.
    for (const site of [LUMBRIDGE_HQ_SITE, MATEO_COURT_SITE]) {
      assert.equal(carsOutside(at(site, GROUND_ARRIVAL_MAX_ELEVATION_M)), 1);
      assert.equal(carsOutside(at(site, GROUND_ARRIVAL_MAX_ELEVATION_M + 0.01)), 0);
    }
  });

  it("agrees with the pure rule at every elevation it is asked about", () => {
    // `arrivalGroundFor` is what the UI, a verifier or a future pack tool would
    // read to predict this without building a scene, so the two must not be
    // able to disagree.
    for (const elevation of [-3, 0, 1.2, 4, 12, 29.9, 30, 30.5, 61, 188, 400]) {
      const expected = arrivalGroundFor(elevation) === "ground" ? 1 : 0;
      assert.equal(
        carsOutside(at(MATEO_COURT_SITE, elevation)),
        expected,
        `elevation ${elevation}`,
      );
    }
  });

  it("keeps the anchor readable even where nothing is drawn", () => {
    // The suppressed exterior is still labelled with the stall it declined to
    // build, because a caller that wants to caption "Podium kerb" should not
    // have to reach back into the pack for it — and because a silently empty
    // Group is indistinguishable from a bug.
    const materials = new MaterialRegistry({ quality: "low" });
    assert.ok(LUMBRIDGE_HQ_SITE.arrival);
    const exterior = createOfficeExterior({
      site: LUMBRIDGE_HQ_SITE,
      arrival: LUMBRIDGE_HQ_SITE.arrival,
      assets: kit,
      materials,
      rand: seeded(1),
      detail: "corridor",
    });
    assert.equal(exterior.object.userData.suppressed, "no-ground");
    assert.equal(exterior.object.userData.label, "Podium kerb");
    assert.equal(exterior.object.userData.arrivalKind, "vehicle-stall");
    exterior.dispose();
  });
});
