// ignore_for_file: use_build_context_synchronously
// lib/features/navigation_mode/navigation_mode_screen.dart
//
// NavigationModeScreen — map + current location + search Nominatim + OSRM route + TTS
// Stack yang dipakai: flutter_map, latlong2, geolocator, dio, flutter_tts (via TtsService)

import 'dart:async';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';

import '../../app/injection_container.dart';
import '../../core/network/api_client.dart';
import '../../core/services/tts_service.dart';

// ─── helpers ────────────────────────────────────────────────────────────────

Dio get _api => sl<ApiClient>().dio;

// Nominatim search result
class _Place {
  final String displayName;
  final LatLng position;
  const _Place({required this.displayName, required this.position});
}

// One turn-by-turn step
class _Step {
  final String instruction;
  final double distanceM;
  final LatLng point;
  const _Step(
      {required this.instruction,
      required this.distanceM,
      required this.point});
}

// ─── BLoC-lite state (plain ChangeNotifier to avoid heavy BLoC boilerplate
//      while staying consistent with the rest of screens.dart approach) ─────

enum _NavPhase { idle, locating, routing, navigating, error }

class _NavState extends ChangeNotifier {
  _NavPhase phase = _NavPhase.idle;
  String statusText = 'Ketuk tombol lokasi atau cari tujuan.';
  LatLng? currentPosition;
  _Place? destination;
  List<LatLng> routePoints = const [];
  List<_Step> steps = const [];
  int currentStepIndex = 0;
  String? errorMessage;

  // tracking
  StreamSubscription<Position>? _posStream;

  void _set(_NavPhase p, String status) {
    phase = p;
    statusText = status;
    notifyListeners();
  }

  // ── locate ──────────────────────────────────────────────────────────────
  Future<bool> locate() async {
    _set(_NavPhase.locating, 'Mencari lokasi GPS…');
    try {
      LocationPermission perm = await Geolocator.checkPermission();
      if (perm == LocationPermission.denied) {
        perm = await Geolocator.requestPermission();
      }
      if (perm == LocationPermission.deniedForever) {
        _set(_NavPhase.error, 'Izin lokasi diblokir permanen.');
        return false;
      }
      final pos = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
      ).timeout(const Duration(seconds: 12));
      currentPosition = LatLng(pos.latitude, pos.longitude);
      _set(_NavPhase.idle, 'Lokasi ditemukan. Cari tujuan atau ketuk peta.');
      _reportToBackend(pos);
      return true;
    } on TimeoutException {
      _set(_NavPhase.error, 'GPS timeout. Pastikan GPS aktif.');
      return false;
    } catch (e) {
      _set(_NavPhase.error, 'GPS error: $e');
      return false;
    }
  }

  void _reportToBackend(Position pos) {
    _api
        .post('/user/location', data: {
          'lat': pos.latitude,
          'lng': pos.longitude,
          'accuracy': pos.accuracy,
          'speed': pos.speed,
          'heading': pos.heading,
        })
        .timeout(const Duration(seconds: 5))
        .ignore();
  }

  // ── search Nominatim ─────────────────────────────────────────────────────
  Future<List<_Place>> searchPlaces(String query) async {
    if (query.trim().isEmpty) return const [];
    try {
      final res = await Dio().get(
        'https://nominatim.openstreetmap.org/search',
        queryParameters: {
          'q': query,
          'format': 'jsonv2',
          'limit': 6,
          'addressdetails': 0,
          if (currentPosition != null) 'viewbox': _viewbox(currentPosition!),
          if (currentPosition != null) 'bounded': 0,
        },
        options: Options(
          headers: {'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'},
          receiveTimeout: const Duration(seconds: 8),
        ),
      );
      final list = res.data as List;
      return list.map((e) {
        final lat = double.tryParse(e['lat'].toString()) ?? 0;
        final lng = double.tryParse(e['lon'].toString()) ?? 0;
        return _Place(
          displayName: e['display_name'].toString(),
          position: LatLng(lat, lng),
        );
      }).toList();
    } catch (_) {
      return const [];
    }
  }

  String _viewbox(LatLng c) =>
      '${c.longitude - 0.5},${c.latitude + 0.5},${c.longitude + 0.5},${c.latitude - 0.5}';

  // ── reverse geocode ──────────────────────────────────────────────────────
  Future<String> reverseGeocode(LatLng pos) async {
    try {
      final res = await Dio().get(
        'https://nominatim.openstreetmap.org/reverse',
        queryParameters: {
          'lat': pos.latitude,
          'lon': pos.longitude,
          'format': 'jsonv2',
        },
        options: Options(
          headers: {'User-Agent': 'WalkGuide/1.0 (walkguide@campus.ac.id)'},
          receiveTimeout: const Duration(seconds: 6),
        ),
      );
      return res.data['display_name']?.toString() ??
          '${pos.latitude.toStringAsFixed(5)}, ${pos.longitude.toStringAsFixed(5)}';
    } catch (_) {
      return '${pos.latitude.toStringAsFixed(5)}, ${pos.longitude.toStringAsFixed(5)}';
    }
  }

  // ── OSRM routing ─────────────────────────────────────────────────────────
  Future<void> buildRoute(_Place dest) async {
    if (currentPosition == null) {
      await locate();
      if (currentPosition == null) return;
    }
    destination = dest;
    _set(_NavPhase.routing, 'Menghitung rute ke ${_shortName(dest)}…');

    final origin = currentPosition!;
    try {
      final url = 'http://router.project-osrm.org/route/v1/foot/'
          '${origin.longitude},${origin.latitude};'
          '${dest.position.longitude},${dest.position.latitude}'
          '?steps=true&geometries=geojson&overview=full&annotations=false';

      final res = await Dio().get(
        url,
        options: Options(receiveTimeout: const Duration(seconds: 12)),
      );

      final route = res.data['routes'][0];
      final geom = route['geometry']['coordinates'] as List;
      routePoints = geom.map((c) {
        final lst = c as List;
        return LatLng((lst[1] as num).toDouble(), (lst[0] as num).toDouble());
      }).toList();

      // parse steps
      final legs = route['legs'] as List;
      final rawSteps = <_Step>[];
      for (final leg in legs) {
        for (final step in leg['steps'] as List) {
          final maneuver = step['maneuver'] as Map;
          final instruction = _buildInstruction(maneuver, step);
          final dist = (step['distance'] as num?)?.toDouble() ?? 0;
          final loc = maneuver['location'] as List;
          rawSteps.add(_Step(
            instruction: instruction,
            distanceM: dist,
            point:
                LatLng((loc[1] as num).toDouble(), (loc[0] as num).toDouble()),
          ));
        }
      }
      steps = rawSteps;
      currentStepIndex = 0;

      _set(_NavPhase.navigating,
          steps.isNotEmpty ? steps[0].instruction : 'Ikuti rute biru.');
      notifyListeners();
      _startTracking();
    } catch (e) {
      _set(_NavPhase.error, 'Gagal mendapat rute: $e');
    }
  }

  String _shortName(_Place p) {
    final parts = p.displayName.split(',');
    return parts.first.trim();
  }

  String _buildInstruction(Map maneuver, Map step) {
    final type = maneuver['type']?.toString() ?? '';
    final modifier = maneuver['modifier']?.toString() ?? '';
    final dist = (step['distance'] as num?)?.toInt() ?? 0;
    final streetName = step['name']?.toString() ?? '';
    final distStr = dist > 0 ? ' dalam ${dist}m' : '';
    final street = streetName.isNotEmpty ? ' ke $streetName' : '';
    switch (type) {
      case 'depart':
        return 'Mulai berjalan$street.';
      case 'arrive':
        return 'Anda telah tiba di tujuan.';
      case 'turn':
        return 'Belok ${_modifier(modifier)}$distStr$street.';
      case 'continue':
        return 'Lanjutkan lurus$distStr$street.';
      case 'merge':
        return 'Bergabung ke jalan$street.';
      case 'roundabout':
        return 'Masuk bundaran$street.';
      case 'exit roundabout':
        return 'Keluar bundaran$street.';
      default:
        return 'Lanjutkan$distStr$street.';
    }
  }

  String _modifier(String m) {
    switch (m) {
      case 'left':
        return 'kiri';
      case 'right':
        return 'kanan';
      case 'slight left':
        return 'sedikit ke kiri';
      case 'slight right':
        return 'sedikit ke kanan';
      case 'sharp left':
        return 'tajam ke kiri';
      case 'sharp right':
        return 'tajam ke kanan';
      case 'uturn':
        return 'balik arah';
      default:
        return m;
    }
  }

  // ── live tracking ─────────────────────────────────────────────────────────
  void _startTracking() {
    _posStream?.cancel();
    _posStream = Geolocator.getPositionStream(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: 5,
      ),
    ).listen((pos) {
      currentPosition = LatLng(pos.latitude, pos.longitude);
      _reportToBackend(pos);
      _updateStep();
      notifyListeners();
    });
  }

  void _updateStep() {
    if (steps.isEmpty || phase != _NavPhase.navigating) return;
    if (currentStepIndex >= steps.length - 1) return;

    final current = steps[currentStepIndex];
    final dist =
        const Distance().as(LengthUnit.Meter, currentPosition!, current.point);

    // advance step when within 20 m
    if (dist < 20) {
      currentStepIndex++;
      if (currentStepIndex < steps.length) {
        final next = steps[currentStepIndex];
        statusText = next.instruction;
        sl<TtsService>().speak(next.instruction);
        notifyListeners();
      }
    }
  }

  void stopNavigation() {
    _posStream?.cancel();
    _posStream = null;
    destination = null;
    routePoints = const [];
    steps = const [];
    currentStepIndex = 0;
    _set(_NavPhase.idle, 'Navigasi dihentikan. Cari tujuan baru.');
  }

  @override
  void dispose() {
    _posStream?.cancel();
    super.dispose();
  }
}

// ─── Screen ─────────────────────────────────────────────────────────────────

class NavigationModeScreen extends StatefulWidget {
  const NavigationModeScreen({super.key});

  @override
  State<NavigationModeScreen> createState() => _NavigationModeScreenState();
}

class _NavigationModeScreenState extends State<NavigationModeScreen> {
  final _navState = _NavState();
  final _mapCtrl = MapController();
  final _searchCtrl = TextEditingController();
  final _searchFocus = FocusNode();

  List<_Place> _suggestions = const [];
  bool _searchLoading = false;
  bool _showSuggestions = false;
  Timer? _debounce;
  bool _followUser = true;

  @override
  void initState() {
    super.initState();
    _navState.addListener(_onStateChange);
    WidgetsBinding.instance.addPostFrameCallback((_) => _init());
  }

  Future<void> _init() async {
    sl<TtsService>()
        .speak('Mode navigasi. Ucapkan tujuan atau gunakan kolom pencarian.');
    await _navState.locate();
    if (_navState.currentPosition != null) {
      _mapCtrl.move(_navState.currentPosition!, 16);
    }
  }

  void _onStateChange() {
    if (!mounted) return;
    setState(() {});
    final pos = _navState.currentPosition;
    if (pos != null && _followUser) {
      _mapCtrl.move(pos, _mapCtrl.camera.zoom);
    }
  }

  Future<void> _onSearchChanged(String query) async {
    _debounce?.cancel();
    if (query.trim().isEmpty) {
      setState(() {
        _suggestions = const [];
        _showSuggestions = false;
      });
      return;
    }
    _debounce = Timer(const Duration(milliseconds: 450), () async {
      setState(() => _searchLoading = true);
      final results = await _navState.searchPlaces(query);
      if (mounted) {
        setState(() {
          _suggestions = results;
          _showSuggestions = results.isNotEmpty;
          _searchLoading = false;
        });
      }
    });
  }

  Future<void> _selectPlace(_Place place) async {
    _searchCtrl.text = place.displayName.split(',').first.trim();
    _searchFocus.unfocus();
    setState(() => _showSuggestions = false);
    _followUser = false;
    _mapCtrl.move(place.position, 15);
    await _navState.buildRoute(place);
    // TTS first instruction
    if (_navState.steps.isNotEmpty) {
      sl<TtsService>().speak(_navState.steps.first.instruction);
    }
    _followUser = true;
  }

  Future<void> _onMapTap(TapPosition _, LatLng pos) async {
    if (_navState.phase == _NavPhase.navigating) return;
    final name = await _navState.reverseGeocode(pos);
    final place = _Place(displayName: name, position: pos);
    _selectPlace(place);
  }

  void _centerOnUser() {
    final pos = _navState.currentPosition;
    if (pos == null) {
      _navState.locate();
      return;
    }
    _followUser = true;
    _mapCtrl.move(pos, 17);
  }

  void _stopNav() {
    _navState.stopNavigation();
    _searchCtrl.clear();
    setState(() => _showSuggestions = false);
    sl<TtsService>().speak('Navigasi dihentikan.');
  }

  @override
  void dispose() {
    _debounce?.cancel();
    _navState.removeListener(_onStateChange);
    _navState.dispose();
    _searchCtrl.dispose();
    _searchFocus.dispose();
    super.dispose();
  }

  // ── build ──────────────────────────────────────────────────────────────
  @override
  Widget build(BuildContext context) {
    final phase = _navState.phase;
    final pos = _navState.currentPosition;
    final isNavigating = phase == _NavPhase.navigating;

    return SafeArea(
      child: Stack(
        children: [
          // ── map ──────────────────────────────────────────────────────
          FlutterMap(
            mapController: _mapCtrl,
            options: MapOptions(
              initialCenter: pos ?? const LatLng(-7.2575, 112.7521),
              initialZoom: 15,
              onTap: _onMapTap,
              onPositionChanged: (_, hasGesture) {
                if (hasGesture) _followUser = false;
              },
            ),
            children: [
              // OSM tile layer
              TileLayer(
                urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                userAgentPackageName: 'com.walkguide.app',
              ),
              // route polyline
              if (_navState.routePoints.isNotEmpty)
                PolylineLayer(
                  polylines: [
                    Polyline(
                      points: _navState.routePoints,
                      strokeWidth: 5,
                      color: const Color(0xFF1A56DB),
                    ),
                  ],
                ),
              // markers
              MarkerLayer(
                markers: [
                  // current position – big blue pulsing dot
                  if (pos != null)
                    Marker(
                      point: pos,
                      width: 56,
                      height: 56,
                      child: const _PulsingDot(),
                    ),
                  // destination pin
                  if (_navState.destination != null)
                    Marker(
                      point: _navState.destination!.position,
                      width: 40,
                      height: 48,
                      alignment: Alignment.topCenter,
                      child: const Icon(
                        Icons.location_pin,
                        color: Color(0xFFDC2626),
                        size: 40,
                      ),
                    ),
                  // step waypoints (dimmed)
                  for (int i = 0; i < _navState.steps.length; i++)
                    Marker(
                      point: _navState.steps[i].point,
                      width: 14,
                      height: 14,
                      child: Container(
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          color: i == _navState.currentStepIndex
                              ? const Color(0xFF1A56DB)
                              : const Color(0xFF93C5FD),
                          border: Border.all(color: Colors.white, width: 2),
                        ),
                      ),
                    ),
                ],
              ),
            ],
          ),

          // ── top search panel ─────────────────────────────────────────
          Positioned(
            top: 12,
            left: 12,
            right: 12,
            child: Column(
              children: [
                // search bar
                _SearchBar(
                  controller: _searchCtrl,
                  focusNode: _searchFocus,
                  loading: _searchLoading,
                  onChanged: _onSearchChanged,
                  onClear: () {
                    _searchCtrl.clear();
                    setState(() => _showSuggestions = false);
                  },
                ),
                // suggestions dropdown
                if (_showSuggestions)
                  _SuggestionList(
                    items: _suggestions,
                    onSelect: _selectPlace,
                  ),
              ],
            ),
          ),

          // ── turn-by-turn banner (when navigating) ────────────────────
          if (isNavigating && _navState.steps.isNotEmpty)
            Positioned(
              bottom: 130,
              left: 12,
              right: 12,
              child: _TurnCard(
                step: _navState.steps[_navState.currentStepIndex],
                stepIndex: _navState.currentStepIndex,
                totalSteps: _navState.steps.length,
                onRepeat: () => sl<TtsService>().speak(
                    _navState.steps[_navState.currentStepIndex].instruction),
              ),
            ),

          // ── bottom status bar ────────────────────────────────────────
          Positioned(
            bottom: 16,
            left: 12,
            right: 12,
            child: _StatusBar(
              phase: phase,
              text: _navState.statusText,
              isNavigating: isNavigating,
              onStop: _stopNav,
            ),
          ),

          // ── FAB: center on user ──────────────────────────────────────
          Positioned(
            right: 16,
            bottom: isNavigating ? 196 : 80,
            child: FloatingActionButton.small(
              heroTag: 'nav_center',
              backgroundColor: Colors.white,
              foregroundColor: const Color(0xFF1A56DB),
              onPressed: _centerOnUser,
              child: const Icon(Icons.my_location),
            ),
          ),

          // ── loading overlay ──────────────────────────────────────────
          if (phase == _NavPhase.locating || phase == _NavPhase.routing)
            Positioned.fill(
              child: Container(
                color: Colors.black26,
                child: const Center(child: CircularProgressIndicator()),
              ),
            ),
        ],
      ),
    );
  }
}

// ─── Sub-widgets ─────────────────────────────────────────────────────────────

class _SearchBar extends StatelessWidget {
  final TextEditingController controller;
  final FocusNode focusNode;
  final bool loading;
  final ValueChanged<String> onChanged;
  final VoidCallback onClear;

  const _SearchBar({
    required this.controller,
    required this.focusNode,
    required this.loading,
    required this.onChanged,
    required this.onClear,
  });

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 4,
      borderRadius: BorderRadius.circular(14),
      child: TextField(
        controller: controller,
        focusNode: focusNode,
        onChanged: onChanged,
        textInputAction: TextInputAction.search,
        decoration: InputDecoration(
          hintText: 'Cari tujuan…',
          prefixIcon: const Icon(Icons.search, color: Color(0xFF1A56DB)),
          suffixIcon: loading
              ? const Padding(
                  padding: EdgeInsets.all(12),
                  child: SizedBox(
                    width: 16,
                    height: 16,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  ),
                )
              : controller.text.isNotEmpty
                  ? IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: onClear,
                    )
                  : null,
          border: OutlineInputBorder(
            borderRadius: BorderRadius.circular(14),
            borderSide: BorderSide.none,
          ),
          filled: true,
          fillColor: Colors.white,
          contentPadding:
              const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        ),
      ),
    );
  }
}

class _SuggestionList extends StatelessWidget {
  final List<_Place> items;
  final ValueChanged<_Place> onSelect;

  const _SuggestionList({required this.items, required this.onSelect});

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 6,
      borderRadius: const BorderRadius.vertical(bottom: Radius.circular(14)),
      child: ClipRRect(
        borderRadius: const BorderRadius.vertical(bottom: Radius.circular(14)),
        child: Column(
          children: [
            for (final place in items)
              InkWell(
                onTap: () => onSelect(place),
                child: Padding(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                  child: Row(
                    children: [
                      const Icon(Icons.place_outlined,
                          color: Color(0xFF64748B), size: 20),
                      const SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          place.displayName,
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                          style: const TextStyle(fontSize: 14),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

class _TurnCard extends StatelessWidget {
  final _Step step;
  final int stepIndex;
  final int totalSteps;
  final VoidCallback onRepeat;

  const _TurnCard({
    required this.step,
    required this.stepIndex,
    required this.totalSteps,
    required this.onRepeat,
  });

  IconData _directionIcon(String instruction) {
    final lower = instruction.toLowerCase();
    if (lower.contains('kiri')) return Icons.turn_left;
    if (lower.contains('kanan')) return Icons.turn_right;
    if (lower.contains('balik')) return Icons.u_turn_left;
    if (lower.contains('bundaran')) return Icons.roundabout_right;
    if (lower.contains('tiba')) return Icons.flag;
    return Icons.straight;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(14),
      decoration: BoxDecoration(
        color: const Color(0xFF1A56DB),
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.18),
            blurRadius: 12,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Row(
        children: [
          Icon(_directionIcon(step.instruction), color: Colors.white, size: 40),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  step.instruction,
                  style: const TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.w700),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 4),
                Text(
                  'Langkah ${stepIndex + 1} dari $totalSteps'
                  '${step.distanceM > 0 ? ' · ${step.distanceM.toInt()} m' : ''}',
                  style:
                      const TextStyle(color: Color(0xFFBFDBFE), fontSize: 12),
                ),
              ],
            ),
          ),
          IconButton(
            onPressed: onRepeat,
            icon: const Icon(Icons.volume_up, color: Colors.white),
            tooltip: 'Ulangi instruksi',
          ),
        ],
      ),
    );
  }
}

class _StatusBar extends StatelessWidget {
  final _NavPhase phase;
  final String text;
  final bool isNavigating;
  final VoidCallback onStop;

  const _StatusBar({
    required this.phase,
    required this.text,
    required this.isNavigating,
    required this.onStop,
  });

  Color get _bgColor {
    switch (phase) {
      case _NavPhase.error:
        return const Color(0xFFFEF2F2);
      case _NavPhase.navigating:
        return const Color(0xFFF0FDF4);
      default:
        return Colors.white;
    }
  }

  Color get _textColor {
    switch (phase) {
      case _NavPhase.error:
        return const Color(0xFF991B1B);
      case _NavPhase.navigating:
        return const Color(0xFF166534);
      default:
        return const Color(0xFF0F172A);
    }
  }

  IconData get _icon {
    switch (phase) {
      case _NavPhase.error:
        return Icons.error_outline;
      case _NavPhase.navigating:
        return Icons.navigation;
      case _NavPhase.locating:
        return Icons.gps_fixed;
      default:
        return Icons.map;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
      decoration: BoxDecoration(
        color: _bgColor,
        borderRadius: BorderRadius.circular(14),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: 0.1),
            blurRadius: 10,
          ),
        ],
        border: Border.all(
          color: phase == _NavPhase.error
              ? const Color(0xFFFECACA)
              : const Color(0xFFE2E8F0),
        ),
      ),
      child: Row(
        children: [
          Icon(_icon, color: _textColor, size: 20),
          const SizedBox(width: 10),
          Expanded(
            child: Text(
              text,
              style: TextStyle(color: _textColor, fontWeight: FontWeight.w600),
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
          ),
          if (isNavigating) ...[
            const SizedBox(width: 8),
            TextButton.icon(
              onPressed: onStop,
              icon: const Icon(Icons.stop_circle_outlined,
                  color: Color(0xFFDC2626), size: 18),
              label: const Text('Stop',
                  style: TextStyle(color: Color(0xFFDC2626))),
              style: TextButton.styleFrom(
                  padding: const EdgeInsets.symmetric(horizontal: 8)),
            ),
          ],
        ],
      ),
    );
  }
}

/// Animated blue dot for current position
class _PulsingDot extends StatefulWidget {
  const _PulsingDot();

  @override
  State<_PulsingDot> createState() => _PulsingDotState();
}

class _PulsingDotState extends State<_PulsingDot>
    with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;
  late Animation<double> _scale;
  late Animation<double> _opacity;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 1400))
      ..repeat(reverse: false);
    _scale = Tween(begin: 0.6, end: 1.4)
        .animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
    _opacity = Tween(begin: 0.6, end: 0.0)
        .animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
  }

  @override
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _ctrl,
      builder: (_, __) => Stack(
        alignment: Alignment.center,
        children: [
          // pulse ring
          Transform.scale(
            scale: _scale.value,
            child: Container(
              width: 44,
              height: 44,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: const Color(0xFF1A56DB)
                    .withValues(alpha: _opacity.value * 0.4),
                border: Border.all(
                  color:
                      const Color(0xFF1A56DB).withValues(alpha: _opacity.value),
                  width: 2,
                ),
              ),
            ),
          ),
          // solid dot
          Container(
            width: 20,
            height: 20,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: const Color(0xFF1A56DB),
              border: Border.all(color: Colors.white, width: 3),
              boxShadow: [
                BoxShadow(
                  color: const Color(0xFF1A56DB).withValues(alpha: 0.4),
                  blurRadius: 6,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
