import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:http/http.dart' as http;
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/controller/functions/tts.dart';
import 'package:sefer_driver/controller/home/navigation/decode_polyline_isolate.dart';
import 'package:sefer_driver/env/env.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/print.dart';
import 'dart:ui';
import 'package:sefer_driver/services/offline_map_service.dart';

class RouteData {
  final List<LatLng> coordinates;
  final List<Map<String, dynamic>> steps;
  final double distanceM;
  final double durationS;
  final String points;

  RouteData({
    required this.coordinates,
    required this.steps,
    required this.distanceM,
    required this.durationS,
    required this.points,
  });
}

class NavigationController extends GetxController
    with GetSingleTickerProviderStateMixin {
  static const Duration _recordInterval = Duration(seconds: 4);
  static const Duration _uploadInterval = Duration(minutes: 2);
  static const double _minMoveToRecord = 10.0;
  static const double _minMoveToProcess = 2.0;
  static const double _offRouteThresholdM = 25.0;
  static const int _offRouteTriggerSeconds = 6;

  bool isLoading = false;
  IntaleqMapController? mapController;
  bool isStyleLoaded = false;
  final TextEditingController placeDestinationController =
      TextEditingController();

  LatLng? myLocation;

  double _oldHeading = 0.0;
  double _targetHeading = 0.0;
  double _smoothedHeading = 0.0;

  AnimationController? _animController;
  LatLng? _oldLoc;
  LatLng? _targetLoc;

  double currentSpeed = 0.0;
  double totalDistance = 0.0;

  Set<Marker> markers = {};
  Set<Polyline> polylines = {};
  Set<Circle> circles = {};
  Set<Polygon> polygons = {};

  StreamSubscription<Position>? _locationStreamSubscription;
  LatLng? _lastProcessedLocation;

  List<dynamic> placesDestination = [];
  Timer? _debounce;

  // Alternative route handling
  bool _hasAlternativeRoutes = false;
  DateTime? _lastAutoRerouteTime;

  LatLng? _finalDestination;
  LatLng? _intermediateStop;
  List<Map<String, dynamic>> routeSteps = [];
  List<LatLng> _fullRouteCoordinates = [];
  int _lastTraveledIndexInFullRoute = 0;

  bool _nextInstructionSpoken = false;
  String currentInstruction = "";
  String nextInstruction = "";
  int currentStepIndex = 0;
  String distanceToNextStep = "";
  String totalDistanceRemaining = "";
  String estimatedTimeRemaining = "";
  dynamic currentManeuverModifier = 0;
  String arrivalTime = "--:--"; 

  double _routeTotalDistanceM = 0;
  double _routeTotalDurationS = 0;

  bool isNavigating = false;
  bool isMuted = false; 
  String distanceWithUnit = "";
  bool _cameraLockedToUser = true;
  bool _mapReady = false;

  bool isSelectingPlaceLocation = false;

  void togglePlaceSelectionMode() {
    isSelectingPlaceLocation = !isSelectingPlaceLocation;
    update();
  }

  Future<void> submitNewPlace(String name, String category) async {
    if (mapController == null || name.isEmpty || category.isEmpty) return;

    final LatLng pickedPos = mapController!.cameraPosition!.target;

    isLoading = true;
    update();

    final String country =
        box.read(BoxName.countryCode) == 'SY' ? 'syria' : 'jordan';

    final Map<String, dynamic> payload = {
      'name': name,
      'category': category,
      'lat': pickedPos.latitude,
      'lng': pickedPos.longitude,
      'country': country,
    };

    try {
      final response = await CRUD().postMapSaas(
        link: AppLink.mapSaasPlaces,
        payload: payload,
      );

      isLoading = false;
      if (response != null) {
        HapticFeedback.lightImpact();
        mySnackbarSuccess('Place added successfully! Thanks for your contribution.'.tr);
        isSelectingPlaceLocation = false;
      } else {
        mySnackbarWarning('Failed to add place. Please try again later.'.tr);
      }
      update();
    } catch (e) {
      isLoading = false;
      mySnackbarWarning('An error occurred while connecting to the server.'.tr);
      update();
    }
  }

  DateTime? _offRouteStartTime;
  bool _autoRecalcInProgress = false;

  final List<Map<String, dynamic>> _trackBuffer = [];
  Timer? _recordTimer;
  Timer? _uploadBatchTimer;
  LatLng? _lastBufferedLocation;
  DateTime? _lastBufferedTime;
  LatLng? _lastDistanceLocation;

  List<RouteData> routes = [];
  int selectedRouteIndex = 0;

  List<Map<String, dynamic>> recentLocations = [];

  double get _targetZoom {
    if (currentSpeed < 15) return 19.0;
    if (currentSpeed < 40) return 18.0;
    if (currentSpeed < 70) return 17.0;
    if (currentSpeed < 100) return 16.0;
    return 15.0;
  }

  double get _targetTilt {
    if (currentSpeed < 10) return 0.0;
    if (currentSpeed < 40) return 40.0;
    return 55.0;
  }

  static final List<Map<String, String>> placeCategories = [
    {
      'id': 'restaurant',
      'en': 'Restaurant',
      'ar': 'مطعم',
      'icon': 'restaurant'
    },
    {'id': 'cafe', 'en': 'Cafe', 'ar': 'مقهى', 'icon': 'coffee'},
    {
      'id': 'supermarket',
      'en': 'Supermarket',
      'ar': 'سوبر ماركت',
      'icon': 'shopping_basket'
    },
    {
      'id': 'pharmacy',
      'en': 'Pharmacy',
      'ar': 'صيدلية',
      'icon': 'local_pharmacy'
    },
    {
      'id': 'gas_station',
      'en': 'Gas Station',
      'ar': 'محطة وقود',
      'icon': 'local_gas_station'
    },
    {'id': 'atm', 'en': 'ATM', 'ar': 'صراف آلي', 'icon': 'atm'},
    {'id': 'bank', 'en': 'Bank', 'ar': 'بنك', 'icon': 'account_balance'},
    {'id': 'mosque', 'en': 'Mosque', 'ar': 'مسجد', 'icon': 'mosque'},
    {
      'id': 'hospital',
      'en': 'Hospital',
      'ar': 'مستشفى',
      'icon': 'local_hospital'
    },
    {'id': 'school', 'en': 'School', 'ar': 'مدرسة', 'icon': 'school'},
    {
      'id': 'university',
      'en': 'University',
      'ar': 'جامعة',
      'icon': 'account_balance'
    },
    {'id': 'park', 'en': 'Park', 'ar': 'منتزه', 'icon': 'park'},
    {'id': 'hotel', 'en': 'Hotel', 'ar': 'فندق', 'icon': 'hotel'},
    {
      'id': 'mall',
      'en': 'Shopping Mall',
      'ar': 'مركز تسوق',
      'icon': 'shopping_mall'
    },
    {'id': 'gym', 'en': 'Gym', 'ar': 'نادي رياضي', 'icon': 'fitness_center'},
    {
      'id': 'salon',
      'en': 'Beauty Salon',
      'ar': 'صالون تجميل',
      'icon': 'content_cut'
    },
    {'id': 'bakery', 'en': 'Bakery', 'ar': 'مخبز', 'icon': 'bakery_dining'},
    {
      'id': 'laundry',
      'ar': 'مصبغة',
      'en': 'Laundry',
      'icon': 'local_laundry_service'
    },
    {
      'id': 'car_repair',
      'en': 'Car Repair',
      'ar': 'تصليح سيارات',
      'icon': 'build'
    },
    {
      'id': 'government',
      'en': 'Government Office',
      'ar': 'دائرة حكومية',
      'icon': 'gavel'
    },
  ];

  IconData get currentManeuverIcon {
    switch (currentManeuverModifier) {
      case 4: // Arrive
        return Icons.place_rounded;
      case 6: // Roundabout
        return Icons.roundabout_right_rounded;
      case 2: // Right
        return Icons.turn_right_rounded;
      case 3: // Slight Right
        return Icons.turn_slight_right_rounded;
      case -2: // Left
        return Icons.turn_left_rounded;
      case -1: // Slight Left
        return Icons.turn_slight_left_rounded;
      case 7: // Keep Right
        return Icons.turn_right_rounded;
      case -7: // Keep Left
        return Icons.turn_left_rounded;
      case 0: // Straight
        return Icons.straight_rounded;
      default:
        return Icons.straight_rounded;
    }
  }

  void toggleMute() {
    isMuted = !isMuted;
    update();
  }

  @override
  void onInit() {
    super.onInit();
    _animController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 1000));
    _animController!.addListener(() {
      if (_oldLoc != null && _targetLoc != null && _mapReady) {
        final t = _animController!.value;
        final lat = lerpDouble(_oldLoc!.latitude, _targetLoc!.latitude, t)!;
        final lng = lerpDouble(_oldLoc!.longitude, _targetLoc!.longitude, t)!;
        myLocation = LatLng(lat, lng);
        _smoothedHeading = _lerpAngle(_oldHeading, _targetHeading, t);

        if (isStyleLoaded) {
          if (_cameraLockedToUser) {
            animateCameraToPosition(myLocation!,
                bearing: _smoothedHeading,
                zoom: isNavigating ? _targetZoom : 17.0,
                tilt: isNavigating ? _targetTilt : 0.0);
          }
        }
      }
    });
    _initialize();
  }

  Future<void> _initialize() async {
    _loadRecentLocations();
    await _getCurrentLocationAndStartUpdates();
  }

  void _loadRecentLocations() {
    final dynamic stored = box.read(BoxName.recentLocations);
    if (stored != null) {
      try {
        List<dynamic> parsed;
        if (stored is String) {
          parsed = jsonDecode(stored);
        } else if (stored is List) {
          parsed = stored;
        } else {
          parsed = [];
        }

        recentLocations = parsed
            .map((e) => Map<String, dynamic>.from(e))
            .toList()
            .reversed // Most recent first
            .take(3)
            .toList();
      } catch (e) {
        Log.print("Error decoding recent locations: $e");
        recentLocations = [];
      }
    } else {
      recentLocations = [];
    }
    update();
  }

  @override
  void onClose() {
    _locationStreamSubscription?.cancel();
    _recordTimer?.cancel();
    _uploadBatchTimer?.cancel();
    _debounce?.cancel();
    _animController?.dispose();
    mapController = null;
    placeDestinationController.dispose();
    _flushBufferToServer();
    super.onClose();
  }

  void onMapCreated(IntaleqMapController controller) async {
    Log.print("DEBUG: NavigationController.onMapCreated called");
    mapController = controller;
    await onStyleLoaded();
  }

  Future<void> onStyleLoaded() async {
    Log.print("DEBUG: NavigationController.onStyleLoaded called");
    isStyleLoaded = true;
    await _loadCustomIcons();

    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await Future.delayed(const Duration(milliseconds: 300));
      if (!_mapReady) {
        Log.print("DEBUG: NavigationController setting _mapReady = true");
        _mapReady = true;
        if (myLocation != null) {
          Log.print("DEBUG: Animating camera to initial location: $myLocation");
          animateCameraToPosition(myLocation!);
        }
        if (_fullRouteCoordinates.isNotEmpty) {
          Log.print("DEBUG: Updating initial polylines");
          _updatePolylinesSets([], _fullRouteCoordinates);
        }
      }
    });
  }

  void onMapTapped(Point<double> point, LatLng tappedPoint) {
    if (isNavigating || routes.isEmpty) return;

    int? bestIndex;
    double minDistance = 100.0; 

    for (int i = 0; i < routes.length; i++) {
      for (var coord in routes[i].coordinates) {
        final dist = Geolocator.distanceBetween(
          tappedPoint.latitude,
          tappedPoint.longitude,
          coord.latitude,
          coord.longitude,
        );
        if (dist < minDistance) {
          minDistance = dist;
          bestIndex = i;
        }
      }
    }

    if (bestIndex != null && bestIndex != selectedRouteIndex) {
      HapticFeedback.selectionClick();
      selectRoute(bestIndex);
    }
  }

  Future<void> onMapLongPressed(Point<double> point, LatLng tappedPoint) async {
    HapticFeedback.mediumImpact();
    Get.dialog(
      AlertDialog(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
        title: Text('Start Navigation?'.tr,
            style: const TextStyle(fontWeight: FontWeight.bold)),
        content: Text('Do you want to go to this location?'.tr),
        actions: [
          TextButton(
              child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey)),
              onPressed: () => Get.back()),
          ElevatedButton(
            style: ElevatedButton.styleFrom(
                backgroundColor: const Color(0xFF0D47A1),
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12))),
            child:
                Text('Go Now'.tr, style: const TextStyle(color: Colors.white)),
            onPressed: () {
              Get.back();
              startNavigationTo(tappedPoint, infoWindowTitle: 'Selected Location'.tr);
            },
          ),
        ],
      ),
    );
  }

  Future<void> _getCurrentLocationAndStartUpdates() async {
    try {
      Log.print("DEBUG: Getting initial location...");
      final position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.high);
      myLocation = LatLng(position.latitude, position.longitude);
      Log.print("DEBUG: Initial location acquired: $myLocation");
      _targetHeading = position.heading;
      _oldHeading = position.heading;
      _smoothedHeading = position.heading;
      update();
      if (isStyleLoaded) animateCameraToPosition(myLocation!);
      _startLocationStream();
      _startBatchTimers();
    } catch (e) {
      Log.print("DEBUG: Error getting initial location: $e");
    }
  }

  void _startLocationStream() {
    _locationStreamSubscription?.cancel();
    _locationStreamSubscription = Geolocator.getPositionStream(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: 2, 
      ),
    ).listen(
      (Position position) {
        _handleLocationUpdate(position);
      },
      onError: (error) {
        Log.print("DEBUG: Location stream error: $error");
      },
    );
  }

  bool _isProcessing = false;
  Future<void> _handleLocationUpdate(Position position) async {
    if (_isProcessing) return;
    _isProcessing = true;

    try {
      final newLoc = LatLng(position.latitude, position.longitude);
      currentSpeed = position.speed * 3.6; 

      if (_lastProcessedLocation != null) {
        final d = Geolocator.distanceBetween(
          newLoc.latitude,
          newLoc.longitude,
          _lastProcessedLocation!.latitude,
          _lastProcessedLocation!.longitude,
        );
        if (d < _minMoveToProcess) {
          _isProcessing = false;
          return;
        }
      }

      Log.print(
          "DEBUG: Location update - Speed: ${currentSpeed.toStringAsFixed(1)} km/h, Loc: $newLoc");

      if (_lastDistanceLocation != null) {
        final d = Geolocator.distanceBetween(
          _lastDistanceLocation!.latitude,
          _lastDistanceLocation!.longitude,
          newLoc.latitude,
          newLoc.longitude,
        );
        if (d > 5.0) totalDistance += d;
      }
      _lastDistanceLocation = newLoc;

      _oldLoc = myLocation ?? newLoc;
      _targetLoc = newLoc;

      _oldHeading = _smoothedHeading;
      if (currentSpeed > 1.5 && _oldLoc != null) {
        _targetHeading = Geolocator.bearingBetween(
          _oldLoc!.latitude,
          _oldLoc!.longitude,
          _targetLoc!.latitude,
          _targetLoc!.longitude,
        );
      } else {
        _targetHeading = position.heading;
      }

      _animController?.forward(from: 0.0);
      _lastProcessedLocation = newLoc;

      if (_fullRouteCoordinates.isNotEmpty) {
        _updateTraveledPolylineSmart(newLoc);
        _checkNavigationStep(newLoc);
        _recomputeETA();
        _checkOffRoute(newLoc);
      }
      update();
    } catch (e) {
      Log.print("DEBUG: Error in _handleLocationUpdate: $e");
    } finally {
      _isProcessing = false;
    }
  }

  double _lerpAngle(double from, double to, double t) {
    final double diff = ((to - from + 540.0) % 360.0) - 180.0;
    return (from + diff * t + 360.0) % 360.0;
  }

  void _checkOffRoute(LatLng pos) {
    if (_autoRecalcInProgress || isLoading) return;
    if (_fullRouteCoordinates.isEmpty) return;

    const int searchWindow = 80;
    final int start = _lastTraveledIndexInFullRoute;
    final int end = min(start + searchWindow, _fullRouteCoordinates.length);

    double minDist = double.infinity;
    for (int i = start; i < end; i++) {
      final d = Geolocator.distanceBetween(
        pos.latitude,
        pos.longitude,
        _fullRouteCoordinates[i].latitude,
        _fullRouteCoordinates[i].longitude,
      );
      if (d < minDist) minDist = d;
    }

    if (minDist > _offRouteThresholdM) {
      if (_offRouteStartTime == null) {
        _offRouteStartTime = DateTime.now();
      } else {
        final elapsed =
            DateTime.now().difference(_offRouteStartTime!).inSeconds;
        if (elapsed >= _offRouteTriggerSeconds) {
          _offRouteStartTime = null;
          _autoRecalcInProgress = true;
          _smartRecalculateRoute(pos);
        }
      }
    } else {
      _offRouteStartTime = null;
    }
  }

  Future<void> _smartRecalculateRoute(LatLng currentPos) async {
    try {
      if (routes.isNotEmpty && selectedRouteIndex < routes.length - 1) {
        final nextIndex = selectedRouteIndex + 1;
        final nextRoute = routes[nextIndex];

        double minDist = double.infinity;
        for (var coord in nextRoute.coordinates) {
          final d = Geolocator.distanceBetween(
            currentPos.latitude,
            currentPos.longitude,
            coord.latitude,
            coord.longitude,
          );
          if (d < minDist) minDist = d;
        }

        if (minDist < 100) {
          selectRoute(nextIndex);
          Log.print("DEBUG: Switched to alternative route due to deviation");
          _autoRecalcInProgress = false;
          return;
        }
      }

      if (_finalDestination != null) {
        await recalculateRoute();
      }
      _autoRecalcInProgress = false;
    } catch (e) {
      Log.print("DEBUG: Error in smart recalculate: $e");
      _autoRecalcInProgress = false;
    }
  }

  void _startBatchTimers() {
    _recordTimer?.cancel();
    _uploadBatchTimer?.cancel();
    _recordTimer = Timer.periodic(_recordInterval, (_) => _recordToBuffer());
    _uploadBatchTimer =
        Timer.periodic(_uploadInterval, (_) => _flushBufferToServer());
  }

  void _recordToBuffer() {
    if (myLocation == null ||
        (myLocation!.latitude == 0 && myLocation!.longitude == 0)) {
      return;
    }
    final now = DateTime.now();
    final distFromLast = _lastBufferedLocation == null
        ? 999.0
        : Geolocator.distanceBetween(
            _lastBufferedLocation!.latitude,
            _lastBufferedLocation!.longitude,
            myLocation!.latitude,
            myLocation!.longitude);
    final bool moved = distFromLast > _minMoveToRecord && currentSpeed > 0.5;
    final bool timeForced = _lastBufferedTime == null ||
        now.difference(_lastBufferedTime!).inSeconds >= 60;
    if (!moved && !timeForced) return;

    _lastBufferedLocation = myLocation;
    _lastBufferedTime = now;

    _trackBuffer.add({
      'lat': double.parse(myLocation!.latitude.toStringAsFixed(6)),
      'lng': double.parse(myLocation!.longitude.toStringAsFixed(6)),
      'spd': double.parse(currentSpeed.toStringAsFixed(1)),
      'head': _smoothedHeading.toStringAsFixed(0),
      'dist': double.parse(totalDistance.toStringAsFixed(1)),
      'ts': now.toIso8601String(),
    });
  }

  Future<void> _flushBufferToServer() async {
    if (_trackBuffer.isEmpty) return;
    final batch = List<Map<String, dynamic>>.from(_trackBuffer);
    _trackBuffer.clear();
    final String driverId = (box.read(BoxName.driverID) ?? '').toString();

    try {
      await CRUD().post(
        link: '${AppLink.locationServerSide}/add_batch.php',
        payload: {
          'driver_id': driverId,
          'batch_data': jsonEncode(batch),
          'session_dist': totalDistance.toStringAsFixed(1),
        },
      );
    } catch (e) {
      _trackBuffer.insertAll(0, batch);
    }
  }

  void animateCameraToPosition(LatLng position,
      {double? zoom, double bearing = 0.0, double tilt = 0.0}) {
    if (!_mapReady || mapController == null) return;
    mapController!.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
        target: position,
        zoom: zoom ?? (isNavigating ? _targetZoom : 16.0),
        bearing: bearing,
        tilt: tilt)));
  }

  Future<void> _safeAnimateCameraBounds(LatLngBounds? bounds,
      {double left = 60,
      double top = 60,
      double right = 60,
      double bottom = 60}) async {
    if (bounds == null || mapController == null) return;
    try {
      final latSpan =
          (bounds.northeast.latitude - bounds.southwest.latitude).abs();
      final lngSpan =
          (bounds.northeast.longitude - bounds.southwest.longitude).abs();
      if (latSpan < 0.0001 && lngSpan < 0.0001) {
        mapController
            ?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 16));
        return;
      }
      await Future.delayed(const Duration(milliseconds: 200));
      await mapController?.animateCamera(CameraUpdate.newLatLngBounds(bounds,
          left: left, top: top, right: right, bottom: bottom));
    } catch (e) {
      try {
        await mapController
            ?.animateCamera(CameraUpdate.newLatLngZoom(bounds!.northeast, 14));
      } catch (_) {}
    }
  }

  void onUserPanned() {
    _cameraLockedToUser = false;
    update();
  }

  void relockCameraToUser() {
    _cameraLockedToUser = true;
    if (myLocation != null) {
      animateCameraToPosition(myLocation!,
          bearing: _smoothedHeading, zoom: _targetZoom, tilt: _targetTilt);
    }
    update();
  }

  bool get isCameraLocked => _cameraLockedToUser;

  void _updateTraveledPolylineSmart(LatLng currentPos) {
    if (_fullRouteCoordinates.isEmpty) return;
    const int searchWindow = 60;
    final int startIndex = _lastTraveledIndexInFullRoute;
    final int endIndex =
        min(startIndex + searchWindow, _fullRouteCoordinates.length);

    double minDist = double.infinity;
    int closestIdx = startIndex;
    bool foundCloser = false;

    for (int i = startIndex; i < endIndex; i++) {
      final d = Geolocator.distanceBetween(
        currentPos.latitude,
        currentPos.longitude,
        _fullRouteCoordinates[i].latitude,
        _fullRouteCoordinates[i].longitude,
      );
      if (d < minDist) {
        minDist = d;
        closestIdx = i;
        foundCloser = true;
      }
    }

    if (foundCloser &&
        minDist < 50 &&
        closestIdx > _lastTraveledIndexInFullRoute) {
      _lastTraveledIndexInFullRoute = closestIdx;
      _updatePolylinesSets(_fullRouteCoordinates.sublist(0, closestIdx + 1),
          _fullRouteCoordinates.sublist(closestIdx));
    }
  }

  Future<void> _updatePolylinesSets(
      List<LatLng> traveled, List<LatLng> remaining) async {
    Set<Polyline> newPolylines = {};

    for (int i = 0; i < routes.length; i++) {
      if (i == selectedRouteIndex) continue;
      newPolylines.add(Polyline(
        polylineId: PolylineId('alt_$i'),
        points: routes[i].coordinates,
        color: const Color(0xFFB0BEC5).withOpacity(0.8),
        width: 6,
      ));
    }

    if (remaining.isNotEmpty) {
      newPolylines.add(Polyline(
        polylineId: const PolylineId('remaining'),
        points: remaining,
        color: const Color(0xFF00E5FF),
        width: 8,
      ));
    }

    if (traveled.isNotEmpty) {
      newPolylines.add(Polyline(
        polylineId: const PolylineId('traveled'),
        points: traveled,
        color: const Color(0xFFBDBDBD).withOpacity(0.6),
        width: 5,
      ));
    }

    polylines = newPolylines;
    update();
  }

  void selectRoute(int index) {
    if (index < 0 || index >= routes.length) return;
    selectedRouteIndex = index;
    final r = routes[index];
    _fullRouteCoordinates = r.coordinates;
    routeSteps = r.steps;
    _routeTotalDistanceM = r.distanceM;
    _routeTotalDurationS = r.durationS;

    _lastTraveledIndexInFullRoute = 0;
    _recomputeETA();
    _updatePolylinesSets([], _fullRouteCoordinates);
    update();
  }

  void goToFavorite(String type) {
    LatLng? dest;
    switch (type) {
      case 'home':
        dest = getHomeLatLng();
        break;
      case 'work':
        dest = getWorkLatLng();
        break;
      case 'airport':
        dest = getAirportLatLng();
        break;
    }

    if (dest != null && myLocation != null) {
      getRoute(myLocation!, dest);
    } else {
      mySnackbarWarning(box.read(BoxName.lang) == 'ar' ? 'الموقع غير متاح حالياً.' : 'Location not available.');
    }
  }

  LatLng? getHomeLatLng() {
    final dynamic stored = box.read(BoxName.addHome);
    if (stored != null && stored is String && stored.contains(',')) {
      final parts = stored.split(',');
      return LatLng(double.parse(parts[0]), double.parse(parts[1]));
    }
    return null;
  }

  LatLng? getWorkLatLng() {
    final dynamic stored = box.read(BoxName.addWork);
    if (stored != null && stored is String && stored.contains(',')) {
      final parts = stored.split(',');
      return LatLng(double.parse(parts[0]), double.parse(parts[1]));
    }
    return null;
  }

  LatLng getAirportLatLng() {
    final String country = box.read(BoxName.countryCode) ?? 'JO';
    if (country == 'SY') {
      return const LatLng(33.4111, 36.5147); 
    }
    return const LatLng(31.7225, 35.9933); 
  }

  Future<void> getRoute(LatLng origin, LatLng destination) async {
    isLoading = true;
    update();

    final String langCode = box.read(BoxName.lang) ?? 'ar';
    final Map<String, String> queryParams = {
      'fromLat': origin.latitude.toString(),
      'fromLng': origin.longitude.toString(),
      'toLat': destination.latitude.toString(),
      'toLng': destination.longitude.toString(),
      'steps': 'true',
      'alternatives': 'true',
      'locale': langCode,
    };

    if (_intermediateStop != null) {
      queryParams['stop1Lat'] = _intermediateStop!.latitude.toString();
      queryParams['stop1Lng'] = _intermediateStop!.longitude.toString();
    }
    final saasUri =
        Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);

    try {
      final response =
          await http.get(saasUri, headers: {'x-api-key': Env.mapSaasKey});

      if (response.statusCode != 200) {
        isLoading = false;
        update();
        mySnackbarWarning(box.read(BoxName.lang) == 'ar' ? 'تعذر الاتصال بخدمة التوجيه.' : 'Failed to connect to routing service.');
        return;
      }

      final data = jsonDecode(response.body);

      routes.clear();
      final primaryPts = data['points']?.toString() ?? "";
      if (primaryPts.isNotEmpty) {
        final coords = await compute<String, List<LatLng>>(
            decodePolylineIsolate, primaryPts);
        routes.add(RouteData(
          coordinates: coords,
          steps: List<Map<String, dynamic>>.from(data['instructions'] ?? []),
          distanceM: (data['distance'] as num).toDouble(),
          durationS: (data['duration'] as num).toDouble(),
          points: primaryPts,
        ));
      }

      if (data['alternatives'] != null && data['alternatives'] is List) {
        _hasAlternativeRoutes = (data['alternatives'] as List).isNotEmpty;
        for (var alt in data['alternatives']) {
          final altPts = alt['points']?.toString() ?? "";
          if (altPts.isEmpty) continue;
          final altCoords = await compute<String, List<LatLng>>(
              decodePolylineIsolate, altPts);
          routes.add(RouteData(
            coordinates: altCoords,
            steps: List<Map<String, dynamic>>.from(alt['instructions'] ?? []),
            distanceM: (alt['distance'] as num).toDouble(),
            durationS: (alt['duration'] as num).toDouble(),
            points: altPts,
          ));
        }
      } else {
        _hasAlternativeRoutes = false;
      }

      if (routes.isEmpty) {
        isLoading = false;
        update();
        mySnackbarWarning(box.read(BoxName.lang) == 'ar' ? 'لم يتم العثور على مسار.' : 'No route found.');
        return;
      }

      selectedRouteIndex = 0;
      final selected = routes[0];
      _fullRouteCoordinates = selected.coordinates;
      routeSteps = selected.steps;
      _routeTotalDistanceM = selected.distanceM;
      _routeTotalDurationS = selected.durationS;

      _lastTraveledIndexInFullRoute = 0;
      if (isStyleLoaded) _updatePolylinesSets([], _fullRouteCoordinates);

      if (_fullRouteCoordinates.isNotEmpty) {
        OfflineMapService.instance
            .downloadRegion(_fullRouteCoordinates.last, radiusKm: 2.0);
      }

      _recomputeETA();

      currentStepIndex = 0;
      _nextInstructionSpoken = false;

      isNavigating = false;
      _cameraLockedToUser = false;
      _offRouteStartTime = null;
      isLoading = false;

      update();

      if (routeSteps.isNotEmpty) {
        currentInstruction = routeSteps[0]['text'] ?? "";
        currentManeuverModifier = routeSteps[0]['sign'] ?? 0;
        nextInstruction = routeSteps.length > 1
            ? (langCode == 'ar'
                ? "ثم ${routeSteps[1]['text']}"
                : "Then ${routeSteps[1]['text']}")
            : (langCode == 'ar' ? "الوجهة النهائية" : "Destination");

        if (!isMuted) {
          Get.find<TextToSpeechController>().speakText(currentInstruction);
        }
      }

      if (_fullRouteCoordinates.length >= 2) {
        final bounds =
            data['bbox'] != null && (data['bbox'] as List).length == 4
                ? LatLngBounds(
                    southwest: LatLng(data['bbox'][1], data['bbox'][0]),
                    northeast: LatLng(data['bbox'][3], data['bbox'][2]))
                : _boundsFromLatLngList(_fullRouteCoordinates);
        await _safeAnimateCameraBounds(bounds,
            bottom: 320, top: 150, left: 50, right: 50);
      }
      update();
    } catch (e) {
      isLoading = false;
      update();
      Log.print("GetRoute Error: $e");
    }
  }

  void _recomputeETA() {
    if (_routeTotalDistanceM == 0 || _fullRouteCoordinates.isEmpty) return;
    final fraction =
        (_fullRouteCoordinates.length - _lastTraveledIndexInFullRoute) /
            _fullRouteCoordinates.length;
    final remainingM = _routeTotalDistanceM * fraction;
    final remainingS = _routeTotalDurationS * fraction;

    final String langCode = box.read(BoxName.lang) ?? 'ar';
    if (remainingM > 1000) {
      totalDistanceRemaining = (remainingM / 1000).toStringAsFixed(1);
    } else {
      totalDistanceRemaining = remainingM.toStringAsFixed(0);
    }
    distanceWithUnit = _formatDistance(remainingM, langCode);

    final minutes = (remainingS / 60).round();
    estimatedTimeRemaining = minutes.toString();

    final arrival = DateTime.now().add(Duration(seconds: remainingS.toInt()));
    final h = arrival.hour > 12
        ? arrival.hour - 12
        : (arrival.hour == 0 ? 12 : arrival.hour);
    final m = arrival.minute.toString().padLeft(2, '0');
    final ampm = arrival.hour >= 12 ? 'PM' : 'AM';
    arrivalTime = "$h:$m $ampm";
  }

  Future<void> startNavigationTo(LatLng destination,
      {String infoWindowTitle = ''}) async {
    isLoading = true;
    update();
    try {
      _finalDestination = destination;
      await clearRoute(isNewRoute: true);

      markers = markers.where((m) => m.markerId.value == 'car').toSet();

      markers.add(Marker(
        markerId: const MarkerId('destination'),
        position: destination,
        icon: InlqBitmap.fromStyleImage('dest_icon'),
        infoWindow: infoWindowTitle.isNotEmpty
            ? InfoWindow(title: infoWindowTitle)
            : InfoWindow.noText,
      ));

      if (myLocation != null) {
        markers.add(Marker(
          markerId: const MarkerId('origin'),
          position: myLocation!,
          icon: InlqBitmap.fromStyleImage('start_icon'),
        ));
        await getRoute(myLocation!, destination);
      }
    } finally {
      isLoading = false;
      update();
    }
  }

  Future<void> recalculateRoute() async {
    if (myLocation == null || _finalDestination == null || isLoading) return;
    isLoading = true;
    update();
    mySnackbarInfo(box.read(BoxName.lang) == 'ar' ? 'جاري حساب مسار جديد...' : 'Calculating new route...');
    await getRoute(myLocation!, _finalDestination!);
    isLoading = false;
    update();
  }

  Future<void> startActiveNavigation() async {
    if (routes.isEmpty) {
      mySnackbarWarning(box.read(BoxName.lang) == 'ar'
          ? 'لا يوجد مسار لبدء الملاحة.'
          : 'No route to start navigation.');
      return;
    }
    if (isNavigating) return;

    isNavigating = true;
    _cameraLockedToUser = true;

    _recomputeETA();

    if (routeSteps.isNotEmpty && currentStepIndex < routeSteps.length) {
      currentInstruction = routeSteps[currentStepIndex]['text'] ?? "";
      currentManeuverModifier = routeSteps[currentStepIndex]['sign'] ?? 0;
      nextInstruction = (currentStepIndex + 1) < routeSteps.length
          ? (box.read(BoxName.lang) == 'ar'
              ? "ثم ${routeSteps[currentStepIndex + 1]['text']}"
              : "Then ${routeSteps[currentStepIndex + 1]['text']}")
          : (box.read(BoxName.lang) == 'ar' ? 'الوجهة' : 'Destination');

      if (!isMuted) {
        try {
          Get.find<TextToSpeechController>().speakText(currentInstruction);
        } catch (_) {}
      }
    }

    if (myLocation != null) {
      animateCameraToPosition(myLocation!,
          bearing: _smoothedHeading, zoom: _targetZoom, tilt: _targetTilt);
    }

    update();
  }

  Future<void> clearEverything() async {
    placeDestinationController.clear();
    placesDestination = [];
    await clearRoute();
  }

  Future<void> clearRoute({bool isNewRoute = false}) async {
    _offRouteStartTime = null;
    _autoRecalcInProgress = false;
    if (!isNewRoute) {
      markers = {};
      polylines = {};
      circles = {};
      polygons = {};
      _finalDestination = null;
      isNavigating = false;
      routes = [];
      await _flushBufferToServer();
    }
    routeSteps = [];
    _fullRouteCoordinates = [];
    _lastTraveledIndexInFullRoute = 0;
    currentInstruction = "";
    nextInstruction = "";
    currentManeuverModifier = "intaleq";
    distanceToNextStep = "";
    totalDistanceRemaining = "";
    estimatedTimeRemaining = "";
    arrivalTime = "--:--";
    _routeTotalDistanceM = 0;
    _routeTotalDurationS = 0;

    update();
  }

  Future<void> _loadCustomIcons() async {
    if (mapController == null) return;
    try {
      final carBytes = await rootBundle.load('assets/images/car.png');
      final startBytes = await rootBundle.load('assets/images/A.png');
      final destBytes = await rootBundle.load('assets/images/b.png');
      await mapController!.addImage('car_icon', carBytes.buffer.asUint8List());
      await mapController!
          .addImage('start_icon', startBytes.buffer.asUint8List());
      await mapController!.addImage('dest_icon', destBytes.buffer.asUint8List());
    } catch (e) {
      Log.print("Error loading custom icons: $e");
    }
  }

  void _checkNavigationStep(LatLng pos) {
    if (routeSteps.isEmpty || currentStepIndex >= routeSteps.length) return;

    final interval = routeSteps[currentStepIndex]['interval'] as List;
    final endIdx = interval[1] as int;

    if (endIdx >= _fullRouteCoordinates.length) return;

    final endLatLng = _fullRouteCoordinates[endIdx];
    final distance = Geolocator.distanceBetween(
        pos.latitude, pos.longitude, endLatLng.latitude, endLatLng.longitude);

    distanceToNextStep = distance > 1000
        ? "${(distance / 1000).toStringAsFixed(1)} km"
        : "${distance.toStringAsFixed(0)} m";

    if (distance < 50 &&
        !_nextInstructionSpoken &&
        nextInstruction.isNotEmpty) {
      if (!isMuted) {
        Get.find<TextToSpeechController>().speakText(nextInstruction);
      }
      _nextInstructionSpoken = true;
    }
    if (distance < 20) _advanceStep();
  }

  void _advanceStep() {
    currentStepIndex++;
    final String langCode = box.read(BoxName.lang) ?? 'ar';
    if (currentStepIndex < routeSteps.length) {
      currentInstruction = routeSteps[currentStepIndex]['text'] ?? "";
      currentManeuverModifier = routeSteps[currentStepIndex]['sign'] ?? 0;
      nextInstruction = (currentStepIndex + 1) < routeSteps.length
          ? (langCode == 'ar'
              ? "ثم ${routeSteps[currentStepIndex + 1]['text']}"
              : "Then ${routeSteps[currentStepIndex + 1]['text']}")
          : (langCode == 'ar' ? "ستصل إلى وجهتك" : "Arriving soon");
      _nextInstructionSpoken = false;
      update();
    } else {
      _finishNavigation();
    }
  }

  void _finishNavigation() {
    final String langCode = box.read(BoxName.lang) ?? 'ar';
    currentInstruction =
        langCode == 'ar' ? "لقد وصلت إلى وجهتك" : "You have arrived";
    currentManeuverModifier = 4;
    nextInstruction = "";
    distanceToNextStep = "";
    isNavigating = false;
    if (!isMuted) {
      Get.find<TextToSpeechController>().speakText(currentInstruction);
    }
    _flushBufferToServer();
    update();
  }

  Future<void> getPlaces() async {
    final q = placeDestinationController.text.trim();
    if (q.length < 3) {
      placesDestination = [];
      update();
      return;
    }
    if (mapController == null) return;

    try {
      final results = await mapController!.searchPlaces(q);
      
      if (myLocation != null) {
        for (final p in results) {
          final plat = double.tryParse(p['latitude']?.toString() ?? '0') ?? 0.0;
          final plng = double.tryParse(p['longitude']?.toString() ?? '0') ?? 0.0;
          p['distanceKm'] = _haversineKm(myLocation!.latitude, myLocation!.longitude, plat, plng);
        }
        results.sort((a, b) =>
            (a['distanceKm'] as double).compareTo(b['distanceKm'] as double));
      }
      
      placesDestination = results;
      update();
    } catch (e) {
      Log.print('getPlaces error: $e');
    }
  }

  Future<void> selectDestination(dynamic place) async {
    placeDestinationController.clear();
    placesDestination = [];
    final lat = double.parse(place['latitude'].toString());
    final lng = double.parse(place['longitude'].toString());
    await startNavigationTo(LatLng(lat, lng),
        infoWindowTitle: place['name'] ?? (box.read(BoxName.lang) == 'ar' ? 'وجهة' : 'Destination'));
  }

  void onSearchChanged(String query) {
    if (_debounce?.isActive ?? false) _debounce!.cancel();
    _debounce = Timer(const Duration(milliseconds: 500), () => getPlaces());
  }

  double _haversineKm(double lat1, double lon1, double lat2, double lon2) {
    const R = 6371.0;
    final dLat = (lat2 - lat1) * (pi / 180.0);
    final dLon = (lon2 - lon1) * (pi / 180.0);
    final a = sin(dLat / 2) * sin(dLat / 2) +
        cos(lat1 * pi / 180) *
            cos(lat2 * pi / 180) *
            sin(dLon / 2) *
            sin(dLon / 2);
    return R * 2 * atan2(sqrt(a), sqrt(1 - a));
  }

  LatLngBounds _boundsFromLatLngList(List<LatLng> list) {
    double? x0, x1, y0, y1;
    for (final ll in list) {
      if (x0 == null) {
        x0 = x1 = ll.latitude;
        y0 = y1 = ll.longitude;
      } else {
        if (ll.latitude > x1!) x1 = ll.latitude;
        if (ll.latitude < x0) x0 = ll.latitude;
        if (ll.longitude > y1!) y1 = ll.longitude;
        if (ll.longitude < y0!) y0 = ll.longitude;
      }
    }
    return LatLngBounds(
        northeast: LatLng(x1!, y1!), southwest: LatLng(x0!, y0!));
  }

  void setIntermediateStop(LatLng stop) {
    _intermediateStop = stop;
    if (myLocation != null && _finalDestination != null) {
      getRoute(myLocation!, _finalDestination!);
    }
    update();
  }

  void clearIntermediateStop() {
    _intermediateStop = null;
    if (myLocation != null && _finalDestination != null) {
      getRoute(myLocation!, _finalDestination!);
    }
    update();
  }

  String _formatDistance(double meters, String lang) {
    if (meters >= 1000) {
      return "${(meters / 1000).toStringAsFixed(1)} ${lang == 'ar' ? 'كم' : 'km'}";
    } else {
      return "${meters.toStringAsFixed(0)} ${lang == 'ar' ? 'م' : 'm'}";
    }
  }

  Future<void> submitPlaceSuggestion(String name) async {
    if (name.trim().isEmpty || myLocation == null) return;
    isLoading = true;
    update();
    try {
      final payload = {
        'name': name,
        'lat': myLocation!.latitude.toString(),
        'lng': myLocation!.longitude.toString(),
        'driver_id': box.read(BoxName.driverID),
      };
      await CRUD().post(link: AppLink.getPlacesSyria, payload: payload);
      mySnackbarInfo(box.read(BoxName.lang) == 'ar'
          ? "تم استلام اقتراحك! شكراً لمساهمتك."
          : "Suggestion received! Thanks for your contribution.");
    } finally {
      isLoading = false;
      update();
    }
  }
}
