const EARTH_RADIUS_M = 6371000;

export interface LatLng {
  latitude: number;
  longitude: number;
}

export function haversineDistanceMeters(a: LatLng, b: LatLng): number {
  const toRad = (deg: number) => (deg * Math.PI) / 180;
  const dLat = toRad(b.latitude - a.latitude);
  const dLng = toRad(b.longitude - a.longitude);
  const lat1 = toRad(a.latitude);
  const lat2 = toRad(b.latitude);
  const h =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
  return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
}

export function isWithinRadiusMeters(
  point: LatLng,
  center: LatLng,
  radiusMeters: number,
): boolean {
  return haversineDistanceMeters(point, center) <= radiusMeters;
}

/** Simple moving-average smoothing over recent GPS points. */
export function smoothGpsPoint(
  points: LatLng[],
  window = 3,
): LatLng | null {
  if (!points.length) return null;
  const slice = points.slice(-window);
  const latitude =
    slice.reduce((s, p) => s + p.latitude, 0) / slice.length;
  const longitude =
    slice.reduce((s, p) => s + p.longitude, 0) / slice.length;
  return { latitude, longitude };
}

export function calculateRouteDistance(points: LatLng[]): number {
  let total = 0;
  for (let i = 1; i < points.length; i++) {
    total += haversineDistanceMeters(points[i - 1], points[i]);
  }
  return total;
}

export function deduplicatePoints(
  points: Array<LatLng & { timestamp?: Date }>,
  thresholdMeters: number,
): typeof points {
  if (!points.length) return [];
  const result = [points[0]];
  for (let i = 1; i < points.length; i++) {
    const last = result[result.length - 1];
    if (haversineDistanceMeters(last, points[i]) >= thresholdMeters) {
      result.push(points[i]);
    }
  }
  return result;
}

/** Douglas-Peucker route compression. */
export function compressRoute(
  points: LatLng[],
  epsilonMeters: number,
): LatLng[] {
  if (points.length <= 2) return points;

  const first = points[0];
  const last = points[points.length - 1];
  let maxDist = 0;
  let index = 0;

  for (let i = 1; i < points.length - 1; i++) {
    const dist = perpendicularDistanceMeters(points[i], first, last);
    if (dist > maxDist) {
      maxDist = dist;
      index = i;
    }
  }

  if (maxDist > epsilonMeters) {
    const left = compressRoute(points.slice(0, index + 1), epsilonMeters);
    const right = compressRoute(points.slice(index), epsilonMeters);
    return [...left.slice(0, -1), ...right];
  }
  return [first, last];
}

function perpendicularDistanceMeters(
  point: LatLng,
  lineStart: LatLng,
  lineEnd: LatLng,
): number {
  const dx = lineEnd.longitude - lineStart.longitude;
  const dy = lineEnd.latitude - lineStart.latitude;
  if (dx === 0 && dy === 0) {
    return haversineDistanceMeters(point, lineStart);
  }
  const t =
    ((point.longitude - lineStart.longitude) * dx +
      (point.latitude - lineStart.latitude) * dy) /
    (dx * dx + dy * dy);
  const clamped = Math.max(0, Math.min(1, t));
  const proj: LatLng = {
    latitude: lineStart.latitude + clamped * dy,
    longitude: lineStart.longitude + clamped * dx,
  };
  return haversineDistanceMeters(point, proj);
}
