import 'dart:convert'; import 'dart:developer'; import 'package:dio/dio.dart'; abstract class TripRemoteDataSource { /// Fetch available landmarks for the provided preferences/start payload. Future>> fetchLandmarks(Map body); /// Create a new trip using the optimizer payload (that includes landmarks). Future> createTrip(Map body); /// Fetch an existing trip by UUID Future> fetchTrip(String uuid); } class TripRemoteDataSourceImpl implements TripRemoteDataSource { final Dio dio; TripRemoteDataSourceImpl({required this.dio}); @override Future>> fetchLandmarks(Map body) async { final response = await dio.post('/get/landmarks', data: body); if (response.statusCode != 200) { throw Exception('Server error fetching landmarks: ${response.statusCode}'); } if (response.data is! List) { throw Exception('Unexpected landmarks response format'); } return _normalizeLandmarks(List.from(response.data as List)); } @override Future> createTrip(Map body) async { log('Creating trip with body: $body'); // convert body to actual json String bodyJson = jsonEncode(body); log('Trip request JSON: $bodyJson'); final response = await dio.post('/optimize/trip', data: body); if (response.statusCode != 200) { throw Exception('Server error: ${response.statusCode}'); } if (response.data is! Map) { throw Exception('Unexpected response format'); } final Map json = Map.from(response.data as Map); _ensureLandmarks(json); if (json.containsKey('landmarks') && json['landmarks'] is List) { return json; } final String? firstUuid = json['first_landmark_uuid'] as String?; if (firstUuid == null) { return json; } final List> landmarks = []; String? next = firstUuid; while (next != null) { final lm = await _fetchLandmarkByUuid(next); landmarks.add(lm); final dynamic nxt = lm['next_uuid']; next = (nxt is String) ? nxt : null; } json['landmarks'] = landmarks; return json; } @override Future> fetchTrip(String uuid) async { final response = await dio.get('/trip/$uuid'); if (response.statusCode != 200) { throw Exception('Server error: ${response.statusCode}'); } if (response.data is! Map) { throw Exception('Unexpected response format'); } final Map json = Map.from(response.data as Map); // Normalize same as createTrip: if trip contains first_landmark_uuid, expand chain if (json.containsKey('landmarks') && json['landmarks'] is List) { _ensureLandmarks(json); return json; } final String? firstUuid = json['first_landmark_uuid'] as String?; if (firstUuid == null) return json; final List> landmarks = []; String? next = firstUuid; while (next != null) { final lm = await _fetchLandmarkByUuid(next); landmarks.add(lm); final dynamic nxt = lm['next_uuid']; next = (nxt is String) ? nxt : null; } json['landmarks'] = landmarks; return json; } // Fetch a single landmark by uuid via the /landmark/{landmark_uuid} endpoint Future> _fetchLandmarkByUuid(String uuid) async { final response = await dio.get('/landmark/$uuid'); if (response.statusCode != 200) { throw Exception('Failed to fetch landmark $uuid: ${response.statusCode}'); } if (response.data is! Map) { throw Exception('Unexpected landmark response format'); } return _normalizeLandmark(Map.from(response.data as Map)); } static void _ensureLandmarks(Map json) { final raw = json['landmarks']; if (raw is List) { json['landmarks'] = _normalizeLandmarks(raw); } } static List> _normalizeLandmarks(List rawList) { return rawList .map((item) => _normalizeLandmark(Map.from(item as Map))) .toList(); } static Map _normalizeLandmark(Map source) { final normalized = Map.from(source); final typeValue = normalized['type']; if (typeValue is String) { normalized['type'] = {'type': typeValue}; } return normalized; } }