package dev.inmo.postssystem.features.roles.common

import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*

@Serializable(RoleSerializer::class)
interface Role {
    companion object {
        fun serializer() = RoleSerializer
    }
}

@Serializable
data class UnknownRole(val originalJson: JsonElement) : Role

@Serializer(Role::class)
object RoleSerializer : KSerializer<Role> {
    private val userRoleFormat = Json { ignoreUnknownKeys = true }
    private const val keyField = "key"
    private const val valueField = "value"
    private val serializers = mutableMapOf<String, KSerializer<out Role>>()
    override val descriptor: SerialDescriptor = String.serializer().descriptor

    @OptIn(InternalSerializationApi::class)
    override fun deserialize(decoder: Decoder): Role {
        return if (decoder is JsonDecoder) {
            val originalJson = decoder.decodeJsonElement().jsonObject
            val type = originalJson[keyField]?.jsonPrimitive ?.content
            return if (type == null || !serializers.containsKey(type)) {
                UnknownRole(originalJson)
            } else {
                userRoleFormat.decodeFromJsonElement(
                    serializers.getValue(type),
                    originalJson[valueField] ?: buildJsonObject {  }
                )
            }
        } else {
            val encoded = decoder.decodeString()
            userRoleFormat.decodeFromString(this, encoded)
        }
    }

    @InternalSerializationApi
    private fun <T : Role> T.toJson(): JsonElement {
        return userRoleFormat.encodeToJsonElement(this::class.serializer() as KSerializer<T>, this)
    }

    @OptIn(InternalSerializationApi::class)
    override fun serialize(encoder: Encoder, value: Role) {
        if (encoder is JsonEncoder) {
            if (value is UnknownRole) {
                encoder.encodeJsonElement(value.originalJson)
            } else {
                val valueSerializer = value::class.serializer()
                val type = serializers.keys.first { serializers[it] == valueSerializer }
                encoder.encodeJsonElement(
                    buildJsonObject {
                        put(keyField, type)
                        put(valueField, value.toJson())
                    }
                )
            }
        } else {
            encoder.encodeString(
                userRoleFormat.encodeToString(this, value)
            )
        }
    }

    fun <T : Role> includeSerializer(
        type: String,
        kSerializer: KSerializer<T>
    ) { serializers[type] = kSerializer }

    fun excludeSerializer(type: String) {
        serializers.remove(type)
    }
}
