package com.fabledsword.minstrel.update.data

import com.fabledsword.minstrel.BuildConfig
import com.fabledsword.minstrel.di.ApplicationScope
import com.fabledsword.minstrel.models.UpdateInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

private const val POLL_INTERVAL_MS = 24 * 60 * 60 * 1000L

/**
 * Drives the shell's soft "update available" banner. Polls
 * `/api/client/version` at launch + every 24h and, when the bundled
 * APK is strictly newer than this build, exposes its [UpdateInfo] so
 * [com.fabledsword.minstrel.update.ui.UpdateBanner] can nudge an
 * install. Mirrors Flutter's `ClientUpdateController`.
 *
 * Dismissals are kept in memory and keyed by version, so dismissing
 * one release's banner still lets a later release re-surface — and a
 * restart re-shows it, which is acceptable nudging for v1 (matches
 * Flutter). Server 404 / network errors stay silent. Constructed at
 * launch via the construct-the-singleton trick in `MinstrelApplication`.
 */
@Singleton
class UpdateBannerController @Inject constructor(
    @ApplicationScope private val scope: CoroutineScope,
    private val repository: UpdateRepository,
) {
    private val latest = MutableStateFlow<UpdateInfo?>(null)
    private val dismissed = MutableStateFlow<Set<String>>(emptySet())

    /** The available update, or null when none / dismissed / unreachable. */
    val available: StateFlow<UpdateInfo?> =
        combine(latest, dismissed) { info, seen ->
            info?.takeIf { it.version !in seen }
        }.stateIn(scope, SharingStarted.Eagerly, null)

    init {
        scope.launch {
            while (true) {
                runOnce()
                delay(POLL_INTERVAL_MS)
            }
        }
    }

    fun dismiss(version: String) {
        dismissed.value = dismissed.value + version
    }

    private suspend fun runOnce() {
        val info = runCatching { repository.getLatest() }.getOrNull() ?: return
        latest.value = info.takeIf { isVersionNewer(it.version, BuildConfig.VERSION_NAME) }
    }
}
