/*
 * 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.core.result.ApiResult
import com.runicgateway.app.data.api.dto.ChampDto
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 champion-spawn board (PLAN.md §6.2): loaded once from `/public/shard/champs`,
 * then kept live by merging `champ.update` / `champ.remove` frames in place. Rows
 * are ordered by category then name for a stable display.
 */
@HiltViewModel
class ChampsViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

    private val board = LiveBoard<ChampDto> { it.serial.toString() }

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

    private val _connected = MutableStateFlow(false)
    val connected: StateFlow<Boolean> = _connected.asStateFlow()

    init {
        load()
        collectLive()
    }

    fun load() {
        _state.value = UiState.Loading
        viewModelScope.launch {
            when (val result = repository.champs()) {
                is ApiResult.Ok -> {
                    board.seed(result.data)
                    publish()
                }
                else -> _state.value = result.toShardUiState()
            }
        }
    }

    private fun collectLive() {
        viewModelScope.launch {
            repository.liveEvents().collect { event ->
                when (event) {
                    is ShardStreamEvent.Open -> _connected.value = true
                    is ShardStreamEvent.Closed -> _connected.value = false
                    is ShardStreamEvent.Frame -> applyFrame(event)
                }
            }
        }
    }

    private fun applyFrame(frame: ShardStreamEvent.Frame) {
        when (frame.kind) {
            "champ.update" -> repository.champFrame(frame.data)?.let { board.upsert(it) }
            // Serial is an opaque hex-string key ("0x…"), not a number — read as a
            // string (reading it as a Long silently dropped every champ.remove).
            "champ.remove" -> FrameFields.stringField(frame.data, "serial")?.let { board.remove(it) }
            else -> return
        }
        // Only republish when the board actually changed (Success state only).
        if (_state.value is UiState.Success) publish()
    }

    private fun publish() {
        val rows = board.values().sortedWith(
            compareBy({ it.category ?: "" }, { it.name ?: it.type ?: "" }),
        )
        _state.value = UiState.Success(rows)
    }
}
