/**
 * Implements a utility to automatically transfer focus to an element when a certain condition holds true.
 */

package compose.utils

import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.modifier.ModifierLocal
import androidx.compose.ui.modifier.modifierLocalConsumer
import compose.input.onMousePress
import kotlinx.coroutines.launch


/**
 * When the given [ModifierLocal]'s value is `true`, requests focus for the node to which this modifier is applied.
 */
@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.requestFocusWhen(modifierLocal: ModifierLocal<Boolean?>, focusRequester: FocusRequester) = composed {
    val coroutineScope = rememberCoroutineScope()
    var currentValue: Boolean? by remember(modifierLocal) { mutableStateOf(null) }
    modifierLocalConsumer {
        // For some reason, when we lose focus, this function is called with the default value of `ModifierLocal`
        // (which is presumably `null`) and then immediately again with `true`. In these cases, we don't want to
        // request focus, because that would prevent any other element taking focus away from us (because we'd be
        // re-requesting back immediately).
        val newValue = modifierLocal.current
        if ((currentValue != true) && (newValue == true)) {
            coroutineScope.launch {
                focusRequester.requestFocus()
            }
        }
        if (newValue != null)
            currentValue = newValue
    }
}


/**
 * Requests focus using the given [FocusRequester] when the node, or one of its parents is marked as selected via
 * [markSelected].
 */
fun Modifier.requestFocusWhenSelected(focusRequester: FocusRequester) = requestFocusWhen(ModifierLocalSelected, focusRequester)


/**
 * Requests focus when the node, or one of its parents is marked as selected via [markSelected].
 */
fun Modifier.requestFocusWhenSelected() = composed {
    val focusRequester = remember { FocusRequester() }
    this
        .focusRequester(focusRequester)
        .requestFocusWhenSelected(focusRequester)
}


/**
 * Requests focus for the element when it is clicked.
 */
fun Modifier.focusWhenClicked(
    focusRequester: FocusRequester? = null,
    interactionSource: MutableInteractionSource? = null,
) = composed {
    val rememberedFocusRequester = remember(focusRequester) { focusRequester ?: FocusRequester() }
    Modifier
        .focusRequester(rememberedFocusRequester)
        .focusable(interactionSource = interactionSource)
        .onMousePress {
            rememberedFocusRequester.requestFocus()
        }
}


/**
 * Returns a [Modifier] that attaches a [FocusRequester] and requests the initial focus on it.
 */
@Composable
fun Modifier.requestInitialFocus(): Modifier {
    val focusRequester = remember { FocusRequester() }
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
    return this.focusRequester(focusRequester)
}