import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/data/models/trip.dart'; import 'package:travel_mate/services/error_service.dart'; import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_state.dart' as user_state; import '../../blocs/trip/trip_bloc.dart'; import '../../blocs/trip/trip_event.dart'; import '../../blocs/trip/trip_state.dart'; import '../../blocs/group/group_bloc.dart'; import '../../blocs/group/group_event.dart'; import '../../data/models/group.dart'; import '../../data/models/group_member.dart'; import '../../services/user_service.dart'; import '../../repositories/group_repository.dart'; class CreateTripContent extends StatefulWidget { final Trip? tripToEdit; const CreateTripContent({ super.key, this.tripToEdit, }); @override State createState() => _CreateTripContentState(); } class _CreateTripContentState extends State { final _errorService = ErrorService(); final _formKey = GlobalKey(); final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); final _locationController = TextEditingController(); final _budgetController = TextEditingController(); final _userService = UserService(); final _groupRepository = GroupRepository(); DateTime? _startDate; DateTime? _endDate; bool _isLoading = false; final List _participants = []; final _participantController = TextEditingController(); bool get isEditing => widget.tripToEdit != null; @override void initState() { super.initState(); _initializeFormWithTrip(); } Future _initializeFormWithTrip() async { if (widget.tripToEdit != null) { final trip = widget.tripToEdit!; setState(() { _titleController.text = trip.title; _descriptionController.text = trip.description; _locationController.text = trip.location; _budgetController.text = trip.budget?.toString() ?? ''; _startDate = trip.startDate; _endDate = trip.endDate; }); await _loadParticipantEmails(trip.participants); } } Future _loadParticipantEmails(List participantIds) async { final userState = context.read().state; String? currentUserId; if (userState is user_state.UserLoaded) { currentUserId = userState.user.id; } for (String userId in participantIds) { if (userId == currentUserId) continue; try { final userDoc = await _userService.getUserById(userId); if (userDoc != null && userDoc.email.isNotEmpty) { setState(() { _participants.add(userDoc.email); }); } } catch (e) { _errorService.logError( 'create_trip_content.dart', 'Erreur chargement participant $userId: $e', ); } } } @override void dispose() { _titleController.dispose(); _descriptionController.dispose(); _locationController.dispose(); _budgetController.dispose(); _participantController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, tripState) { // Écouter l'état TripCreated pour récupérer l'ID du voyage if (tripState is TripCreated) { _createGroupForTrip(tripState.tripId); } else if (tripState is TripOperationSuccess) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(tripState.message), backgroundColor: Colors.green, ), ); Navigator.pop(context); if (isEditing) { Navigator.pop(context); // Retour supplémentaire en mode édition } } } else if (tripState is TripError) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(tripState.message), backgroundColor: Colors.red, ), ); setState(() { _isLoading = false; }); } } }, child: BlocBuilder( builder: (context, userState) { if (userState is! user_state.UserLoaded) { return Scaffold( appBar: AppBar( title: Text(isEditing ? 'Modifier le voyage' : 'Créer un voyage'), ), body: Center(child: Text('Veuillez vous connecter')), ); } return Scaffold( appBar: AppBar( title: Text(isEditing ? 'Modifier le voyage' : 'Créer un voyage'), backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, ), body: SingleChildScrollView( padding: EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionTitle('Informations générales'), SizedBox(height: 16), TextFormField( controller: _titleController, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Titre requis'; } return null; }, decoration: InputDecoration( labelText: 'Titre du voyage *', hintText: 'ex: Voyage à Paris', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), prefixIcon: Icon(Icons.travel_explore), ), ), SizedBox(height: 16), TextFormField( controller: _descriptionController, maxLines: 3, decoration: InputDecoration( labelText: 'Description', hintText: 'Décrivez votre voyage...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), prefixIcon: Icon(Icons.description), ), ), SizedBox(height: 16), TextFormField( controller: _locationController, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Destination requise'; } return null; }, decoration: InputDecoration( labelText: 'Destination *', hintText: 'ex: Paris, France', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), prefixIcon: Icon(Icons.location_on), ), ), SizedBox(height: 24), _buildSectionTitle('Dates du voyage'), SizedBox(height: 16), Row( children: [ Expanded( child: _buildDateField( label: 'Date de début *', date: _startDate, onTap: () => _selectStartDate(context), ), ), SizedBox(width: 16), Expanded( child: _buildDateField( label: 'Date de fin *', date: _endDate, onTap: () => _selectEndDate(context), ), ), ], ), SizedBox(height: 24), _buildSectionTitle('Budget'), SizedBox(height: 16), TextFormField( controller: _budgetController, keyboardType: TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( labelText: 'Budget estimé', hintText: 'ex: 1200.50', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), prefixIcon: Icon(Icons.euro), suffixText: '€', ), ), SizedBox(height: 24), _buildSectionTitle('Participants'), SizedBox(height: 8), Text( 'Ajoutez les emails des personnes que vous souhaitez inviter', style: TextStyle(color: Colors.grey[600], fontSize: 14), ), SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _participantController, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( labelText: 'Email du participant', hintText: 'ex: ami@email.com', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), prefixIcon: Icon(Icons.person_add), ), ), ), SizedBox(width: 8), ElevatedButton( onPressed: _addParticipant, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.all(16), ), child: Icon(Icons.add), ), ], ), SizedBox(height: 16), if (_participants.isNotEmpty) ...[ Text( 'Participants ajoutés (${_participants.length})', style: TextStyle(fontWeight: FontWeight.w500), ), SizedBox(height: 8), Container( width: double.infinity, padding: EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(12), ), child: Wrap( spacing: 8, runSpacing: 8, children: _participants .map( (email) => Chip( label: Text(email, style: TextStyle(fontSize: 12)), deleteIcon: Icon(Icons.close, size: 18), onDeleted: () => _removeParticipant(email), backgroundColor: Theme.of(context) .colorScheme .primary .withValues(alpha: 0.1), ), ) .toList(), ), ), ], SizedBox(height: 32), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _isLoading ? null : () => _saveTrip(userState.user), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text( isEditing ? 'Mettre à jour le voyage' : 'Créer le voyage', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), SizedBox(height: 20), ], ), ), ), ); }, ), ); } Widget _buildSectionTitle(String title) { return Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey[700], ), ); } Widget _buildDateField({ required String label, required DateTime? date, required VoidCallback onTap, }) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; final textColor = isDarkMode ? Colors.white : Colors.black; final labelColor = isDarkMode ? Colors.white70 : Colors.grey[600]; final iconColor = isDarkMode ? Colors.white70 : Colors.grey[600]; final placeholderColor = isDarkMode ? Colors.white38 : Colors.grey[500]; return InkWell( onTap: onTap, child: Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: isDarkMode ? Colors.white24 : Colors.grey[400]!), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontSize: 12, color: labelColor)), SizedBox(height: 8), Row( children: [ Icon(Icons.calendar_today, size: 16, color: iconColor), SizedBox(width: 8), Text( date != null ? '${date.day}/${date.month}/${date.year}' : 'Sélectionner', style: TextStyle( fontSize: 16, color: date != null ? textColor : placeholderColor, ), ), ], ), ], ), ), ); } Future _selectStartDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: _startDate ?? DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(Duration(days: 365 * 2)), ); if (picked != null) { setState(() { _startDate = picked; if (_endDate != null && _endDate!.isBefore(picked)) { _endDate = null; } }); } } Future _selectEndDate(BuildContext context) async { if (_startDate == null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Veuillez d\'abord sélectionner la date de début')), ); } return; } final DateTime? picked = await showDatePicker( context: context, initialDate: _endDate ?? _startDate!.add(Duration(days: 1)), firstDate: _startDate!, lastDate: DateTime.now().add(Duration(days: 365 * 2)), ); if (picked != null && mounted) { setState(() { _endDate = picked; }); } } void _addParticipant() { final email = _participantController.text.trim(); if (email.isEmpty) return; final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(email)) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Email invalide'))); } return; } if (_participants.contains(email)) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Ce participant est déjà ajouté'))); } return; } setState(() { _participants.add(email); _participantController.clear(); }); } void _removeParticipant(String email) { setState(() { _participants.remove(email); }); } // Mettre à jour le groupe avec les nouveaux membres Future _updateGroupMembers( String tripId, user_state.UserModel currentUser, List> participantsData, ) async { try { final group = await _groupRepository.getGroupByTripId(tripId); if (group == null) { _errorService.logError( 'create_trip_content.dart', 'Groupe non trouvé pour le voyage $tripId', ); return; } final newMembers = [ GroupMember( userId: currentUser.id, firstName: currentUser.prenom, pseudo: currentUser.prenom, role: 'admin', ), ...participantsData.map((p) => GroupMember( userId: p['id'] as String, firstName: p['firstName'] as String, pseudo: p['firstName'] as String, role: 'member', )), ]; final currentMembers = await _groupRepository.getGroupMembers(group.id); final currentMemberIds = currentMembers.map((m) => m.userId).toSet(); final newMemberIds = newMembers.map((m) => m.userId).toSet(); final membersToAdd = newMembers.where((m) => !currentMemberIds.contains(m.userId)).toList(); final membersToRemove = currentMembers .where((m) => !newMemberIds.contains(m.userId) && m.role != 'admin') .toList(); for (final member in membersToAdd) { context.read().add(AddMemberToGroup(group.id, member)); } for (final member in membersToRemove) { context.read().add(RemoveMemberFromGroup(group.id, member.userId)); } } catch (e) { _errorService.logError( 'create_trip_content.dart', 'Erreur lors de la mise à jour du groupe: $e', ); } } // NOUVELLE MÉTHODE : Créer le groupe après la création du voyage Future _createGroupForTrip(String tripId) async { try { final userState = context.read().state; if (userState is! user_state.UserLoaded) return; final currentUser = userState.user; final participantsData = await _getParticipantsData(_participants); // Créer le groupe avec le tripId récupéré final group = Group( id: '', // Sera généré par Firestore name: _titleController.text.trim(), tripId: tripId, // ✅ ID du voyage récupéré createdBy: currentUser.id, ); final groupMembers = [ GroupMember( userId: currentUser.id, firstName: currentUser.prenom, pseudo: currentUser.prenom, role: 'admin', ), ...participantsData.map((p) => GroupMember( userId: p['id'] as String, firstName: p['firstName'] as String, pseudo: p['firstName'] as String, role: 'member', )), ]; context.read().add(CreateGroupWithMembers( group: group, members: groupMembers, )); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Voyage et groupe créés avec succès !'), backgroundColor: Colors.green, ), ); setState(() { _isLoading = false; }); Navigator.pop(context); } } catch (e) { _errorService.logError( 'create_trip_content.dart', 'Erreur lors de la création du groupe: $e', ); if (mounted) { setState(() { _isLoading = false; }); } } } Future _saveTrip(user_state.UserModel currentUser) async { if (!_formKey.currentState!.validate()) { return; } if (_startDate == null || _endDate == null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Veuillez sélectionner les dates')), ); } return; } setState(() { _isLoading = true; }); try { final participantsData = await _getParticipantsData(_participants); List participantIds = participantsData.map((p) => p['id'] as String).toList(); if (!participantIds.contains(currentUser.id)) { participantIds.insert(0, currentUser.id); } final trip = Trip( id: isEditing ? widget.tripToEdit!.id : '', title: _titleController.text.trim(), description: _descriptionController.text.trim(), location: _locationController.text.trim(), startDate: _startDate!, endDate: _endDate!, budget: double.tryParse(_budgetController.text) ?? 0.0, createdBy: currentUser.id, participants: participantIds, createdAt: isEditing ? widget.tripToEdit!.createdAt : DateTime.now(), updatedAt: DateTime.now(), ); if (isEditing) { // Mode mise à jour context.read().add(TripUpdateRequested(trip: trip)); await _updateGroupMembers( widget.tripToEdit!.id!, currentUser, participantsData, ); } else { // Mode création - Le groupe sera créé dans le listener TripCreated context.read().add(TripCreateRequested(trip: trip)); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: $e'), backgroundColor: Colors.red, ), ); setState(() { _isLoading = false; }); } } } Future>> _getParticipantsData(List emails) async { List> participantsData = []; for (String email in emails) { try { final userId = await _userService.getUserIdByEmail(email); if (userId != null) { final userDoc = await _userService.getUserById(userId); final firstName = userDoc?.prenom ?? 'Utilisateur'; participantsData.add({ 'id': userId, 'firstName': firstName, }); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Utilisateur non trouvé: $email'), backgroundColor: Colors.orange, ), ); } } } catch (e) { _errorService.logError( 'create_trip_content.dart', 'Erreur lors de la récupération de l\'utilisateur $email: $e', ); } } return participantsData; } }