package no.svorka.mcms.core

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.doubleOrNull
import kotlinx.serialization.json.intOrNull

/**
 * Shared lenient JSON. MCMS documents are heterogeneous Mongo docs, so we work
 * with raw [JsonElement] trees (the Kotlin analogue of the iOS `JSONValue`) and
 * read fields through the accessors below rather than modelling every field.
 */
val MCMSJson: Json = Json {
    ignoreUnknownKeys = true
    isLenient = true
    encodeDefaults = true
}

/** Object-member access that tolerates a null receiver and non-object values. */
operator fun JsonElement?.get(key: String): JsonElement? =
    (this as? JsonObject)?.get(key)

val JsonElement?.string: String?
    get() = (this as? JsonPrimitive)?.takeIf { it.isString }?.content

val JsonElement?.int: Int?
    get() = (this as? JsonPrimitive)?.let { it.intOrNull ?: it.doubleOrNull?.toInt() }

val JsonElement?.double: Double?
    get() = (this as? JsonPrimitive)?.let { it.doubleOrNull ?: it.intOrNull?.toDouble() }

val JsonElement?.bool: Boolean?
    get() = (this as? JsonPrimitive)?.booleanOrNull

val JsonElement?.array: List<JsonElement>?
    get() = this as? JsonArray

val JsonElement?.obj: Map<String, JsonElement>?
    get() = this as? JsonObject

val JsonElement?.isObject: Boolean
    get() = this is JsonObject

/** A compact human rendering for error/detail surfaces. */
fun JsonElement?.compactDescription(): String = when (val e = this) {
    null, JsonNull -> "null"
    is JsonPrimitive -> e.content
    is JsonArray -> e.joinToString(", ") { it.compactDescription() }
    is JsonObject -> e.entries.joinToString("; ") { "${it.key}: ${it.value.compactDescription()}" }
}
