package wang.yaojia.webterm.api.models

import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.decodeFromJsonElement
import wang.yaojia.webterm.wire.ClaudeStatus
import java.util.UUID

/**
 * Decode a server session id as [UUID]. Server ids are lowercase `crypto.randomUUID()` strings;
 * a non-UUID string throws, so `LossyDecode` drops that list element (matches iOS decoding
 * `UUID.self` — a malformed id must not surface). Serializes back lowercase (`UUID.toString()`).
 */
internal object UuidSerializer : KSerializer<UUID> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
    override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
    override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
}

/**
 * Decode [ClaudeStatus] by its `wire` value; an unknown/future value maps to
 * [ClaudeStatus.UNKNOWN] rather than dropping the entry — a new server status must never make a
 * running session invisible (iOS `rawStatus.flatMap(...) ?? .unknown`).
 */
internal object ClaudeStatusSerializer : KSerializer<ClaudeStatus> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ClaudeStatus", PrimitiveKind.STRING)
    override fun deserialize(decoder: Decoder): ClaudeStatus =
        ClaudeStatus.fromWire(decoder.decodeString()) ?: ClaudeStatus.UNKNOWN
    override fun serialize(encoder: Encoder, value: ClaudeStatus) = encoder.encodeString(value.wire)
}

/**
 * A `List<T>` serializer that drops malformed elements and degrades a non-array to `[]` — the
 * Android analogue of iOS's `LossyList.decode` for NESTED arrays (`ProjectInfo.sessions`,
 * `ProjectDetail.worktrees`). A bad nested element must not fail the whole parent object.
 */
internal class LossyListSerializer<T>(private val element: KSerializer<T>) : KSerializer<List<T>> {
    private val delegate = ListSerializer(element)
    override val descriptor: SerialDescriptor = delegate.descriptor
    override fun serialize(encoder: Encoder, value: List<T>) = delegate.serialize(encoder, value)
    override fun deserialize(decoder: Decoder): List<T> {
        val json = decoder as? JsonDecoder ?: return delegate.deserialize(decoder)
        val array = json.decodeJsonElement() as? JsonArray ?: return emptyList()
        return array.mapNotNull { runCatching { json.json.decodeFromJsonElement(element, it) }.getOrNull() }
    }
}

internal object ProjectSessionRefListSerializer :
    KSerializer<List<ProjectSessionRef>> by LossyListSerializer(ProjectSessionRef.serializer())

internal object WorktreeInfoListSerializer :
    KSerializer<List<WorktreeInfo>> by LossyListSerializer(WorktreeInfo.serializer())
