/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui.shard

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.runicgateway.app.R
import com.runicgateway.app.data.api.dto.AtlasCreatureDto
import com.runicgateway.app.data.api.dto.AtlasPlaceDto
import com.runicgateway.app.data.api.dto.AtlasSpawnerDto
import com.runicgateway.app.ui.UiState
import com.runicgateway.app.ui.components.EmptyView
import com.runicgateway.app.ui.components.ErrorView
import com.runicgateway.app.ui.components.LoadingView
import com.runicgateway.app.ui.components.PillTone
import com.runicgateway.app.ui.components.SectionLabel
import com.runicgateway.app.ui.components.ShardCard
import com.runicgateway.app.ui.components.StatusPill

/**
 * The spawn atlas / bestiary (PLAN.md §9 M11): "where do I find X".
 *
 * The whole point of the feature is the placement transform the server does — a spawn
 * at 5411,1234 becomes *"Despise, Felucca"* — so a row leads with where a creature is
 * found, not with coordinates.
 */
@Composable
fun AtlasScreen(
    onOpenCreature: (String) -> Unit,
    modifier: Modifier = Modifier,
    viewModel: AtlasViewModel = hiltViewModel(),
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val query by viewModel.query.collectAsStateWithLifecycle()

    Column(modifier.fillMaxSize()) {
        OutlinedTextField(
            value = query,
            onValueChange = viewModel::onQueryChange,
            label = { Text(stringResource(R.string.atlas_search_label)) },
            singleLine = true,
            keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
            keyboardActions = KeyboardActions(onSearch = { viewModel.search() }),
            modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
        )

        when (val s = state) {
            is UiState.Loading -> LoadingView()
            is UiState.Error -> ErrorView(s.kind, onRetry = viewModel::load)
            is UiState.Success -> {
                if (s.data.creatures.isEmpty()) {
                    EmptyView(stringResource(R.string.atlas_empty))
                } else {
                    LazyColumn(
                        modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp),
                        verticalArrangement = Arrangement.spacedBy(8.dp),
                        contentPadding = androidx.compose.foundation.layout.PaddingValues(bottom = 16.dp),
                    ) {
                        items(s.data.creatures, key = { it.slug.orEmpty() }) { creature ->
                            CreatureCard(creature, onOpenCreature)
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun CreatureCard(creature: AtlasCreatureDto, onOpenCreature: (String) -> Unit) {
    val slug = creature.slug
    ShardCard(
        Modifier
            .fillMaxWidth()
            .then(if (slug != null) Modifier.clickable { onOpenCreature(slug) } else Modifier),
    ) {
        Column(Modifier.padding(16.dp)) {
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
                Text(
                    text = creature.name ?: slug.orEmpty(),
                    style = MaterialTheme.typography.titleSmall,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                    modifier = Modifier.weight(1f),
                )
                // `points` is a COUNT of spawners on this route; `spawners` is the list,
                // and only the detail route sends it.
                creature.points?.let {
                    Text(
                        text = pluralStringResource(R.plurals.atlas_spawner_count, it, it),
                        style = MaterialTheme.typography.labelSmall,
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                    )
                }
            }
            facetSummary(creature)?.let {
                Text(
                    text = it,
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
            }
        }
    }
}

/** One creature: every spawner, where it stands, and what shares its spawns. */
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun AtlasCreatureScreen(
    slug: String,
    modifier: Modifier = Modifier,
    viewModel: AtlasCreatureViewModel = hiltViewModel(),
) {
    LaunchedEffect(slug) { viewModel.load(slug) }
    val state by viewModel.state.collectAsStateWithLifecycle()

    when (val s = state) {
        is UiState.Loading -> LoadingView(modifier)
        is UiState.Error -> ErrorView(s.kind, onRetry = viewModel::retry, modifier = modifier)
        is UiState.Success -> {
            val creature = s.data
            LazyColumn(
                modifier = modifier.fillMaxSize().padding(horizontal = 16.dp),
                verticalArrangement = Arrangement.spacedBy(8.dp),
                contentPadding = androidx.compose.foundation.layout.PaddingValues(vertical = 16.dp),
            ) {
                item {
                    Column {
                        Text(
                            creature.name ?: creature.slug.orEmpty(),
                            style = MaterialTheme.typography.titleLarge,
                        )
                        creature.total?.let {
                            Text(
                                stringResource(R.string.atlas_total_alive, it),
                                style = MaterialTheme.typography.bodyMedium,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                            )
                        }
                        if (creature.facets.isNotEmpty()) {
                            FlowRow(
                                Modifier.padding(top = 8.dp),
                                horizontalArrangement = Arrangement.spacedBy(6.dp),
                                verticalArrangement = Arrangement.spacedBy(6.dp),
                            ) {
                                creature.facets.entries.sortedBy { it.key }.forEach { (facet, count) ->
                                    StatusPill(
                                        text = stringResource(R.string.atlas_facet_count, facet, count),
                                        tone = PillTone.Neutral,
                                    )
                                }
                            }
                        }
                    }
                }
                // The aggregate comes first: "where is it" is the question, and the
                // individual coordinates below are the follow-up. Same ordering as web.
                if (creature.places.isNotEmpty()) {
                    item { SectionLabel(stringResource(R.string.atlas_section_places)) }
                    items(
                        creature.places,
                        key = { "${it.facet.orEmpty()}:${it.label.orEmpty()}" },
                    ) { place ->
                        PlaceRow(place)
                    }
                }
                if (creature.spawners.isNotEmpty()) {
                    item { SectionLabel(stringResource(R.string.atlas_section_spawners)) }
                    items(creature.spawners, key = { it.id ?: it.hashCode().toLong() }) { spawner ->
                        SpawnerRow(spawner)
                    }
                    if (creature.spawnersTruncated) {
                        item {
                            Text(
                                stringResource(R.string.atlas_spawners_truncated),
                                style = MaterialTheme.typography.labelSmall,
                                color = MaterialTheme.colorScheme.onSurfaceVariant,
                            )
                        }
                    }
                }
                if (creature.alsoHere.isNotEmpty()) {
                    item { SectionLabel(stringResource(R.string.atlas_section_also_here)) }
                    item {
                        Text(
                            creature.alsoHere.mapNotNull { it.name ?: it.slug }.joinToString(", "),
                            style = MaterialTheme.typography.bodySmall,
                            color = MaterialTheme.colorScheme.onSurfaceVariant,
                        )
                    }
                }
            }
        }
    }
}

@Composable
private fun PlaceRow(place: AtlasPlaceDto) {
    Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
        Text(
            text = placeLabel(place),
            style = MaterialTheme.typography.bodyMedium,
        )
        val meta = listOfNotNull(
            place.facet,
            place.spawners?.let { pluralStringResource(R.plurals.atlas_spawner_count, it, it) },
            place.maxAlive?.let { stringResource(R.string.atlas_place_max_alive, it) },
        ).joinToString("  ·  ")
        if (meta.isNotBlank()) {
            Text(meta, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
        }
    }
}

@Composable
private fun SpawnerRow(spawner: AtlasSpawnerDto) {
    Column(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
        Text(
            text = spawnerPlace(spawner),
            style = MaterialTheme.typography.bodyMedium,
        )
        val meta = listOfNotNull(
            spawner.maxCount?.let { stringResource(R.string.atlas_max_count, it) },
            // Seconds, normalised server-side — the raw XmlSpawner values are minutes
            // OR seconds per record.
            formatRespawn(spawner.minDelay, spawner.maxDelay)
                ?.let { stringResource(R.string.atlas_respawn, it) },
        ).joinToString("  ·  ")
        if (meta.isNotBlank()) {
            Text(meta, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
        }
    }
}

// ── Pure helpers (unit-tested) ───────────────────────────────────────────────

/**
 * Where a spawner stands, preferring the server's own placement label — the
 * point-in-rect transform is what turns a coordinate into "Despise, Felucca" and is
 * the reason this feature exists. Falls back through region, landmark, and finally the
 * raw coordinates, which is honest rather than useless for the ~17% of spawns that
 * resolve to no named place.
 */
internal fun spawnerPlace(spawner: AtlasSpawnerDto): String {
    spawner.label?.takeIf { it.isNotBlank() }?.let { return it }
    val place = spawner.region ?: spawner.landmark
    val facet = spawner.facet
    return when {
        place != null && facet != null -> "$place, $facet"
        place != null -> place
        spawner.x != null && spawner.y != null ->
            listOfNotNull(facet, "${spawner.x}, ${spawner.y}").joinToString(" ")
        else -> facet.orEmpty()
    }
}

/**
 * The name of an aggregated place. [AtlasPlaceDto.label] is already the server's
 * resolved answer and falls back to "Wilderness" there, so the only case left here is
 * a place that carried no label at all — then the facet is better than nothing.
 */
internal fun placeLabel(place: AtlasPlaceDto): String =
    place.label?.takeIf { it.isNotBlank() } ?: place.facet.orEmpty()

/**
 * A creature's facets as one line, most spawners first — "where is it *mostly*" is the
 * question a search result answers.
 */
internal fun facetSummary(creature: AtlasCreatureDto): String? {
    if (creature.facets.isEmpty()) return null
    return creature.facets.entries
        .sortedByDescending { it.value }
        .joinToString(", ") { it.key }
}
