/*
 * 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.result.ApiResult
import com.runicgateway.app.data.api.dto.MarketMetaDto
import com.runicgateway.app.data.api.dto.MarketPageDto
import com.runicgateway.app.data.api.dto.MarketVendorDto
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-wide player-vendor marketplace (PLAN.md §9 M11, `docs/link/v3.md` §8):
 * search every shop's stock at once.
 *
 * **Not live, on purpose.** The `market` feature ships with its SSE fan-out disabled —
 * a firehose of full vendor inventories would be the site's biggest bandwidth consumer
 * and no screen needs it live — so this is a plain paginated read. It is also the
 * first genuinely **rate-limited** public endpoint, which is why the query is applied
 * on submit rather than on every keystroke.
 *
 * The staleness stamp from [meta] is not decoration: the shard sweeps vendors
 * round-robin, so a listing can legitimately be a full cycle behind, and a screen that
 * implied live prices would send someone to an item that sold twenty minutes ago.
 */
@HiltViewModel
class MarketViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

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

    private val _meta = MutableStateFlow<MarketMetaDto?>(null)
    val meta: StateFlow<MarketMetaDto?> = _meta.asStateFlow()

    private val _query = MutableStateFlow("")
    val query: StateFlow<String> = _query.asStateFlow()

    private val _sort = MutableStateFlow(ShardRepository.SORT_PRICE_ASC)
    val sort: StateFlow<String> = _sort.asStateFlow()

    init {
        load()
    }

    fun onQueryChange(value: String) {
        // Bounded to what the server accepts, so an over-long query is trimmed here
        // rather than bounced as a 400.
        _query.value = value.take(MAX_QUERY)
    }

    fun onSortChange(value: String) {
        if (value == _sort.value) return
        _sort.value = value
        search()
    }

    /** Run the current query. Called on submit, not per keystroke — this endpoint is rate-limited. */
    fun search() = load()

    fun load() {
        _state.value = UiState.Loading
        viewModelScope.launch {
            // Meta is secondary: the staleness banner and filter options are worth
            // having, but a failure there must not blank the results.
            _meta.value = (repository.marketMeta() as? ApiResult.Ok)?.data
            _state.value = repository.market(
                query = _query.value,
                sort = _sort.value,
            ).toShardUiState()
        }
    }

    companion object {
        /** The server rejects a longer `q`. */
        const val MAX_QUERY = 60
    }
}

/**
 * One shop and its stock. The only surface that can render the two states a result
 * list cannot: a [MarketVendorDto.truncated] shop, and a location an admin has gated
 * away — which is a real answer ("the shard doesn't publish where this is") rather
 * than an empty coordinate.
 */
@HiltViewModel
class MarketVendorViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

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

    private var serial: String? = null

    fun load(serial: String) {
        this.serial = serial
        _state.value = UiState.Loading
        viewModelScope.launch {
            _state.value = repository.marketVendor(serial).toShardUiState()
        }
    }

    fun retry() {
        serial?.let { load(it) }
    }
}
