/**
 * Three packs, one place: do the boards agree about where a thing is?
 *
 * `World.lngScale` is `latScale × cos(centre.lat)`, and each pack takes the
 * cosine at its **own** centre — 37.30°, 37.77°, 33.82°. A degree of longitude
 * is then worth 88,552 m on the state board and 92,484 m on Southern
 * California, so the two boards put the same coordinate in two different places
 * and the gap grows with distance from the anchor. At a 109 km stand-off the
 * worst of it is about 3% of the frame: a visible sideways slide in any
 * transition that shows both boards at once, and a permanent disagreement about
 * where Riverside is.
 *
 * The numbers below are measured on these packs, not quoted from a brief.
 */

import assert from "node:assert/strict";
import { afterEach, describe, it } from "node:test";
import CALIFORNIA from "../cities/california.ts";
import { REFERENCE_LAT, setReconcile } from "../cities/reconcile.ts";
import SF from "../cities/sf.ts";
import SOCAL from "../cities/socal.ts";
import { World } from "../engine/world.ts";

/** Downtown Los Angeles: inside both southern boards, so a fair shared origin. */
const ANCHOR: [number, number] = [34.05, -118.24];

const PLACES: [string, number, number][] = [
  ["the shared north-east corner", 34.36, -117.22],
  ["Riverside", 33.98, -117.37],
  ["the south-west corner", 33.28, -118.88],
];

/** Where a board puts a place, in true metres from the anchor. */
function offset(world: World, lat: number, lng: number): [number, number] {
  const [x0, z0] = world.project(...ANCHOR);
  const [x1, z1] = world.project(lat, lng);
  return [(x1 - x0) * world.metresPerUnit, (z1 - z0) * world.metresPerUnit];
}

function disagreement(a: World, b: World, lat: number, lng: number): number {
  const [ax, az] = offset(a, lat, lng);
  const [bx, bz] = offset(b, lat, lng);
  return Math.hypot(ax - bx, az - bz);
}

afterEach(() => setReconcile(null));

describe("projection", () => {
  it("disagrees by kilometres today", () => {
    setReconcile(false);
    const california = new World(CALIFORNIA);
    const socal = new World(SOCAL);
    const worst = Math.max(...PLACES.map(([, lat, lng]) => disagreement(california, socal, lat, lng)));
    assert.ok(worst > 3_500, `expected kilometres of drift, measured ${worst.toFixed(0)} m`);
    assert.ok(worst < 4_200, `measured ${worst.toFixed(0)} m — the packs moved, re-read this test`);
  });

  it("agrees within 50 m once every board squashes longitude the same way", () => {
    setReconcile(["projection"]);
    const worlds = [CALIFORNIA, SF, SOCAL].map((pack) => new World(pack));
    for (const [name, lat, lng] of PLACES) {
      for (const a of worlds) {
        for (const b of worlds) {
          const gap = disagreement(a, b, lat, lng);
          assert.ok(gap < 50, `${name}: ${a.city.id} vs ${b.city.id} is ${gap.toFixed(0)} m apart`);
        }
      }
    }
  });

  it("puts every board on the one reference latitude", () => {
    setReconcile(["projection"]);
    for (const pack of [CALIFORNIA, SF, SOCAL]) {
      const world = new World(pack);
      const expected = pack.latScale * Math.cos((REFERENCE_LAT * Math.PI) / 180);
      assert.ok(Math.abs(world.lngScale - expected) < 1e-9, pack.id);
      // And the hill squash follows it, or hills stop being circles in scene
      // space on the two boards whose own latitude is not the reference.
      assert.ok(Math.abs(world.lngSquash - world.lngScale / pack.latScale) < 1e-15, pack.id);
    }
  });

  /**
   * The price, stated so it cannot be discovered later. Southern California is
   * projected at 37.3° rather than at its own 33.82°, so it draws narrower in x
   * than a projection taken at its own latitude would — measured, 4.25% — and
   * San Francisco 0.64% wider. That is the trade: local aspect, for
   * registration that is exact by construction.
   */
  it("costs Southern California 4.25% of its width and San Francisco 0.64%", () => {
    setReconcile(["projection"]);
    const shrink = (pack: typeof SOCAL): number => {
      const before = pack.latScale * Math.cos((pack.center.lat * Math.PI) / 180);
      return new World(pack).lngScale / before - 1;
    };
    assert.ok(Math.abs(shrink(SOCAL) + 0.0425) < 0.0005, String(shrink(SOCAL)));
    assert.ok(Math.abs(shrink(SF) - 0.0064) < 0.0005, String(shrink(SF)));
    assert.equal(shrink(CALIFORNIA), 0);
  });
});
