import "dart:convert"; import "package:http/http.dart" as http; import "../models/restaurant.dart"; import "../models/service_point.dart"; class ApiRawResponse { final int statusCode; final String rawBody; final Map? json; const ApiRawResponse({ required this.statusCode, required this.rawBody, required this.json, }); } class Api { static String? _userToken; // MVP hardcode static int _mvpBusinessId = 17; static void setAuthToken(String? token) => _userToken = token; static void setBusinessId(int? businessId) { if (businessId != null && businessId > 0) { _mvpBusinessId = businessId; } } static void clearCookies() { // no-op } 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 b = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl; final p = path.startsWith("/") ? path : "/$path"; return Uri.parse("$b$p"); } static Map _headers({required bool json, int? businessIdOverride}) { final h = {}; if (json) h["Content-Type"] = "application/json; charset=utf-8"; final tok = _userToken; if (tok != null && tok.isNotEmpty) { h["X-User-Token"] = tok; } final int bid = (businessIdOverride != null && businessIdOverride > 0) ? businessIdOverride : _mvpBusinessId; h["X-Business-ID"] = bid.toString(); return h; } static Map? _tryDecodeJsonMap(String body) { try { final decoded = jsonDecode(body); if (decoded is Map) return decoded; } catch (_) {} return null; } static Future _getRaw(String path, {int? businessIdOverride}) async { final url = _u(path); final resp = await http.get(url, headers: _headers(json: false, businessIdOverride: businessIdOverride)); final body = resp.body; final j = _tryDecodeJsonMap(body); // ignore: avoid_print print("API GET => $url"); // ignore: avoid_print print("STATUS => ${resp.statusCode}"); // ignore: avoid_print print("BODY => ${body.length > 2000 ? body.substring(0, 2000) : body}"); return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j); } static Future _postRaw( String path, Map payload, { int? businessIdOverride, }) async { final url = _u(path); final resp = await http.post( url, headers: _headers(json: true, businessIdOverride: businessIdOverride), body: jsonEncode(payload), ); final body = resp.body; final j = _tryDecodeJsonMap(body); // 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 > 2000 ? body.substring(0, 2000) : body}"); return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j); } static bool _ok(Map j) => j["OK"] == true || j["ok"] == true; static String _err(Map j) => (j["ERROR"] ?? j["error"] ?? "").toString(); static List? _pickArray(Map j, List keys) { for (final k in keys) { final v = j[k]; if (v is List) return v; } return null; } static Map _requireJson(ApiRawResponse raw, String label) { final j = raw.json; if (j == null) { throw StateError("$label request failed: ${raw.statusCode}\nNon-JSON response."); } return j; } // ------------------------- // Businesses (legacy model name: Restaurant) // ------------------------- static Future listRestaurantsRaw() async { return _getRaw("/businesses/list.cfm", businessIdOverride: _mvpBusinessId); } static Future> listRestaurants() async { final raw = await listRestaurantsRaw(); final j = _requireJson(raw, "Businesses"); if (!_ok(j)) { throw StateError( "Businesses API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}", ); } final arr = _pickArray(j, const ["Businesses", "BUSINESSES"]); if (arr == null) { throw StateError("Businesses JSON missing Businesses array.\nRaw: ${raw.rawBody}"); } final out = []; for (final e in arr) { if (e is Map) { out.add(Restaurant.fromJson(e)); } else if (e is Map) { out.add(Restaurant.fromJson(e.cast())); } } return out; } // ------------------------- // Service Points // ------------------------- static Future> listServicePoints({required int businessId}) async { // CRITICAL: endpoint is behaving like it reads JSON body, not query/header. final raw = await _postRaw( "/servicepoints/list.cfm", {"BusinessID": businessId}, businessIdOverride: businessId, ); final j = _requireJson(raw, "ServicePoints"); if (!_ok(j)) { throw StateError( "ServicePoints API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}", ); } final arr = _pickArray(j, const ["ServicePoints", "SERVICEPOINTS"]); if (arr == null) { throw StateError("ServicePoints JSON missing ServicePoints array.\nRaw: ${raw.rawBody}"); } final out = []; for (final e in arr) { if (e is Map) { out.add(ServicePoint.fromJson(e)); } else if (e is Map) { out.add(ServicePoint.fromJson(e.cast())); } } return out; } // ------------------------- // Ordering API (stubs referenced by OrderHomeScreen) // ------------------------- static Future listMenuItems({required int businessId}) async { throw StateError("endpoint_not_implemented: Api.listMenuItems"); } static Future getOrCreateCart({ required int userId, required int businessId, required int servicePointId, required int orderTypeId, }) async { throw StateError("endpoint_not_implemented: Api.getOrCreateCart"); } static Future getCart({required int orderId}) async { throw StateError("endpoint_not_implemented: Api.getCart"); } static Future setLineItem({ required int orderId, required int parentOrderLineItemId, required int itemId, required int qty, required List selectedChildItemIds, }) async { throw StateError("endpoint_not_implemented: Api.setLineItem"); } static Future submitOrder({required int orderId}) async { throw StateError("endpoint_not_implemented: Api.submitOrder"); } }