import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logger/logger.dart'; import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; import 'package:real_estate_mobile/features/messaging/data/models/messaging_models.dart'; import 'package:real_estate_mobile/features/messaging/data/messaging_repository.dart'; final _log = Logger(printer: PrettyPrinter(methodCount: 0)); class MessagingState { final List conversations; final Map> messages; final Map pagination; final Set typingUsers; final bool isLoadingConversations; final bool isLoadingMessages; final bool isSending; final String? error; final String? activeConversationId; final int unreadCount; const MessagingState({ this.conversations = const [], this.messages = const {}, this.pagination = const {}, this.typingUsers = const {}, this.isLoadingConversations = false, this.isLoadingMessages = false, this.isSending = false, this.error, this.activeConversationId, this.unreadCount = 0, }); MessagingState copyWith({ List? conversations, Map>? messages, Map? pagination, Set? typingUsers, bool? isLoadingConversations, bool? isLoadingMessages, bool? isSending, String? error, String? activeConversationId, int? unreadCount, bool clearError = false, bool clearActiveConversation = false, }) { return MessagingState( conversations: conversations ?? this.conversations, messages: messages ?? this.messages, pagination: pagination ?? this.pagination, typingUsers: typingUsers ?? this.typingUsers, isLoadingConversations: isLoadingConversations ?? this.isLoadingConversations, isLoadingMessages: isLoadingMessages ?? this.isLoadingMessages, isSending: isSending ?? this.isSending, error: clearError ? null : (error ?? this.error), activeConversationId: clearActiveConversation ? null : (activeConversationId ?? this.activeConversationId), unreadCount: unreadCount ?? this.unreadCount, ); } } class MessagingNotifier extends StateNotifier { final MessagingRepository _repository; final Ref _ref; MessagingNotifier(this._repository, this._ref) : super(const MessagingState()); String? get _currentUserId => _ref.read(authProvider).user?.id; Future loadConversations() async { state = state.copyWith(isLoadingConversations: true, clearError: true); try { final result = await _repository.getConversations(); state = state.copyWith( conversations: result, isLoadingConversations: false, ); } catch (e, stack) { _log.e('Failed to load conversations', error: e, stackTrace: stack); state = state.copyWith( isLoadingConversations: false, error: 'Failed to load conversations', ); } } Future loadMessages( String conversationId, { bool loadMore = false, }) async { if (loadMore) { final existing = state.pagination[conversationId]; if (existing != null && !existing.hasMore) return; } state = state.copyWith(isLoadingMessages: true, clearError: true); try { final nextPage = loadMore ? ((state.pagination[conversationId]?.page ?? 0) + 1) : 1; final result = await _repository.getMessages( conversationId, page: nextPage, ); final newMessages = result.messages; final paginationInfo = result.pagination; final existingMessages = loadMore ? (state.messages[conversationId] ?? []) : []; final mergedMessages = loadMore ? [...existingMessages, ...newMessages] : newMessages; final updatedMessages = Map>.from(state.messages) ..[conversationId] = mergedMessages; final updatedPagination = Map.from(state.pagination) ..[conversationId] = paginationInfo; state = state.copyWith( messages: updatedMessages, pagination: updatedPagination, isLoadingMessages: false, ); } catch (_) { state = state.copyWith( isLoadingMessages: false, error: 'Failed to load messages', ); } } Future sendMessage(String conversationId, String content) async { final currentUserId = _currentUserId; if (currentUserId == null) return; final tempId = 'temp_${DateTime.now().millisecondsSinceEpoch}'; final now = DateTime.now().toIso8601String(); final tempMessage = ChatMessage( id: tempId, conversationId: conversationId, senderId: currentUserId, content: content, messageType: MessageType.text, status: MessageStatus.sent, createdAt: now, updatedAt: now, sender: MessageSender( id: currentUserId, role: 'user', ), ); // Add optimistic message at the beginning (newest-first) final currentMessages = List.from(state.messages[conversationId] ?? []); currentMessages.insert(0, tempMessage); final updatedMessages = Map>.from(state.messages) ..[conversationId] = currentMessages; state = state.copyWith( messages: updatedMessages, isSending: true, clearError: true, ); try { final realMessage = await _repository.sendMessage( conversationId, content: content, ); final messagesAfterSend = List.from(state.messages[conversationId] ?? []); final tempIndex = messagesAfterSend.indexWhere((m) => m.id == tempId); if (tempIndex != -1) { messagesAfterSend[tempIndex] = realMessage; } final finalMessages = Map>.from(state.messages) ..[conversationId] = messagesAfterSend; // Update conversation's last message final updatedConversations = state.conversations.map((c) { if (c.id == conversationId) { return c.copyWith( lastMessageText: realMessage.content, lastMessageAt: realMessage.createdAt, ); } return c; }).toList(); state = state.copyWith( messages: finalMessages, conversations: updatedConversations, isSending: false, ); } catch (_) { final messagesAfterError = List.from(state.messages[conversationId] ?? []); messagesAfterError.removeWhere((m) => m.id == tempId); final errorMessages = Map>.from(state.messages) ..[conversationId] = messagesAfterError; state = state.copyWith( messages: errorMessages, isSending: false, error: 'Failed to send message', ); } } Future markAsRead(String conversationId) async { try { await _repository.markAsRead(conversationId); final updatedConversations = state.conversations.map((c) { if (c.id == conversationId) { return c.copyWith(unreadCount: 0); } return c; }).toList(); final totalUnread = updatedConversations.fold( 0, (sum, c) => sum + c.unreadCount, ); state = state.copyWith( conversations: updatedConversations, unreadCount: totalUnread, ); } catch (_) {} } void setActiveConversation(String? conversationId) { if (conversationId == null) { state = state.copyWith(clearActiveConversation: true); } else { state = state.copyWith(activeConversationId: conversationId); } } Future loadUnreadCount() async { try { final count = await _repository.getUnreadCount(); state = state.copyWith(unreadCount: count); } catch (_) {} } Future startConversation(String agentProfileId) async { state = state.copyWith(isLoadingConversations: true, clearError: true); try { final conversation = await _repository.startConversation(agentProfileId); final exists = state.conversations.any((c) => c.id == conversation.id); final updatedConversations = exists ? state.conversations : [conversation, ...state.conversations]; state = state.copyWith( conversations: updatedConversations, isLoadingConversations: false, ); return conversation.id; } catch (_) { state = state.copyWith( isLoadingConversations: false, error: 'Failed to start conversation', ); return null; } } } final messagingProvider = StateNotifierProvider((ref) { return MessagingNotifier(ref.watch(messagingRepositoryProvider), ref); }); final currentUserIdProvider = Provider((ref) { return ref.watch(authProvider).user?.id; });