export type LatLng = { lat: number; lng: number };

export function haversineMeters(
  a: LatLng,
  b: LatLng,
): number {
  const R = 6371000;
  const toRad = (d: number) => (d * Math.PI) / 180;
  const dLat = toRad(b.lat - a.lat);
  const dLon = toRad(b.lng - a.lng);
  const x =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLon / 2) ** 2;
  return 2 * R * Math.asin(Math.min(1, Math.sqrt(x)));
}

export function pathLengthMeters(points: LatLng[]): number {
  let sum = 0;
  for (let i = 1; i < points.length; i++) {
    sum += haversineMeters(points[i - 1], points[i]);
  }
  return sum;
}

/** Downsample to ~1 point per minSpacingM (keeps endpoints). */
export function downsamplePath(
  points: LatLng[],
  minSpacingM = 20,
): LatLng[] {
  if (points.length <= 2) return points;
  const out: LatLng[] = [points[0]];
  let last = points[0];
  for (let i = 1; i < points.length - 1; i++) {
    const p = points[i];
    if (haversineMeters(last, p) >= minSpacingM) {
      out.push(p);
      last = p;
    }
  }
  out.push(points[points.length - 1]);
  return out;
}

export function chunkArray<T>(arr: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    out.push(arr.slice(i, i + size));
  }
  return out;
}

export function encodePipePolyline(points: LatLng[]): string {
  return points.map((p) => `${p.lat},${p.lng}`).join('|');
}


/** Decode Google's encoded polyline algorithm into lat/lng points. */
export function decodeGoogleEncodedPolyline(encoded: string): LatLng[] {
  const points: LatLng[] = [];
  let index = 0;
  let lat = 0;
  let lng = 0;

  while (index < encoded.length) {
    let shift = 0;
    let result = 0;
    let b: number;
    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);
    const dlat = (result & 1) !== 0 ? ~(result >> 1) : result >> 1;
    lat += dlat;

    shift = 0;
    result = 0;
    do {
      b = encoded.charCodeAt(index++) - 63;
      result |= (b & 0x1f) << shift;
      shift += 5;
    } while (b >= 0x20);
    const dlng = (result & 1) !== 0 ? ~(result >> 1) : result >> 1;
    lng += dlng;

    points.push({ lat: lat / 1e5, lng: lng / 1e5 });
  }

  return points;
}

export function roundKm(meters: number): number {
  return Math.round((meters / 1000) * 1000) / 1000;
}

/** Classify a segment from mean accuracy + gap duration. */
export function classifySegmentKind(opts: {
  meanAccuracy?: number;
  gapSeconds?: number;
}): 'gps_verified' | 'map_matched' | 'estimated' {
  if ((opts.gapSeconds ?? 0) >= 90) return 'estimated';
  if ((opts.meanAccuracy ?? 99) <= 20) return 'gps_verified';
  return 'map_matched';
}

export function confidenceForKind(
  kind: 'gps_verified' | 'map_matched' | 'estimated',
  meanAccuracy?: number,
): number {
  if (kind === 'gps_verified') return 0.9;
  if (kind === 'estimated') return 0.35;
  const acc = meanAccuracy ?? 40;
  return Math.max(0.4, Math.min(0.85, 1 - acc / 120));
}
