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

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.runicgateway.app.core.net.ShardStreamEvent
import com.runicgateway.app.data.api.dto.RulesetDto
import com.runicgateway.app.data.repository.ShardRepository
import com.runicgateway.app.ui.UiState
import com.runicgateway.app.ui.toShardUiState
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

/**
 * The shard ruleset (PLAN.md §9 M11, `docs/link/v3.md` §5): what this world is
 * configured to do — systems on/off, caps, account and housing limits, the champion
 * and Felucca tables, the save/restart schedule.
 *
 * Two states the screen must tell apart, which is why the success type is nullable:
 * a `null` body means the shard has **never published** a ruleset (the plugin is old,
 * or `RulesetEnabled=false`), while the feature being switched off is a 404 folded
 * into `ErrorKind.FEATURE_UNAVAILABLE`.
 *
 * Kept live by the `world.ruleset` frame, which the shard re-emits on every sidecar
 * reconnect — so a shard that restarts with edited config updates the open screen.
 */
@HiltViewModel
class RulesViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

    private val _state = MutableStateFlow<UiState<RulesetDto?>>(UiState.Loading)
    val state: StateFlow<UiState<RulesetDto?>> = _state.asStateFlow()

    init {
        load()
        collectLive()
    }

    fun load() {
        _state.value = UiState.Loading
        viewModelScope.launch {
            _state.value = repository.ruleset().toShardUiState()
        }
    }

    private fun collectLive() {
        viewModelScope.launch {
            repository.liveEvents().collect { event ->
                if (event !is ShardStreamEvent.Frame || event.kind != "world.ruleset") return@collect
                // The frame IS the whole ruleset — replace rather than merge. A frame the
                // app can't decode is skipped, leaving the loaded copy in place.
                repository.rulesetFrame(event.data)?.let { _state.value = UiState.Success(it) }
            }
        }
    }
}
