package theorycrafter.ui.widgets

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import compose.utils.Padding
import compose.widgets.FlatButtonWithText
import compose.widgets.SingleLineText
import theorycrafter.ui.TheorycrafterTheme


/**
 * The button to set the initial focus on.
 */
enum class DialogInitiallyFocusedButton {

    None,
    Dismiss,
    Confirm

}


/**
 * The [InnerDialog] padding.
 */
val InnerDialogPadding = Padding(
    top = TheorycrafterTheme.spacing.xlarge,
    bottom = TheorycrafterTheme.spacing.small,
    start = TheorycrafterTheme.spacing.horizontalEdgeMargin,
    end = TheorycrafterTheme.spacing.horizontalEdgeMargin
)


/**
 * The title padding.
 */
private val TitlePadding = InnerDialogPadding.copy(top = 0.dp, bottom = 0.dp)


/**
 * The button panel padding.
 */
private val ButtonPanelPadding = InnerDialogPadding.copy(top = 0.dp, bottom = 0.dp)


/**
 * A dialog displayed in the center of its window.
 */
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun InnerDialog(
    title: String? = null,
    confirmText: String? = null,
    dismissText: String? = "Cancel",
    onConfirm: (() -> Unit)? = null,
    onDismiss: () -> Unit,
    confirmEnabled: Boolean = true,
    modifier: Modifier = Modifier,
    contentPadding: PaddingValues = Padding(start = InnerDialogPadding.start, end = InnerDialogPadding.end),
    initiallyFocusedButton: DialogInitiallyFocusedButton = DialogInitiallyFocusedButton.None,
    extraButtons: (@Composable RowScope.() -> Unit)? = null,
    content: @Composable () -> Unit
) {
    Dialog(
        onDismissRequest = onDismiss,
        properties = DialogProperties(
            dismissOnClickOutside = false,
            scrimColor = TheorycrafterTheme.colors.innerDialogScrim()
        )
    ) {
        Surface(
            elevation = 6.dp,
            shape = TheorycrafterTheme.dialogShape,
            modifier = Modifier
                .width(IntrinsicSize.Max)
                .then(modifier),
        ) {
            Column(
                modifier = Modifier
                    .padding(
                        top = InnerDialogPadding.top,
                        bottom = InnerDialogPadding.bottom
                    ),
                verticalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.larger)
            ) {
                // Title
                if (title != null) {
                    SingleLineText(
                        text = title,
                        fontWeight = FontWeight.Bold,
                        softWrap = false,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(TitlePadding)
                        )
                }

                // Content
                Box(modifier = Modifier.padding(contentPadding)) {
                    content()
                }

                // Button panel
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(ButtonPanelPadding),
                    horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.large, Alignment.End)
                ) {
                    val inputModeManager = LocalInputModeManager.current

                    if (dismissText != null) {
                        val dismissFocusRequester = remember { FocusRequester() }
                        FlatButtonWithText(
                            text = dismissText,
                            onClick = onDismiss,
                            modifier = Modifier.focusRequester(dismissFocusRequester)
                        )

                        if (initiallyFocusedButton == DialogInitiallyFocusedButton.Dismiss) {
                            LaunchedEffect(Unit){
                                dismissFocusRequester.requestFocus()
                                inputModeManager.requestInputMode(InputMode.Keyboard)  // Shows the focus indication
                            }
                        }
                    }

                    extraButtons?.invoke(this)

                    if (confirmText != null) {
                        if (onConfirm == null)
                            throw IllegalArgumentException("onConfirm callback cannot be null with non-null confirmText")
                        val confirmFocusRequester = remember { FocusRequester() }
                        FlatButtonWithText(
                            text = confirmText,
                            enabled = confirmEnabled,
                            onClick = {
                                onConfirm()
                                onDismiss()
                            },
                            modifier = Modifier.focusRequester(confirmFocusRequester)
                        )

                        if (initiallyFocusedButton == DialogInitiallyFocusedButton.Confirm) {
                            LaunchedEffect(Unit){
                                confirmFocusRequester.requestFocus()
                                inputModeManager.requestInputMode(InputMode.Keyboard)  // Shows the focus indication
                            }
                        }
                    }
                }
            }
        }
    }
}


/**
 * A dialog asking the user to confirm an action.
 */
@Composable
fun ConfirmationDialog(
    title: String? = null,
    text: String,
    confirmText: String,
    dismissText: String = "Cancel",
    onConfirm: () -> Unit,
    onDismiss: () -> Unit
) {
    InnerDialog(
        title = title,
        confirmText = confirmText,
        dismissText = dismissText,
        onDismiss = onDismiss,
        onConfirm = onConfirm,
        confirmEnabled = true,
        initiallyFocusedButton = DialogInitiallyFocusedButton.Confirm,
    ) {
        SelectionContainer {
            Text(
                text = text,
                modifier = Modifier.widthIn(min = 200.dp, max = 400.dp)
            )
        }
    }
}


/**
 * A dialog alerting the user to some error.
 */
@Composable
fun ErrorDialog(
    title: String? = null,
    text: String,
    onDismiss: () -> Unit,
    focusConfirmButton: Boolean = true
) {
    InnerDialog(
        title = title,
        confirmText = "Close",
        dismissText = null,
        onDismiss = onDismiss,
        onConfirm = onDismiss,
        confirmEnabled = true,
        initiallyFocusedButton = if (focusConfirmButton)
            DialogInitiallyFocusedButton.Confirm
        else
            DialogInitiallyFocusedButton.None,
    ) {
        SelectionContainer {
            Text(
                text = text,
                modifier = Modifier.widthIn(min = 200.dp, max = 400.dp)
            )
        }
    }
}


/**
 * A dialog informing the user of something.
 */
@Composable
fun InfoDialog(
    title: String? = null,
    text: String,
    onDismiss: () -> Unit,
    modifier: Modifier = Modifier,
    focusConfirmButton: Boolean = true,
    extraButtons: (@Composable RowScope.() -> Unit)? = null,
) {
    InnerDialog(
        title = title,
        confirmText = "Close",
        dismissText = null,
        onDismiss = onDismiss,
        onConfirm = onDismiss,
        confirmEnabled = true,
        modifier = modifier,
        initiallyFocusedButton = if (focusConfirmButton)
            DialogInitiallyFocusedButton.Confirm
        else
            DialogInitiallyFocusedButton.None,
        extraButtons = extraButtons,
    ) {
        SelectionContainer {
            Text(
                text = text,
                modifier = Modifier.widthIn(min = 200.dp, max = 400.dp)
            )
        }
    }
}


/**
 * A dialog telling the user some action is in progress, allowing him to cancel it.
 */
@Composable
fun ActionInProgressDialog(
    title: String? = null,
    text: String,
    stopActionText: String = "Cancel",
    onStopAction: () -> Unit
) {
    InnerDialog(
        title = title,
        confirmText = stopActionText,
        dismissText = null,
        onDismiss = onStopAction,
        onConfirm = onStopAction,
        confirmEnabled = true,
        initiallyFocusedButton = DialogInitiallyFocusedButton.Confirm,
    ) {
        Text(
            text = text,
            modifier = Modifier.widthIn(min = 200.dp, max = 400.dp)
        )
    }
}


/**
 * The interface for objects that allow showing various standard dialogs.
 */
interface StandardDialogs {


    /**
     * Shows an [ErrorDialog] with the given message.
     */
    fun showErrorDialog(message: String, title: String? = null, focusConfirmButton: Boolean = true)


    /**
     * Shows an [InfoDialog] with the given message.
     */
    fun showInfoDialog(message: String, title: String? = null, focusConfirmButton: Boolean = true)


    /**
     * Shows a [ConfirmationDialog] with the given message and confirmation button text.
     */
    fun showConfirmDialog(text: String, confirmText: String, title: String? = null, onConfirm: () -> Unit)


}


/**
 * The composition local for a [StandardDialogs].
 */
val LocalStandardDialogs = compositionLocalOf<StandardDialogs> { error("No StandardDialogs provided") }


/**
 * Provides a [LocalStandardDialogs].
 */
@Composable
fun ProvideStandardDialogs(content: @Composable () -> Unit) {

    class DialogState(val title: String?, val message: String, val focusConfirmButton: Boolean)
    class ConfirmDialogState(val title: String?, val message: String, val confirmText: String, val onConfirm: () -> Unit)

    var errorDialogState: DialogState? by remember { mutableStateOf(null) }
    var infoDialogState: DialogState? by remember { mutableStateOf(null) }
    var confirmDialogState: ConfirmDialogState? by remember { mutableStateOf(null) }

    val standardDialogs: StandardDialogs = remember {
        object: StandardDialogs {

            override fun showErrorDialog(message: String, title: String?, focusConfirmButton: Boolean) {
                errorDialogState = DialogState(title, message, focusConfirmButton)
            }

            override fun showInfoDialog(message: String, title: String?, focusConfirmButton: Boolean) {
                infoDialogState = DialogState(title, message, focusConfirmButton)
            }

            override fun showConfirmDialog(text: String, confirmText: String, title: String?, onConfirm: () -> Unit) {
                confirmDialogState = ConfirmDialogState(title, text, confirmText, onConfirm)
            }

        }
    }

    CompositionLocalProvider(LocalStandardDialogs provides standardDialogs) {
        content()
    }

    errorDialogState?.let {
        ErrorDialog(
            title = it.title,
            text = it.message,
            onDismiss = { errorDialogState = null },
            focusConfirmButton = it.focusConfirmButton
        )
    }

    infoDialogState?.let {
        InfoDialog(
            title = it.title,
            text = it.message,
            onDismiss = { infoDialogState = null },
            focusConfirmButton = it.focusConfirmButton
        )
    }

    confirmDialogState?.let {
        ConfirmationDialog(
            title = it.title,
            text = it.message,
            confirmText = it.confirmText,
            onConfirm = it.onConfirm,
            onDismiss = { confirmDialogState = null }
        )
    }
}