import { CurrentWeatherResponse, ForecastResponse, HistoricalResponse, AssessmentResponse, Thresholds, APIError, } from './types' const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api/v1' class APIClient { private baseURL: string constructor(baseURL: string) { this.baseURL = baseURL } private async request( endpoint: string, options?: RequestInit ): Promise { const url = `${this.baseURL}${endpoint}` try { const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }) if (!response.ok) { const error: APIError = await response.json().catch(() => ({ error: 'An error occurred', detail: response.statusText, })) throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`) } const json = await response.json() // Unwrap {success, data} response if present return (json.data || json) as T } catch (error) { if (error instanceof Error) { throw error } throw new Error('An unexpected error occurred') } } /** * Get current weather conditions and flyability assessment */ async getCurrentWeather( lat?: number, lon?: number ): Promise { const params = new URLSearchParams() if (lat !== undefined) params.append('lat', lat.toString()) if (lon !== undefined) params.append('lon', lon.toString()) const query = params.toString() ? `?${params.toString()}` : '' const data = await this.request(`/weather/current${query}`) // Transform backend response to frontend format return { location: data.location || { lat: 0, lon: 0 }, current: { timestamp: data.current?.time || data.current?.timestamp, wind_speed: data.current?.windSpeed || data.current?.wind_speed || 0, wind_direction: data.current?.windDirection || data.current?.wind_direction || 0, wind_gust: data.current?.windGust || data.current?.wind_gust || 0, temperature: data.current?.temperature || 0, cloud_cover: data.current?.cloudCover || data.current?.cloud_cover || 0, precipitation: data.current?.precipitation || 0, visibility: data.current?.visibility || 0, pressure: data.current?.pressure || 0, humidity: data.current?.humidity || 0, }, assessment: { is_flyable: data.assessment?.FlyableNow || data.assessment?.is_flyable || false, reasons: data.assessment?.Reason ? [data.assessment.Reason] : data.assessment?.reasons || [], score: data.assessment?.score || 0, }, last_updated: data.timestamp || data.last_updated || new Date().toISOString(), } } /** * Get weather forecast for the next 7 days */ async getForecast( lat?: number, lon?: number ): Promise { const params = new URLSearchParams() if (lat !== undefined) params.append('lat', lat.toString()) if (lon !== undefined) params.append('lon', lon.toString()) const query = params.toString() ? `?${params.toString()}` : '' const data = await this.request(`/weather/forecast${query}`) // Transform backend response to frontend format return { location: data.location || { lat: 0, lon: 0 }, forecast: (data.forecast || []).map((point: any) => ({ timestamp: point.Time || point.timestamp, wind_speed: point.WindSpeedMPH || point.wind_speed || 0, wind_direction: point.WindDirection || point.wind_direction || 0, wind_gust: point.WindGustMPH || point.wind_gust || 0, temperature: point.temperature || 0, cloud_cover: point.cloud_cover || 0, precipitation: point.precipitation || 0, visibility: point.visibility || 0, pressure: point.pressure || 0, humidity: point.humidity || 0, })), flyable_windows: (data.flyableWindows || data.flyable_windows || []).map((win: any) => ({ start: win.start, end: win.end, duration_hours: win.durationHours || win.duration_hours || 0, avg_conditions: { wind_speed: win.avgConditions?.windSpeed || win.avg_conditions?.wind_speed || 0, wind_gust: win.avgConditions?.windGust || win.avg_conditions?.wind_gust || 0, temperature: win.avgConditions?.temperature || win.avg_conditions?.temperature || 0, cloud_cover: win.avgConditions?.cloudCover || win.avg_conditions?.cloud_cover || 0, }, })), generated_at: data.generated || data.generated_at || new Date().toISOString(), } } /** * Get historical weather data for a specific date * @param date - Date in YYYY-MM-DD format */ async getHistorical( date: string, lat?: number, lon?: number ): Promise { const params = new URLSearchParams({ date }) if (lat !== undefined) params.append('lat', lat.toString()) if (lon !== undefined) params.append('lon', lon.toString()) const data = await this.request(`/weather/historical?${params.toString()}`) // Transform backend response to frontend format return { location: data.location || { lat: 0, lon: 0 }, date: data.date || date, data: (data.data || []).map((point: any) => ({ timestamp: point.Time || point.timestamp, wind_speed: point.WindSpeedMPH || point.wind_speed || 0, wind_direction: point.WindDirection || point.wind_direction || 0, wind_gust: point.WindGustMPH || point.wind_gust || 0, temperature: point.temperature || 0, cloud_cover: point.cloud_cover || 0, precipitation: point.precipitation || 0, visibility: point.visibility || 0, pressure: point.pressure || 0, humidity: point.humidity || 0, })), } } /** * Assess current conditions with custom thresholds */ async assessWithThresholds( thresholds: Thresholds, lat?: number, lon?: number ): Promise { const params = new URLSearchParams() if (lat !== undefined) params.append('lat', lat.toString()) if (lon !== undefined) params.append('lon', lon.toString()) const query = params.toString() ? `?${params.toString()}` : '' return this.request(`/weather/assess${query}`, { method: 'POST', body: JSON.stringify({ thresholds }), }) } } // Export singleton instance export const apiClient = new APIClient(API_BASE_URL) // Export individual functions for convenience export const getCurrentWeather = (lat?: number, lon?: number) => apiClient.getCurrentWeather(lat, lon) export const getForecast = (lat?: number, lon?: number) => apiClient.getForecast(lat, lon) export const getHistorical = (date: string, lat?: number, lon?: number) => apiClient.getHistorical(date, lat, lon) export const assessWithThresholds = ( thresholds: Thresholds, lat?: number, lon?: number ) => apiClient.assessWithThresholds(thresholds, lat, lon)