package moe.lava.neon.api.gateway

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import moe.lava.neon.api.ApiConstants
import moe.lava.neon.api.gateway.content.GuildSerializer
import moe.lava.neon.api.objects.Guild
import moe.lava.neon.api.objects.User

sealed interface Payload {
    val op: Int
    val d: Any?

    sealed interface WithSequence : Payload {
        val s: Int?
        val t: String?
    }

    @Serializable
    data class Incoming<T : Event.Incoming>(
        override val op: Int,
        override val d: T,
        override val s: Int?,
        override val t: String?,
    ) : WithSequence

    @Serializable
    data class Outgoing<T : Event.Outgoing>(
        override val op: Int,
        override val d: T,
    ) : Payload

    @Serializable
    data class Unknown(
        override val op: Int,
        override val d: JsonElement?,
        override val s: Int?,
        override val t: String?,
    ) : WithSequence
}

@Serializable
sealed interface Event {
    sealed interface Incoming : Event
    sealed interface Outgoing : Event
    sealed class Dispatch : Incoming

    // 1
    @JvmInline
    @Serializable
    value class Heartbeat(val lastSequence: Int?) : Incoming, Outgoing

    // 2
    @Serializable
    data class Identify(
        val token: String,
        val properties: ApiConstants.GatewayProperties = ApiConstants.GatewayProperties(),
        val capabilities: Int = Capability.from { listOf(
            LAZY_USER_NOTES,
            NO_AFFINE_USER_IDS,
            DEDUPE_USER_OBJECTS,
            USER_SETTINGS_PROTO,
            DEBOUNCE_MESSAGE_REACTIONS,
            CLIENT_STATE_V2,
        ) },
        // TODO: Client state v2
//        val clientState: ClientState,
    ) : Outgoing

    // 6
    @Serializable
    data class Resume(
        val token: String,
        val sessionId: String,
        val seq: Int,
    ) : Outgoing

    // 7
    @Serializable
    data object Reconnect : Incoming

    // 9
    @JvmInline
    @Serializable
    value class InvalidSession(val resumable: Boolean) : Incoming

    // 10
    @Serializable
    data class Hello(val heartbeatInterval: Int) : Incoming

    // 11
    @Serializable
    data object HeartbeatAck : Incoming

    // 40
    @Serializable
    data class QoSHeartbeat(
        val seq: Int?,
        val qos: QoSPayload = QoSPayload(),
    ) : Outgoing {
        @Serializable
        data class QoSPayload(
            val ver: Int = 27,
            val active: Boolean = true,
            val reasons: List<String> = listOf("foregrounded"),
        )
    }

    @Serializable
    data class Ready(
        val v: Int,
        val user: User,
        val guilds: List<@Serializable(GuildSerializer::class) Guild>,
        val sessionId: String,
        val resumeGatewayUrl: String,
//        val application: Application,
    ) : Dispatch()

    @Serializable
    data object Resumed : Dispatch()
}
