/**
 * T10 — replay corpus synthesiser (CONTRACTS.md §"Replay corpus").
 *
 * FINDING (per CONTRACTS.md): no public `.drive.jsonl` schema ships with
 * MapMap's docs. This implements the shape CONTRACTS.md specifies —
 * `{t, lat, lon, speed, bearing}` per line, ~1Hz — and keeps it isolated in
 * `driveSampleToJsonLine` below so a schema correction (once the real SDK
 * docs/format are confirmed) is a one-line change.
 */
import type { Route } from "../../modules/mapmap-react-native/src/MapmapNav.types";
import { cumulativeDistancesAt, pathLengthMeters, pointAtDistance } from "./geo";

/** One `.drive.jsonl` line. Kept isolated so a schema correction is one-line (see file header). */
export type DriveSample = {
  /** Unix epoch milliseconds. Monotonically increasing across the corpus. */
  t: number;
  lat: number;
  lon: number;
  /** Metres/second. */
  speed: number;
  /** Degrees, [0, 360). */
  bearing: number;
};

export type SynthesizeDriveCorpusOptions = {
  /** Cruising speed away from maneuvers, m/s. Default 13.4 (~30 mph). */
  cruiseSpeedMps?: number;
  /** Speed within `slowRadiusMeters` of a maneuver position, m/s. Default 5. */
  slowSpeedMps?: number;
  /** Radius around each maneuver position that triggers the slow-down, metres. Default 40. */
  slowRadiusMeters?: number;
  /** Timestamp of the first sample. Default `Date.now()`. */
  startEpochMs?: number;
  /** Sampling cadence, ms. Default 1000 (~1Hz). */
  sampleIntervalMs?: number;
};

function driveSampleToJsonLine(sample: DriveSample): string {
  return JSON.stringify(sample);
}

/**
 * Synthesises a plausible `.drive.jsonl` corpus by walking `route.geometry`
 * at ~1Hz, slowing near each maneuver position and holding cruise speed
 * elsewhere. Timestamps are exactly `sampleIntervalMs` apart (monotonic by
 * construction); the last sample lands exactly on the route's final point.
 */
export function synthesizeDriveCorpus(route: Route, opts: SynthesizeDriveCorpusOptions = {}): string {
  const {
    cruiseSpeedMps = 13.4,
    slowSpeedMps = 5,
    slowRadiusMeters = 40,
    startEpochMs = Date.now(),
    sampleIntervalMs = 1000,
  } = opts;

  if (route.geometry.length === 0) {
    throw new Error("synthesizeDriveCorpus: route.geometry is empty");
  }

  const totalDistanceMeters = pathLengthMeters(route.geometry);
  const maneuverDistances = cumulativeDistancesAt(
    route.geometry,
    route.maneuvers.map((maneuver) => maneuver.position)
  );

  const speedAt = (distanceAlongRoute: number): number => {
    const nearManeuver = maneuverDistances.some((d) => Math.abs(distanceAlongRoute - d) <= slowRadiusMeters);
    return nearManeuver ? slowSpeedMps : cruiseSpeedMps;
  };

  const lines: string[] = [];
  let travelledMeters = 0;
  let sampleIndex = 0;

  // eslint-disable-next-line no-constant-condition
  while (true) {
    const distanceAlongRoute = Math.min(travelledMeters, totalDistanceMeters);
    const { point, bearing } = pointAtDistance(route.geometry, distanceAlongRoute);
    const speed = totalDistanceMeters === 0 ? 0 : speedAt(distanceAlongRoute);

    lines.push(
      driveSampleToJsonLine({
        t: startEpochMs + sampleIndex * sampleIntervalMs,
        lat: point.latitude,
        lon: point.longitude,
        speed,
        bearing,
      })
    );

    if (distanceAlongRoute >= totalDistanceMeters) {
      break;
    }

    // Guard against a zero speed stalling the loop forever.
    const stepMeters = Math.max(speed, 0.1) * (sampleIntervalMs / 1000);
    travelledMeters += stepMeters;
    sampleIndex += 1;
  }

  return lines.map((line) => `${line}\n`).join("");
}
