package theorycrafter.ui.widgets

import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.DropdownMenuState
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpRect
import androidx.compose.ui.unit.dp
import compose.input.*
import compose.utils.*
import compose.widgets.*
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.fiteditor.*
import theorycrafter.utils.AutoSuggest
import theorycrafter.utils.NoMatches
import theorycrafter.utils.onLostFocus
import theorycrafter.utils.thenIf


/**
 * A selection widget for letting the user select any type of items, using an [AutoSuggest].
 */
@Composable
fun <P> Selector(
    modifier: Modifier = Modifier,
    onItemSelected: (P) -> Unit,
    onEditingCancelled: () -> Unit,
    onBeforeItemChosen: (P) -> Boolean = { true },
    autoSuggest: AutoSuggest<P>,
    autoSuggestInputTransform: ((String) -> String)? = null,
    autoSuggestItemToString: ((P) -> String) = { "" },
    onValueChange: ((String) -> Unit)? = null,
    suggestedItemsFooter: @Composable (() -> Unit)? = null,
    suggestedItemContent: @Composable RowScope.(P, String) -> Unit = { _, text -> defaultTextItemContent(text) },
    autoSuggestHorizontalAnchorPadding: DpRect = DpRect.Zero,
    hint: String,
    selectNextPrimaryRow: () -> Unit,
) {
    val interactionSource = remember { MutableInteractionSource() }

    AutoSuggestTextField(
        autoSuggest = autoSuggest,
        itemToString = autoSuggestItemToString,
        onValueChange = onValueChange,
        onSuggestionChosen = { value, action ->
            onItemSelected(value)

            val pressedShortcut = (action as? SuggestionChosenAction.KeyPressed)?.shortcut
            if (pressedShortcut == FitEditorKeyShortcuts.ChooseSuggestedValueAndSelectNextRow)
                selectNextPrimaryRow()
        },
        inputTextTransform = autoSuggestInputTransform,
        onBeforeSuggestionChosen = onBeforeItemChosen,
        chooseSuggestedValueKeyShortcuts = FitEditorKeyShortcuts.ChooseSuggestedValue,
        modifier = modifier
            .requestInitialFocus()
            .onKeyShortcut(CancelEditingItem, onPreview = true) {
                onEditingCancelled()
            }
            .onKeyEvent { true },  // Consume all events to prevent e.g. W/S working while the item is edited.
        anchorPadding = DpRect(
            top = TheorycrafterTheme.spacing.xxsmall,
            bottom = TheorycrafterTheme.spacing.xxsmall,
            left = autoSuggestHorizontalAnchorPadding.left,
            right = autoSuggestHorizontalAnchorPadding.right,
        ),
        dropdownMenuDecorator = { suggestions, dropdownMenu ->
            Column {
                if ((suggestions != null) && suggestions.isEmpty())
                    NoMatches()
                else {
                    dropdownMenu(
                        Modifier.heightIn(max = TheorycrafterTheme.sizes.itemSelectorSuggestionDropdownMaxHeight)
                    )

                    if (!suggestions.isNullOrEmpty()) {
                        suggestedItemsFooter?.invoke()
                    }
                }
            }
        },
        suggestedItemContent = suggestedItemContent,
    ) { value, onTextValueChange, textFieldModifier ->
        val color = LocalContentColor.current
        val editedTextStyle = LocalTextStyle.current.copy(color = color)
        val placeholderTextStyle = editedTextStyle.copy(
            color = editedTextStyle.color.copy(alpha = 0.6f),
            fontWeight = FontWeight.ExtraLight
        )
        BasicTextField(
            value = value,
            onValueChange = onTextValueChange,
            modifier = textFieldModifier,
            singleLine = true,
            textStyle = editedTextStyle,
            cursorBrush = SolidColor(color),
            decorationBox = { innerTextField ->
                Box(contentAlignment = Alignment.CenterStart){
                    if (value.text.isEmpty())
                        Text(hint, style = placeholderTextStyle)
                    innerTextField()
                }
            },
            interactionSource = interactionSource
        )
    }

    interactionSource.onLostFocus {
        onEditingCancelled()
    }
}


/**
 * A row in a [SimpleGrid] that displays and allows editing any type of items.
 */
@Composable
fun GridScope.SlotRow(
    modifier: Modifier = Modifier,
    modifierWhenNotEditing: Modifier = Modifier,
    contextActions: List<SlotContextAction> = emptyList(),
    extraContextMenuContent: @Composable (ColumnScope.(closeMenu: () -> Unit) -> Unit)? = null,
    invalidityReason: String? = null,
    isEditingState: MutableState<Boolean> = remember { mutableStateOf(false) },
    editedRowContent: (@Composable (GridScope.GridRowScope.(onEditingCompleted: () -> Unit) -> Unit))?,
    slotContent: @Composable (GridScope.GridRowScope.() -> Unit)
) {
    val isEditable = editedRowContent != null
    val filteredContextActions = remember(contextActions) {
        // Trim separators from both ends
        contextActions
            .dropWhile { it == SlotContextAction.Separator }
            .dropLastWhile { it == SlotContextAction.Separator }
            .let {  // Remove consecutive duplicate separators
                it.filterIndexed { index, value ->
                    (index == 0) || (value != SlotContextAction.Separator) || (value != it[index - 1])
                }
            }
    }
    val contextActionsKeyModifier = remember(filteredContextActions) {
        contextActions.toModifier()
    }
    val contextMenuState by remember { mutableStateOf(DropdownMenuState()) }

    var isEditing by isEditingState
    row(
        modifier = modifier
            .fillMaxWidth()
            .highlightOnHover()
            .slotRowValidity(invalidityReason)
            .thenIf(isEditable && !isEditing) {
                // Add shortcuts only when not editing, because BasicTextField doesn't consume some key presses
                // Note that this needs to be above the padding modifier in order to catch clicks on the padding
                onMousePress(clickCount = ClickCount.DOUBLE) {
                    isEditing = true
                }
                .onKeyShortcut(FitEditorKeyShortcuts.EditItem) {
                    isEditing = true
                }
            }
            .thenIf(!isEditing) {
                val focusRequester = remember { FocusRequester() }
                bringIntoViewWhenFocusedWithMargins(
                    PaddingValues(
                        vertical = TheorycrafterTheme.sizes.fitEditorSlotRowHeight/2 +
                                TheorycrafterTheme.spacing.xxxsmall
                    )
                )
                    .focusRequester(focusRequester)
                    .focusable()
                    .requestFocusWhenSelected(focusRequester)
                    .focusWhenClicked(focusRequester)  // Needed to focus an already-selected slot
                    .then(contextActionsKeyModifier)
                    .then(modifierWhenNotEditing)
            }
            .thenIf(!isEditing) {
                // This must be before padding, so right-clicks on the padding are detected, but we must subtract the
                // padding because the node in which the menu is actually shown is offset by the padding.
                // and the ContextMenu is positioned correctly.
                val layoutDirection = LocalLayoutDirection.current
                val density = LocalDensity.current
                onOpenContextMenu(enabled = filteredContextActions.isNotEmpty()) { position ->
                    val positionInsidePadding = with(density) {
                        position.minus(
                            Offset(
                                x = SLOT_ROW_PADDING.calculateLeftPadding(layoutDirection).toPx(),
                                y = SLOT_ROW_PADDING.calculateTopPadding().toPx(),
                            )
                        )
                    }
                    contextMenuState.status = DropdownMenuState.Status.Open(positionInsidePadding)
                }
            }
            .padding(SLOT_ROW_PADDING)
            .height(TheorycrafterTheme.sizes.fitEditorSlotRowHeight)  // Make the height independent on editing
    ) {
        if (isEditing) {
            editedRowContent!! {
                isEditing = false
            }
        }
        else {
            slotContent()
        }

        ContextMenu(contextMenuState) {
            // Keep a remembered copy of the context actions in order to avoid being recomposed when one
            // of the actions changes the content of the slot and causes the list of context actions to change.
            val localContextActions = remember { filteredContextActions }
            val hasKeyShortcuts = localContextActions.any { it.shortcuts.isNotEmpty() }
            for (contextAction in localContextActions) {
                if (contextAction == SlotContextAction.Separator)
                    MenuSeparator()
                else
                    MenuItem(contextAction, reserveSpaceForKeyShortcut = hasKeyShortcuts, contextMenuState::close)
            }

            extraContextMenuContent?.invoke(this, contextMenuState::close)
        }
    }
}



/**
 * The key shortcut to cancel editing an item.
 */
private val CancelEditingItem = KeyShortcut(Key.Escape)



/**
 * The padding on each row of the grid.
 */
val SLOT_ROW_PADDING = PaddingValues(
    horizontal =  TheorycrafterTheme.spacing.horizontalEdgeMargin,
    vertical = 1.dp
)


/**
 * Returns the horizontal part of the [SLOT_ROW_PADDING].
 */
val HORIZONTAL_SLOT_ROW_PADDING = PaddingValues(
    horizontal =  TheorycrafterTheme.spacing.horizontalEdgeMargin,
)

