package wang.yaojia.webterm.api.models

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
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

/**
 * Why a [PrStatus] has (or lacks) PR data (`src/types.ts` `PrAvailability`). Drives the detail
 * chip's copy. Decoded via [PrAvailabilitySerializer]: an unknown/future value **degrades to
 * [ERROR]** (never throws) — a new server availability must never make the chip crash.
 */
public enum class PrAvailability(public val wire: String) {
    /** A PR exists for the current branch; the sibling fields are populated. */
    OK("ok"),

    /** gh works but the branch has no PR (or no remote/default repo). */
    NO_PR("no-pr"),

    /** `gh` binary not found on PATH (ENOENT). */
    NOT_INSTALLED("not-installed"),

    /** gh present but not logged in (needs `gh auth login`). */
    UNAUTHENTICATED("unauthenticated"),

    /** `GH_ENABLED=0` — feature off, never spawns gh. */
    DISABLED("disabled"),

    /** gh spawned but failed for another reason (timeout, etc.); also the unknown/missing fallback. */
    ERROR("error"),

    ;

    public companion object {
        /** Map the wire string; unknown → [ERROR] (mirror of the FE never treating non-`ok` as fatal). */
        public fun fromWire(wire: String): PrAvailability =
            entries.firstOrNull { it.wire == wire } ?: ERROR
    }
}

/**
 * Decode [PrAvailability] by its `wire` value; an unknown/future value maps to [PrAvailability.ERROR]
 * rather than throwing (mirror of [ClaudeStatusSerializer]). Serializes back the `wire` string.
 */
internal object PrAvailabilitySerializer : KSerializer<PrAvailability> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("PrAvailability", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): PrAvailability =
        PrAvailability.fromWire(decoder.decodeString())

    override fun serialize(encoder: Encoder, value: PrAvailability) =
        encoder.encodeString(value.wire)
}

/** Rolled-up CI check counts from gh's statusCheckRollup (`src/types.ts` `PrCheckSummary`). */
@Serializable
public data class PrCheckSummary(
    val total: Int = 0,
    val passing: Int = 0,
    val failing: Int = 0,
    val pending: Int = 0,
)

/**
 * `GET /projects/pr` result (`src/types.ts` `PrStatus`). Every field except [availability] is
 * optional (present only when `availability == ok`); [availability] itself defaults to
 * [PrAvailability.ERROR] so a body missing the field still decodes (never throws). `state` /
 * `mergeable` are lower-cased string unions on the wire — kept as raw INERT strings here (rendered
 * as plain text; no enum needed for display).
 */
@Serializable
public data class PrStatus(
    @Serializable(with = PrAvailabilitySerializer::class)
    val availability: PrAvailability = PrAvailability.ERROR,
    val number: Int? = null,
    val title: String? = null,
    val url: String? = null,
    val state: String? = null,
    val isDraft: Boolean? = null,
    val mergeable: String? = null,
    val headRefName: String? = null,
    val baseRefName: String? = null,
    val checks: PrCheckSummary? = null,
)
