import { describe, expect, it } from "vitest";

import { guidanceEventFromProgress, prepareRoute, progressAlongRoute } from "./routeProgress";
import type { LatLng, Maneuver, Route } from "../../../modules/mapmap-react-native/src/MapmapNav.types";

function point(latitude: number, longitude: number): LatLng {
  return { latitude, longitude };
}

function maneuver(
  overrides: Partial<Maneuver> & Pick<Maneuver, "type" | "position">
): Maneuver {
  return { instruction: "", distanceMeters: 0, ...overrides };
}

// A route: depart on Mill Road → roundabout → exit onto A24. The roundabout leg
// is short, so once past it the current road must flip to "A24".
const ROUTE: Route = {
  index: 0,
  distanceMeters: 0,
  durationSeconds: 0,
  geometry: [point(0, 0), point(0, 0.01), point(0, 0.011), point(0.01, 0.011)],
  maneuvers: [
    maneuver({ type: "depart", position: point(0, 0), roadName: "Mill Road" }),
    maneuver({
      type: "roundabout",
      position: point(0, 0.01),
      roadName: "Offington Corner Roundabout",
    }),
    maneuver({ type: "straight", position: point(0, 0.011), roadName: "A24/Horsham Road" }),
    maneuver({ type: "arrive", position: point(0.01, 0.011) }),
  ],
};

describe("progressAlongRoute currentRoadName", () => {
  const prepared = prepareRoute(ROUTE);

  it("names the departure road at the start", () => {
    const p = progressAlongRoute(ROUTE, prepared, 0, 10);
    expect(p.currentRoadName).toBe("Mill Road");
  });

  it("flips to the exit road once past the roundabout (no stale name)", () => {
    // Just past the roundabout node — on the A24 leg.
    const past = progressAlongRoute(ROUTE, prepared, prepared.totalMetres * 0.95, 10);
    expect(past.currentRoadName).toBe("A24/Horsham Road");
  });

  it("omits the name on an unnamed leg", () => {
    const unnamed: Route = {
      ...ROUTE,
      maneuvers: [
        maneuver({ type: "depart", position: point(0, 0) }),
        maneuver({ type: "arrive", position: point(0.01, 0.011) }),
      ],
    };
    const p = progressAlongRoute(unnamed, prepareRoute(unnamed), 0, 10);
    expect(p.currentRoadName).toBeUndefined();
  });
});

describe("guidanceEventFromProgress off-route position", () => {
  const prepared = prepareRoute(ROUTE);

  it("renders the raw GPS position + heading when off-route (not the snapped line)", () => {
    const raw = point(2, 3); // nowhere near the route
    const ev = guidanceEventFromProgress(ROUTE, prepared, 100, 10, 88, true, false, 1000, raw);
    expect(ev.location.latitude).toBe(2);
    expect(ev.location.longitude).toBe(3);
    expect(ev.location.bearingDegrees).toBe(88); // GPS heading, not route bearing
    expect(ev.isOffRoute).toBe(true);
  });

  it("keeps the map-matched point when on-route (raw position ignored)", () => {
    const raw = point(2, 3);
    const ev = guidanceEventFromProgress(ROUTE, prepared, 0, 10, undefined, false, false, 1000, raw);
    expect(ev.location.latitude).not.toBe(2);
    expect(ev.location.longitude).not.toBe(3);
    expect(ev.isOffRoute).toBe(false);
  });

  it("falls back to the snapped point if off-route but no raw position supplied", () => {
    const ev = guidanceEventFromProgress(ROUTE, prepared, 0, 10, undefined, true, false, 1000);
    // Snapped to the route origin (0,0), never the missing raw fix.
    expect(ev.location.latitude).toBeCloseTo(0, 5);
  });
});
