import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../config/app_config.dart'; import 'landmark_database.dart'; import 'offline_routing_engine.dart'; import 'offline_routing_package_service.dart'; enum PackageSyncStatus { notInstalled, downloading, installed, error, } class OfflinePackageInfo { final String packageId; final String name; final String version; final int totalLandmarks; final int totalRoadNodes; final String sizeFormatted; final DateTime? lastSyncTime; final PackageSyncStatus status; final bool canSyncNow; final int daysUntilNextSync; /// هل غراف الطرق الحقيقي (Valhalla) مثبت على الجهاز؟ final bool realRoadRoutingReady; const OfflinePackageInfo({ required this.packageId, required this.name, required this.version, required this.totalLandmarks, required this.totalRoadNodes, required this.sizeFormatted, this.lastSyncTime, this.status = PackageSyncStatus.notInstalled, this.canSyncNow = true, this.daysUntilNextSync = 0, this.realRoadRoutingReady = false, }); } class OfflinePackageManager { static const String _pkgKey = 'offline_jordan_pkg_v2'; static const int syncCooldownDays = 14; // Cooldown enabled: updates allowed every 14 days static String get defaultServerUrl => AppConfig.serverUrl; static String get defaultApiKey => AppConfig.apiKey; static ValueNotifier syncProgress = ValueNotifier(0.0); static ValueNotifier syncStatusMessage = ValueNotifier('جاهز للمزامنة'); static ValueNotifier packageStatus = ValueNotifier(PackageSyncStatus.installed); /// Reset sync lock and preferences / تصفير قفل المزامنة بالكامل static Future resetSyncLock() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('${_pkgKey}_date'); await prefs.remove('${_pkgKey}_synced'); } static Future getPackageInfo() async { final prefs = await SharedPreferences.getInstance(); final syncDateStr = prefs.getString('${_pkgKey}_date'); final isSynced = prefs.getBool('${_pkgKey}_synced') ?? true; final lastDate = syncDateStr != null ? DateTime.tryParse(syncDateStr) : null; bool canSync = true; int remainingDays = 0; if (lastDate != null) { final daysSinceSync = DateTime.now().difference(lastDate).inDays; if (daysSinceSync < syncCooldownDays) { canSync = false; remainingDays = syncCooldownDays - daysSinceSync; } } // حالة حزمة التوجيه الحقيقية (شبكة الطرق الكاملة) final routingInstalled = await OfflineRoutingPackageService.isInstalled(); final routingVersion = await OfflineRoutingPackageService.installedVersion(); return OfflinePackageInfo( packageId: 'jordan-tactical-offline-v2', name: 'حزمة الأردن التكتيكية الميدانية الكاملة (Jordan Tactical Pack)', version: routingInstalled ? (routingVersion ?? '2.4.0') : '2.4.0', totalLandmarks: LandmarkDatabase.allLandmarks.length, totalRoadNodes: routingInstalled ? 450000 : 36, sizeFormatted: routingInstalled ? 'غراف طرق الأردن الحقيقي + SRTM مثبت محلياً' : '3.5 MB (شبكة الطرق) + 25 MB (المعالم)', lastSyncTime: lastDate, status: isSynced ? PackageSyncStatus.installed : PackageSyncStatus.notInstalled, canSyncNow: canSync, daysUntilNextSync: remainingDays, realRoadRoutingReady: routingInstalled, ); } /// Download and sync full Jordan tactical data package from server with 14-day throttle protection static Future downloadFullPackage({ String? serverUrl, String? apiKey, bool force = false, }) async { if (!force) { final info = await getPackageInfo(); if (!info.canSyncNow && info.status == PackageSyncStatus.installed) { debugPrint('Package sync skipped (14-day cooldown active. ${info.daysUntilNextSync} days left).'); return true; // Already synced and in cooldown } } final activeUrl = serverUrl ?? defaultServerUrl; final activeKey = apiKey ?? defaultApiKey; try { packageStatus.value = PackageSyncStatus.downloading; syncProgress.value = 0.1; syncStatusMessage.value = 'جاري الاتصال بخادم الخرائط السيادي...'; // 1. Fetch Package Manifest final manifestUrl = Uri.parse('$activeUrl/api/tactical/offline-package/jordan'); try { final mResp = await http.get(manifestUrl, headers: {'x-api-key': activeKey}).timeout(const Duration(seconds: 6)); if (mResp.statusCode == 200) { syncProgress.value = 0.35; syncStatusMessage.value = 'تم استقبال بيانات الحزمة... جاري تحميل معالم PostGIS'; } } catch (_) { // Fallback to local cache } syncProgress.value = 0.50; syncStatusMessage.value = 'جاري مزامنة معالم الأردن وتحديث جداول PostGIS...'; // 2. Fetch and Cache PostGIS Landmarks final count = await LandmarkDatabase.syncFromServer(serverUrl: activeUrl, apiKey: activeKey); debugPrint('Synced $count landmarks to local storage.'); syncProgress.value = 0.75; syncStatusMessage.value = 'جاري تنزيل شبكة الطرق الحقيقية للأردن (غراف التوجيه + الارتفاعات)...'; // 3. Download & install the real Jordan road graph routing package // (Valhalla tiles built from the same OSM data as server GraphHopper) final routingOk = await OfflineRoutingPackageService.downloadAndInstall( serverUrl: activeUrl, apiKey: activeKey, ); if (!routingOk) { final hasLocal = await OfflineRoutingPackageService.isInstalled(); if (!hasLocal) { debugPrint('Routing package failed to download and no local offline cache exists.'); syncStatusMessage.value = 'خطأ: تعذر تنزيل شبكة الطرق الأوفلاين. يرجى التحقق من الاتصال بالخادم.'; packageStatus.value = PackageSyncStatus.error; return false; } else { debugPrint('Using previously installed local routing package.'); } } syncProgress.value = 0.90; syncStatusMessage.value = 'جاري بناء وتحديث محرك التوجيه المحلي...'; // 4. Initialize & warm up on-device routing engine OfflineRoutingEngine.initialize(); syncProgress.value = 0.95; syncStatusMessage.value = 'جاري تثبيت الحزمة في الذاكرة الدائمة وتفعيل قفل الـ 14 يوماً...'; final prefs = await SharedPreferences.getInstance(); await prefs.setBool('${_pkgKey}_synced', true); await prefs.setString('${_pkgKey}_date', DateTime.now().toIso8601String()); syncProgress.value = 1.0; syncStatusMessage.value = 'اكتملت المزامنة بنجاح! الحزمة جاهزة للعمل 100% بدون إنترنت (تحديث قادم بعد 14 يوماً).'; packageStatus.value = PackageSyncStatus.installed; return true; } catch (e) { debugPrint('Package download error: $e'); packageStatus.value = PackageSyncStatus.error; syncStatusMessage.value = 'تعذر الاتصال بالسيرفر. تم تفعيل الحزمة المدمجة المسبقة.'; return false; } } }