import { haversineMeters } from "./haversine";
import { haversineDurationSec } from "./config";
import { etaTable as defaultEtaTable } from "./osrm";
import type {
  Coordinates,
  EtaResult,
  EtaTableFn,
  GeoCandidate,
  OrderedResult,
} from "./types";

/**
 * Sortiert Suchtreffer aufsteigend nach Eintreffzeit ab `origin`.
 *
 * - OSRM zuerst (Fahrzeit-Matrix). Wirft OSRM (oder fehlt der Dienst),
 *   wird KOMPLETT auf Haversine-Luftlinie zurückgefallen (`isFallback=true`).
 * - Kandidaten ohne Koordinaten landen stets am Ende (`durationSec=null`).
 * - Stabile aufsteigende Sortierung: bei gleicher Dauer bleibt die Eingabe-
 *   reihenfolge erhalten.
 *
 * Die OSRM-Tabellenfunktion ist injizierbar, damit der Pfad ohne laufenden
 * Dienst testbar ist (Erfolg / Wurf / null-Koordinaten).
 */
export async function orderByEintreffzeit<T extends GeoCandidate>(
  origin: Coordinates,
  candidates: T[],
  etaTable: EtaTableFn = defaultEtaTable,
): Promise<OrderedResult<T>[]> {
  if (candidates.length === 0) return [];

  const withCoords: { cand: T; idx: number; coords: Coordinates }[] = [];
  const withoutCoords: { cand: T; idx: number }[] = [];
  candidates.forEach((cand, idx) => {
    if (cand.brigadeCoords) {
      withCoords.push({ cand, idx, coords: cand.brigadeCoords });
    } else {
      withoutCoords.push({ cand, idx });
    }
  });

  const etas = await computeEtas(
    origin,
    withCoords.map((w) => w.coords),
    etaTable,
  );

  const ranked: OrderedResult<T>[] = withCoords
    .map((w, i): { cand: T; idx: number; eta: EtaResult } => ({
      cand: w.cand,
      idx: w.idx,
      eta: etas[i] ?? haversineEta(origin, w.coords),
    }))
    // Stabil: erst nach Dauer (null ans Ende), dann nach ursprünglichem Index.
    .sort((a, b) => {
      const da = a.eta.durationSec;
      const db = b.eta.durationSec;
      if (da == null && db == null) return a.idx - b.idx;
      if (da == null) return 1;
      if (db == null) return -1;
      return da === db ? a.idx - b.idx : da - db;
    })
    .map((w) => ({ ...w.cand, eta: w.eta }));

  const tail: OrderedResult<T>[] = withoutCoords
    .sort((a, b) => a.idx - b.idx)
    .map((w) => ({
      ...w.cand,
      eta: {
        durationSec: null,
        distanceMeters: null,
        mode: "haversine" as const,
        isFallback: true,
      },
    }));

  return [...ranked, ...tail];
}

/** Liefert ETAs in derselben Reihenfolge wie `coords`; bei OSRM-Wurf alles Haversine. */
async function computeEtas(
  origin: Coordinates,
  coords: Coordinates[],
  etaTable: EtaTableFn,
): Promise<EtaResult[]> {
  if (coords.length === 0) return [];
  try {
    const table = await etaTable(origin, coords);
    const durations = table.durations[0] ?? [];
    const distances = table.distances?.[0] ?? [];
    return coords.map((c, i) => {
      const durationSec = durations[i + 1] ?? null; // Index 0 = origin->origin
      const distanceMeters = distances[i + 1] ?? null;
      if (durationSec == null) {
        // OSRM kennt keine Route -> einzelner Haversine-Fallback.
        return haversineEta(origin, c);
      }
      return {
        durationSec,
        distanceMeters,
        mode: "osrm",
        isFallback: false,
      };
    });
  } catch {
    // Kompletter Fallback: OSRM nicht erreichbar.
    return coords.map((c) => haversineEta(origin, c));
  }
}

function haversineEta(origin: Coordinates, dest: Coordinates): EtaResult {
  const distanceMeters = haversineMeters(origin, dest);
  return {
    durationSec: haversineDurationSec(distanceMeters),
    distanceMeters,
    mode: "haversine",
    isFallback: true,
  };
}
