import 'package:votianlt_app/services/developer.dart' as developer; enum ChatDirection { incoming, outgoing } enum ChatMessageType { general, jobRelated } enum ChatContentType { text, image } ChatDirection chatDirectionFromString( String? value, { ChatDirection fallback = ChatDirection.incoming, }) { switch (value?.toUpperCase()) { case 'CLIENT': case 'OUTGOING': return ChatDirection.outgoing; case 'SERVER': case 'INCOMING': return ChatDirection.incoming; default: return fallback; } } String chatDirectionToString(ChatDirection direction) { return direction == ChatDirection.outgoing ? 'CLIENT' : 'SERVER'; } ChatMessageType chatMessageTypeFromString( String? value, { ChatMessageType fallback = ChatMessageType.general, }) { switch (value?.toUpperCase()) { case 'JOB_RELATED': return ChatMessageType.jobRelated; case 'GENERAL': return ChatMessageType.general; default: return fallback; } } String chatMessageTypeToString(ChatMessageType type) { return type == ChatMessageType.jobRelated ? 'JOB_RELATED' : 'GENERAL'; } ChatContentType chatContentTypeFromString( String? value, { ChatContentType fallback = ChatContentType.text, }) { switch (value?.toUpperCase()) { case 'IMAGE': return ChatContentType.image; case 'TEXT': return ChatContentType.text; default: return fallback; } } String chatContentTypeToString(ChatContentType type) { return type == ChatContentType.image ? 'IMAGE' : 'TEXT'; } class ChatMessage { final String id; final String content; final DateTime createdAt; final ChatDirection direction; final ChatMessageType messageType; final ChatContentType contentType; final String? jobId; final String? jobNumber; final bool read; final bool pendingSync; const ChatMessage({ required this.id, required this.content, required this.createdAt, required this.direction, required this.messageType, this.contentType = ChatContentType.text, this.jobId, this.jobNumber, this.read = false, this.pendingSync = false, }); factory ChatMessage.fromJson(Map json) { final rawId = (json['messageId'] ?? json['id'] ?? '').toString(); final rawContent = (json['content'] ?? json['text'] ?? '').toString(); final rawContentType = json['contentType']?.toString(); DateTime createdAt; final createdAtRaw = json['createdAt'] ?? json['timestamp']; if (createdAtRaw is DateTime) { createdAt = createdAtRaw; } else { createdAt = DateTime.tryParse(createdAtRaw?.toString() ?? '') ?? DateTime.now(); } var direction = chatDirectionFromString( json['origin']?.toString() ?? json['direction']?.toString(), fallback: json['isOwn'] == true ? ChatDirection.outgoing : ChatDirection.incoming, ); var messageType = chatMessageTypeFromString( json['messageType']?.toString(), fallback: ((json['jobId']?.toString().isNotEmpty ?? false) || (json['jobNumber']?.toString().isNotEmpty ?? false)) ? ChatMessageType.jobRelated : ChatMessageType.general, ); final jobIdRaw = json['jobId']?.toString(); final jobNumberRaw = json['jobNumber']?.toString(); final jobId = jobIdRaw?.trim(); final jobNumber = jobNumberRaw?.trim(); if (rawId.isEmpty || rawContent.isEmpty) { developer.log( 'Invalid ChatMessage payload (one or more required fields missing): $json', ); } return ChatMessage( id: rawId, content: rawContent, createdAt: createdAt, direction: direction, messageType: messageType, contentType: chatContentTypeFromString(rawContentType), jobId: jobId?.isEmpty ?? true ? null : jobId, jobNumber: jobNumber?.isEmpty ?? true ? null : jobNumber, read: json['read'] == true, pendingSync: json['pendingSync'] == true, ); } Map toJson() { final map = { 'messageId': id, 'content': content, 'origin': chatDirectionToString(direction), 'messageType': chatMessageTypeToString(messageType), 'contentType': chatContentTypeToString(contentType), 'createdAt': createdAt.toIso8601String(), 'read': read, }; if (jobId != null && jobId!.isNotEmpty) { map['jobId'] = jobId; } if (jobNumber != null && jobNumber!.isNotEmpty) { map['jobNumber'] = jobNumber; } if (pendingSync) { map['pendingSync'] = true; } return map; } bool get isOwn => direction == ChatDirection.outgoing; ChatMessage copyWith({ String? id, String? content, DateTime? createdAt, ChatDirection? direction, ChatMessageType? messageType, ChatContentType? contentType, String? jobId, String? jobNumber, bool? read, bool? pendingSync, }) { return ChatMessage( id: id ?? this.id, content: content ?? this.content, createdAt: createdAt ?? this.createdAt, direction: direction ?? this.direction, messageType: messageType ?? this.messageType, contentType: contentType ?? this.contentType, jobId: jobId ?? this.jobId, jobNumber: jobNumber ?? this.jobNumber, read: read ?? this.read, pendingSync: pendingSync ?? this.pendingSync, ); } @override String toString() { return 'ChatMessage(id: $id, direction: $direction, messageType: $messageType, contentType: $contentType, createdAt: $createdAt)'; } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is ChatMessage && other.id == id; } @override int get hashCode => id.hashCode; }