/**
 * Manoeuvre-arrow geometry (pure) — the Mapbox "route arrow" technique.
 *
 * For each MAJOR turn we don't stamp a single icon: we slice the route polyline
 * ~SLICE_M metres before and after the manoeuvre and emit that slice as a
 * LineString ("shaft") — because it's real route geometry it bends through the
 * corner and follows the road. A separate Point ("head") at the shaft's end,
 * tagged with the end bearing, positions the arrowhead. RouteArrows renders the
 * shaft as a white-over-dark line and the head as a rotated triangle above it.
 */
import type {
  LatLng,
  ManeuverType,
  Route,
} from "../../../modules/mapmap-react-native/src/MapmapNav.types";
import { distanceMeters, pointAtDistance } from "../../../modules/mapmap-react-native/src/geo";

/** Turn types worth an on-road arrow. Excludes depart/arrive (endpoints),
 * straight/continue (no bend), and slight-left/right (near-invisible kink). */
const MAJOR_MANEUVER_TYPES: ReadonlySet<ManeuverType> = new Set([
  "turn-left",
  "turn-right",
  "sharp-left",
  "sharp-right",
  "uturn",
  "roundabout",
  "merge",
  "on-ramp",
  "off-ramp",
  "fork-left",
  "fork-right",
]);

/** Shaft slice: mostly BEFORE the turn, only a little after — so the arrowhead
 * (at the shaft's end) lands right at the corner where the driver looks, not
 * 35m down the new road. */
const SLICE_BEFORE_M = 42;
const SLICE_AFTER_M = 12;

export type ArrowHeadProps = { bearing: number };

export type ManeuverArrows = {
  shafts: GeoJSON.FeatureCollection<GeoJSON.LineString>;
  heads: GeoJSON.FeatureCollection<GeoJSON.Point, ArrowHeadProps>;
};

const EMPTY: ManeuverArrows = {
  shafts: { type: "FeatureCollection", features: [] },
  heads: { type: "FeatureCollection", features: [] },
};

/** Cumulative arc length (metres) at each vertex. */
function cumulativeDistances(geometry: readonly LatLng[]): number[] {
  const cumulative = [0];
  for (let i = 1; i < geometry.length; i += 1) {
    cumulative.push(cumulative[i - 1] + distanceMeters(geometry[i - 1], geometry[i]));
  }
  return cumulative;
}

/** Arc length (metres) of the vertex nearest `position` (straight-line). */
function distanceAtNearestVertex(
  geometry: readonly LatLng[],
  cumulative: readonly number[],
  position: LatLng
): number {
  let bestIndex = 0;
  let bestDistance = Infinity;
  for (let i = 0; i < geometry.length; i += 1) {
    const d = distanceMeters(geometry[i], position);
    if (d < bestDistance) {
      bestDistance = d;
      bestIndex = i;
    }
  }
  return cumulative[bestIndex];
}

/** The polyline between two arc-lengths, with interpolated endpoints. */
function sliceLine(
  geometry: readonly LatLng[],
  cumulative: readonly number[],
  startDist: number,
  endDist: number
): LatLng[] {
  const total = cumulative[cumulative.length - 1];
  const start = Math.max(0, Math.min(startDist, total));
  const end = Math.max(start, Math.min(endDist, total));
  const points: LatLng[] = [pointAtDistance(geometry, start).point];
  for (let i = 0; i < geometry.length; i += 1) {
    if (cumulative[i] > start && cumulative[i] < end) points.push(geometry[i]);
  }
  points.push(pointAtDistance(geometry, end).point);
  return points;
}

/**
 * Build shaft + head features for every major turn on the route. Pure —
 * exported for unit testing.
 */
export function maneuverArrows(route: Route | undefined): ManeuverArrows {
  if (!route || route.geometry.length < 2 || route.maneuvers.length === 0) return EMPTY;

  const cumulative = cumulativeDistances(route.geometry);
  const total = cumulative[cumulative.length - 1];
  const shafts: GeoJSON.Feature<GeoJSON.LineString>[] = [];
  const heads: GeoJSON.Feature<GeoJSON.Point, ArrowHeadProps>[] = [];

  for (const maneuver of route.maneuvers) {
    if (!MAJOR_MANEUVER_TYPES.has(maneuver.type)) continue;

    const at = distanceAtNearestVertex(route.geometry, cumulative, maneuver.position);
    const headDist = Math.min(at + SLICE_AFTER_M, total);
    const line = sliceLine(route.geometry, cumulative, at - SLICE_BEFORE_M, headDist);
    if (line.length < 2) continue;

    shafts.push({
      type: "Feature",
      properties: {},
      geometry: {
        type: "LineString",
        coordinates: line.map((p) => [p.longitude, p.latitude]),
      },
    });

    const head = pointAtDistance(route.geometry, headDist);
    heads.push({
      type: "Feature",
      properties: { bearing: head.bearing },
      geometry: { type: "Point", coordinates: [head.point.longitude, head.point.latitude] },
    });
  }

  return {
    shafts: { type: "FeatureCollection", features: shafts },
    heads: { type: "FeatureCollection", features: heads },
  };
}
