package ch.dissem.yaep.ui.common

import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import ch.dissem.yaep.domain.Game
import ch.dissem.yaep.ui.common.focus.FocusFollowingFocusable
import ch.dissem.yaep.ui.common.focus.SelectionManager
import ch.dissem.yaep.ui.common.layout.AdaptiveGameLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlin.coroutines.CoroutineContext
import kotlin.time.ExperimentalTime

@Composable
@OptIn(ExperimentalTime::class)
fun App(
    modifier: Modifier = Modifier,
    rootSelectionManager: SelectionManager<FocusFollowingFocusable>,
    spacing: Dp,
    game: Game,
    onNewGame: () -> Unit,
    resetCluesBeacon: Any = Any()
) {
    val horizontalClues =
        remember(game, resetCluesBeacon) { game.horizontalClues.map { DisplayClue(it) } }
    val verticalClues =
        remember(game, resetCluesBeacon) { game.verticalClues.map { DisplayClue(it) } }
    val timer = remember(game) { GameTimer() }
    val time by timer.elapsedTime.collectAsState("00:00")
    var isSolved by remember(game) { mutableStateOf(false) }
    LaunchedEffect(game) {
        logGame(game)

        game.onStart {
            timer.start()
        }

        game.onSolved {
            timer.stop()
            isSolved = true
        }
    }

    Box(modifier = modifier) {
        AdaptiveGameLayout(
            modifier = Modifier.blurOnFinished(isSolved),
            selectionManager = rootSelectionManager,
            grid = { selectionManager ->
                PuzzleGrid(
                    selectionManager = selectionManager,
                    grid = game.grid,
                    spacing = spacing,
                    onUpdate = {
                        horizontalClues.forEach { it.update(game.grid) }
                        verticalClues.forEach { it.update(game.grid) }
                    }
                )
            },
            horizontalClues = { selectionManager ->
                for (clue in horizontalClues) {
                    HorizontalClue(
                        modifier = Modifier
                            .focus(remember(selectionManager) {
                                selectionManager.add(
                                    primaryAction = {
                                        clue.isActive = !clue.isActive
                                    },
                                    secondaryAction = {
                                        clue.isActive = false
                                    }
                                )
                            })
                            .forClue(clue, spacing),
                        spacing = spacing,
                        clue = clue.clue,
                        isClueViolated = clue.isViolated
                    )
                }
            },
            verticalClues = { selectionManager ->
                if (verticalClues.isNotEmpty()) {
                    for (clue in verticalClues) {
                        VerticalClue(
                            modifier = Modifier
                                .focus(remember(selectionManager) {
                                    selectionManager.add(
                                        primaryAction = {
                                            clue.isActive = !clue.isActive
                                        },
                                        secondaryAction = {
                                            clue.isActive = false
                                        }
                                    )
                                })
                                .forClue(clue, spacing),
                            spacing = spacing,
                            clue = clue.clue,
                            isClueViolated = clue.isViolated
                        )
                    }
                }
            },
            time = {
                Text(
                    time,
                    fontSize = TextUnit(4f, TextUnitType.Em),
                    textAlign = TextAlign.End
                )
            },
            game, resetCluesBeacon
        )
        EndOfGame(isSolved = isSolved, time = time, onRestart = onNewGame)
    }
}

expect fun CoroutineScope.logGame(game: Game, dispatcher: CoroutineContext = Dispatchers.IO)
