import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { formatLatLon, parseLatLon } from "../apps/geolibre-desktop/src/lib/coordinates";

/** Assert a parsed coordinate is close to the expected lat/lon. */
function assertClose(
  actual: { lat: number; lon: number } | null,
  lat: number,
  lon: number,
  tolerance = 1e-4,
) {
  assert.ok(actual, "expected a parsed coordinate, got null");
  assert.ok(
    Math.abs(actual.lat - lat) <= tolerance,
    `lat ${actual.lat} not within ${tolerance} of ${lat}`,
  );
  assert.ok(
    Math.abs(actual.lon - lon) <= tolerance,
    `lon ${actual.lon} not within ${tolerance} of ${lon}`,
  );
}

describe("parseLatLon — decimal degrees", () => {
  it("parses a comma-separated lat,lon pair", () => {
    assertClose(parseLatLon("51.5074, -0.1278"), 51.5074, -0.1278);
  });

  it("parses a whitespace-separated pair", () => {
    assertClose(parseLatLon("35.475317 -97.514272"), 35.475317, -97.514272);
  });

  it("swaps when the first value is out of latitude range (lon,lat)", () => {
    assertClose(parseLatLon("-97.514272, 35.475317"), 35.475317, -97.514272);
  });

  it("honors hemisphere letters on bare decimals regardless of order", () => {
    assertClose(parseLatLon("0.1278W, 51.5074N"), 51.5074, -0.1278);
  });
});

describe("parseLatLon — DMS", () => {
  it("parses degrees/minutes/seconds with hemisphere letters", () => {
    // 51°30'26"N = 51.5072, 0°07'39"W = -0.1275
    assertClose(parseLatLon(`51°30'26"N, 0°07'39"W`), 51.5072, -0.1275);
  });

  it("parses DMS without a comma between the axes", () => {
    assertClose(parseLatLon(`51°30'26"N 0°07'39"W`), 51.5072, -0.1275);
  });
});

describe("parseLatLon — DDM", () => {
  it("parses degrees and decimal minutes", () => {
    // 51°30.44'N = 51.50733, 0°07.65'W = -0.1275
    assertClose(parseLatLon(`51°30.44'N, 0°07.65'W`), 51.50733, -0.1275);
  });
});

describe("parseLatLon — rejection", () => {
  it("returns null for empty input", () => {
    assert.equal(parseLatLon(""), null);
    assert.equal(parseLatLon("   "), null);
  });

  it("returns null for a single value", () => {
    assert.equal(parseLatLon("51.5074"), null);
  });

  it("returns null for out-of-range coordinates", () => {
    assert.equal(parseLatLon("200, 300"), null);
    assert.equal(parseLatLon(`95°00'00"N, 0°00'00"W`), null);
  });

  it("returns null when both halves name the same axis", () => {
    assert.equal(parseLatLon("51.5N, 0.1N"), null);
  });

  it("returns null for non-coordinate text", () => {
    assert.equal(parseLatLon("hello world"), null);
  });

  it("returns null for malformed minutes >= 60", () => {
    assert.equal(parseLatLon(`51°75'00"N, 0°00'00"W`), null);
  });

  it("returns null for malformed seconds >= 60", () => {
    assert.equal(parseLatLon(`51°30'75"N, 0°00'00"W`), null);
  });

  it("returns null for a leading minus that contradicts the hemisphere", () => {
    assert.equal(parseLatLon("-51.5N, 0.1W"), null);
  });

  it("returns null for decimal degrees with trailing minutes/seconds", () => {
    assert.equal(parseLatLon(`51.5 30 26N, 0 7 39W`), null);
  });
});

describe("formatLatLon", () => {
  it("formats a decimal-degree coordinate as `lat, lon`", () => {
    assert.equal(formatLatLon({ lat: 51.5074, lon: -0.1278 }), "51.5074, -0.1278");
  });

  it("rounds to at most 6 decimals and drops trailing zeros", () => {
    assert.equal(formatLatLon({ lat: 35.4753170123, lon: -97.5 }), "35.475317, -97.5");
  });

  it("round-trips through parseLatLon", () => {
    const formatted = formatLatLon({ lat: 40.7128, lon: -74.006 });
    assertClose(parseLatLon(formatted), 40.7128, -74.006);
  });
});
