import type { FieldsParam, GeocodingResult, Place, PlacePrediction, PredictionFiltersParam, } from './types'; import { v4 as uuidv4 } from 'uuid'; let apiKey: string | null = null; let sessionToken: string | null = null; const PLACES_API_URL = 'https://maps.googleapis.com/maps/api/place'; const GEOCODING_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json'; export function initialize(key: string) { apiKey = key; } export async function fetchPredictions( query: string, filters: PredictionFiltersParam = {} ): Promise { if (!apiKey) { throw new Error('API key not set. Call initialize(apiKey) first.'); } const { types, countries, locationBias, locationRestriction, origin } = filters; const params = new URLSearchParams({ input: query, key: apiKey, }); if (!sessionToken) { sessionToken = uuidv4(); } params.append('sessiontoken', sessionToken); if (types) { params.append('types', types.join('|')); } if (countries) { params.append('components', countries.map((c) => `country:${c}`).join('|')); } if (locationBias) { const { northEast, southWest } = locationBias; params.append( 'locationbias', `rectangle:${southWest.latitude},${southWest.longitude}|${northEast.latitude},${northEast.longitude}` ); } if (locationRestriction) { const { northEast, southWest } = locationRestriction; params.append( 'locationrestriction', `rectangle:${southWest.latitude},${southWest.longitude}|${northEast.latitude},${northEast.longitude}` ); } if (origin) { params.append('origin', `${origin.latitude},${origin.longitude}`); } const response = await fetch( `${PLACES_API_URL}/autocomplete/json?${params.toString()}` ); const data = await response.json(); if (data.status !== 'OK') { throw new Error(`Google Places API error: ${data.status}`); } return data.predictions.map((prediction: any) => ({ description: prediction.description, placeID: prediction.place_id, primaryText: prediction.structured_formatting.main_text, secondaryText: prediction.structured_formatting.secondary_text, types: prediction.types, distanceMeters: prediction.distance_meters, })); } export async function fetchPlaceByID( placeID: string, fields: FieldsParam = [] ): Promise { if (!apiKey) { throw new Error('API key not set. Call initialize(apiKey) first.'); } const params = new URLSearchParams({ place_id: placeID, key: apiKey, }); if (sessionToken) { params.append('sessionToken', sessionToken); } if (fields.length > 0) { params.append('fields', fields.join(',')); } const response = await fetch( `${PLACES_API_URL}/details/json?${params.toString()}` ); const data = await response.json(); if (data.status !== 'OK') { throw new Error(`Google Places API error: ${data.status}`); } sessionToken = null; return data.result as Place; } export async function startNewSession() { sessionToken = uuidv4(); return Promise.resolve('NEW_SESSION_STARTED'); } export async function clearSession() { sessionToken = null; return Promise.resolve('SESSION_CLEARED'); } export async function geocode(address: string): Promise { if (!apiKey) { throw new Error('API key not set. Call initialize(apiKey) first.'); } const params = new URLSearchParams({ address, key: apiKey, }); const response = await fetch(`${GEOCODING_API_URL}?${params.toString()}`); const data = await response.json(); if (data.status !== 'OK') { throw new Error(`Google Geocoding API error: ${data.status}`); } return data.results.map((result: any) => ({ formattedAddress: result.formatted_address, geometry: { location: { latitude: result.geometry.location.lat, longitude: result.geometry.location.lng, }, viewport: { northEast: { latitude: result.geometry.viewport.northeast.lat, longitude: result.geometry.viewport.northeast.lng, }, southWest: { latitude: result.geometry.viewport.southwest.lat, longitude: result.geometry.viewport.southwest.lng, }, }, }, placeId: result.place_id, plusCode: result.plus_code?.compound_code ?? null, types: result.types, })); } export async function reverseGeocode( lat: number, lng: number ): Promise { if (!apiKey) { throw new Error('API key not set. Call initialize(apiKey) first.'); } const params = new URLSearchParams({ latlng: `${lat},${lng}`, key: apiKey, }); const response = await fetch(`${GEOCODING_API_URL}?${params.toString()}`); const data = await response.json(); if (data.status !== 'OK') { throw new Error(`Google Geocoding API error: ${data.status}`); } return data.results.map((result: any) => ({ formattedAddress: result.formatted_address, geometry: { location: { latitude: result.geometry.location.lat, longitude: result.geometry.location.lng, }, viewport: { northEast: { latitude: result.geometry.viewport.northeast.lat, longitude: result.geometry.viewport.northeast.lng, }, southWest: { latitude: result.geometry.viewport.southwest.lat, longitude: result.geometry.viewport.southwest.lng, }, }, }, placeId: result.place_id, plusCode: result.plus_code?.compound_code ?? null, types: result.types, })); }