import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import ts from "typescript";

const source = await readFile(new URL("../apps/catalog/src/mapSpiralMath.ts", import.meta.url), "utf8");
const { outputText } = ts.transpileModule(source, {
  compilerOptions: {
    module: ts.ModuleKind.ES2022,
    target: ts.ScriptTarget.ES2022,
  },
  fileName: "mapSpiralMath.ts",
});
const spiralMath = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`);
const {
  MAX_SPIRAL_RADIUS_METERS,
  directGeodesicDestination,
  normalizeRadians,
  spiralAngleAtArcLength,
  spiralArcLengthAtAngle,
  spiralRadiusAtAngle,
  spiralSurfaceFrame,
} = spiralMath;

const twoPi = Math.PI * 2;

test("one complete spiral turn increases radius by exactly one pitch", () => {
  assert.equal(spiralRadiusAtAngle(twoPi, 10_000), 10_000);
});

test("arc-length inversion keeps configured surface distance across the route", () => {
  for (const distance of [0, 1, 20, 1_000, 25_000, 1_000_000]) {
    const angle = spiralAngleAtArcLength(distance, 10_000);
    const restored = spiralArcLengthAtAngle(angle, 10_000);
    assert.ok(Math.abs(restored - distance) <= Math.max(1e-6, distance * 1e-10), `${distance} → ${restored}`);
  }
});

test("equal elapsed time produces equal traveled surface distance", () => {
  const speed = 250;
  const pitch = 10_000;
  const atOneSecond = spiralAngleAtArcLength(speed, pitch);
  const atTenSeconds = spiralAngleAtArcLength(speed * 10, pitch);
  assert.ok(atTenSeconds > atOneSecond);
  assert.ok(spiralRadiusAtAngle(atTenSeconds, pitch) > spiralRadiusAtAngle(atOneSecond, pitch));
  assert.ok(Math.abs(spiralArcLengthAtAngle(atTenSeconds, pitch) - speed * 10) < 1e-6);
});

test("WGS84 direct destination remains finite across dateline and high latitude", () => {
  const destination = directGeodesicDestination({
    longitude: 179.9 * Math.PI / 180,
    latitude: 84 * Math.PI / 180,
  }, Math.PI / 3, 500_000);
  assert.ok(Number.isFinite(destination.longitude));
  assert.ok(Number.isFinite(destination.latitude));
  assert.ok(destination.longitude >= -Math.PI && destination.longitude <= Math.PI);
  assert.ok(destination.latitude >= -Math.PI / 2 && destination.latitude <= Math.PI / 2);
});

test("eastbound WGS84 destination at equator matches the ellipsoid circumference", () => {
  const destination = directGeodesicDestination({ longitude: 0, latitude: 0 }, Math.PI / 2, 100_000);
  assert.ok(Math.abs(destination.latitude) < 1e-12);
  assert.ok(Math.abs(destination.longitude - 100_000 / 6_378_137) < 1e-10);
});

test("spiral frame starts at the current point and follows the current heading", () => {
  const origin = { longitude: 37.6173 * Math.PI / 180, latitude: 55.7558 * Math.PI / 180 };
  const heading = 0.7;
  const frame = spiralSurfaceFrame(origin, heading, 0, 10_000);
  assert.ok(Math.abs(normalizeRadians(frame.longitude - origin.longitude)) < 1e-12);
  assert.ok(Math.abs(frame.latitude - origin.latitude) < 1e-12);
  assert.equal(frame.radiusMeters, 0);
  assert.ok(Math.abs(normalizeRadians(frame.tangentHeading - heading)) < 1e-12);
});

test("route stops at the documented regional WGS84 safety boundary", () => {
  const pitch = 100_000;
  const angle = (MAX_SPIRAL_RADIUS_METERS + pitch) * twoPi / pitch;
  const distance = spiralArcLengthAtAngle(angle, pitch);
  assert.throws(
    () => spiralSurfaceFrame({ longitude: 0, latitude: 0 }, 0, distance, pitch),
    /spiral_extent_limit/,
  );
});

test("invalid animation inputs fail closed", () => {
  assert.throws(() => spiralAngleAtArcLength(100, 0), /spiral_pitch_invalid/);
  assert.throws(() => directGeodesicDestination({ longitude: 0, latitude: Number.NaN }, 0, 10), /spiral_origin_invalid/);
});
