import '../map/map_provider_interface.dart'; import 'route_models.dart'; /// Result of a place/location search query. class PlaceSearchResult { final String name; final String address; final double latitude; final double longitude; final String? placeId; const PlaceSearchResult({ required this.name, this.address = '', required this.latitude, required this.longitude, this.placeId, }); AppLatLng get latLng => AppLatLng(latitude, longitude); } /// Result of calculating a route along ordered stops. class RouteCalculationResult { final double straightLineDistanceMeters; final double? estimatedDistanceMeters; final int? estimatedDurationSeconds; final List geometry; final bool isStraightLine; final String providerName; const RouteCalculationResult({ required this.straightLineDistanceMeters, this.estimatedDistanceMeters, this.estimatedDurationSeconds, required this.geometry, required this.isStraightLine, required this.providerName, }); /// Alias for geometry points. List get points => geometry; /// Total straight-line distance in meters. double get totalDistanceMeters => straightLineDistanceMeters; /// Alias for isStraightLine. bool get isHonestStraightLine => isStraightLine; /// Total distance in miles. double get distanceMiles { final meters = estimatedDistanceMeters ?? straightLineDistanceMeters; return meters * 0.000621371; } /// Truthful distance description. String get distanceLabel { if (estimatedDistanceMeters != null) { return '${distanceMiles.toStringAsFixed(1)} mi (Road Est.)'; } return '${distanceMiles.toStringAsFixed(1)} mi (Straight-line)'; } } /// Abstract provider interface for calculating routes and searching places. abstract class RouteProvider { String get name; /// Calculates route geometry and metrics between ordered stops. Future calculateRoute(List stops); /// Searches for places by textual query. Future> searchPlaces( String query, { AppLatLng? nearLocation, }); } /// Default geodesic straight-line preview implementation. /// /// Truthfully connects stops with direct segments and calculates geodesic /// distance without fabricating road paths or speculative turn ETAs. class StraightLineRouteProvider implements RouteProvider { const StraightLineRouteProvider(); @override String get name => 'Straight-line Preview'; @override Future calculateRoute(List stops) async { final ordered = List.from(stops); ordered.sort((a, b) => a.order.compareTo(b.order)); final distanceMeters = MotorSignalRoute.calculateTotalDistanceMeters(ordered); final points = ordered.map((s) => s.latLng).toList(); return RouteCalculationResult( straightLineDistanceMeters: distanceMeters, estimatedDistanceMeters: null, estimatedDurationSeconds: null, geometry: points, isStraightLine: true, providerName: name, ); } @override Future> searchPlaces( String query, { AppLatLng? nearLocation, }) async { final clean = query.trim(); if (clean.isEmpty) return const []; // Support direct coordinate input (e.g. "34.5378, -83.9849") final parts = clean.split(RegExp(r'[,\s]+')); if (parts.length == 2) { final lat = double.tryParse(parts[0]); final lng = double.tryParse(parts[1]); if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) { return [ PlaceSearchResult( name: 'Coordinate Pin', address: '${lat.toStringAsFixed(5)}, ${lng.toStringAsFixed(5)}', latitude: lat, longitude: lng, ), ]; } } // Default fallback: return query name centered at reference location if provided if (nearLocation != null) { return [ PlaceSearchResult( name: clean, address: 'Near Current Map Location', latitude: nearLocation.latitude, longitude: nearLocation.longitude, ), ]; } return const []; } }