import 'package:flutter/foundation.dart'; import 'dart:async'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/services/container_service.dart'; import 'package:em2rp/services/data_service.dart'; import 'package:em2rp/services/api_service.dart'; import 'package:em2rp/utils/debug_log.dart'; class ContainerProvider with ChangeNotifier { final ContainerService _containerService = ContainerService(); final DataService _dataService = DataService(FirebaseFunctionsApiService()); // Timer pour le debouncing de la recherche Timer? _searchDebounceTimer; // Liste paginée pour la page de gestion List _paginatedContainers = []; bool _hasMore = true; bool _isLoadingMore = false; String? _lastVisible; // Cache complet pour compatibilité List _containers = []; // Filtres et recherche ContainerType? _selectedType; EquipmentStatus? _selectedStatus; String _searchQuery = ''; bool _isLoading = false; bool _isInitialized = false; // Mode de chargement (pagination vs full) bool _usePagination = false; // Getters List get containers => _usePagination ? _paginatedContainers : _containers; ContainerType? get selectedType => _selectedType; EquipmentStatus? get selectedStatus => _selectedStatus; String get searchQuery => _searchQuery; bool get isLoading => _isLoading; bool get isLoadingMore => _isLoadingMore; bool get hasMore => _hasMore; bool get isInitialized => _isInitialized; bool get usePagination => _usePagination; /// S'assure que les conteneurs sont chargés (charge si nécessaire) Future ensureLoaded() async { if (_isInitialized || _isLoading) { return; } await loadContainers(); } /// Charger tous les containers via l'API (avec pagination automatique) Future loadContainers() async { _isLoading = true; notifyListeners(); try { _containers.clear(); String? lastVisible; bool hasMore = true; int pageCount = 0; // Charger toutes les pages en boucle while (hasMore) { pageCount++; print('[ContainerProvider] Loading page $pageCount...'); final result = await _dataService.getContainersPaginated( limit: 100, // Charger 100 par page pour aller plus vite startAfter: lastVisible, sortBy: 'id', sortOrder: 'asc', type: _selectedType?.name, status: _selectedStatus?.name, searchQuery: _searchQuery, ); final containers = (result['containers'] as List) .map((data) => ContainerModel.fromMap(data, data['id'] as String)) .toList(); _containers.addAll(containers); hasMore = result['hasMore'] as bool? ?? false; lastVisible = result['lastVisible'] as String?; print('[ContainerProvider] Loaded ${containers.length} containers, total: ${_containers.length}, hasMore: $hasMore'); } _isLoading = false; _isInitialized = true; notifyListeners(); } catch (e) { print('Error loading containers: $e'); _isLoading = false; notifyListeners(); } } /// Récupérer les containers avec filtres appliqués Future> getContainers() async { return await _containerService.getContainers( type: _selectedType, status: _selectedStatus, searchQuery: _searchQuery, ); } /// Stream des containers - retourne un stream depuis les données en cache /// Pour compatibilité avec les widgets existants qui utilisent StreamBuilder Stream> get containersStream async* { // Si les données ne sont pas chargées, charger d'abord if (!_isInitialized) { await loadContainers(); } // Émettre les données actuelles yield _containers; // Continuer à émettre les mises à jour du cache // Note: Pour un vrai temps réel, il faudrait implémenter un StreamController // et notifier quand les données changent } /// Définir le type sélectionné void setSelectedType(ContainerType? type) async { if (_selectedType == type) return; _selectedType = type; if (_usePagination) { await reload(); } else { notifyListeners(); } } /// Définir le statut sélectionné void setSelectedStatus(EquipmentStatus? status) async { if (_selectedStatus == status) return; _selectedStatus = status; if (_usePagination) { await reload(); } else { notifyListeners(); } } /// Définir la requête de recherche (avec debouncing) void setSearchQuery(String query) { if (_searchQuery == query) return; _searchQuery = query; // Annuler le timer précédent _searchDebounceTimer?.cancel(); if (_usePagination) { // Attendre 500ms avant de recharger (debouncing) _searchDebounceTimer = Timer(const Duration(milliseconds: 500), () { reload(); }); } else { notifyListeners(); } } @override void dispose() { _searchDebounceTimer?.cancel(); super.dispose(); } // ============================================================================ // PAGINATION - Nouvelles méthodes // ============================================================================ /// Active le mode pagination (pour la page de gestion) void enablePagination() { if (!_usePagination) { _usePagination = true; DebugLog.info('[ContainerProvider] Pagination mode enabled'); } } /// Désactive le mode pagination (pour les autres pages) void disablePagination() { if (_usePagination) { _usePagination = false; DebugLog.info('[ContainerProvider] Pagination mode disabled'); } } /// Charge la première page (réinitialise tout) Future loadFirstPage() async { DebugLog.info('[ContainerProvider] Loading first page...'); _paginatedContainers.clear(); _lastVisible = null; _hasMore = true; _isLoading = true; notifyListeners(); try { await loadNextPage(); _isInitialized = true; } catch (e) { DebugLog.error('[ContainerProvider] Error loading first page', e); _isLoading = false; notifyListeners(); rethrow; } } /// Charge la page suivante (scroll infini) Future loadNextPage() async { if (_isLoadingMore || !_hasMore) { DebugLog.info('[ContainerProvider] Skip loadNextPage: isLoadingMore=$_isLoadingMore, hasMore=$_hasMore'); return; } DebugLog.info('[ContainerProvider] Loading next page... (current: ${_paginatedContainers.length})'); _isLoadingMore = true; _isLoading = true; notifyListeners(); try { final result = await _dataService.getContainersPaginated( limit: 20, startAfter: _lastVisible, type: _selectedType != null ? containerTypeToString(_selectedType!) : null, searchQuery: _searchQuery.isNotEmpty ? _searchQuery : null, sortBy: 'id', sortOrder: 'asc', ); final newContainers = (result['containers'] as List) .map((data) { final map = data as Map; return ContainerModel.fromMap(map, map['id'] as String); }) .toList(); _paginatedContainers.addAll(newContainers); _hasMore = result['hasMore'] as bool? ?? false; _lastVisible = result['lastVisible'] as String?; DebugLog.info('[ContainerProvider] Loaded ${newContainers.length} containers, total: ${_paginatedContainers.length}, hasMore: $_hasMore'); _isLoadingMore = false; _isLoading = false; notifyListeners(); } catch (e) { DebugLog.error('[ContainerProvider] Error loading next page', e); _isLoadingMore = false; _isLoading = false; notifyListeners(); rethrow; } } /// Recharge en changeant de filtre ou recherche Future reload() async { DebugLog.info('[ContainerProvider] Reloading with new filters...'); await loadFirstPage(); } /// Créer un nouveau container Future createContainer(ContainerModel container) async { await _containerService.createContainer(container); notifyListeners(); } /// Mettre à jour un container Future updateContainer(String id, Map data) async { await _containerService.updateContainer(id, data); notifyListeners(); } /// Supprimer un container Future deleteContainer(String id) async { await _containerService.deleteContainer(id); notifyListeners(); } /// Récupérer un container par ID Future getContainerById(String id) async { return await _containerService.getContainerById(id); } /// Charge plusieurs conteneurs par leurs IDs (optimisé pour les détails d'événement) Future> getContainersByIds(List containerIds) async { if (containerIds.isEmpty) return []; print('[ContainerProvider] Loading ${containerIds.length} containers by IDs...'); try { // Vérifier d'abord le cache local final cachedContainers = []; final missingIds = []; for (final id in containerIds) { final cached = _containers.firstWhere( (c) => c.id == id, orElse: () => ContainerModel( id: '', name: '', type: ContainerType.flightCase, status: EquipmentStatus.available, equipmentIds: [], createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ); if (cached.id.isNotEmpty) { cachedContainers.add(cached); } else { missingIds.add(id); } } print('[ContainerProvider] Found ${cachedContainers.length} in cache, ${missingIds.length} missing'); // Si tous sont en cache, retourner directement if (missingIds.isEmpty) { return cachedContainers; } // Charger les manquants depuis l'API final containersData = await _dataService.getContainersByIds(missingIds); final loadedContainers = containersData.map((data) { return ContainerModel.fromMap(data, data['id'] as String); }).toList(); // Ajouter au cache for (final container in loadedContainers) { if (!_containers.any((c) => c.id == container.id)) { _containers.add(container); } } print('[ContainerProvider] Loaded ${loadedContainers.length} containers from API'); // Retourner tous les conteneurs (cache + chargés) return [...cachedContainers, ...loadedContainers]; } catch (e) { print('[ContainerProvider] Error loading containers by IDs: $e'); rethrow; } } /// Ajouter un équipement à un container Future> addEquipmentToContainer({ required String containerId, required String equipmentId, String? userId, }) async { final result = await _containerService.addEquipmentToContainer( containerId: containerId, equipmentId: equipmentId, userId: userId, ); notifyListeners(); return result; } /// Retirer un équipement d'un container Future removeEquipmentFromContainer({ required String containerId, required String equipmentId, String? userId, }) async { await _containerService.removeEquipmentFromContainer( containerId: containerId, equipmentId: equipmentId, userId: userId, ); notifyListeners(); } /// Vérifier la disponibilité d'un container Future> checkContainerAvailability({ required String containerId, required DateTime startDate, required DateTime endDate, String? excludeEventId, }) async { return await _containerService.checkContainerAvailability( containerId: containerId, startDate: startDate, endDate: endDate, excludeEventId: excludeEventId, ); } /// Récupérer les équipements d'un container Future> getContainerEquipment(String containerId) async { return await _containerService.getContainerEquipment(containerId); } /// Trouver tous les containers contenant un équipement Future> findContainersWithEquipment(String equipmentId) async { return await _containerService.findContainersWithEquipment(equipmentId); } /// Vérifier si un ID existe Future checkContainerIdExists(String id) async { return await _containerService.checkContainerIdExists(id); } /// Générer un ID unique pour un container /// Format: BOX_{TYPE}_{NAME}_{NUMBER} static String generateContainerId({ required ContainerType type, required String name, int? number, }) { // Obtenir le type en majuscules final typeStr = containerTypeToString(type); // Nettoyer le nom (enlever espaces, caractères spéciaux) final cleanName = name .replaceAll(' ', '_') .replaceAll(RegExp(r'[^a-zA-Z0-9_-]'), '') .toUpperCase(); if (number != null) { return 'BOX_${typeStr}_${cleanName}_#$number'; } return 'BOX_${typeStr}_$cleanName'; } /// Assurer l'unicité d'un ID de container static Future ensureUniqueContainerId( String baseId, ContainerService service, ) async { String uniqueId = baseId; int counter = 1; while (await service.checkContainerIdExists(uniqueId)) { uniqueId = '${baseId}_$counter'; counter++; } return uniqueId; } }