package com.sptmobile.messages import com.sptmobile.endpoint.HistoryRow import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json /** * One endpoint's slice of the history-fetch-all union (wire `EndpointHistory` * in `rust/link-proto`, verbatim): its id plus its rows in append order. */ @Serializable data class EndpointHistoryGroup( val endpoint: String, val entries: List, ) /** * One row of a gateway thread: a conversation-history message labeled by the * endpoint it belongs to. NO digest content — this is the raw in/out log * (DESIGN.md ruling 8). [outbound] is relative to the endpoint (`out` = the * phone/host sent to it). */ data class MessageThreadRow( val endpoint: String, val msgId: String?, val tsMs: Long, val outbound: Boolean, val from: String, val body: String, ) { /** * Stable list key: msg-id is the exact dedup axis (scoped by endpoint, * since ids may repeat across a host's endpoints), with a content-stable * fallback for the rare msg-id-less row. */ val stableKey: String get() = "$endpoint/" + (msgId ?: "$tsMs@$from#${body.hashCode()}") } // [impl->REQ-GATEWAY-THREAD] /** * Pure parse + flatten of the history-fetch-all union into one per-gateway * thread (Messages tab). Every endpoint's slice is unified and sorted by * timestamp, each row tagged with its endpoint. Free of Android/JNI so the * flatten semantics are JVM-unit-testable; the viewmodel only moves data. */ object MessageThread { private val json = Json { ignoreUnknownKeys = true } fun parse(reply: String): List = json.decodeFromString(reply) /** * Flatten every endpoint slice into one stable, timestamp-ordered thread. * `sortedBy` is stable, so equal-timestamp rows keep the host's per-slice * append order. */ fun flatten(groups: List): List = groups.flatMap { group -> group.entries.map { row -> MessageThreadRow( endpoint = group.endpoint, msgId = row.msg_id, tsMs = row.ts_ms, outbound = row.dir == "out", from = row.from, body = row.body, ) } }.sortedBy { it.tsMs } fun parseThread(reply: String): List = flatten(parse(reply)) }