import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import '../services/offline_road_graph_engine.dart'; import '../services/offline_routing_engine.dart'; import '../services/offline_routing_package_service.dart'; import '../services/tactical_api_service.dart'; import '../services/turn_by_turn_navigation_engine.dart'; import '../services/valhalla_offline_engine.dart'; /// ============================================================================ /// [NavigationController] - وحدة التحكم بالملاحة وتوجيه القوافل التكتيكية /// ============================================================================ class NavigationController extends GetxController { // ── Observables / الحالات التفاعلية ────────────────────────────────────── final Rx activeRoute = Rx(null); final Rx vehicleProfile = Rx(TacticalVehicleProfile.convoy); final RxBool isCalculating = false.obs; /// هل التوجيه المحلي الحقيقي (شبكة الطرق الكاملة) جاهز على الجهاز؟ final RxBool isRealRoadEngineReady = false.obs; NavigationController() { _refreshEngineStatus(); } Future _refreshEngineStatus() async { final pkgInstalled = await OfflineRoutingPackageService.isInstalled(); final dbReady = await OfflineRoadGraphEngine.isDatabaseAvailable(); isRealRoadEngineReady.value = pkgInstalled || dbReady; } /// Calculate Hybrid Tactical Route / احتساب مسار تكتيكي هجين Future calculateRoute({ required LatLng origin, required LatLng destination, TacticalVehicleProfile? profile, bool autoStart = false, IntaleqMapController? mapController, }) async { final activeProfile = profile ?? vehicleProfile.value; vehicleProfile.value = activeProfile; isCalculating.value = true; OfflineRoutePlan? plan; // 1. Try Online NestJS Server Routing API try { final serverRoute = await TacticalApiService.calculateTacticalRoute( origin: origin, destination: destination, profile: activeProfile.name, ); if (serverRoute != null && serverRoute.points.isNotEmpty) { plan = OfflineRoutePlan( polylinePoints: serverRoute.points, totalDistanceKm: serverRoute.distanceKm, estimatedDurationMinutes: serverRoute.durationMinutes, profile: activeProfile, tacticalWaypoints: const ['موقع الانطلاق', 'الهدف التكتيكي المحدد'], isOffline: false, usesRealRoadNetwork: true, ); } } catch (e) { debugPrint('Online route fallback: $e'); } // ── Offline chain preparation ── // تأكد من وجود حزمة التوجيه محلياً؛ إن غابت وحُاول الحساب أثناء توفر // الشبكة تُنزَّل مرة واحدة تلقائياً (يفشل بسرعة عند انقطاع الإنترنت). final routingPackageReady = await OfflineRoutingPackageService.ensureInstalled(); if (!routingPackageReady) { debugPrint('Navigation: routing package unavailable — offline engines will degrade gracefully'); } // 2. On-Device SQLite road graph engine (667K real edges, Arabic street names) // الأولوية الأعلى أوفلاين: يحتوي على كامل شبكة طرق الأردن الحقيقية // (1.99 مليون عقدة، 667 ألف حافة) مع أسماء شوارع عربية وانحناءات واقعية. // يعمل بسرعة < 150ms حتى للمسافات الطويلة (عمان ← العقبة). plan ??= await OfflineRoadGraphEngine.calculateRoute( start: origin, destination: destination, profile: activeProfile, ); // 3. On-Device Valhalla engine (native bridge — may not be available on all platforms) // يحترم الاتجاه الممنوع وقيود الدوران + ارتفاعات SRTM. plan ??= await ValhallaOfflineEngine.calculateOfflineRoute( start: origin, destination: destination, profile: activeProfile, regionDir: await OfflineRoutingPackageService.installDirPath(), ); if (plan != null) { debugPrint('Navigation: using real road network route ' '(${plan.polylinePoints.length} pts, ${plan.totalDistanceKm.toStringAsFixed(1)} km, ' '${plan.maneuvers.length} maneuvers, offline=${plan.isOffline})'); } // 4. Last resort: legacy built-in synthetic graph (36 strategic nodes only) plan ??= OfflineRoutingEngine.calculateOnDeviceRoute( start: origin, destination: destination, profile: activeProfile, ); activeRoute.value = plan; isCalculating.value = false; if (autoStart) { startNavigation(mapController: mapController); } return plan; } /// Start Active Turn-by-Turn Guidance / بدء الإرشاد الملاحي الحي void startNavigation({IntaleqMapController? mapController}) { if (activeRoute.value == null) return; TurnByTurnNavigationEngine.startNavigation( plan: activeRoute.value!, simulate: false, controller: mapController, ); } /// Stop Navigation / إنهاء الملاحة void stopNavigation() { TurnByTurnNavigationEngine.stopNavigation(); } /// Reset Route State / إعادة ضبط المسار void reset() { stopNavigation(); activeRoute.value = null; } }