import "dart:convert"; import "package:http/http.dart" as http; import "../models/restaurant.dart"; import "../models/service_point.dart"; class Api { static String get baseUrl { const v = String.fromEnvironment("API_BASE_URL"); if (v.isEmpty) { throw StateError( "API_BASE_URL is not set. Example (Android emulator): " "--dart-define=API_BASE_URL=http://10.0.2.2:8888/biz.payfrit.com/api", ); } return v; } static Uri _u(String path) { final normalizedBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl; final normalizedPath = path.startsWith("/") ? path : "/$path"; return Uri.parse("$normalizedBase$normalizedPath"); } static Future<_ApiResponse> _getJson(String path) async { final url = _u(path); final resp = await http.get(url); final body = resp.body; Map? json; try { final decoded = jsonDecode(body); if (decoded is Map) { json = decoded; } } catch (_) { // leave json null } // ignore: avoid_print print("API GET => $url"); // ignore: avoid_print print("STATUS => ${resp.statusCode}"); // ignore: avoid_print print("BODY => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}"); return _ApiResponse( statusCode: resp.statusCode, rawBody: body, json: json, ); } static Future<_ApiResponse> _postJson(String path, Map payload) async { final url = _u(path); final resp = await http.post( url, headers: {"Content-Type": "application/json"}, body: jsonEncode(payload), ); final body = resp.body; Map? json; try { final decoded = jsonDecode(body); if (decoded is Map) { json = decoded; } } catch (_) { // leave json null } // ignore: avoid_print print("API POST => $url"); // ignore: avoid_print print("BODY IN => ${jsonEncode(payload)}"); // ignore: avoid_print print("STATUS => ${resp.statusCode}"); // ignore: avoid_print print("BODY OUT => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}"); return _ApiResponse( statusCode: resp.statusCode, rawBody: body, json: json, ); } static void _throwIfApiError(Map data, String context) { if (data.containsKey("OK") && data["OK"] is bool && (data["OK"] as bool) == false) { final err = data["ERROR"]; final detail = data["DETAIL"]; final msg = data["MESSAGE"]; throw StateError( "$context API returned OK=false" "${err != null ? "\nERROR: $err" : ""}" "${msg != null ? "\nMESSAGE: $msg" : ""}" "${detail != null ? "\nDETAIL: $detail" : ""}", ); } } static Future<_ApiResponse> listRestaurantsRaw() async { return _getJson("/businesses/list.cfm"); } static Future> listRestaurants() async { final r = await listRestaurantsRaw(); if (r.statusCode != 200) { throw StateError("Restaurants request failed: ${r.statusCode}\n${r.rawBody}"); } final data = r.json; if (data == null) { throw StateError("Restaurants response was not JSON.\n${r.rawBody}"); } _throwIfApiError(data, "Restaurants"); final rows = data["Businesses"]; if (rows is! List) { throw StateError( "Restaurants JSON missing Businesses array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}", ); } return rows .whereType>() .map((e) => Restaurant.fromJson(e)) .toList(); } static Future<_ApiResponse> listServicePointsRaw({required int businessId}) async { return _postJson("/servicepoints/list.cfm", {"BusinessID": businessId}); } static Future> listServicePoints({required int businessId}) async { final r = await listServicePointsRaw(businessId: businessId); if (r.statusCode != 200) { throw StateError("ServicePoints request failed: ${r.statusCode}\n${r.rawBody}"); } final data = r.json; if (data == null) { throw StateError("ServicePoints response was not JSON.\n${r.rawBody}"); } _throwIfApiError(data, "ServicePoints"); // STRICT: hump case final rows = data["ServicePoints"]; if (rows is! List) { throw StateError( "ServicePoints JSON missing ServicePoints array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}", ); } return rows .whereType>() .map((e) => ServicePoint.fromJson(e)) .toList(); } } class _ApiResponse { final int statusCode; final String rawBody; final Map? json; const _ApiResponse({ required this.statusCode, required this.rawBody, required this.json, }); }