/**
 * Posted speed limits for a route.
 *
 * The gateway `/route` response doesn't carry per-edge speed limits, but the
 * routing graph HAS them (OSM `maxspeed` + OpenStreetMapSpeeds observed
 * defaults). `POST /trace_attributes` map-snaps a shape and returns the
 * graph's per-edge attributes, including `speed_limit` (km/h). We fetch that
 * once per route and turn it into an arc-length → limit lookup the guidance UI
 * reads at the puck's position — driving the speed-limit sign and the
 * over-limit warning.
 *
 * (This is a stopgap: ideally `/route` or the guidance stream would carry the
 * limit directly — see PLATFORM-IMPROVEMENTS.md.)
 */
const API_HOST = "https://api.mapmap.ai";

export type SpeedLimitTrack = {
  /** Cumulative END distance (metres) of each edge along the trace. */
  cumMeters: number[];
  /** Posted limit per edge, km/h; 0 = unposted/unknown. */
  limitsKph: number[];
  totalMeters: number;
};

type TraceEdge = { length?: number; speed_limit?: number };

/**
 * Fetch posted limits for the route's encoded polyline. Returns null on any
 * failure (no key/shape, network, non-2xx, empty) so the caller just hides the
 * sign — never throws.
 */
export async function fetchSpeedLimits(
  apiKey: string,
  encodedShape: string,
  fetchImpl: typeof fetch = fetch
): Promise<SpeedLimitTrack | null> {
  if (!apiKey || !encodedShape) return null;
  try {
    const res = await fetchImpl(`${API_HOST}/trace_attributes`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        encoded_polyline: encodedShape,
        costing: "auto",
        shape_match: "map_snap",
        filters: { attributes: ["edge.speed_limit", "edge.length"], action: "include" },
      }),
    });
    if (!res.ok) return null;
    const body = (await res.json()) as { edges?: TraceEdge[] };
    const edges = body.edges ?? [];
    if (edges.length === 0) return null;

    const cumMeters: number[] = [];
    const limitsKph: number[] = [];
    let cum = 0;
    for (const e of edges) {
      cum += Math.max(0, e.length ?? 0) * 1000; // km → m
      cumMeters.push(cum);
      limitsKph.push(typeof e.speed_limit === "number" && e.speed_limit > 0 ? e.speed_limit : 0);
    }
    if (cum <= 0) return null;
    return { cumMeters, limitsKph, totalMeters: cum };
  } catch {
    return null;
  }
}

/**
 * Posted limit (km/h) at `fraction` (0..1) along the route, or null when the
 * current edge is unposted. Driven by fraction, not raw metres, so a small
 * trace-vs-route total-length difference from map-snapping doesn't misalign it.
 */
export function speedLimitKphAtFraction(
  track: SpeedLimitTrack,
  fraction: number
): number | null {
  if (track.totalMeters <= 0 || track.cumMeters.length === 0) return null;
  const pos = Math.max(0, Math.min(1, fraction)) * track.totalMeters;
  // Binary-search the first edge whose cumulative end is at/after pos.
  let lo = 0;
  let hi = track.cumMeters.length - 1;
  while (lo < hi) {
    const mid = (lo + hi) >> 1;
    if (track.cumMeters[mid] < pos) lo = mid + 1;
    else hi = mid;
  }
  const limit = track.limitsKph[lo];
  return limit > 0 ? limit : null;
}
