package ch.dissem.yaep.ui.common

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
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.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import ch.dissem.yaep.domain.GameCell
import ch.dissem.yaep.domain.GameRow
import ch.dissem.yaep.domain.Grid
import ch.dissem.yaep.domain.Item
import ch.dissem.yaep.domain.ItemClass

@Composable
fun PuzzleGrid(
    modifier: Modifier = Modifier,
    selectDirectly: Boolean,
    spacing: Dp = 8.dp,
    grid: Grid,
    onUpdate: () -> Unit
) {
    Column(modifier = modifier) {
        for (row in grid) {
            PuzzleRow(
                row = row,
                onUpdate = onUpdate,
                onSnapshot = { grid.snapshot() },
                onUndo = { grid.undo() },
                spacing = spacing,
                selectDirectly = selectDirectly
            )
        }
    }
}

@Composable
private fun PuzzleRow(
    row: GameRow<ItemClass<*>>,
    onUpdate: () -> Unit,
    onSnapshot: () -> Unit,
    onUndo: () -> Boolean,
    spacing: Dp,
    selectDirectly: Boolean
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight()
    ) {
        val allOptions = row.options
        for (cell in row) {
            var selection by remember(cell) { mutableStateOf(cell.selection) }
            val options = remember(cell) {
                allOptions.map { Toggleable(it, cell.options.contains(it)) }
            }
            LaunchedEffect(cell) {
                cell.optionsChangedListeners.add { enabled ->
                    options.forEach { it.enabled = enabled.contains(it.item) }
                }
                cell.selectionChangedListeners.add {
                    selection = it
                    onUpdate()
                }
            }
            Selector(
                modifier = Modifier
                    .padding(spacing)
                    .weight(1f),
                spacing,
                selectDirectly = selectDirectly,
                options = options,
                onOptionRemoved = {
                    onSnapshot()
                    cell.options.remove(it)
                    row.cleanupOptions()
                },
                onOptionAdded = {
                    cell.options.add(it)
                },
                selectedItem = selection,
                onSelectItem = { selectedItem ->
                    onSelectItem(row, cell, options, selectedItem, onSnapshot, onUndo)
                }
            )
        }
    }
}

private fun onSelectItem(
    row: GameRow<ItemClass<*>>,
    cell: GameCell<ItemClass<*>>,
    options: List<Toggleable<Item<ItemClass<*>>>>,
    selectedItem: Item<ItemClass<*>>?,
    onSnapshot: () -> Unit,
    onUndo: () -> Boolean
) {
    if (selectedItem != null) {
        onSnapshot()
        cell.selection = selectedItem
        row.cleanupOptions()
    } else {
        while (cell.selection != null) {
            if (!onUndo()) break
        }
        options.forEach { option ->
            option.enabled = cell.options.contains(option.item)
        }
    }
}
