package ru.durakscore.app

import android.content.Context
import android.content.Intent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.util.Date
import kotlinx.coroutines.delay
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlin.random.Random

data class FinalGamePreview(
    val id: String,
    val type: String,
    val targetScore: Int,
    val players: List<GamePlayer>,
    val scores: Map<String, Int>,
    val history: List<String>,
    val startedAt: Timestamp?,
    val loserId: String
)

private data class FinalGameData(
    val id: String,
    val type: String,
    val status: String,
    val targetScore: Int,
    val players: List<GamePlayer>,
    val scores: Map<String, Int>,
    val history: List<String>,
    val startedAt: Timestamp?,
    val finishedAt: Timestamp?,
    val loserId: String?,
    val loserName: String?,
    val loserGender: String?
)

private data class FinalReactionRecord(
    val emoji: String,
    val senderName: String?
)

private data class FinalGameStats(
    val cleanestNames: String,
    val cleanestScore: Int,
    val longestStreakName: String?,
    val longestStreak: Int,
    val totalReactions: Int,
    val emotionalNames: String?,
    val emotionalCount: Int,
    val favoriteReaction: String?,
    val favoriteReactionCount: Int,
    val calmestNames: String?,
    val durationText: String,
    val pointsGiven: Int
)

private data class FinalGameTitle(
    val icon: String,
    val title: String,
    val playerNames: String,
    val detail: String
)

private fun ServerLiveGame.toFinalGameData(): FinalGameData {

    val effectiveScores =
        if (
            status ==
            "finished" &&
            finalScores.isNotEmpty()
        ) {
            finalScores
        } else {
            scores
        }

    return FinalGameData(
        id =
            id,
        type =
            gameType,
        status =
            status,
        targetScore =
            targetScore,
        players =
            players.map {
                    player ->

                GamePlayer(
                    id =
                        player.id,
                    name =
                        player.name,
                    gender =
                        player.gender,
                    isCore =
                        player.isCore,
                    isGuest =
                        player.isGuest
                )
            },
        scores =
            effectiveScores,
        history =
            history,
        startedAt =
            startedAtEpochMillis
                .takeIf {
                    it > 0L
                }
                ?.let {
                    Timestamp(
                        Date(
                            it
                        )
                    )
                },
        finishedAt =
            finishedAtEpochMillis
                ?.takeIf {
                    it > 0L
                }
                ?.let {
                    Timestamp(
                        Date(
                            it
                        )
                    )
                },
        loserId =
            loserId,
        loserName =
            loserName,
        loserGender =
            loserGender
    )
}


private fun ServerFinishedGame.toFinalGameData(): FinalGameData {

    return FinalGameData(
        id =
            id,
        type =
            gameType,
        status =
            "finished",
        targetScore =
            targetScore,
        players =
            players.map {
                    player ->

                GamePlayer(
                    id =
                        player.id,
                    name =
                        player.name,
                    gender =
                        player.gender,
                    isCore =
                        player.isCore,
                    isGuest =
                        player.isGuest
                )
            },
        scores =
            finalScores,
        history =
            history,
        startedAt =
            startedAtEpochMillis
                .takeIf {
                    it > 0L
                }
                ?.let {
                    Timestamp(
                        Date(
                            it
                        )
                    )
                },
        finishedAt =
            finishedAtEpochMillis
                .takeIf {
                    it > 0L
                }
                ?.let {
                    Timestamp(
                        Date(
                            it
                        )
                    )
                },
        loserId =
            loserId,
        loserName =
            loserName,
        loserGender =
            loserGender
    )
}


private fun FinalGamePreview.toData(): FinalGameData {

    val loser =
        players.firstOrNull {
            it.id == loserId
        }

    return FinalGameData(
        id = id,
        type = type,
        status = "active",
        targetScore = targetScore,
        players = players,
        scores = scores,
        history = history,
        startedAt = startedAt,
        finishedAt = null,
        loserId = loserId,
        loserName = loser?.name,
        loserGender = loser?.gender
    )
}

private fun calculateFinalStats(
    game: FinalGameData,
    reactions: List<FinalReactionRecord>
): FinalGameStats {

    val minScore =
        game.players
            .minOfOrNull {
                game.scores[it.id]
                    ?: 0
            }
            ?: 0

    val cleanestNames =
        game.players
            .filter {
                (game.scores[it.id] ?: 0) ==
                        minScore
            }
            .joinToString(" и ") {
                it.name
            }
            .ifBlank {
                "—"
            }

    val currentStreak =
        game.players.associate {
            it.id to 0
        }
            .toMutableMap()

    val bestStreak =
        game.players.associate {
            it.id to 0
        }
            .toMutableMap()

    game.history.forEach { loserId ->

        game.players.forEach { player ->

            if (
                player.id ==
                loserId
            ) {
                currentStreak[player.id] =
                    0
            } else {

                val next =
                    (currentStreak[player.id]
                        ?: 0) + 1

                currentStreak[player.id] =
                    next

                if (
                    next >
                    (bestStreak[player.id]
                        ?: 0)
                ) {
                    bestStreak[player.id] =
                        next
                }
            }
        }
    }

    val longestEntry =
        bestStreak
            .maxByOrNull {
                it.value
            }

    val longestPlayer =
        longestEntry
            ?.key
            ?.let { id ->
                game.players
                    .firstOrNull {
                        it.id == id
                    }
            }

    val emojiCounts =
        reactions
            .groupingBy {
                it.emoji
            }
            .eachCount()

    val favoriteReaction =
        GAME_REACTION_EMOJIS
            .maxByOrNull {
                emojiCounts[it]
                    ?: 0
            }
            ?.takeIf {
                (emojiCounts[it] ?: 0) > 0
            }

    val senderCounts =
        reactions
            .mapNotNull {
                it.senderName
                    ?.trim()
                    ?.takeIf { name ->
                        name.isNotBlank()
                    }
            }
            .groupingBy {
                it
            }
            .eachCount()

    val maxSenderCount =
        senderCounts.values
            .maxOrNull()
            ?: 0

    val emotionalNames =
        senderCounts
            .filterValues {
                it == maxSenderCount
            }
            .keys
            .sorted()
            .joinToString(" и ")
            .takeIf {
                it.isNotBlank()
            }

    val playerReactionCounts =
        game.players.associate { player ->
            player.name to
                    (
                        senderCounts[player.name]
                            ?: 0
                        )
        }

    val calmestCount =
        playerReactionCounts.values
            .minOrNull()
            ?: 0

    val calmestNames =
        if (
            reactions.isNotEmpty() &&
            playerReactionCounts.isNotEmpty()
        ) {
            playerReactionCounts
                .filterValues {
                    it == calmestCount
                }
                .keys
                .joinToString(" и ")
                .takeIf {
                    it.isNotBlank()
                }
        } else {
            null
        }

    val durationText =
        formatGameDuration(
            startedAt =
                game.startedAt,
            finishedAt =
                game.finishedAt
        )

    return FinalGameStats(
        cleanestNames =
            cleanestNames,
        cleanestScore =
            minScore,
        longestStreakName =
            longestPlayer?.name,
        longestStreak =
            longestEntry?.value
                ?: 0,
        totalReactions =
            reactions.size,
        emotionalNames =
            emotionalNames,
        emotionalCount =
            maxSenderCount,
        favoriteReaction =
            favoriteReaction,
        favoriteReactionCount =
            favoriteReaction
                ?.let {
                    emojiCounts[it]
                }
                ?: 0,
        calmestNames =
            calmestNames,
        durationText =
            durationText,
        pointsGiven =
            game.history.size
    )
}

private fun formatGameDuration(
    startedAt: Timestamp?,
    finishedAt: Timestamp?
): String {

    if (startedAt == null) {
        return "—"
    }

    val endMillis =
        finishedAt
            ?.toDate()
            ?.time
            ?: System.currentTimeMillis()

    val totalMinutes =
        (
            (endMillis -
                    startedAt.toDate().time)
                .coerceAtLeast(0L) /
                    60_000L
            )
            .toInt()

    val hours =
        totalMinutes / 60

    val minutes =
        totalMinutes % 60

    return when {
        hours > 0 &&
        minutes > 0 ->
            "$hours ч $minutes мин"

        hours > 0 ->
            "$hours ч"

        else ->
            "$minutes мин"
    }
}

private val CLASSIC_LOSER_ROASTS =
    listOf(
        "{name} забрал {target} очков и заслуженно отправляется нахуй со званием дурака.",

        "{name} всю катку старательно собирал +1. Собрал {target}. Пиздец какая целеустремлённость.",

        "{name} первым добрался до {target}. Жаль, что это единственная гонка, которую стоило проиграть.",

        "Сегодня колода выбрала жертву: {name}. Итог — {target} очков и почётное звание дурака.",

        "{name} тащил катку как мог. Правда, строго в сторону собственного поражения. {target} из {target} — безупречно.",

        "{name} пришёл с планом. Судя по {target} очкам, план был обосраться максимально убедительно.",

        "{name} официально закрыл сбор очков: {target} штук. Остальные играли в карты, а тут шёл накопительный счёт.",

        "{name} дошёл до {target} быстрее, чем до понимания, что происходит. Дурак определён, расходимся.",

        "Финальный ответ стола: {name}. {target} очков — улики железобетонные, адвокат уже не поможет.",

        "{name} сегодня доказал главное: проигрывать тоже можно стабильно. {target} очков — аплодисменты этому пиздецу."
    )

private fun loserRoast(
    loserName: String,
    targetScore: Int,
    gameType: String,
    classicRoastIndex: Int
): String {

    if (
        gameType !=
        "classic"
    ) {
        return "$loserName забрал $targetScore очков и заслуженно отправляется нахуй со званием дурака."
    }

    val template =
        CLASSIC_LOSER_ROASTS[
            classicRoastIndex
                .mod(
                    CLASSIC_LOSER_ROASTS.size
                )
        ]

    return template
        .replace(
            "{name}",
            loserName
        )
        .replace(
            "{target}",
            targetScore.toString()
        )
}

private fun buildResultFact(
    game: FinalGameData,
    stats: FinalGameStats
): String {

    return when {

        stats.emotionalNames != null &&
        stats.emotionalCount >= 8 ->

            "${stats.emotionalNames} отправил(а) ${stats.emotionalCount} реакций. Человек играл не только картами, но и нервной системой."

        stats.longestStreakName != null &&
        stats.longestStreak >= 7 ->

            "${stats.longestStreakName} продержался(ась) ${stats.longestStreak} раздач без +1. Стол пытался, но не очень успешно."

        stats.totalReactions >= 20 ->

            "За катку прилетело ${stats.totalReactions} реакций. Спокойной эту компанию назвать сложно."

        else -> {

            val loserName =
                game.loserName
                    ?: game.players
                        .firstOrNull {
                            it.id ==
                            game.loserId
                        }
                        ?.name
                    ?: "Кто-то"

            "Очков раздали ${stats.pointsGiven}. Больше всего эта статистика почему-то не радует $loserName."
        }
    }
}


private fun buildGameTitles(
    game: FinalGameData,
    stats: FinalGameStats,
    loserName: String
): List<FinalGameTitle> {

    val titles =
        mutableListOf<FinalGameTitle>()

    titles.add(
        FinalGameTitle(
            icon = "💩",
            title = "Почётный долбоёб катки",
            playerNames = loserName,
            detail =
                "${game.targetScore} очков. Старался как мог — получилось хуже всех."
        )
    )

    if (
        stats.longestStreakName != null &&
        stats.longestStreak >
        0
    ) {
        titles.add(
            FinalGameTitle(
                icon = "🧱",
                title = "Хуй пробьёшь",
                playerNames =
                    stats.longestStreakName,
                detail =
                    "${stats.longestStreak} раздач без +1."
            )
        )
    }

    if (
        stats.emotionalNames != null &&
        stats.emotionalCount >
        0
    ) {
        titles.add(
            FinalGameTitle(
                icon = "🤬",
                title = "Ебаный пульт эмоций",
                playerNames =
                    stats.emotionalNames,
                detail =
                    "${stats.emotionalCount} реакций за одну катку."
            )
        )
    }

    if (
        stats.cleanestNames.isNotBlank()
    ) {
        titles.add(
            FinalGameTitle(
                icon = "👑",
                title = "Чистый, аж бесит",
                playerNames =
                    stats.cleanestNames,
                detail =
                    "Всего ${stats.cleanestScore} очков."
            )
        )
    }

    if (
        stats.calmestNames != null &&
        stats.totalReactions >
        0
    ) {
        titles.add(
            FinalGameTitle(
                icon = "🗿",
                title = "Похуй на происходящее",
                playerNames =
                    stats.calmestNames,
                detail =
                    "Меньше всех дёргал кнопки реакций."
            )
        )
    }

    return titles
}

private fun shareFinalResult(
    context: Context,
    game: FinalGameData,
    loserName: String,
    roast: String,
    stats: FinalGameStats,
    titles: List<FinalGameTitle>
) {

    val scoreText =
        game.players
            .joinToString(
                separator = "\n"
            ) {
                    player ->

                val score =
                    game.scores[player.id]
                        ?: 0

                val loserMark =
                    if (
                        player.id ==
                        game.loserId
                    ) {
                        " 💩"
                    } else {
                        ""
                    }

                "${player.name}: $score${loserMark}"
            }

    val titlesText =
        titles
            .take(5)
            .joinToString(
                separator = "\n"
            ) {
                    title ->
                "${title.icon} ${title.title}: ${title.playerNames}"
            }

    val reactionText =
        if (
            stats.totalReactions >
            0
        ) {
            "\n💥 Реакций: ${stats.totalReactions}"
        } else {
            ""
        }

    val shareText =
        """
        ♠ ДУРАКОМЕТР — ИТОГИ КАТКИ

        💩 $loserName — ДУРАК
        $roast

        Итоговый счёт:
        $scoreText

        Титулы:
        $titlesText

        ⏱ ${stats.durationText}$reactionText
        """.trimIndent()

    val intent =
        Intent(
            Intent.ACTION_SEND
        ).apply {
            type =
                "text/plain"

            putExtra(
                Intent.EXTRA_TEXT,
                shareText
            )
        }

    context.startActivity(
        Intent.createChooser(
            intent,
            "Поделиться итогами"
        )
    )
}

@Composable
fun FinalGameScreen(
    gameId: String,
    preview: FinalGamePreview? = null,
    showResultControls: Boolean,
    canConfirmResult: Boolean,
    isFinishingResult: Boolean,
    onConfirmResult: () -> Unit,
    onUndoLastPoint: () -> Unit,
    onResultRolledBack: () -> Unit,
    onBackToMenu: () -> Unit
) {

    val context =
        LocalContext.current

    var serverGame by remember(
        gameId
    ) {
        mutableStateOf<FinalGameData?>(
            null
        )
    }

    var reactions by remember(
        gameId
    ) {
        mutableStateOf<List<FinalReactionRecord>>(
            emptyList()
        )
    }

    var loadError by remember(
        gameId
    ) {
        mutableStateOf<String?>(
            null
        )
    }

    /*
     * Нужен только для последовательного выбора подъёба:
     * 1-я классическая катка -> фраза 1,
     * 2-я -> фраза 2,
     * ...
     * 10-я -> фраза 10,
     * 11-я снова -> фраза 1.
     *
     * Считаем только завершённые classic-игры текущего TEST_MODE.
     */
    var finishedClassicGames by remember(
        gameId
    ) {
        mutableStateOf<List<FinalGameData>>(
            emptyList()
        )
    }

    val currentUser =
        DurakAuthSession.current()

    DisposableEffect(
        gameId,
        currentUser?.uid
    ) {

        var active =
            true

        val user =
            currentUser

        if (
            user ==
            null
        ) {
            loadError =
                "Пользователь не авторизован"

            onDispose {
                active =
                    false
            }

        } else {

            /*
             * V1.7.1:
             * Финальный экран больше не читает Firestore.
             *
             * Пока партия ещё active и последнее очко уже достигло лимита,
             * берём её из live snapshot.
             * После подтверждения finish тот же gameId приходит как
             * lastFinishedGame — экран плавно переключается на сохранённый
             * результат без перезапуска.
             */
            val gamePolling =
                DurakServerApi
                    .startLivePolling(
                        user =
                            user,
                        isTest =
                            TEST_MODE,
                        intervalMs =
                            450L,
                        onSnapshot = {
                                snapshot ->

                            if (
                                !active
                            ) {
                                return@startLivePolling
                            }

                            val matchingGame =
                                when {

                                    snapshot
                                        .activeGame
                                        ?.id ==
                                        gameId ->

                                        snapshot
                                            .activeGame

                                    snapshot
                                        .lastFinishedGame
                                        ?.id ==
                                        gameId ->

                                        snapshot
                                            .lastFinishedGame

                                    else ->
                                        null
                                }

                            if (
                                matchingGame !=
                                null
                            ) {
                                serverGame =
                                    matchingGame
                                        .toFinalGameData()

                                loadError =
                                    null
                            }
                        },
                        onError = {
                                message ->

                            if (
                                active &&
                                serverGame ==
                                null
                            ) {
                                loadError =
                                    message.ifBlank {
                                        "Не удалось загрузить результат"
                                    }
                            }
                        }
                    )

            val reactionsPolling =
                DurakServerApi
                    .startReactionPolling(
                        user =
                            user,
                        gameId =
                            gameId,
                        intervalMs =
                            450L,
                        onSnapshot = {
                                serverReactions ->

                            if (
                                active
                            ) {
                                reactions =
                                    serverReactions
                                        .map {
                                                reaction ->

                                            FinalReactionRecord(
                                                emoji =
                                                    reaction.emoji,
                                                senderName =
                                                    reaction.senderName
                                            )
                                        }
                            }
                        },
                        onError = {
                            /*
                             * Реакции вторичны для финального экрана:
                             * ошибка их загрузки не должна оставлять
                             * весь экран на вечном спиннере.
                             */
                        }
                    )

            DurakServerApi
                .loadFinishedGames(
                    user =
                        user,
                    onSuccess = {
                            games ->

                        if (
                            active
                        ) {
                            finishedClassicGames =
                                games
                                    .asSequence()
                                    .filter {
                                        it.isTest ==
                                            TEST_MODE
                                    }
                                    .filter {
                                        it.gameType ==
                                            "classic"
                                    }
                                    .map {
                                        it.toFinalGameData()
                                    }
                                    .toList()
                        }
                    },
                    onError = {
                        /*
                         * Список старых игр нужен только для последовательной
                         * шуточной фразы. Сам итог текущей партии от него
                         * не зависит.
                         */
                    }
                )

            onDispose {
                active =
                    false

                gamePolling.remove()
                reactionsPolling.remove()
            }
        }
    }

    val serverHasResult =
        serverGame
            ?.let { game ->

                game.status ==
                    "finished" ||
                        game.scores
                            .values
                            .any {
                                it >=
                                game.targetScore
                            }
            }
            ?: false

    val game =
        when {
            serverHasResult ->
                serverGame

            preview != null ->
                preview.toData()

            else ->
                serverGame
        }

    /*
     * Наблюдатель уже перешёл на финальный экран,
     * но ведущий отменил последнее очко.
     * Возвращаем наблюдателя обратно к live-столу.
     */
    LaunchedEffect(
        serverGame?.status,
        serverGame?.scores,
        serverGame?.targetScore,
        preview
    ) {

        val current =
            serverGame
                ?: return@LaunchedEffect

        val stillHasLoser =
            current.scores
                .values
                .any {
                    it >=
                    current.targetScore
                }

        if (
            preview == null &&
            current.status != "finished" &&
            !stillHasLoser
        ) {
            delay(500L)
            onResultRolledBack()
        }
    }

    if (
        game == null
    ) {

        PokerScreen {

            PokerHeader(
                title =
                    "Итоги катки",
                subtitle =
                    "Загружаю результат"
            )

            Spacer(
                modifier =
                    Modifier.height(26.dp)
            )

            Row(
                modifier =
                    Modifier.fillMaxWidth(),
                horizontalArrangement =
                    Arrangement.Center
            ) {
                CircularProgressIndicator(
                    color =
                        PokerPalette.Gold
                )
            }

            if (
                loadError != null
            ) {
                Spacer(
                    modifier =
                        Modifier.height(18.dp)
                )

                Text(
                    text =
                        loadError!!,
                    color =
                        PokerPalette.Danger
                )
            }
        }

        return
    }

    val loser =
        game.players
            .firstOrNull {
                it.id ==
                (
                    game.loserId
                        ?: game.scores
                            .entries
                            .firstOrNull {
                                it.value >=
                                game.targetScore
                            }
                            ?.key
                    )
            }

    val loserName =
        game.loserName
            ?: loser?.name
            ?: "Игрок"

    val stats =
        remember(
            game,
            reactions
        ) {
            calculateFinalStats(
                game =
                    game,
                reactions =
                    reactions
            )
        }

    /*
     * Берём количество завершённых classic-игр,
     * которые начались РАНЬШЕ текущей.
     *
     * Это даёт одинаковую последовательность на всех телефонах
     * даже в момент, когда наблюдатель уже увидел финал,
     * а ведущий ещё только подтверждает завершение.
     */
    val classicRoastIndex =
        remember(
            game.id,
            game.type,
            game.startedAt,
            finishedClassicGames
        ) {

            if (
                game.type !=
                "classic"
            ) {
                0
            } else {

                val currentStartedMillis =
                    game.startedAt
                        ?.toDate()
                        ?.time
                        ?: Long.MAX_VALUE

                finishedClassicGames
                    .count { previous ->

                        previous.id !=
                            game.id &&
                            (
                                previous.startedAt
                                    ?.toDate()
                                    ?.time
                                    ?: Long.MIN_VALUE
                                ) <
                                currentStartedMillis
                    }
            }
        }

    val roast =
        remember(
            game.id,
            game.type,
            loserName,
            game.targetScore,
            classicRoastIndex
        ) {
            loserRoast(
                loserName =
                    loserName,
                targetScore =
                    game.targetScore,
                gameType =
                    game.type,
                classicRoastIndex =
                    classicRoastIndex
            )
        }

    val resultFact =
        remember(
            game,
            stats
        ) {
            buildResultFact(
                game =
                    game,
                stats =
                    stats
            )
        }

    val gameTitles =
        remember(
            game,
            stats,
            loserName
        ) {
            buildGameTitles(
                game =
                    game,
                stats =
                    stats,
                loserName =
                    loserName
            )
        }

    var celebrationPlayed by remember(
        gameId
    ) {
        mutableStateOf(false)
    }

    var showCelebration by remember(
        gameId
    ) {
        mutableStateOf(false)
    }

    LaunchedEffect(
        loserName
    ) {

        if (
            loserName != "Игрок" &&
            !celebrationPlayed
        ) {
            celebrationPlayed = true
            showCelebration = true

            delay(10_000L)

            showCelebration = false
        }
    }

    Box(
        modifier =
            Modifier.fillMaxSize()
    ) {

        PokerScreen {

            PokerHeader(
                title =
                    "Итоги катки",
                subtitle =
                    if (
                        game.status ==
                        "finished"
                    ) {
                        "Результат сохранён"
                    } else {
                        "Фиксируем последнее очко"
                    }
            )

            Spacer(
                modifier =
                    Modifier.height(16.dp)
            )

            PokerPanel {

                PokerBadge(
                    text =
                        if (
                            game.status ==
                            "finished"
                        ) {
                            "ПАРТИЯ ОКОНЧЕНА"
                        } else {
                            "ЕСТЬ ДУРАК"
                        }
                )

                Spacer(
                    modifier =
                        Modifier.height(14.dp)
                )

                Text(
                    text =
                        "$loserName — ДУРАК",
                    color =
                        PokerPalette.Gold,
                    fontSize =
                        32.sp,
                    fontWeight =
                        FontWeight.Black
                )

                Spacer(
                    modifier =
                        Modifier.height(10.dp)
                )

                Text(
                    text =
                        roast,
                    color =
                        PokerPalette.TextPrimary,
                    fontSize =
                        18.sp,
                    fontWeight =
                        FontWeight.SemiBold
                )
            }

            Spacer(
                modifier =
                    Modifier.height(18.dp)
            )

            Text(
                text =
                    "Итоговый счёт",
                color =
                    PokerPalette.Gold,
                fontSize =
                    20.sp,
                fontWeight =
                    FontWeight.Bold
            )

            Spacer(
                modifier =
                    Modifier.height(10.dp)
            )

            game.players.forEach { player ->

                FinalScoreRow(
                    player =
                        player,
                    score =
                        game.scores[player.id]
                            ?: 0,
                    isLoser =
                        player.id ==
                        (
                            game.loserId
                                ?: loser?.id
                            )
                )

                Spacer(
                    modifier =
                        Modifier.height(10.dp)
                )
            }

            Spacer(
                modifier =
                    Modifier.height(8.dp)
            )

            Text(
                text =
                    "Титулы катки",
                color =
                    PokerPalette.Gold,
                fontSize =
                    20.sp,
                fontWeight =
                    FontWeight.Bold
            )

            Spacer(
                modifier =
                    Modifier.height(
                        10.dp
                    )
            )

            PokerPanel {

                gameTitles.forEach {
                        title ->

                    FinalStatLine(
                        icon =
                            title.icon,
                        title =
                            title.title,
                        value =
                            title.playerNames
                    )

                    Text(
                        text =
                            title.detail,
                        color =
                            PokerPalette.TextSecondary,
                        fontSize =
                            13.sp
                    )

                    Spacer(
                        modifier =
                            Modifier.height(
                                8.dp
                            )
                    )
                }
            }

            Spacer(
                modifier =
                    Modifier.height(
                        18.dp
                    )
            )

            Text(
                text =
                    "Что было за катку",
                color =
                    PokerPalette.Gold,
                fontSize =
                    20.sp,
                fontWeight =
                    FontWeight.Bold
            )

            Spacer(
                modifier =
                    Modifier.height(10.dp)
            )

            PokerPanel {

                FinalStatLine(
                    icon = "🏆",
                    title = "Самый чистый",
                    value =
                        "${stats.cleanestNames} — ${stats.cleanestScore}"
                )

                if (
                    stats.longestStreakName != null
                ) {
                    FinalStatLine(
                        icon = "🔥",
                        title = "Самая длинная серия",
                        value =
                            "${stats.longestStreakName} — ${stats.longestStreak} без +1"
                    )
                }

                if (
                    stats.emotionalNames != null
                ) {
                    FinalStatLine(
                        icon = "🤬",
                        title = "Самый эмоциональный",
                        value =
                            "${stats.emotionalNames} — ${stats.emotionalCount} реакций"
                    )
                }

                if (
                    stats.favoriteReaction != null
                ) {
                    FinalStatLine(
                        icon =
                            stats.favoriteReaction,
                        title =
                            "Реакция катки",
                        value =
                            "${stats.favoriteReaction} × ${stats.favoriteReactionCount}"
                    )
                }

                if (
                    stats.calmestNames != null
                ) {
                    FinalStatLine(
                        icon = "🧘",
                        title = "Самый спокойный",
                        value =
                            stats.calmestNames
                    )
                }

                FinalStatLine(
                    icon = "💥",
                    title = "Всего реакций",
                    value =
                        stats.totalReactions
                            .toString()
                )

                FinalStatLine(
                    icon = "🎯",
                    title = "Очков раздали",
                    value =
                        stats.pointsGiven
                            .toString()
                )

                FinalStatLine(
                    icon = "⏱",
                    title = "Длительность",
                    value =
                        stats.durationText
                )
            }

            Spacer(
                modifier =
                    Modifier.height(14.dp)
            )

            PokerPanel {

                Text(
                    text =
                        resultFact,
                    color =
                        PokerPalette.TextPrimary,
                    fontSize =
                        17.sp,
                    fontWeight =
                        FontWeight.SemiBold
                )
            }

            Spacer(
                modifier =
                    Modifier.height(18.dp)
            )

            if (
                game.status ==
                "finished"
            ) {

                PokerSecondaryButton(
                    text =
                        "📤  Поделиться итогами",
                    onClick = {
                        shareFinalResult(
                            context =
                                context,
                            game =
                                game,
                            loserName =
                                loserName,
                            roast =
                                roast,
                            stats =
                                stats,
                            titles =
                                gameTitles
                        )
                    }
                )

                Spacer(
                    modifier =
                        Modifier.height(
                            10.dp
                        )
                )
            }

            if (
                game.status != "finished" &&
                showResultControls
            ) {

                PokerPrimaryButton(
                    text =
                        if (
                            isFinishingResult
                        ) {
                            "Сохраняю результат..."
                        } else if (
                            !canConfirmResult
                        ) {
                            "Сохраняю последнее очко..."
                        } else {
                            "Завершить партию"
                        },
                    onClick =
                        onConfirmResult,
                    enabled =
                        canConfirmResult &&
                        !isFinishingResult
                )

                Spacer(
                    modifier =
                        Modifier.height(10.dp)
                )

                PokerSecondaryButton(
                    text =
                        "↶  Отменить последнее очко",
                    onClick =
                        onUndoLastPoint,
                    enabled =
                        !isFinishingResult
                )

            } else if (
                game.status != "finished"
            ) {

                PokerPanel {

                    Text(
                        text =
                            "Ведущий фиксирует результат…",
                        color =
                            PokerPalette.Gold,
                        fontSize =
                            16.sp,
                        fontWeight =
                            FontWeight.Bold
                    )
                }

            } else {

                PokerPrimaryButton(
                    text =
                        "В меню",
                    onClick =
                        onBackToMenu
                )
            }

            Spacer(
                modifier =
                    Modifier.height(20.dp)
            )

            Text(
                text =
                    "♠   ♥   ♦   ♣",
                color =
                    PokerPalette.GoldDark,
                fontSize =
                    20.sp,
                fontWeight =
                    FontWeight.Bold,
                modifier =
                    Modifier.align(
                        Alignment.CenterHorizontally
                    )
            )
        }

        AnimatedVisibility(
            visible =
                showCelebration,
            enter =
                fadeIn(
                    tween(120)
                ) +
                scaleIn(
                    initialScale = 0.96f,
                    animationSpec =
                        tween(180)
                ),
            exit =
                fadeOut(
                    tween(320)
                ) +
                scaleOut(
                    targetScale = 1.03f,
                    animationSpec =
                        tween(320)
                )
        ) {

            FinalCelebrationOverlay(
                gameId =
                    game.id,
                loserName =
                    loserName,
                roast =
                    roast
            )
        }
    }
}

@Composable
private fun FinalScoreRow(
    player: GamePlayer,
    score: Int,
    isLoser: Boolean
) {

    PokerPanel {

        Row(
            modifier =
                Modifier.fillMaxWidth(),
            horizontalArrangement =
                Arrangement.SpaceBetween,
            verticalAlignment =
                Alignment.CenterVertically
        ) {

            Column {

                Text(
                    text =
                        if (
                            player.isGuest
                        ) {
                            "${player.name} · гость"
                        } else {
                            player.name
                        },
                    color =
                        if (
                            isLoser
                        ) {
                            PokerPalette.Gold
                        } else {
                            PokerPalette.TextPrimary
                        },
                    fontSize =
                        21.sp,
                    fontWeight =
                        FontWeight.Bold
                )

                if (
                    isLoser
                ) {
                    Spacer(
                        modifier =
                            Modifier.height(5.dp)
                    )

                    PokerBadge(
                        text =
                            "ДУРАК"
                    )
                }
            }

            Text(
                text =
                    score.toString(),
                color =
                    if (
                        isLoser
                    ) {
                        PokerPalette.Gold
                    } else {
                        PokerPalette.TextPrimary
                    },
                fontSize =
                    34.sp,
                fontWeight =
                    FontWeight.Black
            )
        }
    }
}

@Composable
private fun FinalStatLine(
    icon: String,
    title: String,
    value: String
) {

    Row(
        modifier =
            Modifier
                .fillMaxWidth()
                .padding(
                    vertical = 7.dp
                ),
        horizontalArrangement =
            Arrangement.SpaceBetween,
        verticalAlignment =
            Alignment.CenterVertically
    ) {

        Text(
            text =
                "$icon  $title",
            color =
                PokerPalette.TextSecondary,
            fontSize =
                15.sp,
            modifier =
                Modifier.weight(1f)
        )

        Text(
            text =
                value,
            color =
                PokerPalette.TextPrimary,
            fontSize =
                15.sp,
            fontWeight =
                FontWeight.Bold,
            textAlign =
                TextAlign.End,
            modifier =
                Modifier.weight(1f)
        )
    }
}

private data class CelebrationParticle(
    val symbol: String,
    val startX: Float,
    val drift: Float,
    val delayMs: Long,
    val durationMs: Int,
    val fontSize: Int,
    val rotation: Float
)

@Composable
private fun FinalCelebrationOverlay(
    gameId: String,
    loserName: String,
    roast: String
) {

    val titleScale =
        remember {
            Animatable(0.72f)
        }

    val titleAlpha =
        remember {
            Animatable(0f)
        }

    val blockInteractionSource =
        remember {
            MutableInteractionSource()
        }

    LaunchedEffect(gameId) {

        titleAlpha.animateTo(
            targetValue = 1f,
            animationSpec =
                tween(180)
        )

        titleScale.animateTo(
            targetValue = 1.08f,
            animationSpec =
                tween(260)
        )

        titleScale.animateTo(
            targetValue = 1f,
            animationSpec =
                tween(160)
        )
    }

    Box(
        modifier =
            Modifier
                .fillMaxSize()
                .background(
                    Color(
                        0xEE020705
                    )
                )
                .clickable(
                    interactionSource =
                        blockInteractionSource,
                    indication =
                        null
                ) {
                    // Первые 10 секунд финал не пропускаем.
                }
    ) {

        FinalConfettiStorm(
            seed =
                gameId.hashCode()
        )

        Column(
            modifier =
                Modifier
                    .align(
                        Alignment.Center
                    )
                    .padding(
                        horizontal = 24.dp
                    )
                    .graphicsLayer {
                        scaleX =
                            titleScale.value
                        scaleY =
                            titleScale.value
                        alpha =
                            titleAlpha.value
                    },
            horizontalAlignment =
                Alignment.CenterHorizontally
        ) {

            Text(
                text =
                    "ПАРТИЯ ОКОНЧЕНА",
                color =
                    PokerPalette.Gold,
                fontSize =
                    18.sp,
                fontWeight =
                    FontWeight.Bold
            )

            Spacer(
                modifier =
                    Modifier.height(14.dp)
            )

            Text(
                text =
                    "$loserName\nДУРАК",
                color =
                    PokerPalette.TextPrimary,
                fontSize =
                    46.sp,
                lineHeight =
                    50.sp,
                textAlign =
                    TextAlign.Center,
                fontWeight =
                    FontWeight.Black
            )

            Spacer(
                modifier =
                    Modifier.height(18.dp)
            )

            Text(
                text =
                    roast,
                color =
                    PokerPalette.Gold,
                fontSize =
                    19.sp,
                lineHeight =
                    25.sp,
                textAlign =
                    TextAlign.Center,
                fontWeight =
                    FontWeight.SemiBold
            )

            Spacer(
                modifier =
                    Modifier.height(24.dp)
            )

            Text(
                text =
                    "итоги появятся через несколько секунд",
                color =
                    PokerPalette.TextSecondary,
                fontSize =
                    12.sp
            )
        }
    }
}

@Composable
private fun FinalConfettiStorm(
    seed: Int
) {

    val symbols =
        listOf(
            "🎉",
            "✨",
            "♠",
            "♥",
            "♦",
            "♣"
        )

    val particles =
        remember(seed) {

            val random =
                Random(seed)

            List(48) {

                CelebrationParticle(
                    symbol =
                        symbols[
                            random.nextInt(
                                symbols.size
                            )
                        ],
                    startX =
                        random.nextFloat(),
                    drift =
                        (
                            random.nextFloat() -
                            0.5f
                        ) * 0.28f,
                    delayMs =
                        random.nextLong(
                            0L,
                            2_500L
                        ),
                    durationMs =
                        random.nextInt(
                            5_000,
                            8_000
                        ),
                    fontSize =
                        random.nextInt(
                            18,
                            36
                        ),
                    rotation =
                        if (
                            random.nextBoolean()
                        ) {
                            540f
                        } else {
                            -540f
                        }
                )
            }
        }

    BoxWithConstraints(
        modifier =
            Modifier.fillMaxSize()
    ) {

        val width =
            constraints.maxWidth
                .toFloat()

        val height =
            constraints.maxHeight
                .toFloat()

        particles.forEachIndexed {
                index,
                particle ->

            val progress =
                remember(
                    seed,
                    index
                ) {
                    Animatable(0f)
                }

            LaunchedEffect(
                seed,
                index
            ) {

                delay(
                    particle.delayMs
                )

                progress.animateTo(
                    targetValue =
                        1f,
                    animationSpec =
                        tween(
                            durationMillis =
                                particle.durationMs,
                            easing =
                                LinearEasing
                        )
                )
            }

            val x =
                (
                    particle.startX +
                        particle.drift *
                        progress.value
                    ) *
                    width

            val y =
                -80f +
                    (
                        height +
                        160f
                    ) *
                    progress.value

            Text(
                text =
                    particle.symbol,
                fontSize =
                    particle.fontSize.sp,
                modifier =
                    Modifier
                        .offset {
                            IntOffset(
                                x =
                                    x.roundToInt(),
                                y =
                                    y.roundToInt()
                            )
                        }
                        .graphicsLayer {
                            rotationZ =
                                particle.rotation *
                                progress.value

                            alpha =
                                if (
                                    progress.value >
                                    0.90f
                                ) {
                                    (
                                        1f -
                                        progress.value
                                    ) / 0.10f
                                } else {
                                    1f
                                }
                                    .coerceIn(
                                        0f,
                                        1f
                                    )
                        }
            )
        }
    }
}
