/*
 * 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.GuildDto
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 guild board (PLAN.md §6.2): loaded from `/public/shard/guilds`, kept live by
 * `guild.update` / `guild.remove` frames. `guild.join` is a membership tick that
 * carries no board snapshot, so it is ignored here (the roster count refreshes on
 * the next `guild.update`). Rows are ordered by name.
 */
@HiltViewModel
class GuildsViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

    private val board = LiveBoard<GuildDto> { it.id.toString() }

    private val _state = MutableStateFlow<UiState<List<GuildDto>>>(UiState.Loading)
    val state: StateFlow<UiState<List<GuildDto>>> = _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.guilds()) {
                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) {
            "guild.update" -> repository.guildFrame(frame.data)?.let { board.upsert(it) }
            "guild.remove" -> FrameFields.longField(frame.data, "id")?.let { board.remove(it.toString()) }
            else -> return
        }
        if (_state.value is UiState.Success) publish()
    }

    private fun publish() {
        val rows = board.values().sortedBy { it.name?.lowercase() ?: "" }
        _state.value = UiState.Success(rows)
    }
}
