/**
 * One California, as one pack.
 *
 * These assert the three things about `unifiedCalifornia` that a picture cannot
 * check and that a careless edit would silently undo: that the state stops
 * drawing what a metro now draws, that every metro lot is marked as detail so
 * the level of detail has something to act on, and that the lattice this all
 * rides on is the size the file claims it is. The last one is the whole reason
 * the merge is affordable, and it is one multiplication away from not being.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";

import {
  UNIFIED_COARSE_FACTOR,
  UNIFIED_FINE_METRES,
  unifiedCalifornia,
} from "../cities/unify.ts";
import CALIFORNIA from "../cities/california.ts";
import { reconciledCity } from "../cities/reconcile.ts";
import SAN_FRANCISCO from "../cities/sf.ts";
import SOCAL from "../cities/socal.ts";

const { city, report } = unifiedCalifornia();

/** `buildAxis`, restated — the lattice cost is the claim being checked. */
function axisLength(min: number, max: number, fine: number, coarse: number, bands: number[][]): number {
  let n = 1;
  let x = min;
  const inFine = (v: number) => bands.some(([a, b]) => v >= (a as number) - coarse && v <= (b as number) + coarse);
  while (x < max) {
    x += inFine(x) ? fine : coarse;
    n += 1;
  }
  return n;
}

describe("one California", () => {
  it("is still California, by id, because everything downstream keys on that", () => {
    // The fire gate, the ladder's region table, `?city=`, and the budget
    // harness's `data-board` assertion all name "california" already. A fourth
    // board id would mean teaching every one of them about a board that is the
    // same place.
    assert.equal(city.id, CALIFORNIA.id);
    assert.equal(city.bounds.minLat, CALIFORNIA.bounds.minLat);
    assert.equal(city.bounds.maxLat, CALIFORNIA.bounds.maxLat);
  });

  it("stops drawing the state's coarse stand-ins where a metro now stands", () => {
    /*
     * Hills *sum*: `World.elevationAt` blends every hill covering a point, so a
     * coarse state hill left under a metro's fine ones lifts the whole Bay. And
     * the state's districts include coarse stand-ins for both metros, which
     * drawn together with the real ones is two cities in one place.
     */
    assert.ok(report.hills.dropped > 0, "no state hill was cleared for a metro");
    assert.ok(report.districts.dropped > 0, "no state district was cleared for a metro");

    const metroBounds = [SAN_FRANCISCO.bounds, SOCAL.bounds];
    const inside = (lat: number, lng: number) =>
      metroBounds.some((b) => lat >= b.minLat && lat <= b.maxLat && lng >= b.minLng && lng <= b.maxLng);

    /*
     * By **identity**, not by name, and that is a finding rather than a style
     * choice: six hills are named in both the state pack and a metro pack —
     * Marin Headlands, San Bruno Mountain, Mount Diablo, Bolinas Ridge, Mount
     * Gleason, Mount Wilson — because two packs independently named the same
     * real hill. Keyed on the string, San Francisco's own Marin Headlands reads
     * as a state hill that survived inside a metro and fails this test for a
     * bug that is not there. `ladder.ts` hit the same thing with "Whole Board"
     * on two boards; the rule both times is that a name is not an identity.
     *
     * The merge spreads the arrays without copying their elements, so the state
     * pack's own hill objects are the ones to look for.
     */
    const stateHills = new Set<unknown>(CALIFORNIA.hills);
    let checked = 0;
    for (const hill of city.hills) {
      if (!stateHills.has(hill)) continue;
      checked += 1;
      assert.ok(!inside(hill.lat, hill.lng), `state hill "${hill.name}" is still inside a metro`);
    }
    assert.ok(checked > 0, "no state hill survived at all, so this asserted nothing");
  });

  it("marks every metro lot as detail, which is what the LOD acts on", () => {
    const detail = city.districts.filter((d) => d.detail === true);
    const base = city.districts.filter((d) => d.detail !== true);
    assert.equal(detail.length, SAN_FRANCISCO.districts.length + SOCAL.districts.length);
    assert.equal(base.length, report.districts.state - report.districts.dropped);
    // And the state's own districts are never marked, or the board would hide
    // California when you stood back from it.
    assert.ok(base.length > 0, "the state kept no districts of its own");
  });

  it("keeps the lattice the size this whole merge depends on", () => {
    const cf = city.coarseFactor ?? 1;
    const lat = axisLength(
      city.bounds.minLat, city.bounds.maxLat, city.cellLat, city.cellLat * cf,
      (city.focusRegions ?? []).map((r) => [r.minLat, r.maxLat]),
    );
    const lng = axisLength(
      city.bounds.minLng, city.bounds.maxLng, city.cellLng, city.cellLng * cf,
      (city.focusRegions ?? []).map((r) => [r.minLng, r.maxLng]),
    );
    const points = lat * lng;
    /*
     * The ceiling, not a description. At 150 m / x8 this was 1.23M points and
     * the board rendered 1,114,226 triangles against a 440,000 cap — terrain,
     * not buildings. Anything that pushes this back over half a million points
     * is re-opening that, and should be measured rather than assumed.
     */
    assert.ok(points < 500_000, `${points} lattice points — the terrain budget was found at ~180k`);
    assert.equal(UNIFIED_COARSE_FACTOR, cf);
    assert.ok(Math.abs(city.cellLat * 111_320 - UNIFIED_FINE_METRES) < 1);
  });

  it("carries both metros' ports, which the state board never had", () => {
    // The most visible single consequence of the merge: the state board now has
    // a harbour on it, and `main.ts` attaches the vessel layer to any pack with
    // one. Before this, `sf.ts` and `california.ts` had zero berths between them.
    assert.ok((city.ports?.length ?? 0) >= (SOCAL.ports?.length ?? 0));
    assert.ok((city.ports?.length ?? 0) > (CALIFORNIA.ports?.length ?? 0));
  });

  it("is memoised, because World builds one per mount", () => {
    assert.equal(unifiedCalifornia().city, city);
  });
});

describe("one California's chapters", () => {
  /*
   * The metro rungs were merged, photographed and reverted at `64618cb`, and
   * this test used to assert the revert so that whoever merged them again did it
   * deliberately. This is that deliberate merge, and the reason is in `unify.ts`
   * in full: the revert's stated blocker was the corridor's 4.7 km atlas glyph
   * filling every close frame, and `23e4bb8` — the next commit — withdraws that
   * glyph inside `DETAIL_STANDOFF_M`. The guard is kept and turned around: it
   * now asserts the conversion, which is the part a careless edit breaks
   * silently.
   */
  it("carries the state's rungs first, in their authored order", () => {
    // `createScene` opens on `chapters[0]` and the capture harness reaches the
    // state's chapters by index, so the state's list has to stay a prefix.
    assert.deepEqual(
      city.chapters.slice(0, CALIFORNIA.chapters.length).map((c) => c.id),
      CALIFORNIA.chapters.map((c) => c.id),
    );
  });

  it("adds both metros' rungs, without their whole-board duplicates", () => {
    const ids = city.chapters.map((c) => c.id);
    for (const metro of [SAN_FRANCISCO, SOCAL]) {
      for (const chapter of metro.chapters) {
        if (chapter.id === "all") {
          // `california-overview` already frames the whole board; a second rung
          // at the same range is a repeated step on the ladder.
          assert.ok(!ids.includes(chapter.id), `whole-board rung "${chapter.id}" was merged`);
          continue;
        }
        assert.ok(ids.includes(chapter.id), `metro rung "${chapter.id}" is missing`);
      }
    }
    // Nothing merged twice, which a naive concat of two packs sharing an id
    // would do without failing anything else.
    assert.equal(new Set(ids).size, ids.length);
    assert.ok(ids.includes("fidi"), "the visitor landing names a chapter the merge dropped");
  });

  it("restates each merged rung in this board's units, keeping its true metres", () => {
    /*
     * `distance` is horizontal and converts on `latScale` alone. `height` is
     * vertical, has already been through the source pack's exaggeration, and
     * has to carry the ratio of exaggerations as well — otherwise the camera
     * arrives at the right altitude over relief drawn 4.2x taller and every
     * authored angle flattens. Both are checked against the arithmetic rather
     * than against a recorded number, so a pack edit cannot falsify them.
     */
    /*
     * Reconciled on both sides, because `unifiedCalifornia` merges reconciled
     * packs and `reconciledCity` is **not** the identity: the relief rule
     * rewrites `verticalExaggeration`, which takes San Francisco from the 3.6
     * its file authors to 5.78. Checking the arithmetic against the raw packs
     * measures a board nobody is looking at — and it fails, which is the only
     * reason this is written down rather than assumed.
     */
    const here = reconciledCity(CALIFORNIA);
    for (const raw of [SAN_FRANCISCO, SOCAL]) {
      const metro = reconciledCity(raw);
      const horizontal = here.latScale / metro.latScale;
      const vertical =
        horizontal * (here.verticalExaggeration / metro.verticalExaggeration);
      for (const source of metro.chapters) {
        if (source.id === "all") continue;
        const merged = city.chapters.find((c) => c.id === source.id);
        assert.ok(merged, `metro rung "${source.id}" is missing`);
        // The place itself is absolute and must not have moved at all.
        assert.equal(merged.focus.lat, source.focus.lat);
        assert.equal(merged.focus.lng, source.focus.lng);
        assert.ok(
          Math.abs(merged.focus.distance - source.focus.distance * horizontal) < 1e-9,
          `"${source.id}" stand-off was not restated in this board's units`,
        );
        assert.ok(
          Math.abs(merged.focus.height - source.focus.height * vertical) < 1e-9,
          `"${source.id}" height ignored the exaggeration ratio`,
        );
      }
    }
  });

  it("puts every merged rung inside the board it now belongs to", () => {
    // A rung whose focus is off the bounds is a camera aimed at empty sea, and
    // the merge is the one operation that can produce one.
    for (const chapter of city.chapters) {
      assert.ok(
        chapter.focus.lat >= city.bounds.minLat && chapter.focus.lat <= city.bounds.maxLat,
        `chapter "${chapter.id}" points off the board in latitude`,
      );
      assert.ok(
        chapter.focus.lng >= city.bounds.minLng && chapter.focus.lng <= city.bounds.maxLng,
        `chapter "${chapter.id}" points off the board in longitude`,
      );
    }
  });
});

describe("one California's cities are lotted like cities", () => {
  it("measures a detail district's lot in metres, not in scene units", () => {
    /*
     * The defect this guards: `LOT` is fixed in scene units, which makes a lot
     * 40 m on San Francisco's board and **806 m** on the statewide one. Put
     * San Francisco's fifty-two districts on the statewide board unchanged and
     * they are lotted at 806 m — and lots go as the inverse square of their
     * size, so the Bay Area's ~84,000 buildings become about two hundred and a
     * city renders as a handful of grey slabs.
     *
     * Measured in the browser after the fix: the merged board carried 59,166
     * building instances, of which 2,839 were the state's own and 56,327 the
     * two metros'. Before it, the metros contributed roughly two hundred.
     *
     * The 56,327 is now one rung of several. `DETAIL_LOT_TIERS` builds a detail
     * district at 40, 80, 160, 200, 250 and 400 m and the board draws whichever
     * of them its visible lots fit the budget at — 135,548 lots at the finest
     * and 18,742 at the coarsest, with 160 m still on the ladder and still
     * exactly the 56,327 it was, drawn from the same seed. Which is precisely
     * why this asserts the property rather than the count: the count moves with
     * any pack edit *and* with where the camera is standing, and the property
     * does neither.
     */
    const detail = city.districts.filter((d) => d.detail === true);
    assert.ok(detail.length > 0);
    // The state's own districts must NOT be marked, or they would be re-lotted
    // at metro sizes across the whole of California — every rung of the ladder,
    // for eight state-sized polygons — and the board would never build.
    const base = city.districts.filter((d) => d.detail !== true);
    assert.ok(base.length > 0);
    for (const d of base) assert.equal(d.detail, undefined);
  });

  it("keeps the metros' own boards untouched, which is the point of the flag", () => {
    // `unify` copies districts before marking them, so nothing it does can
    // reach back into `sf.ts` or `socal.ts` and re-lot the dedicated boards.
    for (const d of SAN_FRANCISCO.districts) assert.equal(d.detail, undefined);
    for (const d of SOCAL.districts) assert.equal(d.detail, undefined);
  });
});

describe("the detail repack", () => {
  it("distinguishes 'nothing packed yet' from 'nothing visible'", async () => {
    /*
     * A regression test for a bug this file's own author wrote and the budget
     * caught. `DetailStore.key` remembers which districts the instance buffers
     * currently hold, and it used to start as `""` — the same key an *empty*
     * visible set produces. So the first call, at the whole-board pose where
     * nothing should draw, compared equal to the initial state, took the early
     * return, and left `InstancedMesh.count` at the constructor's value, which
     * is every instance. Measured: 911,541 triangles against a 440,000 cap,
     * with the code meant to prevent it running and doing nothing.
     *
     * The distinction is the whole fix, so it is what is asserted — on the
     * source, because building a real `InstancedMesh` here would need a GPU.
     */
    const source = await import("node:fs/promises").then((fs) =>
      fs.readFile("src/engine/blocks.ts", "utf8"),
    );
    assert.ok(
      /key: string \| null;/.test(source),
      "DetailStore.key must admit a value that no visible set can produce",
    );
    assert.ok(
      /key: null,/.test(source),
      "the store must start at that value, not at an empty key",
    );
    assert.ok(
      !/key: "",/.test(source),
      'an empty-string initial key is the bug: it equals the key of an empty visible set',
    );
  });
});

describe("one California is a detailed board, and the engine must agree", () => {
  it("declares focus regions, which is what the live-feed gates read", async () => {
    /*
     * Live ADS-B and live weather were gated on `id !== "california"` — "not the
     * coarse statewide board", correct on the day it was written. `unify` then
     * built a board that is the whole state AND metro-detailed, keeping the
     * `california` id on purpose so the fire gate, the ladder's region table and
     * every `?city=` deep link keep working. It inherited a gate meant for
     * something else and shipped with live traffic and live weather silently
     * off — a defect that reads as "it feels less alive" and never as an error.
     *
     * The predicate is now `focusRegions`, so this asserts the property the
     * gates depend on, and that the coarse pack still fails it.
     */
    assert.ok((city.focusRegions?.length ?? 0) > 0, "the merged board must declare focus regions");
    assert.equal(CALIFORNIA.focusRegions ?? undefined, undefined);

    const main = await import("node:fs/promises").then((fs) =>
      fs.readFile("src/main.ts", "utf8"),
    );
    assert.ok(
      !/id !== "california" && access\.can\.liveEnvironment/.test(main),
      "a live feed is still gated on the board id rather than on the pack",
    );
    assert.ok(
      !/const initialMarkers = id === "sf"/.test(main),
      "markers are still gated on the board id rather than on the board's bounds",
    );
  });
});
