package wang.yaojia.webterm.api.models

import kotlinx.serialization.Serializable

/**
 * A client result union for the six guarded git-write ops (worktree create/remove/prune, git
 * stage/commit/push). It carries the server's SAFE body only — never raw git stderr (the server
 * classifies + sanitizes every failure, `src/http/git-ops.ts` / `worktrees.ts`, SEC-M10):
 *
 *  - [Ok] — a 200 with the op's route-specific payload [T].
 *  - [Rejected] — a 4xx/5xx with the server's inert `error` string ([message]) to display verbatim.
 *    403 is OVERLOADED (Origin-guard failure AND the disabled kill-switch both 403) so the client
 *    cannot tell them apart by status — it surfaces [message] inertly rather than inventing a typed
 *    variant (plan Edge cases / Security).
 *  - [RateLimited] — a 429 (stage/commit share one limiter, push a tighter one). Do NOT auto-retry.
 *
 * Nothing here throws on a bad body: a missing/garbled payload degrades to defaults (empty sha,
 * empty pruned list) rather than crashing (tolerant-decode discipline, plan §8).
 */
public sealed interface GitWriteOutcome<out T> {
    /** 200 — the op succeeded; [payload] is the route-specific success body. */
    public data class Ok<out T>(val payload: T) : GitWriteOutcome<T>

    /** A 4xx/5xx failure carrying the server's SAFE [message] (inert; may be null if unparseable). */
    public data class Rejected(val status: Int, val message: String?) : GitWriteOutcome<Nothing>

    /** 429 — the server rate-limited this write. */
    public data object RateLimited : GitWriteOutcome<Nothing>
}

// ── Per-op 200 payloads (all fields optional/defaulted → a garbled body degrades, never throws) ──

/** `POST /projects/git/stage` 200 → `{ ok, staged, count }`. */
@Serializable
public data class StageResult(val staged: Boolean = false, val count: Int = 0)

/** `POST /projects/git/commit` 200 → `{ ok, commit }` (short sha; may be `""` — empty is valid). */
@Serializable
public data class CommitResult(val commit: String = "")

/** `POST /projects/git/push` 200 → `{ ok, branch, remote }`. */
@Serializable
public data class PushResult(val branch: String? = null, val remote: String? = null)

/** `POST /projects/worktree` 200 → `{ ok, path, branch }`. */
@Serializable
public data class CreateWorktreeResult(val path: String? = null, val branch: String? = null)

/** `DELETE /projects/worktree` 200 → `{ ok, path }` (git's canonical removed path). */
@Serializable
public data class RemoveWorktreeResult(val path: String? = null)

/** `POST /projects/worktree/prune` 200 → `{ ok, pruned: [...] }` (empty = nothing to prune). */
@Serializable
public data class PruneWorktreesResult(val pruned: List<String> = emptyList())

/** Shape of a failure body — worktree routes emit `{ error }`, git-ops `{ ok:false, error }`; both
 *  carry `error` as a SAFE string. Decoded to surface [error] inertly. */
@Serializable
internal data class GitErrorBody(val ok: Boolean = false, val error: String? = null)

/**
 * Decode a guarded 200 body into [T], degrading a missing/garbled body to the payload's defaults
 * (never throws — the caller already knows the status is 200).
 */
internal fun <T> decodeGitPayload(bytes: ByteArray, deserializer: kotlinx.serialization.KSerializer<T>): T =
    LossyDecode.objectOrNull(bytes, deserializer) ?: ModelJson.decodeFromString(deserializer, "{}")

/** Read the SAFE `error` string from a failure body; null when the body is empty/unparseable. */
internal fun decodeGitError(bytes: ByteArray): String? =
    LossyDecode.objectOrNull(bytes, GitErrorBody.serializer())?.error
