import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:injectable/injectable.dart'; import '../../domain/repositories/membre_repository.dart'; import '../../../../core/errors/failures.dart'; import '../../../../core/models/membre_model.dart'; import 'membres_event.dart'; import 'membres_state.dart'; /// BLoC pour la gestion des membres @injectable class MembresBloc extends Bloc { final MembreRepository _membreRepository; MembresBloc(this._membreRepository) : super(const MembresInitial()) { // Enregistrement des handlers d'événements on(_onLoadMembres); on(_onRefreshMembres); on(_onSearchMembres); on(_onAdvancedSearchMembres); on(_onLoadMembreById); on(_onCreateMembre); on(_onUpdateMembre); on(_onDeleteMembre); on(_onLoadMembresStats); on(_onClearMembresError); on(_onResetMembresState); } /// Handler pour charger la liste des membres Future _onLoadMembres( LoadMembres event, Emitter emit, ) async { emit(const MembresLoading()); try { final membres = await _membreRepository.getMembres(); emit(MembresLoaded(membres: membres)); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour rafraîchir la liste des membres Future _onRefreshMembres( RefreshMembres event, Emitter emit, ) async { // Conserver les données actuelles pendant le refresh final currentState = state; List currentMembres = []; if (currentState is MembresLoaded) { currentMembres = currentState.membres; emit(MembresRefreshing(currentMembres)); } else { emit(const MembresLoading()); } try { final membres = await _membreRepository.getMembres(); emit(MembresLoaded(membres: membres)); } catch (e) { final failure = _mapExceptionToFailure(e); // Si on avait des données, les conserver avec l'erreur if (currentMembres.isNotEmpty) { emit(MembresErrorWithData( failure: failure, membres: currentMembres, )); } else { emit(MembresError(failure: failure)); } } } /// Handler pour rechercher des membres Future _onSearchMembres( SearchMembres event, Emitter emit, ) async { if (event.query.trim().isEmpty) { // Si la recherche est vide, recharger tous les membres add(const LoadMembres()); return; } emit(const MembresLoading()); try { final membres = await _membreRepository.searchMembres(event.query); emit(MembresLoaded( membres: membres, isSearchResult: true, searchQuery: event.query, )); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour recherche avancée des membres avec filtres multiples Future _onAdvancedSearchMembres( AdvancedSearchMembres event, Emitter emit, ) async { // Si aucun filtre n'est appliqué, recharger tous les membres if (event.filters.isEmpty || _areFiltersEmpty(event.filters)) { add(const LoadMembres()); return; } emit(const MembresLoading()); try { final membres = await _membreRepository.advancedSearchMembres(event.filters); emit(MembresLoaded( membres: membres, isSearchResult: true, searchQuery: _buildSearchQueryFromFilters(event.filters), )); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Vérifie si tous les filtres sont vides bool _areFiltersEmpty(Map filters) { return filters.values.every((value) { if (value == null) return true; if (value is String) return value.trim().isEmpty; if (value is List) return value.isEmpty; return false; }); } /// Construit une chaîne de recherche à partir des filtres pour l'affichage String _buildSearchQueryFromFilters(Map filters) { final activeFilters = []; filters.forEach((key, value) { if (value != null && value.toString().isNotEmpty) { switch (key) { case 'nom': activeFilters.add('Nom: $value'); break; case 'prenom': activeFilters.add('Prénom: $value'); break; case 'email': activeFilters.add('Email: $value'); break; case 'telephone': activeFilters.add('Téléphone: $value'); break; case 'actif': activeFilters.add('Statut: ${value == true ? "Actif" : "Inactif"}'); break; case 'profession': activeFilters.add('Profession: $value'); break; case 'ville': activeFilters.add('Ville: $value'); break; case 'ageMin': activeFilters.add('Âge min: $value'); break; case 'ageMax': activeFilters.add('Âge max: $value'); break; } } }); return activeFilters.join(', '); } /// Handler pour charger un membre par ID Future _onLoadMembreById( LoadMembreById event, Emitter emit, ) async { emit(const MembresLoading()); try { final membre = await _membreRepository.getMembreById(event.id); emit(MembreDetailLoaded(membre)); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour créer un membre Future _onCreateMembre( CreateMembre event, Emitter emit, ) async { emit(const MembresLoading()); try { final nouveauMembre = await _membreRepository.createMembre(event.membre); emit(MembreCreated(nouveauMembre)); // Recharger la liste après création add(const LoadMembres()); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour mettre à jour un membre Future _onUpdateMembre( UpdateMembre event, Emitter emit, ) async { emit(const MembresLoading()); try { final membreMisAJour = await _membreRepository.updateMembre( event.id, event.membre, ); emit(MembreUpdated(membreMisAJour)); // Recharger la liste après mise à jour add(const LoadMembres()); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour supprimer un membre Future _onDeleteMembre( DeleteMembre event, Emitter emit, ) async { emit(const MembresLoading()); try { await _membreRepository.deleteMembre(event.id); emit(MembreDeleted(event.id)); // Recharger la liste après suppression add(const LoadMembres()); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour charger les statistiques Future _onLoadMembresStats( LoadMembresStats event, Emitter emit, ) async { emit(const MembresLoading()); try { final stats = await _membreRepository.getMembresStats(); emit(MembresStatsLoaded(stats)); } catch (e) { final failure = _mapExceptionToFailure(e); emit(MembresError(failure: failure)); } } /// Handler pour effacer les erreurs void _onClearMembresError( ClearMembresError event, Emitter emit, ) { final currentState = state; if (currentState is MembresError && currentState.previousState != null) { emit(currentState.previousState!); } else if (currentState is MembresErrorWithData) { emit(MembresLoaded( membres: currentState.membres, isSearchResult: currentState.isSearchResult, searchQuery: currentState.searchQuery, )); } else { emit(const MembresInitial()); } } /// Handler pour réinitialiser l'état void _onResetMembresState( ResetMembresState event, Emitter emit, ) { emit(const MembresInitial()); } /// Convertit une exception en Failure approprié Failure _mapExceptionToFailure(dynamic exception) { if (exception is Failure) { return exception; } final message = exception.toString(); if (message.contains('connexion') || message.contains('network')) { return NetworkFailure(message: message); } else if (message.contains('401') || message.contains('unauthorized')) { return const AuthFailure(message: 'Session expirée. Veuillez vous reconnecter.'); } else if (message.contains('400') || message.contains('validation')) { return ValidationFailure(message: message); } else if (message.contains('500') || message.contains('server')) { return ServerFailure(message: message); } return ServerFailure(message: message); } }