/*
 * 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.AtlasCreatureDto
import com.runicgateway.app.data.api.dto.AtlasCreaturePageDto
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 spawn atlas / bestiary (PLAN.md §9 M11, `docs/link/v3.md` §6): where each
 * creature spawns, derived server-side from the shard's own data files.
 *
 * Static shard **content**, not live state — it does not go offline with the sidecar,
 * and it lives under `/public/atlas`, not `/public/shard`. Unlike the shard routes it
 * IS site-mode gated, so a site in maintenance withholds it independently.
 */
@HiltViewModel
class AtlasViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

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

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

    private val _facet = MutableStateFlow<String?>(null)
    val facet: StateFlow<String?> = _facet.asStateFlow()

    /**
     * The facets this shard actually has. Discovered from the atlas itself — a shard
     * may add, replace or rename facets when its maps change, so nothing here may name
     * one (`v3.md` §6.1 R2).
     */
    private val _facets = MutableStateFlow<List<String>>(emptyList())
    val facets: StateFlow<List<String>> = _facets.asStateFlow()

    init {
        load()
    }

    fun onQueryChange(value: String) {
        _query.value = value
    }

    fun onFacetChange(value: String?) {
        if (value == _facet.value) return
        _facet.value = value
        search()
    }

    fun search() = load()

    fun load() {
        _state.value = UiState.Loading
        viewModelScope.launch {
            val page = repository.atlasCreatures(query = _query.value, facet = _facet.value)
            if (page is ApiResult.Ok && _facets.value.isEmpty()) {
                // Only the first successful page needs to establish the filter options;
                // a filtered page would otherwise narrow them to its own results.
                _facets.value = page.data.creatures
                    .flatMap { it.facets.keys }
                    .distinct()
                    .sorted()
            }
            _state.value = page.toShardUiState()
        }
    }
}

/** One creature's detail page: every spawner, and what else shares them. */
@HiltViewModel
class AtlasCreatureViewModel @Inject constructor(
    private val repository: ShardRepository,
) : ViewModel() {

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

    private var slug: String? = null

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

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

/**
 * A respawn delay as text. **The API carries SECONDS** — XmlSpawner stores minutes
 * except when a delay doesn't divide into whole minutes, and the server's parser
 * normalises the two spellings so a `5` is never ambiguous here (`v3.md` §6.3).
 *
 * Pure, so the unit conversion is unit-tested rather than eyeballed on a page.
 */
internal fun formatRespawn(minSeconds: Int?, maxSeconds: Int?): String? {
    val lo = minSeconds ?: maxSeconds ?: return null
    val hi = maxSeconds ?: minSeconds ?: return null
    return if (lo == hi) humaniseSeconds(lo) else "${humaniseSeconds(lo)}–${humaniseSeconds(hi)}"
}

private fun humaniseSeconds(seconds: Int): String = when {
    seconds < 60 -> "${seconds}s"
    seconds % 60 == 0 -> "${seconds / 60}m"
    else -> "${seconds / 60}m ${seconds % 60}s"
}
