/*
 * 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.HouseDto
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 public houses board (PLAN.md §6.2): the "where are the falling houses" view —
 * loaded from `/public/shard/houses` (IDOC only, location only) and kept live by
 * `house.decay` frames. A house entering IDOC adds/updates its row; any other decay
 * transition (refreshed, collapsed) drops it. Rows are ordered by region.
 */
@HiltViewModel
class HousesViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

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

    private val _state = MutableStateFlow<UiState<List<HouseDto>>>(UiState.Loading)
    val state: StateFlow<UiState<List<HouseDto>>> = _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.houses()) {
                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) {
        if (frame.kind != "house.decay") return
        // Serials are opaque hex-string keys ("0x…"), not numbers — read as a string
        // (reading it as a Long silently dropped every live IDOC update).
        val serial = FrameFields.stringField(frame.data, "serial") ?: return
        // `to` is the new decay stage; only IDOC belongs on the public board.
        val stage = FrameFields.stringField(frame.data, "to")
            ?: FrameFields.stringField(frame.data, "stage")
        if (stage?.equals("IDOC", ignoreCase = true) == true) {
            board.upsert(
                HouseDto(
                    serial = serial,
                    name = FrameFields.stringField(frame.data, "name"),
                    region = FrameFields.stringField(frame.data, "region"),
                    map = FrameFields.stringField(frame.data, "map"),
                    x = FrameFields.longField(frame.data, "x")?.toInt(),
                    y = FrameFields.longField(frame.data, "y")?.toInt(),
                    z = FrameFields.longField(frame.data, "z")?.toInt(),
                    isIdoc = true,
                ),
            )
        } else {
            board.remove(serial.toString())
        }
        if (_state.value is UiState.Success) publish()
    }

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