import type { Bounds, POI } from '../types';

export const STATION_CATEGORIES = ['Rail station', 'Tube station'] as const;
export const STATION_SEARCH_RADIUS_KM = 2;
const PRIMARY_STATION_RADIUS_KM = 1;
const FALLBACK_STATION_LIMIT = 3;
const EARTH_RADIUS_KM = 6371;
const KM_PER_DEGREE_LAT = 111.32;

export interface NearbyStation extends POI {
  distanceKm: number;
}

export interface GeoPoint {
  lat: number;
  lon: number;
}

function toRadians(value: number): number {
  return (value * Math.PI) / 180;
}

export function distanceKm(a: GeoPoint, b: GeoPoint): number {
  const dLat = toRadians(b.lat - a.lat);
  const dLon = toRadians(b.lon - a.lon);
  const lat1 = toRadians(a.lat);
  const lat2 = toRadians(b.lat);

  const h = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;

  return 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(h));
}

export function stationSearchBounds(origin: GeoPoint, radiusKm = STATION_SEARCH_RADIUS_KM): Bounds {
  const latDelta = radiusKm / KM_PER_DEGREE_LAT;
  const cosLat = Math.max(Math.cos(toRadians(origin.lat)), 0.01);
  const lonDelta = radiusKm / (KM_PER_DEGREE_LAT * cosLat);

  return {
    south: origin.lat - latDelta,
    west: origin.lon - lonDelta,
    north: origin.lat + latDelta,
    east: origin.lon + lonDelta,
  };
}

export function selectNearbyStations(pois: POI[], origin: GeoPoint): NearbyStation[] {
  const stations = pois
    .map((poi) => ({
      ...poi,
      distanceKm: distanceKm(origin, { lat: poi.lat, lon: poi.lng }),
    }))
    .filter((poi) => poi.distanceKm <= STATION_SEARCH_RADIUS_KM)
    .sort((a, b) => a.distanceKm - b.distanceKm || a.name.localeCompare(b.name));

  const withinPrimaryRadius = stations.filter(
    (station) => station.distanceKm <= PRIMARY_STATION_RADIUS_KM
  );

  return withinPrimaryRadius.length > 0
    ? withinPrimaryRadius
    : stations.slice(0, FALLBACK_STATION_LIMIT);
}

export function formatStationDistance(distanceKmValue: number): string {
  if (distanceKmValue < 1) {
    return `${Math.round(distanceKmValue * 1000)}m`;
  }

  return `${distanceKmValue.toFixed(1)}km`;
}
