/**
 * The exterior arrival anchor: one marked stall on the ground outside each
 * shipped building.
 *
 * `src/engine/officeExterior.ts` builds an apron and a vehicle there, and it
 * needs a number no other field in the format can give it. `site.lat`/`lng` says
 * where the building is on the earth and nothing about which corner of the lot
 * you park on; `Plan.bounds` is the extent of what was authored and its edge is
 * a wall, not a kerb. So the stall is authored — and the one thing an authored
 * stall can get catastrophically wrong is being **inside the building**, which
 * renders as a car in the lobby and looks entirely plausible in the source.
 *
 * That check is deliberately here and not in `Plan`. A pack may legitimately
 * mean a covered undercroft or a courtyard, and the resolver has no business
 * ruling on architecture; these three packs mean the street, the podium kerb and
 * the apron, and this is where that is stated.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { Plan } from "../../interiors/plan.ts";
import type { Office } from "../../interiors/types.ts";
import LUMBRIDGE_HQ from "../../offices/lumbridge-hq.ts";
import FRONTIER_VALLEY from "../../offices/frontier-valley.ts";
import MATEO_COURT from "../../offices/mateo-court.ts";

const PACKS: readonly Office[] = [LUMBRIDGE_HQ, FRONTIER_VALLEY, MATEO_COURT];

describe("every shipped pack parks a vehicle outside itself", () => {
  for (const pack of PACKS) {
    const arrival = pack.site?.arrival;

    it(`${pack.id} declares a vehicle stall on a level that exists`, () => {
      assert.ok(pack.site, `${pack.id} has no site`);
      assert.ok(arrival, `${pack.id} has no arrival anchor`);
      assert.equal(arrival.kind, "vehicle-stall");
      assert.ok(Number.isFinite(arrival.rotation), `${pack.id} stall has no rotation`);
      assert.ok(Number.isFinite(arrival.position.x) && Number.isFinite(arrival.position.z));
      assert.ok(
        pack.levels.some((level) => level.id === arrival.levelId),
        `${pack.id} stall stands on unknown level "${arrival?.levelId}"`,
      );
    });

    it(`${pack.id} stands its stall outside every room, on every level`, () => {
      assert.ok(arrival);
      const plan = new Plan(pack, { warn: false });
      // The named level is what the criterion is about; the others are checked
      // too because a stall under an upper storey is still under a building.
      for (const level of plan.levels) {
        const room = plan.roomAt(level.id, arrival.position);
        assert.equal(
          room,
          null,
          `${pack.id} parks in ${room?.name} on ${level.id}`,
        );
      }
    });

    /**
     * Stronger than "not in a room", and true of all three by intent rather than
     * by rule: none of them parks in its own courtyard or under its own upper
     * floor. `Plan.bounds` is the union of every level's walls, props and slabs,
     * so a stall outside it is a stall outside the building.
     */
    it(`${pack.id} stands its stall clear of the whole footprint`, () => {
      assert.ok(arrival);
      const bounds = new Plan(pack, { warn: false }).bounds;
      const { x, z } = arrival.position;
      const outside =
        x < bounds.minX || x > bounds.maxX || z < bounds.minZ || z > bounds.maxZ;
      assert.ok(
        outside,
        `${pack.id} stall at (${x}, ${z}) is inside the footprint ` +
          `x ${bounds.minX}..${bounds.maxX}, z ${bounds.minZ}..${bounds.maxZ}`,
      );
    });

    it(`${pack.id} hands the very anchor it authored to Plan`, () => {
      // Identity, not equality — the same argument `office.test.ts` makes about
      // sites. A copy means somebody restated a coordinate on the way through.
      assert.equal(new Plan(pack, { warn: false }).exteriorArrival, arrival);
    });
  }
});

describe("Plan drops an arrival it cannot use", () => {
  function withArrival(levelId: string, kind: string): Office {
    return {
      ...MATEO_COURT,
      site: {
        ...MATEO_COURT.site!,
        arrival: {
          levelId,
          position: { x: 22.6, z: -3.4 },
          rotation: 0,
          kind: kind as "vehicle-stall",
        },
      },
    };
  }

  it("reports an unknown level and resolves to nothing", () => {
    const plan = new Plan(withArrival("level-9", "vehicle-stall"), { warn: false });
    assert.equal(plan.exteriorArrival, null);
    assert.deepEqual(
      plan.problems.map((problem) => `${problem.where}: ${problem.action}`),
      ["site.arrival: dropped"],
    );
  });

  it("reports a kind this build does not know", () => {
    const plan = new Plan(withArrival("level-1", "helipad"), { warn: false });
    assert.equal(plan.exteriorArrival, null);
    assert.match(plan.problems[0]?.message ?? "", /unknown kind "helipad"/);
  });

  /**
   * And a pack with no stall at all is not a pack with a problem. Every field
   * added to this format has to leave the packs written before it existed
   * resolving exactly as they did, which for an office with no vehicle outside
   * it means `null` and silence.
   */
  it("says nothing about a pack that authored no stall", () => {
    const site = { ...MATEO_COURT.site! };
    delete site.arrival;
    const plan = new Plan({ ...MATEO_COURT, site }, { warn: false });
    assert.equal(plan.exteriorArrival, null);
    assert.deepEqual(plan.problems, []);
  });
});
