import 'dart:async';
import 'dart:ui' as ui;
import 'dart:math' show cos, max, min, pi, pow, sqrt;
import 'dart:typed_data';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.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:image/image.dart' as img;

import '../../../constant/colors.dart';
// contains global 'box'
import '../../../print.dart';
import '../../../views/home/map_widget.dart/cancel_raide_page.dart';
import 'location_search_controller.dart';
import 'nearby_drivers_controller.dart';
import 'ride_lifecycle_controller.dart';
import '../points_for_rider_controller.dart';
import '../../../constant/univeries_polygon.dart';

class MapEngineController extends GetxController {
  IntaleqMapController? mapController;
  bool isStyleLoaded = false;
  bool isIconsLoaded = false;

  Set<Marker> markers = {};
  Set<Polyline> polyLines = {};
  List<LatLng> polylineCoordinates = [];
  Set<Polygon> polygons = {};
  Set<Circle> circles = {};

  LatLngBounds? lastComputedBounds;
  bool mapType = false;
  bool mapTrafficON = false;
  bool isMarkersShown = false;

  String markerIcon = "marker_icon";
  String tripIcon = "trip_icon";
  String startIcon = "start_icon";
  String endIcon = "end_icon";
  String carIcon = "car_icon";
  String motoIcon = "moto_icon";
  String ladyIcon = "lady_icon";

  double height = 150;
  double heightMenu = 0;
  double widthMenu = 0;
  double heightPickerContainer = 90;
  double heightPointsPageForRider = 0;
  double mainBottomMenuMapHeight = Get.height * .2;
  double wayPointSheetHeight = 0;
  bool heightMenuBool = false;
  bool isPickerShown = false;
  bool isPointsPageForRider = false;
  bool isBottomSheetShown = false;
  bool reloadStartApp = false;
  bool isCancelRidePageShown = false;
  bool isCashConfirmPageShown = false;
  bool isPaymentMethodPageShown = false;
  bool isRideFinished = false;
  bool rideConfirm = false;
  bool isMainBottomMenuMap = true;

  bool isWayPointSheet = false;
  bool isWayPointStopsSheet = false;
  bool isWayPointStopsSheetUtilGetMap = false;
  double heightBottomSheetShown = 0;
  double cashConfirmPageShown = 250;
  double widthMapTypeAndTraffic = 50;
  double paymentPageShown = Get.height * .6;

  bool isAnotherOreder = false;
  bool isWhatsAppOrder = false;

  Map<String, Timer> _animationTimers = {};
  final int updateIntervalMs = 100;
  final double minMovementThreshold = 1.0;

  void onMapCreated(IntaleqMapController controller) {
    mapController = controller;
    update();
  }

  void onStyleLoaded() async {
    Log.print('🗺️ Siro Map Style Loaded. Initializing...');
    isStyleLoaded = true;
    await _loadMapIcons();

    final locationSearch = Get.find<LocationSearchController>();
    Get.find<RideLifecycleController>().reinit();

    if (mapController != null) {
      if (markers.isNotEmpty && lastComputedBounds != null) {
        await _safeAnimateCameraBounds(lastComputedBounds);
      } else {
        mapController!.animateCamera(
          CameraUpdate.newLatLng(locationSearch.passengerLocation),
        );
      }
    }
    update();
  }

  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 {
      if (bounds.northeast.latitude == bounds.southwest.latitude &&
          bounds.northeast.longitude == bounds.southwest.longitude) {
        Log.print(
            '⚠️ _safeAnimateCameraBounds: Bounds are a single point, zooming to point instead.');
        await mapController
            ?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 15));
        return;
      }

      await Future.delayed(const Duration(milliseconds: 200));

      await mapController?.animateCamera(
        CameraUpdate.newLatLngBounds(
          bounds,
          left: left,
          top: top,
          right: right,
          bottom: bottom,
        ),
      );
    } catch (e) {
      Log.print('❌ _safeAnimateCameraBounds CRASH PREVENTED: $e');
      try {
        await mapController
            ?.animateCamera(CameraUpdate.newLatLngZoom(bounds.northeast, 14));
      } catch (_) {}
    }
  }

  Future<void> _loadMapIcons() async {
    isIconsLoaded = false;
    for (int i = 0; i < 15; i++) {
      if (mapController != null && isStyleLoaded) break;
      await Future.delayed(const Duration(milliseconds: 200));
    }

    if (mapController == null || !isStyleLoaded) {
      Log.print(
          '⚠️ _loadMapIcons: mapController or style not ready. Icons may not load.');
    }

    await _addMapImage(startIcon, 'assets/images/A.png');
    await _addMapImage(endIcon, 'assets/images/b.png');
    await _addMapImage(carIcon, 'assets/images/car.png');
    await _addMapImage(motoIcon, 'assets/images/moto.png');
    await _addMapImage(ladyIcon, 'assets/images/lady.png');
    await _addMapImage('picker_icon', 'assets/images/picker.png');
    await _addMapImage('orange_marker', 'assets/images/moto1.png');
    await _addMapImage('violet_marker', 'assets/images/lady1.png');

    try {
      final walkBytes = await _createWalkMarkerBytes();
      await mapController?.addImage('walk_icon', walkBytes);
      Log.print('delimited: successfully added dynamic walk_icon');
    } catch (e) {
      Log.print('❌ Error loading dynamic walk icon: $e');
    }

    isIconsLoaded = true;
    markers = markers.map((m) => m.copyWith()).toSet();
    update();

    if (Get.isRegistered<NearbyDriversController>()) {
      Get.find<NearbyDriversController>()
          .getCarsLocationByPassengerAndReloadMarker();
    }
  }

  Future<void> _addMapImage(String id, String path) async {
    try {
      final ByteData bytes = await rootBundle.load(path);
      final size = _getImageSize(id);
      if (size != null && (id == carIcon || id == motoIcon || id == ladyIcon)) {
        final resized = await _resizeImage(bytes.buffer.asUint8List(), size);
        await mapController?.addImage(id, resized);
        Log.print(
            'delimited: successfully added resized map image: $id (${size}x${size})');
      } else {
        await mapController?.addImage(id, bytes.buffer.asUint8List());
        Log.print('delimited: successfully added map image: $id');
      }
    } catch (e) {
      Log.print('❌ Error loading map icon $id: $e');
    }
  }

  int? _getImageSize(String id) {
    if (id == carIcon || id == motoIcon || id == ladyIcon) return 120;
    return null;
  }

  Future<Uint8List> _resizeImage(Uint8List bytes, int size) async {
    return await compute((Uint8List data) {
      final image = img.decodeImage(data);
      if (image == null) return data;
      final resized = img.copyResize(image, width: size, height: size);
      return Uint8List.fromList(img.encodePng(resized));
    }, bytes);
  }

  void clearPolyline() {
    polyLines.clear();
    update();
  }

  LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) {
    const double earthRadius = 6378137.0;

    double latDelta = (radiusInMeters / earthRadius) * (180 / pi);
    double lngDelta =
        (radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi);

    double minLat = lat - latDelta;
    double maxLat = lat + latDelta;

    double minLng = lng - lngDelta;
    double maxLng = lng + lngDelta;

    minLat = max(-90.0, minLat);
    maxLat = min(90.0, maxLat);

    minLng = (minLng + 180) % 360 - 180;
    maxLng = (maxLng + 180) % 360 - 180;

    if (minLng > maxLng) {
      double temp = minLng;
      minLng = maxLng;
      maxLng = temp;
    }

    return LatLngBounds(
      southwest: LatLng(minLat, minLng),
      northeast: LatLng(maxLat, maxLng),
    );
  }

  Future<void> playRouteAnimation(
      List<LatLng> coords, LatLngBounds? bounds) async {
    const List<Color> segmentColors = [
      Color(0xFF109642), // Green
      Color(0xFFF59E0B), // Amber
      Color(0xFF7C3AED), // Purple
      Color(0xFFEF4444), // Red
    ];

    Set<Polyline> newPolylines = {};
    final locationSearch = Get.find<LocationSearchController>();

    if (locationSearch.activeMenuWaypointCount > 0) {
      List<int> splitIndices = [];
      for (int w = 0; w < locationSearch.activeMenuWaypointCount; w++) {
        final wp = locationSearch.menuWaypoints[w];
        if (wp == null) continue;
        int bestIdx = 0;
        double bestDist = double.infinity;
        for (int j = 0; j < coords.length; j++) {
          final dx = coords[j].latitude - wp.latitude;
          final dy = coords[j].longitude - wp.longitude;
          final d = dx * dx + dy * dy;
          if (d < bestDist) {
            bestDist = d;
            bestIdx = j;
          }
        }
        splitIndices.add(bestIdx);
      }
      splitIndices.sort();

      List<int> boundaries = [0, ...splitIndices, coords.length - 1];
      for (int s = 0; s < boundaries.length - 1; s++) {
        int from = boundaries[s];
        int to = boundaries[s + 1] + 1;
        if (to > coords.length) to = coords.length;
        if (from >= to - 1) continue;
        final segCoords = coords.sublist(from, to);
        if (segCoords.length < 2) continue;
        final color = segmentColors[s % segmentColors.length];

        newPolylines.add(Polyline(
          polylineId: PolylineId('segment_$s'),
          points: segCoords,
          color: color,
          width: 6,
        ));
      }
    } else {
      newPolylines.add(Polyline(
        polylineId: const PolylineId('route_primary'),
        points: coords,
        color: AppColor.primaryColor,
        width: 6,
      ));
    }

    polyLines = newPolylines;
    update();

    Log.print(
        '🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');

    if (bounds != null) {
      await _safeAnimateCameraBounds(bounds);
    }
  }

  void _fitCameraToPoints(LatLng p1, LatLng p2) async {
    if (mapController == null) return;

    if (p1.latitude == p2.latitude && p1.longitude == p2.longitude) {
      try {
        mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 17));
      } catch (e) {
        Log.print("Error animating to single point: $e");
      }
      return;
    }

    double minLat = min(p1.latitude, p2.latitude);
    double maxLat = max(p1.latitude, p2.latitude);
    double minLng = min(p1.longitude, p2.longitude);
    double maxLng = max(p1.longitude, p2.longitude);

    if ((maxLat - minLat).abs() < 0.002 && (maxLng - minLng).abs() < 0.002) {
      try {
        mapController?.animateCamera(CameraUpdate.newLatLngZoom(p1, 16));
      } catch (e) {
        Log.print("Error animating to single point: $e");
      }
      return;
    }

    double padding = 50.0;

    try {
      await mapController?.animateCamera(
        CameraUpdate.newLatLngBounds(
          LatLngBounds(
            southwest: LatLng(minLat, minLng),
            northeast: LatLng(maxLat, maxLng),
          ),
          left: padding,
          top: padding,
          right: padding,
          bottom: padding,
        ),
      );
    } catch (e) {
      Log.print("Error animating bounds: $e");
      try {
        LatLng center = LatLng((minLat + maxLat) / 2, (minLng + maxLng) / 2);
        mapController?.animateCamera(CameraUpdate.newLatLngZoom(center, 14));
      } catch (_) {}
    }
  }

  void fitCameraToPoints(LatLng p1, LatLng p2) {
    _fitCameraToPoints(p1, p2);
  }

  void clearMarkersExceptStartEndAndDriver() {
    const String currentDriverMarkerId = 'assigned_driver_marker';
    markers.removeWhere((marker) {
      String id = marker.markerId.value;
      if (id == 'start') return false;
      if (id == 'end') return false;
      if (id == currentDriverMarkerId) return false;
      return true;
    });
    update();
  }

  void clearMarkersExceptStartEnd() {
    markers.removeWhere((marker) {
      String id = marker.markerId.value;
      return id != 'start' && id != 'end';
    });
    update();
  }

  void _updateMarkerPosition(
      LatLng newPosition, double newHeading, String icon) {
    const String markerId = 'driverToPassengers';
    final mId = MarkerId(markerId);
    final existingMarker = markers.cast<Marker?>().firstWhere(
          (m) => m?.markerId == mId,
          orElse: () => null,
        );

    if (existingMarker != null) {
      _smoothlyUpdateMarker(existingMarker, newPosition, newHeading, icon);
    } else {
      markers = {
        ...markers,
        Marker(
          markerId: mId,
          position: newPosition,
          rotation: newHeading,
          icon: InlqBitmap.fromStyleImage(icon),
          anchor: const Offset(0.5, 0.5),
        ),
      };
      update();
    }
    mapController?.animateCamera(CameraUpdate.newLatLng(newPosition));
  }

  void updateMarkerPosition(
      LatLng newPosition, double newHeading, String icon) {
    _updateMarkerPosition(newPosition, newHeading, icon);
  }

  void _smoothlyUpdateMarker(
      Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
    double distance = Geolocator.distanceBetween(
        oldMarker.position.latitude,
        oldMarker.position.longitude,
        newPosition.latitude,
        newPosition.longitude);

    if (distance < 2.0) return;

    final MarkerId markerIdKey = oldMarker.markerId;
    _animationTimers[markerIdKey.value]?.cancel();

    int ticks = 0;
    const int totalSteps = 20;
    const int stepDuration = 50;

    double latStep =
        (newPosition.latitude - oldMarker.position.latitude) / totalSteps;
    double lngStep =
        (newPosition.longitude - oldMarker.position.longitude) / totalSteps;
    double headingStep = (newHeading - oldMarker.rotation) / totalSteps;

    LatLng currentPos = oldMarker.position;
    double currentHeading = oldMarker.rotation;

    _animationTimers[markerIdKey.value] =
        Timer.periodic(const Duration(milliseconds: stepDuration), (timer) {
      ticks++;

      currentPos =
          LatLng(currentPos.latitude + latStep, currentPos.longitude + lngStep);
      currentHeading += headingStep;

      final updatedMarker = oldMarker.copyWith(
        position: currentPos,
        rotation: currentHeading,
        icon: InlqBitmap.fromStyleImage(icon),
      );

      markers = {
        ...markers.where((m) => m.markerId != markerIdKey),
        updatedMarker,
      };

      if (mapController != null) {
        mapController!.animateCamera(CameraUpdate.newLatLng(currentPos));
      }

      update();

      if (ticks >= totalSteps) {
        timer.cancel();
        _animationTimers.remove(markerIdKey.value);
      }
    });
  }

  // تحديث موقع العلامة (Marker) واتجاهها بسلاسة على الخريطة.
  // تحسب الدالة المسافة بين الموقع الحالي والجديد؛ وإذا كانت أكبر من مترين،
  // تقوم بتقسيم الحركة والدوران إلى 20 خطوة متباعدة بـ 50 مللي ثانية (إجمالي ثانية واحدة).
  // يتم تحديث موضع العلامة وتحريك الكاميرا تدريجياً لتبدو حركة السيارة انسيابية.
  void smoothlyUpdateMarker(
      Marker oldMarker, LatLng newPosition, double newHeading, String icon) {
    _smoothlyUpdateMarker(oldMarker, newPosition, newHeading, icon);
  }

  void changeBottomSheetShown({bool? forceValue}) {
    if (forceValue != null) {
      isBottomSheetShown = forceValue;
    } else {
      isBottomSheetShown = !isBottomSheetShown;
    }
    heightBottomSheetShown = isBottomSheetShown == true ? 250 : 0;
    update();
  }

  void changeCashConfirmPageShown() {
    isCashConfirmPageShown = !isCashConfirmPageShown;
    final rideLife = Get.find<RideLifecycleController>();
    rideLife.isCashSelectedBeforeConfirmRide = true;
    cashConfirmPageShown = isCashConfirmPageShown == true ? 250 : 0;
    update();
    rideLife.update();
  }

  void changePaymentMethodPageShown() {
    isPaymentMethodPageShown = !isPaymentMethodPageShown;
    paymentPageShown = isPaymentMethodPageShown == true ? Get.height * .6 : 0;
    update();
  }

  void changeMapType() {
    mapType = !mapType;
    update();
  }

  void changeMapTraffic() {
    mapTrafficON = !mapTrafficON;
    update();
  }

  void changeisAnotherOreder(bool val) {
    isAnotherOreder = val;
    update();
  }

  void changeIsWhatsAppOrder(bool val) {
    isWhatsAppOrder = val;
    update();
  }

  void changeCancelRidePageShow() {
    showCancelRideBottomSheet();
    isCancelRidePageShown = !isCancelRidePageShown;
    update();
    if (Get.isRegistered<RideLifecycleController>()) {
      Get.find<RideLifecycleController>().update();
    }
  }

  void getDrawerMenu() {
    heightMenuBool = !heightMenuBool;
    widthMapTypeAndTraffic = heightMenuBool == true ? 0 : 50;
    heightMenu = heightMenuBool == true ? 80 : 0;
    widthMenu = heightMenuBool == true ? 110 : 0;
    update();
  }

  void changeMainBottomMenuMap() {
    if (isWayPointStopsSheetUtilGetMap == true) {
      changeWayPointSheet();
    } else {
      isMainBottomMenuMap = !isMainBottomMenuMap;
      mainBottomMenuMapHeight =
          isMainBottomMenuMap == true ? Get.height * .22 : Get.height * .6;
      isWayPointSheet = false;
      if (heightMenuBool == true) {
        getDrawerMenu();
      }
      Get.find<RideLifecycleController>().initilizeGetStorage();
      update();
    }
  }

  void downPoints() {
    if (Get.find<WayPointController>().wayPoints.length < 2) {
      isWayPointStopsSheetUtilGetMap = false;
      isWayPointSheet = false;
      wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
      update();
    }
    update();
  }

  void changeWayPointSheet() {
    isWayPointSheet = !isWayPointSheet;
    wayPointSheetHeight = isWayPointSheet == false ? 0 : Get.height * .45;
    update();
  }

  void changeWayPointStopsSheet() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.wayPointIndex > -1) {
      isWayPointStopsSheet = true;
      isWayPointStopsSheetUtilGetMap = true;
    }
    isWayPointStopsSheet = !isWayPointStopsSheet;
    wayPointSheetHeight = isWayPointStopsSheet ? Get.height * .45 : 0;
    update();
  }

  void changeHeightPlaces() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.placesDestination.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightStartPlaces() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.placesStart.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightPlacesAll(int index) {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.placeListResponseAll[index].isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightPlaces1() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.wayPoint1.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightPlaces2() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.wayPoint2.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightPlaces3() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.wayPoint3.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void changeHeightPlaces4() {
    final locationSearch = Get.find<LocationSearchController>();
    if (locationSearch.wayPoint4.isEmpty) {
      height = 0;
      update();
    } else {
      height = 150;
      update();
    }
  }

  void hidePlaces() {
    height = 0;
    update();
  }

  void changePickerShown() {
    isPickerShown = !isPickerShown;
    heightPickerContainer = isPickerShown == true ? 150 : 90;
    update();
  }

  void _initializePolygons() {
    List<List<LatLng>> universityPolygons =
        UniversitiesPolygons.universityPolygons;

    for (int i = 0; i < universityPolygons.length; i++) {
      Polygon polygon = Polygon(
        polygonId: PolygonId('univ_$i'),
        points: universityPolygons[i],
        fillColor: Colors.blueAccent.withOpacity(0.2),
        strokeColor: Colors.blueAccent,
        strokeWidth: 2,
      );
      polygons.add(polygon);
    }
    update();
  }

  void _applyLowEndModeIfNeeded() {
    // Placeholder comment from original
  }

  Future<void> _initMinimalIcons() async {
    // Icons are loaded dynamically
  }

  Future<void> _playRouteAnimation(
      List<LatLng> coords, LatLngBounds? bounds) async {
    const List<Color> segmentColors = [
      Color(0xFF109642), // Green
      Color(0xFFF59E0B), // Amber
      Color(0xFF7C3AED), // Purple
      Color(0xFFEF4444), // Red
    ];

    Set<Polyline> newPolylines = {};
    final locSearch = Get.find<LocationSearchController>();

    if (locSearch.activeMenuWaypointCount > 0) {
      List<int> splitIndices = [];
      for (int w = 0; w < locSearch.activeMenuWaypointCount; w++) {
        final wp = locSearch.menuWaypoints[w];
        if (wp == null) continue;
        int bestIdx = 0;
        double bestDist = double.infinity;
        for (int j = 0; j < coords.length; j++) {
          final dx = coords[j].latitude - wp.latitude;
          final dy = coords[j].longitude - wp.longitude;
          final d = dx * dx + dy * dy;
          if (d < bestDist) {
            bestDist = d;
            bestIdx = j;
          }
        }
        splitIndices.add(bestIdx);
      }
      splitIndices.sort();

      List<int> boundaries = [0, ...splitIndices, coords.length - 1];
      for (int s = 0; s < boundaries.length - 1; s++) {
        int from = boundaries[s];
        int to = boundaries[s + 1] + 1;
        if (to > coords.length) to = coords.length;
        if (from >= to - 1) continue;
        final segCoords = coords.sublist(from, to);
        if (segCoords.length < 2) continue;
        final color = segmentColors[s % segmentColors.length];

        newPolylines.add(Polyline(
          polylineId: PolylineId('segment_$s'),
          points: segCoords,
          color: color,
          width: 6,
        ));
      }
    } else {
      newPolylines.add(Polyline(
        polylineId: const PolylineId('route_primary'),
        points: coords,
        color: AppColor.primaryColor,
        width: 6,
      ));
    }

    polyLines = newPolylines;
    update();

    Log.print(
        '🗺️ Drawing ${markers.length} markers + ${polyLines.length} polylines on map');

    update();

    if (bounds != null) {
      await _safeAnimateCameraBounds(bounds);
    }
  }

  void reset() {
    isPickerShown = false;
    isPointsPageForRider = false;
    isBottomSheetShown = false;
    isCancelRidePageShown = false;
    isCashConfirmPageShown = false;
    isPaymentMethodPageShown = false;
    isRideFinished = false;
    rideConfirm = false;
    isMainBottomMenuMap = true;

    isWayPointSheet = false;
    isWayPointStopsSheet = false;
    isWayPointStopsSheetUtilGetMap = false;

    heightBottomSheetShown = 0;
    mainBottomMenuMapHeight = Get.height * 0.22;
    wayPointSheetHeight = 0;

    markers.clear();
    polyLines.clear();
    polylineCoordinates.clear();

    _animationTimers.forEach((key, timer) => timer.cancel());
    _animationTimers.clear();

    update();
  }

  Future<Uint8List> _createWalkMarkerBytes() async {
    final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(pictureRecorder);
    const double size = 60.0;

    final Paint paint = Paint()..color = const Color(0xFF0288D1);
    final Paint borderPaint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 3.0;

    canvas.drawCircle(const Offset(size / 2, size / 2), size / 2.5, paint);
    canvas.drawCircle(
        const Offset(size / 2, size / 2), size / 2.5, borderPaint);

    TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
    iconPainter.text = TextSpan(
      text: String.fromCharCode(Icons.directions_walk.codePoint),
      style: TextStyle(
        fontSize: 30.0,
        fontFamily: Icons.directions_walk.fontFamily,
        color: Colors.white,
      ),
    );
    iconPainter.layout();
    iconPainter.paint(
      canvas,
      Offset((size - iconPainter.width) / 2, (size - iconPainter.height) / 2),
    );

    final ui.Image image = await pictureRecorder
        .endRecording()
        .toImage(size.toInt(), size.toInt());
    final ByteData? data =
        await image.toByteData(format: ui.ImageByteFormat.png);
    return data!.buffer.asUint8List();
  }

  @override
  void onClose() {
    _animationTimers.forEach((key, timer) => timer.cancel());
    _animationTimers.clear();
    mapController = null;
    super.onClose();
  }
}
