package wang.yaojia.webterm.api.models

import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement

/**
 * Tolerant decode config for the UNTRUSTED server boundary (plan §4 / A8). The server is an
 * untrusted input source here: unknown keys are ignored, malformed list elements are dropped one
 * by one, and nothing crashes on bad input. The Android analogue of iOS's per-field `try?`
 * tolerance in `APIClient/Models.swift`.
 *
 * ENCODE is deterministic (`ignoreUnknownKeys`/`isLenient` only affect parsing) so the same
 * instance also encodes request bodies (`/hook/decision`, `/push/fcm-token`, `PUT /prefs`).
 */
internal val ModelJson: Json = Json {
    ignoreUnknownKeys = true
    isLenient = true
}

/**
 * Per-element / per-body tolerant decoders (mirror iOS `LiveSessionInfo.decodeList` /
 * `LossyBox` / `LossyList`).
 *
 * - [listOrNull]: a non-array top level yields `null` (the caller raises
 *   `ApiClientError.InvalidResponseBody` — the pairing "port speaks HTTP but isn't web-terminal"
 *   signal); malformed elements are dropped, good ones kept.
 * - [listOrEmpty]: a non-array top level yields `[]` (matches iOS `TimelineEvent.decodeList`, which
 *   the server itself returns when timeline capture is disabled).
 * - [objectOrNull]: a single object that fails to decode yields `null` (caller raises
 *   `InvalidResponseBody`).
 */
internal object LossyDecode {
    fun <T> listOrNull(bytes: ByteArray, element: KSerializer<T>): List<T>? {
        val array = parseArray(bytes) ?: return null
        return array.mapNotNull { el -> runCatching { ModelJson.decodeFromJsonElement(element, el) }.getOrNull() }
    }

    fun <T> listOrEmpty(bytes: ByteArray, element: KSerializer<T>): List<T> =
        listOrNull(bytes, element) ?: emptyList()

    fun <T> objectOrNull(bytes: ByteArray, deserializer: KSerializer<T>): T? =
        runCatching { ModelJson.decodeFromString(deserializer, bytes.decodeToString()) }.getOrNull()

    private fun parseArray(bytes: ByteArray): JsonArray? =
        runCatching { ModelJson.parseToJsonElement(bytes.decodeToString()) as? JsonArray }.getOrNull()
}
