import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:latlong2/latlong.dart'; import '../../core/constants/auth_constants.dart'; import '../../core/models/colonia.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_widgets.dart'; import '../home/colonias_data.dart'; import 'colonias_provider.dart'; const Map _cpToColonia = { '38000': 'Zona Centro', '38060': 'Las Arboledas', '38027': 'San Juanico', '38037': 'Los Olivos', '38090': 'Rancho Seco', '38080': 'Las Insurgentes', '38086': 'Trojes', }; class AddAddressPage extends ConsumerStatefulWidget { const AddAddressPage({super.key}); @override ConsumerState createState() => _AddAddressPageState(); } class _AddAddressPageState extends ConsumerState { final _mapController = MapController(); final _cpCtrl = TextEditingController(); final _calleCtrl = TextEditingController(); final _labelCtrl = TextEditingController(text: 'Mi Casa'); Colonia? _selectedColonia; LatLng? _selectedLocation; bool _loading = false; @override void dispose() { _mapController.dispose(); _cpCtrl.dispose(); _calleCtrl.dispose(); _labelCtrl.dispose(); super.dispose(); } Future _fetchStreetName(LatLng latlng) async { setState(() => _selectedLocation = latlng); try { final dio = Dio(); final response = await dio.get( 'https://nominatim.openstreetmap.org/reverse', queryParameters: { 'lat': latlng.latitude, 'lon': latlng.longitude, 'format': 'json', 'addressdetails': 1, }, options: kIsWeb ? null : Options(headers: {'User-Agent': 'com.onlineshack.recolecta'}), ); if (response.data?['address'] != null) { final addr = response.data['address'] as Map; final road = addr['road'] ?? addr['pedestrian'] ?? addr['street'] ?? ''; final num = addr['house_number'] ?? ''; if ((road as String).isNotEmpty) { setState(() => _calleCtrl.text = '$road $num'.trim()); } } } catch (e) { debugPrint('Nominatim error: $e'); } } void _validarCP(String cp, List colonias) { if (cp.length != 5) { if (_selectedColonia != null) { setState(() { _selectedColonia = null; _selectedLocation = null; _calleCtrl.clear(); }); } return; } final nombre = _cpToColonia[cp]; if (nombre == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Código postal fuera de nuestra zona de servicio.'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); setState(() { _selectedColonia = null; _selectedLocation = null; }); return; } final backendC = colonias .where((c) => c.nombre.toLowerCase() == nombre.toLowerCase()) .firstOrNull; if (backendC == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Esta colonia aún no tiene horarios configurados.'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); setState(() { _selectedColonia = null; _selectedLocation = null; }); return; } setState(() { _selectedColonia = backendC; _selectedLocation = kColoniasCoordinates[nombre]; }); FocusScope.of(context).unfocus(); } Future _guardar() async { if (_calleCtrl.text.trim().isEmpty || _selectedColonia == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Ingresa tu calle y selecciona una colonia'), behavior: SnackBarBehavior.floating, ), ); return; } setState(() => _loading = true); try { const storage = FlutterSecureStorage(); final token = await storage.read(key: authTokenStorageKey) ?? ''; final dio = Dio( BaseOptions( baseUrl: const String.fromEnvironment( 'API_BASE_URL', defaultValue: 'http://localhost:8000', ), headers: {'Authorization': 'Bearer $token'}, ), ); final body = { 'label': _labelCtrl.text.trim().isEmpty ? 'Mi Casa' : _labelCtrl.text.trim(), 'calle': _calleCtrl.text.trim(), 'colonia': _selectedColonia!.nombre, }; await dio.post('/addresses', data: body); if (mounted) Navigator.pop(context, true); } on DioException catch (e) { if (mounted) { final msg = (e.response?.data is Map) ? e.response!.data['detail'] ?? 'Error al guardar' : 'Error al guardar la dirección'; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: $e'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); } } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { final coloniasList = ref.watch(coloniasProvider).value ?? []; final baseCenter = _selectedColonia != null ? kColoniaCenter(_selectedColonia!.nombre) : const LatLng(20.5222, -100.8123); final mapCenter = _selectedLocation ?? baseCenter; final bounds = _selectedColonia != null ? LatLngBounds( LatLng(baseCenter.latitude - 0.01, baseCenter.longitude - 0.01), LatLng(baseCenter.latitude + 0.01, baseCenter.longitude + 0.01), ) : null; return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, iconTheme: const IconThemeData(color: AppTheme.textPrimary), title: const Text( 'Agregar dirección', style: TextStyle( color: AppTheme.textPrimary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AppFormCard( icon: Icons.home_outlined, title: 'Dirección de tu casa', child: Column( children: [ AppFormField( label: 'Etiqueta', hint: 'Ej. Mi Casa, Trabajo', controller: _labelCtrl, ), const SizedBox(height: 14), AppFormField( label: 'Código Postal', hint: 'Ej. 38000', controller: _cpCtrl, keyboardType: TextInputType.number, onChanged: (v) => _validarCP(v, coloniasList), ), if (_selectedColonia != null) ...[ const SizedBox(height: 14), Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.primaryLight.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(AppTheme.radiusSm), border: Border.all(color: AppTheme.primaryMid), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.check_circle_outline, color: AppTheme.primary, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( 'Colonia: ${_selectedColonia!.nombre}', style: const TextStyle( fontWeight: FontWeight.w600, color: AppTheme.primaryDark, ), ), ), ], ), if (_selectedColonia!.horarioEstimado != null) ...[ const SizedBox(height: 8), Text( 'Horario ${_selectedColonia!.turno?.toLowerCase() ?? ''}', style: const TextStyle( fontSize: 13, color: AppTheme.textPrimary, ), ), Text( _selectedColonia!.horarioEstimado!, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ], ], ), ), const SizedBox(height: 14), AppFormField( label: 'Calle y número', hint: 'Av. Insurgentes 245', controller: _calleCtrl, ), const SizedBox(height: 16), const Text( 'Toca el mapa para ubicar tu casa exacta:', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: AppTheme.textSecondary, ), ), const SizedBox(height: 8), Container( height: 200, decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.radiusSm), border: Border.all(color: AppTheme.border), ), clipBehavior: Clip.hardEdge, child: FlutterMap( mapController: _mapController, options: MapOptions( initialCenter: mapCenter, initialZoom: 15.0, cameraConstraint: bounds != null ? CameraConstraint.containCenter(bounds: bounds) : const CameraConstraint.unconstrained(), onTap: (_, latlng) => _fetchStreetName(latlng), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.onlineshack.recolecta', ), if (_selectedLocation != null) MarkerLayer( markers: [ Marker( point: _selectedLocation!, width: 40, height: 40, child: const Icon( Icons.location_on, color: AppTheme.danger, size: 40, ), ), ], ), ], ), ), ] else ...[ const SizedBox(height: 24), const Center( child: Text( 'Ingresa un código postal con servicio\npara asignar tu colonia.', textAlign: TextAlign.center, style: TextStyle( color: AppTheme.textSecondary, fontSize: 13, ), ), ), ], ], ), ), const SizedBox(height: 28), SizedBox( width: double.infinity, height: 52, child: ElevatedButton( onPressed: _loading ? null : _guardar, child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: _loading ? const SizedBox( key: ValueKey('loading'), width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const FittedBox( key: ValueKey('text'), fit: BoxFit.scaleDown, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.check, size: 18), SizedBox(width: 8), Text('Guardar dirección'), ], ), ), ), ), ), const SizedBox(height: 24), ], ), ), ); } }