/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.runicgateway.app.core.net.BaseUrlHolder
import com.runicgateway.app.core.push.PushManager
import com.runicgateway.app.core.result.ApiResult
import com.runicgateway.app.data.appearance.SiteAppearance
import com.runicgateway.app.data.repository.ConnectionRepository
import com.runicgateway.app.data.repository.SettingsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 * Top-level app gate (PLAN.md §3): decides whether the first-run connect screen
 * or the main UI shows, and holds the per-shard [SiteAppearance] the theme and
 * the drawer are built from. Activity-scoped so the whole app observes one state.
 */
@HiltViewModel
class AppViewModel @Inject constructor(
    private val connectionRepository: ConnectionRepository,
    private val settingsRepository: SettingsRepository,
    private val baseUrlHolder: BaseUrlHolder,
    private val pushManager: PushManager,
) : ViewModel() {

    sealed interface AppState {
        /** Restoring the saved base URL / loading branding. */
        data object Loading : AppState

        /** No shard site configured yet — show the connect screen. */
        data object NeedsConnection : AppState

        /**
         * A site is configured. [appearance] is [SiteAppearance.NONE] when settings
         * couldn't be loaded — the shipped app, still fully usable (§2).
         */
        data class Ready(val appearance: SiteAppearance) : AppState
    }

    private val _state = MutableStateFlow<AppState>(AppState.Loading)
    val state: StateFlow<AppState> = _state.asStateFlow()

    init {
        viewModelScope.launch {
            _state.value = if (connectionRepository.restore()) {
                AppState.Ready(loadAppearance())
            } else {
                AppState.NeedsConnection
            }
        }
    }

    /** Called by the connect screen once a site has been validated + saved. */
    fun onConnected() {
        viewModelScope.launch { _state.value = AppState.Ready(loadAppearance()) }
    }

    /**
     * Re-read the appearance while the app is already running — on resume, beside
     * the session's own re-validation (§5.5). An admin who re-skins the site from
     * a laptop and picks the phone up should see it.
     *
     * Best-effort, and silent either way: a failed refresh **keeps the last good
     * appearance** rather than dropping back to the shipped one, so a moment of
     * no connectivity does not repaint a themed shard. There is no loading state
     * and no error surface. Ignored unless a site is configured.
     */
    fun refreshAppearance() {
        if (_state.value !is AppState.Ready) return
        viewModelScope.launch {
            val settings = (settingsRepository.getSettings() as? ApiResult.Ok)?.data ?: return@launch
            pushManager.setNtfyUrl(settings.push.ntfyUrl)
            // changeServer() may have raced us back to the connect screen while the
            // call was in flight; don't resurrect Ready on top of it.
            if (_state.value is AppState.Ready) {
                _state.value = AppState.Ready(SiteAppearance.from(settings))
            }
        }
    }

    /** Settings → Server switch: hard reset back to the connect screen (§3). */
    fun changeServer() {
        viewModelScope.launch {
            connectionRepository.disconnect()
            _state.value = AppState.NeedsConnection
        }
    }

    /**
     * Load public settings for the appearance and feed the shard's push relay URL into
     * the [PushManager] (§11) — its arrival is what lets push re-register after a restart
     * or sign-in. Returns [SiteAppearance.NONE] if settings couldn't be loaded.
     */
    private suspend fun loadAppearance(): SiteAppearance {
        val settings = (settingsRepository.getSettings() as? ApiResult.Ok)?.data
        pushManager.setNtfyUrl(settings?.push?.ntfyUrl)
        return SiteAppearance.from(settings)
    }

    /**
     * Resolve a possibly site-relative asset path (branding logos, post images)
     * to an absolute URL against the configured base (§8: asset fields may be
     * site-relative). Absolute URLs pass through unchanged; null/blank stays null.
     */
    fun resolveAsset(path: String?): String? {
        if (path.isNullOrBlank()) return null
        val base = baseUrlHolder.current ?: return path
        return base.resolve(path)?.toString() ?: path
    }
}

