package chats.data.local.mappers import chats.data.local.database.MessageEntity import chats.domain.model.Message import chats.domain.model.MessageStatus import com.google.gson.Gson import com.google.gson.reflect.TypeToken private val gson = Gson() /** * Преобразует Domain Message в Entity для сохранения в Room */ fun Message.toEntity(status: MessageStatus = MessageStatus.SENT): MessageEntity { return MessageEntity( localId = this.id.takeIf { it.isNotBlank() } ?: java.util.UUID.randomUUID().toString(), serverId = if (status == MessageStatus.SENT || status == MessageStatus.DELIVERED || status == MessageStatus.READ) this.id else null, idempotencyKey = java.util.UUID.randomUUID().toString(), chatId = this.chatId, senderId = this.senderId, senderName = this.senderName, senderAvatar = this.senderAvatar, content = this.content, sequenceId = this.sequenceId.toLong(), createdAt = this.createdAt, mediaType = this.mediaType.name, mediaJson = gson.toJson(this.media), reactionsJson = gson.toJson(this.reactions), status = status, isPinned = this.isPinned, isForwarded = this.isForwarded, forwardedFromName = this.forwardedFromName, replyToServerId = this.replyTo?.id, createdAtMillis = parseTimestamp(this.createdAt), updatedAtMillis = System.currentTimeMillis() ) } /** * Преобразует Entity в Domain Message для отображения в UI */ fun MessageEntity.toDomain(): Message { val mediaListType = object : TypeToken>() {}.type val reactionsMapType = object : TypeToken>() {}.type return Message( id = this.serverId ?: this.localId, chatId = this.chatId, senderId = this.senderId, senderName = this.senderName, senderAvatar = this.senderAvatar, content = this.content, sequenceId = this.sequenceId.toInt(), createdAt = this.createdAt, media = try { gson.fromJson>(this.mediaJson, mediaListType) ?: emptyList() } catch (e: Exception) { emptyList() }, mediaType = try { chats.domain.model.MediaType.valueOf(this.mediaType) } catch (e: Exception) { chats.domain.model.MediaType.TEXT }, reactions = try { gson.fromJson>(this.reactionsJson, reactionsMapType) ?: emptyMap() } catch (e: Exception) { emptyMap() }, isRead = this.status == MessageStatus.READ, isPinned = this.isPinned, isForwarded = this.isForwarded, forwardedFromName = this.forwardedFromName, replyTo = null, // replyTo загружается отдельно если нужно status = this.status ) } /** * Создает новое локальное сообщение со статусом PENDING */ fun createPendingMessageEntity( chatId: String, senderId: String, senderName: String, content: String?, mediaType: String = "TEXT", mediaJson: String = "[]", replyToId: String? = null ): MessageEntity { val localId = java.util.UUID.randomUUID().toString() return MessageEntity( localId = localId, serverId = null, idempotencyKey = localId, // Используем localId как idempotency key chatId = chatId, senderId = senderId, senderName = senderName, content = content, sequenceId = -1, // Будет обновлено после получения с сервера createdAt = java.time.ZonedDateTime.now().toString(), mediaType = mediaType, mediaJson = mediaJson, status = MessageStatus.PENDING, replyToServerId = replyToId, createdAtMillis = System.currentTimeMillis(), updatedAtMillis = System.currentTimeMillis() ) } private fun parseTimestamp(createdAt: String): Long { return try { java.time.ZonedDateTime.parse(createdAt).toInstant().toEpochMilli() } catch (e: Exception) { System.currentTimeMillis() } }