import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:em2rp/models/event_model.dart'; import 'package:em2rp/services/data_service.dart'; import 'package:em2rp/services/api_service.dart'; import 'package:em2rp/utils/performance_monitor.dart'; class EventProvider with ChangeNotifier { final DataService _dataService = DataService(FirebaseFunctionsApiService()); List _events = []; bool _isLoading = false; // Cache des utilisateurs chargés depuis getEvents Map> _usersCache = {}; // Cache pour éviter les rechargements inutiles (ancien système) DateTime? _lastLoadTime; String? _lastUserId; bool _lastCanViewAll = false; // Nouveau: Cache par mois pour le lazy loading Map> _eventsByMonth = {}; // "2026-02" => [events] String? _currentMonth; // Mois actuellement affiché List get events => _events; bool get isLoading => _isLoading; /// Vérifie si les données doivent être rechargées (cache de 30 secondes) bool _shouldReload(String userId, bool canViewAllEvents) { if (_lastLoadTime == null) return true; if (_lastUserId != userId || _lastCanViewAll != canViewAllEvents) return true; final now = DateTime.now(); final difference = now.difference(_lastLoadTime!); return difference.inSeconds > 30; } /// Charger les événements d'un utilisateur via l'API Future loadUserEvents(String userId, {bool canViewAllEvents = false, bool forceReload = false}) async { PerformanceMonitor.start('EventProvider.loadUserEvents'); // Éviter les rechargements inutiles if (!forceReload && !_shouldReload(userId, canViewAllEvents)) { print('Using cached events (loaded ${DateTime.now().difference(_lastLoadTime!).inSeconds}s ago)'); PerformanceMonitor.end('EventProvider.loadUserEvents'); return; } _isLoading = true; notifyListeners(); try { print('Loading events for user: $userId (canViewAllEvents: $canViewAllEvents)'); PerformanceMonitor.start('EventProvider.getEvents_API'); // Charger via l'API - les permissions sont vérifiées côté serveur final result = await _dataService.getEvents(userId: userId); PerformanceMonitor.end('EventProvider.getEvents_API'); final eventsData = result['events'] as List>; final usersData = result['users'] as Map; // Stocker les utilisateurs dans le cache _usersCache = usersData.map((key, value) => MapEntry(key, value as Map) ); print('Found ${eventsData.length} events from API'); PerformanceMonitor.start('EventProvider.parseEvents'); List allEvents = []; int failedCount = 0; // Parser chaque événement for (var eventData in eventsData) { try { final event = EventModel.fromMap(eventData, eventData['id'] as String); allEvents.add(event); } catch (e) { print('Failed to parse event ${eventData['id']}: $e'); failedCount++; } } PerformanceMonitor.end('EventProvider.parseEvents'); _events = allEvents; _lastLoadTime = DateTime.now(); _lastUserId = userId; _lastCanViewAll = canViewAllEvents; print('Successfully loaded ${_events.length} events (${failedCount} failed)'); _isLoading = false; notifyListeners(); PerformanceMonitor.end('EventProvider.loadUserEvents'); } catch (e) { print('Error loading events: $e'); _isLoading = false; notifyListeners(); PerformanceMonitor.end('EventProvider.loadUserEvents'); rethrow; } } /// Charger les événements d'un mois spécifique (lazy loading optimisé) Future loadMonthEvents(String userId, int year, int month, {bool canViewAllEvents = false, bool forceReload = false, bool silent = false}) async { final monthKey = '$year-${month.toString().padLeft(2, '0')}'; // Vérifier le cache if (!forceReload && _eventsByMonth.containsKey(monthKey)) { print('[EventProvider] Using cached events for $monthKey'); if (!silent) { _currentMonth = monthKey; _events = _eventsByMonth[monthKey]!; notifyListeners(); } return; } if (!silent) { _isLoading = true; notifyListeners(); } try { print('[EventProvider] Loading events for month: $monthKey'); PerformanceMonitor.start('EventProvider.loadMonthEvents_API'); final result = await _dataService.getEventsByMonth( userId: userId, year: year, month: month ); PerformanceMonitor.end('EventProvider.loadMonthEvents_API'); final eventsData = result['events'] as List>; final usersData = result['users'] as Map; // Mettre à jour le cache utilisateurs (addAll pour cumuler) _usersCache.addAll( usersData.map((key, value) => MapEntry(key, value as Map)) ); print('[EventProvider] Found ${eventsData.length} events for $monthKey'); PerformanceMonitor.start('EventProvider.parseMonthEvents'); List monthEvents = []; int failedCount = 0; // Parser les événements for (var eventData in eventsData) { try { final event = EventModel.fromMap(eventData, eventData['id'] as String); monthEvents.add(event); } catch (e) { print('[EventProvider] Failed to parse event ${eventData['id']}: $e'); failedCount++; } } PerformanceMonitor.end('EventProvider.parseMonthEvents'); // Stocker dans le cache par mois _eventsByMonth[monthKey] = monthEvents; // Mettre à jour _events et _currentMonth seulement si ce n'est pas un préchargement silencieux if (!silent) { _currentMonth = monthKey; _events = monthEvents; } // Mettre à jour les infos de cache global _lastLoadTime = DateTime.now(); _lastUserId = userId; _lastCanViewAll = canViewAllEvents; print('[EventProvider] Successfully loaded ${monthEvents.length} events for $monthKey (${failedCount} failed)'); if (!silent) { _isLoading = false; notifyListeners(); } } catch (e) { print('[EventProvider] Error loading month events: $e'); if (!silent) { _isLoading = false; notifyListeners(); } rethrow; } } /// Précharger les mois adjacents en arrière-plan void preloadAdjacentMonths(String userId, int year, int month, {bool canViewAllEvents = false}) { // Mois précédent final prevMonth = month == 1 ? 12 : month - 1; final prevYear = month == 1 ? year - 1 : year; // Mois suivant final nextMonth = month == 12 ? 1 : month + 1; final nextYear = month == 12 ? year + 1 : year; print('[EventProvider] Preloading adjacent months...'); // Charger en arrière-plan (sans bloquer l'UI ni notifier) Future.microtask(() async { try { await loadMonthEvents(userId, prevYear, prevMonth, canViewAllEvents: canViewAllEvents, silent: true); await loadMonthEvents(userId, nextYear, nextMonth, canViewAllEvents: canViewAllEvents, silent: true); print('[EventProvider] Adjacent months preloaded successfully'); } catch (e) { print('[EventProvider] Error preloading adjacent months: $e'); } }); } /// Recharger les événements (utilise le dernier userId) Future refreshEvents(String userId, {bool canViewAllEvents = false}) async { await loadUserEvents(userId, canViewAllEvents: canViewAllEvents, forceReload: true); } /// Récupérer un événement spécifique par ID EventModel? getEventById(String eventId) { try { return _events.firstWhere((event) => event.id == eventId); } catch (e) { return null; } } /// Ajouter un nouvel événement Future addEvent(EventModel event) async { try { // L'événement est créé via l'API dans le service await refreshEvents(_lastUserId ?? '', canViewAllEvents: _lastCanViewAll); } catch (e) { print('Error adding event: $e'); rethrow; } } /// Mettre à jour un événement Future updateEvent(EventModel event) async { try { // Mise à jour locale immédiate final index = _events.indexWhere((e) => e.id == event.id); if (index != -1) { _events[index] = event; notifyListeners(); } } catch (e) { print('Error updating event: $e'); rethrow; } } /// Supprimer un événement Future deleteEvent(String eventId) async { try { await _dataService.deleteEvent(eventId); _events.removeWhere((event) => event.id == eventId); notifyListeners(); } catch (e) { print('Error deleting event: $e'); rethrow; } } /// Récupérer les données d'un utilisateur depuis le cache Map? getUserFromCache(String uid) { return _usersCache[uid]; } /// Récupérer les utilisateurs de la workforce d'un événement List> getWorkforceUsers(EventModel event) { final users = >[]; for (final dynamic userRef in event.workforce) { try { String? uid; // Tenter d'extraire l'UID if (userRef is String) { uid = userRef; } else { // Essayer d'extraire l'ID si c'est une DocumentReference final ref = userRef as DocumentReference?; uid = ref?.id; } if (uid != null) { final userData = getUserFromCache(uid); if (userData != null) { users.add(userData); } } } catch (e) { // Ignorer les références invalides print('Skipping invalid workforce reference: $userRef'); } } return users; } /// Vider la liste des événements void clearEvents() { _events = []; _lastLoadTime = null; _lastUserId = null; _lastCanViewAll = false; notifyListeners(); } }