package com.fabledsword.minstrel.update.data

/**
 * Terminal verdict from the platform on a self-update install (#2438).
 *
 * The old `ACTION_VIEW` handoff had no verdict at all — we fired an intent and
 * assumed. A [PackageInstaller][android.content.pm.PackageInstaller] session
 * reports back, so "declined" and "failed" stop looking identical.
 */
sealed interface InstallOutcome {
    /**
     * The platform completed the install.
     *
     * Rarely observed on a self-update: our process is replaced the moment the
     * new APK lands, so the coroutine awaiting this usually dies before it
     * resumes. Modelled anyway — silently relying on being killed would make
     * the success path invisible to anyone reading this.
     */
    data object Installed : InstallOutcome

    /** The user declined the platform's confirm dialog. Not an error. */
    data object Cancelled : InstallOutcome

    /** The platform refused. [reason] is its own message, where it gave one. */
    data class Failed(val reason: String?) : InstallOutcome
}

/**
 * Where an install has got to, for the two surfaces that show it: the shell's
 * [UpdateBanner][com.fabledsword.minstrel.update.ui.UpdateBanner] and the
 * Settings About card.
 *
 * DOWNLOADING and INSTALLING are deliberately distinct. They used to be one
 * state because the install half was fire-and-forget and took no time from our
 * side; now that we await the platform's verdict, collapsing them would leave
 * the UI claiming "Downloading…" through an install that can sit on a confirm
 * dialog indefinitely.
 */
enum class InstallStage { IDLE, DOWNLOADING, INSTALLING, ERROR }

/** True while an install is underway and a second tap should do nothing. */
fun InstallStage.isBusy(): Boolean =
    this == InstallStage.DOWNLOADING || this == InstallStage.INSTALLING

/**
 * The stage an outcome lands the UI in. A cancelled install returns to IDLE
 * rather than ERROR — the user chose it, so presenting it as a failure would
 * be a lie with a red tint.
 */
fun InstallOutcome.stage(): InstallStage = when (this) {
    InstallOutcome.Installed, InstallOutcome.Cancelled -> InstallStage.IDLE
    is InstallOutcome.Failed -> InstallStage.ERROR
}

/**
 * User-facing copy for an outcome; null when there is nothing worth saying.
 *
 * Lives beside the outcome rather than in either UI package because two
 * separate screens surface the same verdicts and must not drift — the same
 * reasoning that puts [ErrorCopy][com.fabledsword.minstrel.api.ErrorCopy]
 * outside the UI layer.
 */
fun InstallOutcome.message(): String? = when (this) {
    InstallOutcome.Installed -> null
    InstallOutcome.Cancelled -> "Update cancelled."
    is InstallOutcome.Failed -> reason?.let { "Couldn't install update: $it" }
        ?: "Couldn't install update."
}
