import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'road_alert_models.dart'; import 'road_alert_provider_interface.dart'; /// Commercial telematics provider for HERE Technologies Safety Cameras. /// /// Queries HERE Safety Cameras circular search REST endpoint for /// verified mobile speed traps, stationary radar, and red light cameras. class HereRoadAlertProvider implements RoadAlertProvider { final http.Client _client; HereRoadAlertProvider({http.Client? client}) : _client = client ?? http.Client(); @override String get id => 'here_safety_cameras'; @override String get name => 'HERE Safety Cameras'; @override Set get supportedCategories => const { RoadAlertCategory.police, RoadAlertCategory.speedCamera, RoadAlertCategory.hazard, }; @override Future testConnection(String apiKey) async { final key = apiKey.trim(); if (key.isEmpty) { return RoadAlertProviderStatus.notConfigured; } try { // Test circular search around downtown Los Angeles (US sample point) final url = Uri.parse( 'https://safetycameras.hereapi.com/v2/search?in=circle:34.0522,-118.2437;r=500&apiKey=$key', ); final response = await _client.get(url).timeout(const Duration(seconds: 6)); if (response.statusCode == 200) { return RoadAlertProviderStatus.connected; } else if (response.statusCode == 401 || response.statusCode == 403) { return RoadAlertProviderStatus.authError; } else if (response.statusCode == 429) { return RoadAlertProviderStatus.rateLimited; } return RoadAlertProviderStatus.networkError; } catch (_) { return RoadAlertProviderStatus.networkError; } } @override Future> getAlerts({ required double latitude, required double longitude, required double radiusMeters, String? apiKey, }) async { final key = apiKey?.trim(); if (key == null || key.isEmpty) { return []; } try { final r = radiusMeters.clamp(100.0, 10000.0).toInt(); final url = Uri.parse( 'https://safetycameras.hereapi.com/v2/search?in=circle:$latitude,$longitude;r=$r&apiKey=$key', ); final response = await _client.get(url).timeout(const Duration(seconds: 8)); if (response.statusCode != 200) { return []; } final data = json.decode(response.body); final List items = data['items'] ?? (data['safetyCameras'] ?? []); final List alerts = []; for (var item in items) { if (item is Map) { try { final alert = parseHereItem(item); if (alert != null) { alerts.add(alert); } } catch (e) { if (kDebugMode) { debugPrint('[HereRoadAlertProvider] Item parse error: $e'); } } } } return alerts; } catch (e) { if (kDebugMode) { debugPrint('[HereRoadAlertProvider] Request error: $e'); } return []; } } /// Parses a single item from the HERE Safety Cameras response RoadAlert? parseHereItem(Map item) { final id = item['id']?.toString() ?? 'here_${item.hashCode}'; final typeStr = (item['type'] ?? item['cameraType'] ?? '').toString().toUpperCase(); // Map HERE camera / incident types to RoadAlertCategory final RoadAlertCategory category; final RoadAlertSeverity severity; if (typeStr.contains('MOBILE') || typeStr.contains('POLICE')) { category = RoadAlertCategory.police; severity = RoadAlertSeverity.high; } else if (typeStr.contains('SPEED') || typeStr.contains('RED_LIGHT') || typeStr.contains('CAMERA') || typeStr.contains('FIXED')) { category = RoadAlertCategory.speedCamera; severity = RoadAlertSeverity.medium; } else { category = RoadAlertCategory.hazard; severity = RoadAlertSeverity.low; } // Coordinates final position = item['position'] ?? item['coordinate'] ?? {}; final lat = (position['lat'] ?? position['latitude'] as num?)?.toDouble(); final lng = (position['lng'] ?? position['longitude'] as num?)?.toDouble(); if (lat == null || lng == null) return null; final road = item['road'] ?? item['roadName']?.toString(); final speedLimit = item['speedLimit']; final unit = item['speedLimitUnit'] ?? 'MPH'; final heading = (item['heading'] ?? item['bearing'] as num?)?.toDouble(); String desc = item['description']?.toString() ?? ''; if (desc.isEmpty) { if (category == RoadAlertCategory.police) { desc = speedLimit != null ? 'Mobile Speed Trap ($speedLimit $unit zone)' : 'Mobile Police Enforcement Zone'; } else { desc = speedLimit != null ? 'Safety Speed Camera ($speedLimit $unit)' : 'Enforcement Camera'; } } return RoadAlert( id: id, providerId: 'here_safety_cameras', category: category, latitude: lat, longitude: lng, heading: heading, roadName: road, description: desc, reportedAt: DateTime.now(), lastUpdated: DateTime.now(), severity: severity, source: 'HERE Technologies', sourceType: RoadAlertSourceType.providerReported, ); } }