import 'package:real_estate_mobile/core/network/api_client.dart'; /// Resolves S3 keys to presigned download URLs via the backend API. /// Caches results in memory to avoid repeated API calls. class ImageUrlResolver { ImageUrlResolver._(); static final ImageUrlResolver instance = ImageUrlResolver._(); final Map _cache = {}; final Map> _pending = {}; /// Returns a direct image URL. /// - If [imageUrl] is already an http(s) URL, returns it as-is. /// - If it's an S3 key, fetches a presigned download URL from the backend. Future resolve(String? imageUrl) async { if (imageUrl == null || imageUrl.isEmpty) return null; if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) { return imageUrl; } // Check cache if (_cache.containsKey(imageUrl)) return _cache[imageUrl]; // Deduplicate in-flight requests for the same key if (_pending.containsKey(imageUrl)) return _pending[imageUrl]; final future = _fetchPresignedUrl(imageUrl); _pending[imageUrl] = future; try { final result = await future; if (result != null) _cache[imageUrl] = result; return result; } finally { _pending.remove(imageUrl); } } Future _fetchPresignedUrl(String key) async { try { final response = await ApiClient.instance.dio.get( '/upload/presigned-download-url', queryParameters: {'key': key}, ); final data = response.data; // Backend wraps: { success: true, data: { url: "..." } } if (data is Map) { final payload = data['data']; if (payload is Map) { return payload['url'] as String?; } // In case response isn't wrapped (direct controller return) return data['url'] as String?; } return null; } catch (_) { return null; } } void clearCache() => _cache.clear(); /// Evict a single key from the in-memory cache so the next resolve /// fetches a fresh presigned URL. void evict(String? key) { if (key != null) _cache.remove(key); } }