// ignore_for_file: use_build_context_synchronously, deprecated_member_use

import 'dart:async';
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:go_router/go_router.dart';
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../app/injection_container.dart';
import '../app/app_cubit.dart';
import '../core/ai/detection_export.dart';
import '../core/constants/app_constants.dart';
import '../core/network/api_client.dart';
import '../core/services/call_service.dart';
import '../core/services/haptic_service.dart';
import '../core/services/location_reporter_service.dart';
import '../core/services/offline_queue_service.dart';
import '../core/services/tts_service.dart';
import '../core/services/websocket_service.dart';
import '../core/storage/secure_storage.dart';

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

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

  @override
  State<ServerConnectScreen> createState() => _ServerConnectScreenState();
}

class _ServerConnectScreenState extends State<ServerConnectScreen> {
  final _url = TextEditingController(text: 'http://202.46.28.160:8080');
  bool _loading = false;
  bool _ok = false;
  String? _message;

  Future<void> _test() async {
    setState(() {
      _loading = true;
      _ok = false;
      _message = null;
    });
    try {
      final clean = AppConstants.normalizeServerUrl(_url.text);
      final res = await Dio(BaseOptions(
        connectTimeout: AppConstants.pingTimeout,
        receiveTimeout: AppConstants.pingTimeout,
      )).get('$clean/api/v1/auth/ping');
      _ok = res.statusCode == 200 && res.data['success'] == true;
      _message = _ok
          ? 'Server aktif dan siap dipakai.'
          : 'Server merespons dengan format tidak valid.';
    } catch (e) {
      _message = 'Tidak bisa terhubung. Periksa URL dan jaringan.';
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _continue() async {
    final clean = AppConstants.normalizeServerUrl(_url.text);
    await AppConstants.setServerUrl(clean);
    await sl<ApiClient>().init(clean);
    if (mounted) context.go('/splash');
  }

  @override
  Widget build(BuildContext context) {
    return _AuthFrame(
      title: 'Connect to Server',
      subtitle: 'Masukkan URL backend WalkGuide yang diberikan dosen.',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          TextField(
              controller: _url,
              keyboardType: TextInputType.url,
              decoration: const InputDecoration(labelText: 'Server URL')),
          const SizedBox(height: 12),
          OutlinedButton.icon(
            onPressed: _loading ? null : _test,
            icon: _loading
                ? const SizedBox(
                    width: 18,
                    height: 18,
                    child: CircularProgressIndicator(strokeWidth: 2))
                : const Icon(Icons.wifi_tethering),
            label: const Text('Test Connection'),
          ),
          if (_message != null) ...[
            const SizedBox(height: 12),
            _StatusBox(success: _ok, message: _message!),
          ],
          if (_ok) ...[
            const SizedBox(height: 12),
            FilledButton.icon(
                onPressed: _continue,
                icon: const Icon(Icons.arrow_forward),
                label: const Text('Continue')),
          ],
        ],
      ),
    );
  }
}

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

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    _route();
  }

  Future<void> _route() async {
    try {
      await Future.delayed(const Duration(milliseconds: 500));
      final storage = sl<SecureStorage>();
      final token =
          await storage.getAccessToken().timeout(const Duration(seconds: 3));
      final role =
          await storage.getUserRole().timeout(const Duration(seconds: 3));
      if (!mounted) return;
      if (token == null || role == null) {
        context.go('/login');
      } else {
        context.go(role == 'ROLE_GUARDIAN'
            ? '/guardian/dashboard'
            : '/user/walkguide');
      }
    } catch (_) {
      if (mounted) context.go('/login');
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      backgroundColor: Color(0xFF1A56DB),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.navigation_rounded, color: Colors.white, size: 72),
            SizedBox(height: 18),
            Text('WalkGuide',
                style: TextStyle(
                    color: Colors.white,
                    fontSize: 34,
                    fontWeight: FontWeight.w800)),
            SizedBox(height: 32),
            CircularProgressIndicator(color: Colors.white),
          ],
        ),
      ),
    );
  }
}

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

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _email = TextEditingController();
  final _password = TextEditingController();
  bool _loading = false;

  @override
  void initState() {
    super.initState();
    _loadPendingLoginEmail();
  }

  Future<void> _loadPendingLoginEmail() async {
    final prefs = await SharedPreferences.getInstance();
    final pendingEmail = prefs.getString('pending_login_email');
    if (!mounted) return;
    setState(() {
      if (pendingEmail != null && pendingEmail.isNotEmpty) {
        _email.text = pendingEmail;
      }
    });
    await prefs.remove('pending_login_email');
  }

  Future<void> _login() async {
    if (_email.text.trim().isEmpty || _password.text.isEmpty) {
      _snack(context, 'Isi email dan password dulu.');
      return;
    }
    setState(() => _loading = true);
    try {
      final res = await _api.post('/auth/login', data: {
        'email': _email.text.trim(),
        'password': _password.text,
      });
      await _saveAuthAndRoute(
          context, Map<String, dynamic>.from(res.data['data'] as Map));
    } on DioException catch (e) {
      _snack(context, e.response?.data['message'] ?? 'Login gagal');
    } catch (e) {
      _snack(context, 'Login gagal: $e');
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _AuthFrame(
      title: 'Sign in',
      subtitle: 'Masuk sebagai Guardian atau User.',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          TextField(
              controller: _email,
              decoration: const InputDecoration(labelText: 'Email')),
          const SizedBox(height: 12),
          TextField(
              controller: _password,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password')),
          const SizedBox(height: 18),
          FilledButton.icon(
            onPressed: _loading ? null : _login,
            icon: _loading
                ? const SizedBox(
                    width: 18,
                    height: 18,
                    child: CircularProgressIndicator(strokeWidth: 2))
                : const Icon(Icons.login),
            label: const Text('Login'),
          ),
          TextButton(
              onPressed: () => context.go('/register'),
              child: const Text('Buat akun baru')),
        ],
      ),
    );
  }
}

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

  @override
  State<RegisterScreen> createState() => _RegisterScreenState();
}

class _RegisterScreenState extends State<RegisterScreen> {
  final _name = TextEditingController();
  final _email = TextEditingController();
  final _password = TextEditingController();
  String _role = 'USER';
  bool _loading = false;

  Future<void> _register() async {
    setState(() => _loading = true);
    try {
      final res = await _api.post('/auth/register', data: {
        'displayName': _name.text.trim(),
        'email': _email.text.trim(),
        'password': _password.text,
        'role': _role,
      });
      final data = Map<String, dynamic>.from(res.data['data'] as Map);
      if (!mounted) return;
      final prefs = await SharedPreferences.getInstance();
      await prefs.setString('pending_login_email', _email.text.trim());
      await _showRegisterSuccess(context, data);
      if (mounted) context.go('/login');
    } on DioException catch (e) {
      _snack(context, e.response?.data['message'] ?? 'Registrasi gagal');
    } catch (e) {
      _snack(context, 'Registrasi gagal: $e');
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _AuthFrame(
      title: 'Create Account',
      subtitle: 'User akan mendapat Unique ID untuk pairing.',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          SegmentedButton<String>(
            segments: const [
              ButtonSegment(value: 'USER', label: Text('User')),
              ButtonSegment(value: 'GUARDIAN', label: Text('Guardian')),
            ],
            selected: {_role},
            onSelectionChanged: (value) => setState(() => _role = value.first),
          ),
          const SizedBox(height: 16),
          TextField(
              controller: _name,
              decoration: const InputDecoration(labelText: 'Display name')),
          const SizedBox(height: 12),
          TextField(
              controller: _email,
              decoration: const InputDecoration(labelText: 'Email')),
          const SizedBox(height: 12),
          TextField(
              controller: _password,
              obscureText: true,
              decoration: const InputDecoration(labelText: 'Password')),
          const SizedBox(height: 18),
          FilledButton.icon(
            onPressed: _loading ? null : _register,
            icon: const Icon(Icons.person_add_alt_1),
            label: const Text('Register'),
          ),
          TextButton(
              onPressed: () => context.go('/login'),
              child: const Text('Sudah punya akun')),
        ],
      ),
    );
  }
}

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

  @override
  State<WalkGuideScreen> createState() => _WalkGuideScreenState();
}

class _WalkGuideScreenState extends State<WalkGuideScreen> {
  bool _active = false;
  String _status = 'Ready';
  CameraController? _camera;
  DetectionResult? _lastDetection;

  @override
  void dispose() {
    _camera?.dispose();
    sl<LocationReporterService>().stop();
    super.dispose();
  }

  Future<void> _toggle() async {
    final next = !_active;
    if (next) {
      await _startCamera();
      await sl<LocationReporterService>().start(walkGuideActive: true);
    } else {
      await _camera?.dispose();
      _camera = null;
      await sl<LocationReporterService>().stop();
    }
    setState(() {
      _active = next;
      _status = next ? 'Camera stream active. YOLO ready.' : 'Stopped';
    });
    await _api.post(next ? '/user/walkguide/start' : '/user/walkguide/stop');
    sl<TtsService>().speak(next ? 'WalkGuide dimulai' : 'WalkGuide dihentikan');
  }

  Future<void> _startCamera() async {
    if (_camera != null) return;
    try {
      final cameras = await availableCameras();
      if (cameras.isEmpty) return;
      final controller = CameraController(
          cameras.first, ResolutionPreset.medium,
          enableAudio: false);
      await controller.initialize();
      if (!mounted) {
        await controller.dispose();
        return;
      }
      setState(() => _camera = controller);
    } catch (_) {
      setState(() => _status = 'Camera unavailable. Demo mode active.');
    }
  }

  Future<void> _simulateObstacle() async {
    final detection = await sl<YoloDetector>().detectFallback();
    if (detection == null) return;
    _lastDetection = detection;
    await _api.post('/user/obstacle', data: {
      'label': detection.label,
      'confidence': detection.confidence,
      'direction': detection.directionName,
      'estimatedDist': detection.estimatedDistance,
      'lat': null,
      'lng': null,
    });
    await sl<HapticService>().obstacleClose();
    await sl<TtsService>().speakImmediate(detection.spokenId);
    setState(() => _status =
        'Obstacle: ${detection.label} ${detection.directionName} ${detection.estimatedDistance}');
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'WalkGuide',
      subtitle: 'On-device AI detection surface',
      actions: [
        IconButton(
            onPressed: () => context.go('/user/benchmark'),
            icon: const Icon(Icons.speed)),
        IconButton(
            onPressed: () => context.go('/user/pairing'),
            icon: const Icon(Icons.link)),
      ],
      child: Column(
        children: [
          Expanded(
            child: Container(
              width: double.infinity,
              decoration: BoxDecoration(
                  color: const Color(0xFF0F172A),
                  borderRadius: BorderRadius.circular(16)),
              child: Stack(
                children: [
                  if (_camera != null && _camera!.value.isInitialized)
                    Positioned.fill(child: CameraPreview(_camera!))
                  else
                    const Center(
                        child: Icon(Icons.videocam_outlined,
                            color: Colors.white30, size: 96)),
                  Positioned(
                      top: 16,
                      left: 16,
                      child: _Pill(
                          text: _active ? 'AI ACTIVE' : 'STANDBY',
                          color: _active ? Colors.green : Colors.orange)),
                  if (_lastDetection != null)
                    Positioned(
                      top: 64,
                      left: 16,
                      child: _Pill(
                          text:
                              '${_lastDetection!.label} ${_lastDetection!.directionName}',
                          color: Colors.redAccent),
                    ),
                  Positioned(
                      left: 16,
                      right: 16,
                      bottom: 16,
                      child: Text(_status,
                          style: const TextStyle(
                              color: Colors.white,
                              fontSize: 18,
                              fontWeight: FontWeight.w700))),
                ],
              ),
            ),
          ),
          const SizedBox(height: 14),
          Row(
            children: [
              Expanded(
                  child: FilledButton.icon(
                      onPressed: _toggle,
                      icon: Icon(_active ? Icons.stop : Icons.play_arrow),
                      label: Text(_active ? 'Stop' : 'Start'))),
              const SizedBox(width: 10),
              Expanded(
                  child: OutlinedButton.icon(
                      onPressed: _simulateObstacle,
                      icon: const Icon(Icons.radar),
                      label: const Text('Demo Detect'))),
            ],
          ),
        ],
      ),
    );
  }
}

class SosScreen extends StatelessWidget {
  const SosScreen({super.key});

  Future<void> _sendSos() async {
    Position? pos;
    try {
      await Geolocator.requestPermission();
      pos = await Geolocator.getCurrentPosition();
    } catch (_) {}
    await _api.post('/user/sos', data: {
      'triggerType': 'MANUAL',
      'lat': pos?.latitude,
      'lng': pos?.longitude,
    });
    await sl<HapticService>().sosTriggered();
    sl<TtsService>().speak('SOS terkirim ke Guardian.');
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'SOS',
      subtitle: 'Emergency alert with location',
      child: Center(
        child: SizedBox.square(
          dimension: 220,
          child: FilledButton(
            style: FilledButton.styleFrom(
                shape: const CircleBorder(),
                backgroundColor: const Color(0xFFDC2626)),
            onPressed: _sendSos,
            child: const Text('SOS',
                style: TextStyle(fontSize: 42, fontWeight: FontWeight.w900)),
          ),
        ),
      ),
    );
  }
}

class ActivityLogScreen extends StatelessWidget {
  const ActivityLogScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'Activity Logs', endpoint: '/user/activity-logs');
}

class NotificationScreen extends StatelessWidget {
  const NotificationScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'Notifications', endpoint: '/user/notifications');
}

class NavigationModeScreen extends StatelessWidget {
  const NavigationModeScreen({super.key});
  @override
  Widget build(BuildContext context) => const _MapScreen(
        title: 'Navigation',
        subtitle: 'Current position and OSM map',
      );
}

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

  @override
  State<UserSettingsScreen> createState() => _UserSettingsScreenState();
}

class _UserSettingsScreenState extends State<UserSettingsScreen> {
  bool _haptic = true;
  String _language = 'id-ID';

  Future<void> _save() async {
    await sl<TtsService>().setLanguage(_language);
    _snack(context, 'Settings tersimpan di perangkat.');
    try {
      await _api.put('/user/settings', data: {
        'ttsLanguage': _language,
        'hapticEnabled': _haptic,
        'ttsPitch': 1.0,
        'ttsSpeed': 0.9,
        'warnNoGuardian': true,
      }).timeout(const Duration(seconds: 8));
    } catch (e) {
      _snack(context,
          'Server belum menerima settings, tapi pilihan lokal sudah dipakai.');
    }
  }

  Future<void> _logout() async {
    await sl<SecureStorage>().clearAll();
    context.read<AppCubit>().clearSession();
    unawaited(_ignoreFailure(
        _api.post('/auth/logout').timeout(const Duration(seconds: 3))));
    if (mounted) context.go('/login');
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'Settings',
      subtitle: 'TTS, haptic, account, and server',
      child: ListView(
        children: [
          DropdownButtonFormField<String>(
            value: _language,
            decoration: const InputDecoration(labelText: 'TTS language'),
            items: const [
              DropdownMenuItem(value: 'id-ID', child: Text('Bahasa Indonesia')),
              DropdownMenuItem(value: 'en-US', child: Text('English')),
            ],
            onChanged: (value) =>
                setState(() => _language = value ?? _language),
          ),
          const SizedBox(height: 12),
          SwitchListTile(
            value: _haptic,
            onChanged: (value) => setState(() => _haptic = value),
            title: const Text('Haptic obstacle alert'),
          ),
          const SizedBox(height: 12),
          FilledButton.icon(
              onPressed: _save,
              icon: const Icon(Icons.save),
              label: const Text('Save settings')),
          const SizedBox(height: 8),
          OutlinedButton.icon(
            onPressed: () async {
              await AppConstants.clearServerUrl();
              if (context.mounted) context.go('/server-connect');
            },
            icon: const Icon(Icons.dns_outlined),
            label: const Text('Change server'),
          ),
          OutlinedButton.icon(
              onPressed: _logout,
              icon: const Icon(Icons.logout),
              label: const Text('Logout')),
        ],
      ),
    );
  }
}

class GuardianSettingsScreen extends StatelessWidget {
  const GuardianSettingsScreen({super.key});

  Future<void> _logout(BuildContext context) async {
    await sl<SecureStorage>().clearAll();
    context.read<AppCubit>().clearSession();
    unawaited(_ignoreFailure(
        _api.post('/auth/logout').timeout(const Duration(seconds: 3))));
    if (context.mounted) context.go('/login');
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'Guardian Settings',
      subtitle: 'Account, server, pairing, and tools',
      child: ListView(
        children: [
          ListTile(
            leading: const Icon(Icons.link),
            title: const Text('Pair User'),
            subtitle:
                const Text('Masukkan Unique ID User atau cek status pairing.'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () => context.go('/guardian/pairing'),
          ),
          ListTile(
            leading: const Icon(Icons.speed),
            title: const Text('AI Benchmark'),
            subtitle: const Text(
                'Catat waktu capture, model, notification text, dan TTS.'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () => context.go('/guardian/benchmark'),
          ),
          ListTile(
            leading: const Icon(Icons.tune),
            title: const Text('AI Config'),
            subtitle: const Text(
                'Buka konfigurasi AI untuk User yang sudah pairing.'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () => context.go('/guardian/ai-config'),
          ),
          const Divider(height: 28),
          OutlinedButton.icon(
            onPressed: () async {
              await AppConstants.clearServerUrl();
              await sl<SecureStorage>().clearAll();
              if (context.mounted) context.go('/server-connect');
            },
            icon: const Icon(Icons.dns_outlined),
            label: const Text('Change server'),
          ),
          const SizedBox(height: 8),
          FilledButton.icon(
            style: FilledButton.styleFrom(
                backgroundColor: const Color(0xFFDC2626)),
            onPressed: () => _logout(context),
            icon: const Icon(Icons.logout),
            label: const Text('Logout'),
          ),
        ],
      ),
    );
  }
}

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

  @override
  State<UserPairingScreen> createState() => _UserPairingScreenState();
}

class _UserPairingScreenState extends State<UserPairingScreen> {
  String? _uniqueId;

  @override
  void initState() {
    super.initState();
    _loadUniqueId();
  }

  Future<void> _loadUniqueId() async {
    var value = await sl<SecureStorage>().getUniqueUserId();
    if (value == null || value.isEmpty) {
      try {
        final res =
            await _api.get('/user/profile').timeout(const Duration(seconds: 5));
        final data = res.data['data'];
        if (data is Map) value = data['uniqueUserId']?.toString();
      } catch (_) {}
    }
    if (mounted) setState(() => _uniqueId = value);
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'Pairing',
      subtitle: 'Bagikan Unique ID ini ke Guardian untuk terhubung.',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          if (_uniqueId == null || _uniqueId!.isEmpty)
            const _InfoCard(
                title: 'Your Unique ID',
                value: 'Login sebagai User untuk melihat ID',
                icon: Icons.qr_code_2)
          else
            _InfoCard(
                title: 'Your Unique ID',
                value: _uniqueId!,
                icon: Icons.qr_code_2),
          const SizedBox(height: 16),
          const _PairingStatusCard(allowUserResponse: true),
        ],
      ),
    );
  }
}

class CallScreen extends StatelessWidget {
  const CallScreen({super.key});
  @override
  Widget build(BuildContext context) =>
      const _CallPanel(title: 'Call Guardian', channelName: 'walkguide-call');
}

class IncomingCallScreen extends StatelessWidget {
  const IncomingCallScreen({super.key});
  @override
  Widget build(BuildContext context) => const _PlaceholderScreen(
      title: 'Incoming Call',
      icon: Icons.call_received,
      text: 'Accept or reject incoming guardian calls here.');
}

class GuardianDashboardScreen extends StatelessWidget {
  const GuardianDashboardScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'Guardian Dashboard', endpoint: '/guardian/dashboard');
}

class GuardianMapScreen extends StatelessWidget {
  const GuardianMapScreen({super.key});
  @override
  Widget build(BuildContext context) => const _GuardianMapHistoryScreen();
}

class GuardianActivityLogScreen extends StatelessWidget {
  const GuardianActivityLogScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'User Logs', endpoint: '/guardian/activity-logs');
}

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

  @override
  State<GuardianSendNotifScreen> createState() =>
      _GuardianSendNotifScreenState();
}

class _GuardianSendNotifScreenState extends State<GuardianSendNotifScreen> {
  final _message = TextEditingController();
  bool _loading = false;

  Future<void> _send() async {
    setState(() => _loading = true);
    try {
      await _api.post('/guardian/notifications/send',
          data: {'notifType': 'TEXT', 'content': _message.text.trim()});
      _message.clear();
      _snack(context, 'Notifikasi terkirim');
    } on DioException catch (e) {
      _snack(context, e.response?.data['message'] ?? 'Gagal mengirim');
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'Send Notification',
      subtitle: 'Text message to paired User',
      child: Column(
        children: [
          TextField(
              controller: _message,
              minLines: 4,
              maxLines: 6,
              decoration: const InputDecoration(labelText: 'Message')),
          const SizedBox(height: 12),
          SizedBox(
              width: double.infinity,
              child: FilledButton.icon(
                  onPressed: _loading ? null : _send,
                  icon: const Icon(Icons.send),
                  label: const Text('Send'))),
        ],
      ),
    );
  }
}

class GuardianAiConfigScreen extends StatelessWidget {
  const GuardianAiConfigScreen({super.key});
  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'AI Config',
      subtitle: '/guardian/ai-config',
      actions: [
        IconButton(
            onPressed: () => context.go('/guardian/benchmark'),
            icon: const Icon(Icons.speed))
      ],
      child: const _EndpointList(endpoint: '/guardian/ai-config'),
    );
  }
}

class GuardianVoiceCmdScreen extends StatelessWidget {
  const GuardianVoiceCmdScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'Voice Commands', endpoint: '/guardian/voice-commands');
}

class GuardianShortcutScreen extends StatelessWidget {
  const GuardianShortcutScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointListScreen(
      title: 'Hardware Shortcuts', endpoint: '/guardian/shortcuts');
}

class GuardianGeofenceScreen extends StatelessWidget {
  const GuardianGeofenceScreen({super.key});
  @override
  Widget build(BuildContext context) => const _EndpointActionScreen(
      title: 'Geofence', endpoint: '/guardian/geofence');
}

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

  @override
  State<GuardianPairingScreen> createState() => _GuardianPairingScreenState();
}

class _GuardianPairingScreenState extends State<GuardianPairingScreen> {
  final _id = TextEditingController();
  bool _loading = false;
  int _statusReload = 0;

  Future<void> _invite() async {
    final uniqueId = _id.text.trim().toUpperCase();
    if (uniqueId.isEmpty || uniqueId.length != 12) {
      _snack(context, 'Unique ID harus 12 karakter dari akun User.');
      return;
    }
    setState(() => _loading = true);
    try {
      final res = await _api.post('/shared/pairing/invite',
          data: {'uniqueUserId': uniqueId}).timeout(const Duration(seconds: 8));
      _snack(
          context,
          res.data['message']?.toString() ??
              'Invite terkirim. Minta User buka menu Pairing lalu Accept.');
      setState(() => _statusReload++);
    } on DioException catch (e) {
      _snack(
          context,
          _friendlyDioMessage(e,
              fallback:
                  'Invite gagal. Pastikan kamu login sebagai Guardian dan ID User benar.'));
    } on TimeoutException {
      _snack(context,
          'Server terlalu lama merespons invite. Coba Refresh status, jangan klik berkali-kali.');
    } catch (e) {
      _snack(context, 'Invite gagal: $e');
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: 'Pair User',
      subtitle: 'Masukkan 12 karakter Unique ID milik User.',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          TextField(
              controller: _id,
              textCapitalization: TextCapitalization.characters,
              maxLength: 12,
              decoration: const InputDecoration(labelText: 'Unique User ID')),
          FilledButton.icon(
            onPressed: _loading ? null : _invite,
            icon: _loading
                ? const SizedBox(
                    width: 18,
                    height: 18,
                    child: CircularProgressIndicator(strokeWidth: 2))
                : const Icon(Icons.link),
            label: Text(_loading ? 'Sending...' : 'Send Invite'),
          ),
          const SizedBox(height: 20),
          _PairingStatusCard(key: ValueKey(_statusReload)),
        ],
      ),
    );
  }
}

class _GuardianMapHistoryScreen extends StatelessWidget {
  const _GuardianMapHistoryScreen();

  @override
  Widget build(BuildContext context) {
    return const _Page(
      title: 'Live Map',
      subtitle: 'Paired User location and timeline',
      child: Column(
        children: [
          Expanded(
            flex: 3,
            child: _MapScreenBody(guardianEndpoint: '/guardian/user-location'),
          ),
          SizedBox(height: 12),
          Expanded(flex: 2, child: _LocationTimeline()),
        ],
      ),
    );
  }
}

class _LocationTimeline extends StatefulWidget {
  const _LocationTimeline();

  @override
  State<_LocationTimeline> createState() => _LocationTimelineState();
}

class _LocationTimelineState extends State<_LocationTimeline> {
  bool _loading = true;
  String? _error;
  List<Map<String, dynamic>> _items = const [];

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final paired = await _hasActivePairing();
      if (!paired) {
        _items = const [];
        _error =
            'Belum pairing. Timeline lokasi akan muncul setelah Guardian terhubung dengan User.';
        return;
      }
      final res = await _api.get('/guardian/location-history',
          queryParameters: {'size': 80}).timeout(const Duration(seconds: 8));
      final data = res.data['data'];
      final content = data is Map ? data['content'] : null;
      _items = content is List
          ? content
              .whereType<Map>()
              .map((e) => Map<String, dynamic>.from(e))
              .toList()
          : const [];
    } on DioException catch (e) {
      _error = _friendlyDioMessage(e,
          fallback: 'Timeline lokasi belum bisa dimuat.');
    } catch (e) {
      _error = 'Timeline lokasi belum bisa dimuat: $e';
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) return const Center(child: CircularProgressIndicator());
    if (_error != null) {
      return _ErrorPanel(message: _error!);
    }
    if (_items.isEmpty) {
      return _EmptyPanel(
        icon: Icons.timeline,
        title: 'Belum Ada Timeline',
        message:
            'Mulai WalkGuide atau buka Map di akun User supaya titik lokasi tersimpan.',
        action: OutlinedButton.icon(
            onPressed: _load,
            icon: const Icon(Icons.refresh),
            label: const Text('Refresh')),
      );
    }

    final grouped = <String, List<Map<String, dynamic>>>{};
    for (final item in _items) {
      final created =
          DateTime.tryParse(item['createdAt']?.toString() ?? '')?.toLocal();
      final key = created == null ? 'Unknown time' : '${_two(created.hour)}:00';
      grouped.putIfAbsent(key, () => []).add(item);
    }

    return Container(
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
          border: Border.all(color: const Color(0xFFE2E8F0))),
      child: ListView(
        padding: const EdgeInsets.all(12),
        children: [
          Row(
            children: [
              const Expanded(
                  child: Text('Timeline Lokasi',
                      style: TextStyle(
                          fontWeight: FontWeight.w800, fontSize: 16))),
              IconButton(onPressed: _load, icon: const Icon(Icons.refresh)),
            ],
          ),
          for (final entry in grouped.entries) ...[
            Padding(
              padding: const EdgeInsets.only(top: 8, bottom: 6),
              child: Text(entry.key,
                  style: const TextStyle(
                      fontWeight: FontWeight.w800, color: Color(0xFF1A56DB))),
            ),
            for (final item in entry.value) _TimelineTile(data: item),
          ],
        ],
      ),
    );
  }
}

class _TimelineTile extends StatelessWidget {
  final Map<String, dynamic> data;
  const _TimelineTile({required this.data});

  @override
  Widget build(BuildContext context) {
    final created =
        DateTime.tryParse(data['createdAt']?.toString() ?? '')?.toLocal();
    final speed = double.tryParse(data['speed']?.toString() ?? '') ?? 0;
    final mode = speed > 6
        ? 'Kendaraan'
        : speed > 1.2
            ? 'Jalan cepat'
            : 'Jalan kaki / diam';
    final lat = _formatCoord(data['lat']);
    final lng = _formatCoord(data['lng']);
    return ListTile(
      dense: true,
      contentPadding: EdgeInsets.zero,
      leading: Icon(speed > 6 ? Icons.two_wheeler : Icons.directions_walk,
          color: const Color(0xFF1A56DB)),
      title: Text(
          '${created == null ? '--:--' : '${_two(created.hour)}:${_two(created.minute)}'}  $mode'),
      subtitle:
          Text('Lat $lat, Lng $lng, speed ${speed.toStringAsFixed(1)} m/s'),
    );
  }
}

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

  @override
  State<AiBenchmarkScreen> createState() => _AiBenchmarkScreenState();
}

class _AiBenchmarkScreenState extends State<AiBenchmarkScreen> {
  static const _runsKey = 'ai_benchmark_runs';
  List<String> _models = const [];
  String _selectedModel = AppConstants.yoloModelPath;
  List<Map<String, dynamic>> _runs = const [];
  bool _running = false;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final models = await _discoverTfliteModels();
    final selected = await AppConstants.getSelectedYoloModelPath();
    final prefs = await SharedPreferences.getInstance();
    final rawRuns = prefs.getStringList(_runsKey) ?? const [];
    setState(() {
      _models = models.isEmpty ? [selected] : models;
      _selectedModel = models.contains(selected) ? selected : _models.first;
      _runs = rawRuns
          .map((e) => Map<String, dynamic>.from(jsonDecode(e) as Map))
          .toList()
          .reversed
          .toList();
    });
  }

  Future<void> _setModel(String? value) async {
    if (value == null) return;
    await AppConstants.setSelectedYoloModelPath(value);
    sl<YoloDetector>().dispose();
    await sl<YoloDetector>().init();
    setState(() => _selectedModel = value);
    _snack(context, 'Model aktif: ${value.split('/').last}');
  }

  Future<void> _runBenchmark() async {
    setState(() => _running = true);
    final started = DateTime.now();
    final captureMs = await _measureCapture();

    final inferenceWatch = Stopwatch()..start();
    String label = 'person';
    String direction = 'CENTER';
    String distance = 'Demo';
    var modelLoaded = false;
    try {
      await rootBundle.load(_selectedModel).timeout(const Duration(seconds: 3));
      modelLoaded = true;
    } catch (_) {}
    final detection = await sl<YoloDetector>().detectFallback();
    if (detection != null) {
      label = detection.label;
      direction = detection.directionName;
      distance = detection.estimatedDistance;
    }
    inferenceWatch.stop();

    final notifWatch = Stopwatch()..start();
    final text = 'Obstacle $label di $direction, jarak $distance';
    notifWatch.stop();

    final ttsWatch = Stopwatch()..start();
    try {
      await sl<TtsService>()
          .speakImmediate(text)
          .timeout(const Duration(seconds: 3));
    } catch (_) {}
    ttsWatch.stop();

    final run = {
      'time': started.toIso8601String(),
      'model': _selectedModel,
      'modelLoaded': modelLoaded,
      'captureMs': captureMs,
      'inferenceMs': inferenceWatch.elapsedMilliseconds,
      'notificationMs': notifWatch.elapsedMicroseconds / 1000,
      'ttsMs': ttsWatch.elapsedMilliseconds,
      'label': label,
      'direction': direction,
    };
    final prefs = await SharedPreferences.getInstance();
    final next = [
      jsonEncode(run),
      ...((prefs.getStringList(_runsKey) ?? const []).take(24))
    ];
    await prefs.setStringList(_runsKey, next);
    if (mounted) {
      setState(() {
        _runs = [run, ..._runs].take(25).toList();
        _running = false;
      });
    }
  }

  Future<int> _measureCapture() async {
    final watch = Stopwatch()..start();
    CameraController? controller;
    try {
      final cameras =
          await availableCameras().timeout(const Duration(seconds: 3));
      if (cameras.isNotEmpty) {
        controller = CameraController(cameras.first, ResolutionPreset.low,
            enableAudio: false);
        await controller.initialize().timeout(const Duration(seconds: 5));
        await controller.takePicture().timeout(const Duration(seconds: 5));
      }
    } catch (_) {
      await Future<void>.delayed(const Duration(milliseconds: 16));
    } finally {
      await controller?.dispose();
    }
    watch.stop();
    return watch.elapsedMilliseconds;
  }

  Future<void> _clearRuns() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove(_runsKey);
    setState(() => _runs = const []);
  }

  @override
  Widget build(BuildContext context) {
    final hasRealModel = _models.any((model) => model.endsWith('.tflite'));
    return _Page(
      title: 'AI Benchmark',
      subtitle: 'Capture, model, notification text, and TTS timing',
      child: ListView(
        children: [
          DropdownButtonFormField<String>(
            value: _selectedModel,
            decoration: const InputDecoration(labelText: 'Model file'),
            items: [
              for (final model in _models)
                DropdownMenuItem(
                    value: model, child: Text(model.split('/').last))
            ],
            onChanged: _setModel,
          ),
          if (!hasRealModel) ...[
            const SizedBox(height: 10),
            const _StatusBox(
              success: false,
              message:
                  'Belum ada file .tflite di assets/models. Taruh 3-5 model di folder itu, lalu restart app untuk muncul di dropdown.',
            ),
          ],
          const SizedBox(height: 12),
          FilledButton.icon(
            onPressed: _running ? null : _runBenchmark,
            icon: _running
                ? const SizedBox(
                    width: 18,
                    height: 18,
                    child: CircularProgressIndicator(strokeWidth: 2))
                : const Icon(Icons.play_arrow),
            label: Text(_running ? 'Running benchmark...' : 'Run benchmark'),
          ),
          const SizedBox(height: 8),
          OutlinedButton.icon(
              onPressed: _clearRuns,
              icon: const Icon(Icons.delete_outline),
              label: const Text('Clear log')),
          const SizedBox(height: 16),
          for (final run in _runs) _BenchmarkCard(run: run),
          if (_runs.isEmpty)
            const _EmptyPanel(
              icon: Icons.speed,
              title: 'Belum Ada Log',
              message:
                  'Klik Run benchmark untuk mencatat waktu capture, model/inference, text notification, dan TTS.',
            ),
        ],
      ),
    );
  }
}

class _BenchmarkCard extends StatelessWidget {
  final Map<String, dynamic> run;
  const _BenchmarkCard({required this.run});

  @override
  Widget build(BuildContext context) {
    final time = DateTime.tryParse(run['time']?.toString() ?? '')?.toLocal();
    return Card(
      elevation: 0,
      margin: const EdgeInsets.only(bottom: 10),
      shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
          side: const BorderSide(color: Color(0xFFE2E8F0))),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
                time == null
                    ? 'Benchmark run'
                    : '${_two(time.hour)}:${_two(time.minute)}:${_two(time.second)}',
                style: const TextStyle(fontWeight: FontWeight.w800)),
            const SizedBox(height: 8),
            Text(
                'Model: ${run['model'].toString().split('/').last} (${run['modelLoaded'] == true ? 'loaded' : 'fallback'})'),
            Text('Capture: ${run['captureMs']} ms'),
            Text('Model/inference: ${run['inferenceMs']} ms'),
            Text('Notification text: ${run['notificationMs']} ms'),
            Text('TTS start: ${run['ttsMs']} ms'),
            Text('Result: ${run['label']} ${run['direction']}'),
          ],
        ),
      ),
    );
  }
}

class _EndpointListScreen extends StatelessWidget {
  final String title;
  final String endpoint;
  const _EndpointListScreen({required this.title, required this.endpoint});

  @override
  Widget build(BuildContext context) {
    return _Page(
        title: title,
        subtitle: endpoint,
        child: _EndpointList(endpoint: endpoint));
  }
}

class _MapScreen extends StatefulWidget {
  final String title;
  final String subtitle;

  const _MapScreen({
    required this.title,
    required this.subtitle,
  });

  @override
  State<_MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<_MapScreen> {
  final MapController _mapController = MapController();
  LatLng _center = const LatLng(-6.200000, 106.816666);
  String _status = 'Loading map...';

  @override
  void initState() {
    super.initState();
    _loadLocation();
  }

  Future<void> _loadLocation() async {
    try {
      await Geolocator.requestPermission();
      final pos = await Geolocator.getCurrentPosition()
          .timeout(const Duration(seconds: 8));
      _center = LatLng(pos.latitude, pos.longitude);
      _status = 'Lokasi kamu sekarang';
      _mapController.move(_center, 16);
      unawaited(_ignoreFailure(_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))));
    } catch (_) {
      _status = 'GPS belum tersedia. Menampilkan map demo.';
    } finally {
      if (mounted) setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: widget.title,
      subtitle: widget.subtitle,
      actions: [
        IconButton(
            onPressed: _loadLocation, icon: const Icon(Icons.my_location))
      ],
      child: ClipRRect(
        borderRadius: BorderRadius.circular(16),
        child: Stack(
          children: [
            FlutterMap(
              mapController: _mapController,
              options: MapOptions(initialCenter: _center, initialZoom: 16),
              children: [
                TileLayer(
                  urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                  userAgentPackageName: 'com.walkguide.app',
                ),
                MarkerLayer(
                  markers: [
                    Marker(
                      point: _center,
                      width: 48,
                      height: 48,
                      child: const Icon(Icons.location_pin,
                          color: Color(0xFFDC2626), size: 44),
                    ),
                  ],
                ),
              ],
            ),
            Positioned(
                left: 12,
                right: 12,
                bottom: 12,
                child: _MapStatus(text: _status)),
          ],
        ),
      ),
    );
  }
}

class _MapScreenBody extends StatefulWidget {
  final String? guardianEndpoint;
  const _MapScreenBody({this.guardianEndpoint});

  @override
  State<_MapScreenBody> createState() => _MapScreenBodyState();
}

class _MapScreenBodyState extends State<_MapScreenBody> {
  final MapController _mapController = MapController();
  LatLng _center = const LatLng(-6.200000, 106.816666);
  String _status = 'Loading map...';

  @override
  void initState() {
    super.initState();
    _loadLocation();
  }

  Future<void> _loadLocation() async {
    try {
      if (widget.guardianEndpoint != null) {
        final res = await _api
            .get(widget.guardianEndpoint!)
            .timeout(const Duration(seconds: 8));
        final data = res.data['data'];
        if (data is Map && data['lat'] != null && data['lng'] != null) {
          _center = LatLng(
              (data['lat'] as num).toDouble(), (data['lng'] as num).toDouble());
          _status = 'Lokasi user terakhir';
          _mapController.move(_center, 16);
        } else {
          _status = 'Belum ada lokasi dari user';
        }
      } else {
        await Geolocator.requestPermission();
        final pos = await Geolocator.getCurrentPosition()
            .timeout(const Duration(seconds: 8));
        _center = LatLng(pos.latitude, pos.longitude);
        _status = 'Lokasi kamu sekarang';
        _mapController.move(_center, 16);
        unawaited(_ignoreFailure(_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))));
      }
    } catch (_) {
      _status = widget.guardianEndpoint == null
          ? 'GPS belum tersedia. Menampilkan map demo.'
          : 'Lokasi user belum tersedia. Menampilkan map demo.';
    } finally {
      if (mounted) setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(16),
      child: Stack(
        children: [
          FlutterMap(
            mapController: _mapController,
            options: MapOptions(initialCenter: _center, initialZoom: 16),
            children: [
              TileLayer(
                urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                userAgentPackageName: 'com.walkguide.app',
              ),
              MarkerLayer(
                markers: [
                  Marker(
                    point: _center,
                    width: 48,
                    height: 48,
                    child: const Icon(Icons.location_pin,
                        color: Color(0xFFDC2626), size: 44),
                  ),
                ],
              ),
            ],
          ),
          Positioned(
            right: 12,
            top: 12,
            child: FloatingActionButton.small(
              heroTag: 'map_center_${widget.guardianEndpoint ?? 'user'}',
              onPressed: _loadLocation,
              child: const Icon(Icons.my_location),
            ),
          ),
          Positioned(
              left: 12,
              right: 12,
              bottom: 12,
              child: _MapStatus(text: _status)),
        ],
      ),
    );
  }
}

class _CallPanel extends StatefulWidget {
  final String title;
  final String channelName;
  const _CallPanel({required this.title, required this.channelName});

  @override
  State<_CallPanel> createState() => _CallPanelState();
}

class _CallPanelState extends State<_CallPanel> {
  bool _joined = false;
  String _status = 'Ready';

  Future<void> _toggleCall() async {
    if (_joined) {
      await sl<CallService>().leave();
      setState(() {
        _joined = false;
        _status = 'Call ended';
      });
      return;
    }
    final joined =
        await sl<CallService>().joinChannel(channelName: widget.channelName);
    setState(() {
      _joined = joined;
      _status = joined
          ? 'Connected to ${widget.channelName}'
          : 'Backend token or Agora App ID not ready';
    });
  }

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: widget.title,
      subtitle: 'Agora audio channel',
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(_joined ? Icons.call : Icons.call_outlined,
                size: 88,
                color: _joined ? Colors.green : const Color(0xFF1A56DB)),
            const SizedBox(height: 14),
            Text(_status, textAlign: TextAlign.center),
            const SizedBox(height: 18),
            FilledButton.icon(
              onPressed: _toggleCall,
              icon: Icon(_joined ? Icons.call_end : Icons.call),
              label: Text(_joined ? 'End call' : 'Start call'),
            ),
          ],
        ),
      ),
    );
  }
}

class _EndpointActionScreen extends StatelessWidget {
  final String title;
  final String endpoint;
  const _EndpointActionScreen({required this.title, required this.endpoint});

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: title,
      subtitle: endpoint,
      child: _EndpointList(endpoint: endpoint),
    );
  }
}

class _EndpointList extends StatefulWidget {
  final String endpoint;
  const _EndpointList({required this.endpoint});

  @override
  State<_EndpointList> createState() => _EndpointListState();
}

class _EndpointListState extends State<_EndpointList> {
  Object? _data;
  bool _loading = true;
  String? _error;
  bool _needsPairing = false;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    setState(() => _loading = true);
    try {
      _error = null;
      _needsPairing = false;
      if (_endpointNeedsPairing(widget.endpoint)) {
        final paired = await _hasActivePairing();
        if (!paired) {
          _needsPairing = true;
          return;
        }
      }
      final res =
          await _api.get(widget.endpoint).timeout(const Duration(seconds: 8));
      _data = res.data['data'];
    } on DioException catch (e) {
      _error = e.response?.data['message']?.toString() ??
          'Tidak bisa memuat data dari server.';
      _data = null;
    } catch (e) {
      _error = 'Timeout / gagal memuat: $e';
      _data = null;
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const CircularProgressIndicator(),
            const SizedBox(height: 12),
            Text('Memuat ${widget.endpoint}...'),
          ],
        ),
      );
    }
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        OutlinedButton.icon(
            onPressed: _load,
            icon: const Icon(Icons.refresh),
            label: const Text('Refresh')),
        const SizedBox(height: 12),
        if (_needsPairing)
          _PairingRequiredPanel(endpoint: widget.endpoint)
        else if (_error != null)
          _ErrorPanel(message: _error!)
        else
          _JsonCard(data: _data),
      ],
    );
  }
}

bool _endpointNeedsPairing(String endpoint) {
  return endpoint.contains('/guardian/') ||
      endpoint.contains('/notifications') ||
      endpoint.contains('/activity-logs') ||
      endpoint.contains('/voice-commands') ||
      endpoint.contains('/shortcuts') ||
      endpoint.contains('/ai-config') ||
      endpoint.contains('/geofence');
}

Future<bool> _hasActivePairing() async {
  try {
    final res = await _api
        .get('/shared/pairing/status')
        .timeout(const Duration(seconds: 5));
    final data = res.data['data'];
    if (data is Map) return data['status'] == 'ACTIVE';
  } catch (_) {}
  return false;
}

class _PlaceholderScreen extends StatelessWidget {
  final String title;
  final IconData icon;
  final String text;
  const _PlaceholderScreen(
      {required this.title, required this.icon, required this.text});

  @override
  Widget build(BuildContext context) {
    return _Page(
      title: title,
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 72, color: const Color(0xFF1A56DB)),
            const SizedBox(height: 16),
            Text(text, textAlign: TextAlign.center),
          ],
        ),
      ),
    );
  }
}

class _Page extends StatelessWidget {
  final String title;
  final String? subtitle;
  final Widget child;
  final List<Widget>? actions;

  const _Page(
      {required this.title, required this.child, this.subtitle, this.actions});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(title,
                          style: Theme.of(context)
                              .textTheme
                              .headlineSmall
                              ?.copyWith(fontWeight: FontWeight.w800)),
                      if (subtitle != null)
                        Text(subtitle!,
                            style: const TextStyle(color: Color(0xFF64748B))),
                    ],
                  ),
                ),
                ...?actions,
              ],
            ),
            const SizedBox(height: 16),
            Expanded(child: child),
          ],
        ),
      ),
    );
  }
}

class _AuthFrame extends StatelessWidget {
  final String title;
  final String subtitle;
  final Widget child;
  const _AuthFrame(
      {required this.title, required this.subtitle, required this.child});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 460),
            child: Card(
              elevation: 0,
              shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(18),
                  side: const BorderSide(color: Color(0xFFE2E8F0))),
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    const Icon(Icons.navigation_rounded,
                        color: Color(0xFF1A56DB), size: 42),
                    const SizedBox(height: 14),
                    Text(title,
                        textAlign: TextAlign.center,
                        style: Theme.of(context)
                            .textTheme
                            .headlineSmall
                            ?.copyWith(fontWeight: FontWeight.w800)),
                    const SizedBox(height: 4),
                    Text(subtitle,
                        textAlign: TextAlign.center,
                        style: const TextStyle(color: Color(0xFF64748B))),
                    const SizedBox(height: 22),
                    child,
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _EmptyPanel extends StatelessWidget {
  final IconData icon;
  final String title;
  final String message;
  final Widget? action;
  const _EmptyPanel(
      {required this.icon,
      required this.title,
      required this.message,
      this.action});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      constraints: const BoxConstraints(minHeight: 180),
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
          color: const Color(0xFFF8FAFC),
          borderRadius: BorderRadius.circular(12),
          border: Border.all(color: const Color(0xFFE2E8F0))),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, color: const Color(0xFF64748B), size: 48),
          const SizedBox(height: 12),
          Text(title,
              style:
                  const TextStyle(fontWeight: FontWeight.w800, fontSize: 18)),
          const SizedBox(height: 6),
          Text(message, textAlign: TextAlign.center),
          if (action != null) ...[
            const SizedBox(height: 12),
            action!,
          ],
        ],
      ),
    );
  }
}

class _StatusBox extends StatelessWidget {
  final bool success;
  final String message;
  const _StatusBox({required this.success, required this.message});

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        color: success ? const Color(0xFFF0FDF4) : const Color(0xFFFEF2F2),
        borderRadius: BorderRadius.circular(10),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Text(message,
            style: TextStyle(
                color: success
                    ? const Color(0xFF166534)
                    : const Color(0xFF991B1B))),
      ),
    );
  }
}

class _ErrorPanel extends StatelessWidget {
  final String message;
  const _ErrorPanel({required this.message});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      constraints: const BoxConstraints(minHeight: 180),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFFFEF2F2),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFFECACA)),
      ),
      child: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Icon(Icons.error_outline, color: Color(0xFFDC2626)),
            const SizedBox(height: 8),
            Text(message,
                style: const TextStyle(
                    color: Color(0xFF991B1B), fontWeight: FontWeight.w700)),
            const SizedBox(height: 12),
            const Text(
              'Kalau ini endpoint Guardian/User, pastikan akun sudah login dengan role yang benar dan pairing sudah dibuat jika endpoint membutuhkan paired user.',
            ),
          ],
        ),
      ),
    );
  }
}

class _PairingRequiredPanel extends StatelessWidget {
  final String endpoint;
  const _PairingRequiredPanel({required this.endpoint});

  @override
  Widget build(BuildContext context) {
    final isGuardian = endpoint.startsWith('/guardian');
    return Container(
      width: double.infinity,
      constraints: const BoxConstraints(minHeight: 240),
      padding: const EdgeInsets.all(18),
      decoration: BoxDecoration(
        color: const Color(0xFFFFFBEB),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFFDE68A)),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(Icons.link_off, color: Color(0xFFD97706), size: 56),
          const SizedBox(height: 14),
          const Text(
            'Belum Pairing',
            style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.w800,
                color: Color(0xFF92400E)),
          ),
          const SizedBox(height: 8),
          Text(
            isGuardian
                ? 'Fitur ini butuh User yang sudah terhubung. Masukkan Unique ID User di menu Pairing.'
                : 'Fitur ini akan aktif setelah akun kamu terhubung dengan Guardian.',
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 18),
          FilledButton.icon(
            onPressed: () =>
                context.go(isGuardian ? '/guardian/pairing' : '/user/pairing'),
            icon: const Icon(Icons.link),
            label: const Text('Buka Pairing'),
          ),
        ],
      ),
    );
  }
}

class _PairingStatusCard extends StatefulWidget {
  final bool allowUserResponse;
  const _PairingStatusCard({super.key, this.allowUserResponse = false});

  @override
  State<_PairingStatusCard> createState() => _PairingStatusCardState();
}

class _PairingStatusCardState extends State<_PairingStatusCard> {
  String _status = 'Mengecek status pairing...';
  bool _active = false;
  bool _loading = false;
  bool _responding = false;
  Map<String, dynamic>? _data;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    setState(() => _loading = true);
    try {
      final token = await sl<SecureStorage>().getAccessToken();
      if (token == null || token.isEmpty) {
        _active = false;
        _data = null;
        _status = 'Belum login. Login dulu supaya status pairing bisa dicek.';
        return;
      }
      final res = await _api
          .get('/shared/pairing/status')
          .timeout(const Duration(seconds: 5));
      final data = res.data['data'];
      _data = data is Map ? Map<String, dynamic>.from(data) : null;
      _active = data is Map && data['status'] == 'ACTIVE';
      if (data is Map && data['status'] == 'ACTIVE') {
        _active = true;
        _status =
            'Sudah pairing dengan ${data['pairedWithName'] ?? data['pairedWithEmail'] ?? 'akun lain'}.';
      } else if (data is Map && data['status'] == 'PENDING') {
        _status = widget.allowUserResponse
            ? 'Ada undangan pairing dari ${data['pairedWithName'] ?? data['pairedWithEmail'] ?? 'Guardian'}.'
            : 'Invite sudah terkirim. Tunggu User membuka menu Pairing lalu Accept.';
      } else {
        _status = 'Belum pairing. Bagikan Unique ID kamu ke Guardian.';
      }
    } on DioException catch (e) {
      _active = false;
      _data = null;
      _status = _friendlyDioMessage(e,
          fallback:
              'Belum bisa mengecek server, tapi Unique ID tetap bisa dibagikan.');
    } on TimeoutException {
      _active = false;
      _data = null;
      _status =
          'Server terlalu lama merespons status pairing. Cek backend masih running dan URL server benar.';
    } catch (e) {
      _active = false;
      _data = null;
      _status = 'Status pairing belum bisa dicek: $e';
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _respond(bool accept) async {
    final pairingId = _data?['pairingId'];
    if (pairingId == null) {
      _snack(context, 'Tidak ada invite yang bisa direspons.');
      return;
    }
    setState(() => _responding = true);
    try {
      final res = await _api.post('/shared/pairing/respond', data: {
        'pairingId': pairingId,
        'accept': accept,
      }).timeout(const Duration(seconds: 8));
      _snack(
          context,
          res.data['message']?.toString() ??
              (accept ? 'Pairing diterima.' : 'Pairing ditolak.'));
      await _load();
    } on DioException catch (e) {
      _snack(context,
          _friendlyDioMessage(e, fallback: 'Gagal merespons pairing.'));
    } on TimeoutException {
      _snack(context, 'Server terlalu lama merespons pairing.');
    } finally {
      if (mounted) setState(() => _responding = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final pending = _data?['status'] == 'PENDING';
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: _active ? const Color(0xFFF0FDF4) : const Color(0xFFFFFBEB),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
            color: _active ? const Color(0xFFBBF7D0) : const Color(0xFFFDE68A)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Row(
            children: [
              Icon(_active ? Icons.link : Icons.info_outline,
                  color: _active
                      ? const Color(0xFF16A34A)
                      : const Color(0xFFD97706)),
              const SizedBox(width: 12),
              Expanded(child: Text(_status)),
              IconButton(
                  onPressed: _loading ? null : _load,
                  icon: _loading
                      ? const SizedBox(
                          width: 18,
                          height: 18,
                          child: CircularProgressIndicator(strokeWidth: 2))
                      : const Icon(Icons.refresh)),
            ],
          ),
          if (widget.allowUserResponse && pending) ...[
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: FilledButton.icon(
                    onPressed: _responding ? null : () => _respond(true),
                    icon: const Icon(Icons.check),
                    label: const Text('Accept'),
                  ),
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: OutlinedButton.icon(
                    onPressed: _responding ? null : () => _respond(false),
                    icon: const Icon(Icons.close),
                    label: const Text('Reject'),
                  ),
                ),
              ],
            ),
          ],
        ],
      ),
    );
  }
}

class _MapStatus extends StatelessWidget {
  final String text;
  const _MapStatus({required this.text});

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        color: Colors.white.withValues(alpha: 0.92),
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(color: Colors.black.withValues(alpha: 0.12), blurRadius: 18)
        ],
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          children: [
            const Icon(Icons.map, color: Color(0xFF1A56DB)),
            const SizedBox(width: 10),
            Expanded(
                child: Text(text,
                    style: const TextStyle(fontWeight: FontWeight.w700))),
          ],
        ),
      ),
    );
  }
}

class _JsonCard extends StatelessWidget {
  final Object? data;
  const _JsonCard({required this.data});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      constraints: const BoxConstraints(minHeight: 220),
      padding: const EdgeInsets.all(14),
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
          border: Border.all(color: const Color(0xFFE2E8F0))),
      child: SingleChildScrollView(child: Text(data?.toString() ?? 'No data')),
    );
  }
}

class _InfoCard extends StatelessWidget {
  final String title;
  final String value;
  final IconData icon;
  const _InfoCard(
      {required this.title, required this.value, required this.icon});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
          color: const Color(0xFFEFF6FF),
          borderRadius: BorderRadius.circular(12)),
      child: Row(
        children: [
          Icon(icon, color: const Color(0xFF1A56DB)),
          const SizedBox(width: 12),
          Expanded(
              child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                Text(title),
                SelectableText(value,
                    style: const TextStyle(
                        fontSize: 22, fontWeight: FontWeight.w800))
              ])),
        ],
      ),
    );
  }
}

class _Pill extends StatelessWidget {
  final String text;
  final Color color;
  const _Pill({required this.text, required this.color});

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
          color: color.withValues(alpha: 0.16),
          borderRadius: BorderRadius.circular(999),
          border: Border.all(color: color)),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
        child: Text(text,
            style: TextStyle(color: color, fontWeight: FontWeight.w800)),
      ),
    );
  }
}

Future<void> _saveAuthAndRoute(
    BuildContext context, Map<String, dynamic> data) async {
  await sl<SecureStorage>().saveTokens(
    accessToken: data['accessToken'],
    refreshToken: data['refreshToken'],
    role: data['role'],
    userId: data['userId'].toString(),
    displayName: data['displayName'],
    uniqueUserId: data['uniqueUserId'],
  );
  final serverUrl = await AppConstants.getServerUrl();
  if (serverUrl != null) {
    context
        .read<AppCubit>()
        .setSession(role: data['role'], serverUrl: serverUrl);
    _startPostLoginServices(serverUrl);
  }
  sl<TtsService>().speak('Selamat datang ${data['displayName'] ?? ''}');
  if (context.mounted) {
    context.go(data['role'] == 'ROLE_GUARDIAN'
        ? '/guardian/dashboard'
        : '/user/walkguide');
  }
}

void _startPostLoginServices(String serverUrl) {
  Future.microtask(() async {
    try {
      if (!kIsWeb) {
        await sl<WebSocketService>()
            .connect(serverUrl)
            .timeout(const Duration(seconds: 2));
      }
      await sl<OfflineQueueService>()
          .syncPending(sl<ApiClient>())
          .timeout(const Duration(seconds: 3));
    } catch (e) {
      debugPrint('Post-login services skipped: $e');
    }
  });
}

Future<void> _showRegisterSuccess(
    BuildContext context, Map<String, dynamic> data) async {
  final uniqueId = data['uniqueUserId']?.toString();
  final message = uniqueId == null || uniqueId.isEmpty
      ? 'Registrasi berhasil. Silakan login.'
      : 'Registrasi berhasil. Unique User ID kamu: $uniqueId. Silakan login.';
  _snack(context, message);
  await showDialog<void>(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: const Text('Register Successful'),
      content: SelectableText(message),
      actions: [
        FilledButton(
          onPressed: () => Navigator.of(dialogContext).pop(),
          child: const Text('Login sekarang'),
        ),
      ],
    ),
  );
}

void _snack(BuildContext context, String message) {
  if (context.mounted) {
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }
}

String _friendlyDioMessage(DioException e, {required String fallback}) {
  final data = e.response?.data;
  if (data is Map && data['message'] != null) return data['message'].toString();
  if (e.response?.statusCode == 401) {
    return 'Sesi login habis. Logout lalu login ulang.';
  }
  if (e.response?.statusCode == 403) {
    return 'Role akun tidak cocok untuk fitur ini. Pastikan User/Guardian benar.';
  }
  if (e.type == DioExceptionType.connectionTimeout ||
      e.type == DioExceptionType.receiveTimeout) {
    return 'Server terlalu lama merespons. Pastikan backend masih running dan URL server benar.';
  }
  if (e.type == DioExceptionType.connectionError) {
    return 'Tidak bisa ke server. Di Chrome pakai http://localhost:8080. Di HP pakai IP laptop/server, bukan localhost.';
  }
  return fallback;
}

Future<List<String>> _discoverTfliteModels() async {
  try {
    final manifestRaw = await rootBundle.loadString('AssetManifest.json');
    final manifest = jsonDecode(manifestRaw) as Map<String, dynamic>;
    final models = manifest.keys
        .where((key) =>
            key.startsWith('assets/models/') && key.endsWith('.tflite'))
        .toList()
      ..sort();
    return models;
  } catch (_) {
    return const [];
  }
}

String _two(int value) => value.toString().padLeft(2, '0');

String _formatCoord(Object? value) {
  if (value is num) return value.toStringAsFixed(6);
  final parsed = double.tryParse(value?.toString() ?? '');
  return parsed == null ? '-' : parsed.toStringAsFixed(6);
}

Future<void> _ignoreFailure(Future<Object?> future) async {
  try {
    await future;
  } catch (_) {}
}
