package dev.ulfrx.recipe.ui.components.sheet

import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.composables.core.BottomSheetScope
import com.composables.core.DragIndication
import com.composables.core.ModalBottomSheet
import com.composables.core.ModalBottomSheetState
import com.composables.core.Scrim
import com.composables.core.Sheet
import com.composables.core.SheetDetent
import com.composables.core.rememberModalBottomSheetState
import dev.ulfrx.recipe.ui.theme.RecipeTheme
import org.jetbrains.compose.resources.stringResource
import recipe.composeapp.generated.resources.Res
import recipe.composeapp.generated.resources.sheet_drag_handle_a11y

@Composable
fun <T : NavKey> RecipeBottomSheet(
    state: RecipeBottomSheetState<T>,
    modifier: Modifier = Modifier,
    entries: EntryProviderScope<T>.() -> Unit,
) {
    val dismissalGate = remember { SheetDismissalGate() }
    val modalSheetState = rememberModalBottomSheetState(
        initialDetent = SheetDetent.Hidden,
        detents = listOf(SheetDetent.Hidden, SheetDetent.FullyExpanded),
        confirmDetentChange = { detent ->
            detent != SheetDetent.Hidden || !dismissalGate.isBlocked
        },
    )
    val saveableDecorator = rememberSaveableStateHolderNavEntryDecorator<T>()
    val viewModelDecorator = rememberViewModelStoreNavEntryDecorator<T>()

    OpenOrCloseSheetBasedOnVisibility(modalSheetState, state.isOpen)
    EmitDismissOnUserCancel(modalSheetState, state)

    ModalBottomSheet(state = modalSheetState) {
        Scrim(
            scrimColor = SCRIM_COLOR,
            enter = fadeIn(tween(SCRIM_FADE_MILLIS)),
            exit = fadeOut(tween(SCRIM_FADE_MILLIS)),
        )
        Sheet(
            modifier = modifier.fillMaxWidth(),
            enabled = !dismissalGate.isBlocked,
            backgroundColor = RecipeTheme.colors.background,
            shape = RoundedCornerShape(topStart = SHEET_CORNER_RADIUS, topEnd = SHEET_CORNER_RADIUS),
        ) {
            CompositionLocalProvider(LocalSheetDismissalGate provides dismissalGate) {
                SheetBody {
                    if (state.backStack.isNotEmpty()) {
                        NavDisplay(
                            backStack = state.backStack,
                            modifier = Modifier.fillMaxSize(),
                            onBack = { state.pop() },
                            entryDecorators = listOf(saveableDecorator, viewModelDecorator),
                            entryProvider = entryProvider(builder = entries),
                        )
                    } else {
                        Box(modifier = Modifier.fillMaxSize())
                    }
                }
            }
        }
    }
}

@Composable
private fun BottomSheetScope.SheetBody(content: @Composable BoxScope.() -> Unit) {
    Box(modifier = Modifier.fillMaxWidth().fillMaxHeight(SHEET_HEIGHT_FRACTION)) {
        Box(modifier = Modifier.fillMaxSize(), content = content)

        SheetHandle(
            modifier = Modifier.align(Alignment.TopCenter).padding(top = RecipeTheme.spacing.sm),
        )
    }
}

@Composable
private fun OpenOrCloseSheetBasedOnVisibility(
    modalSheetState: ModalBottomSheetState,
    visible: Boolean,
) {
    LaunchedEffect(visible) {
        modalSheetState.targetDetent =
            if (visible) SheetDetent.FullyExpanded else SheetDetent.Hidden
    }
}

@Composable
private fun <T : NavKey> EmitDismissOnUserCancel(
    modalSheetState: ModalBottomSheetState,
    state: RecipeBottomSheetState<T>,
) {
    LaunchedEffect(modalSheetState.isIdle, modalSheetState.currentDetent) {
        if (modalSheetState.isIdle && modalSheetState.currentDetent == SheetDetent.Hidden) {
            state.dismiss()
        }
    }
}

@Composable
private fun BottomSheetScope.SheetHandle(modifier: Modifier = Modifier) {
    val colors = RecipeTheme.colors
    val label = stringResource(Res.string.sheet_drag_handle_a11y)
    DragIndication(
        modifier =
            modifier
                .semantics { this.contentDescription = label }
                .clip(RoundedCornerShape(percent = 50))
                .background(colors.surface.copy(alpha = HandleAlpha))
                .width(HandleWidth)
                .height(HandleHeight),
    )
}

private const val SHEET_HEIGHT_FRACTION = 0.92f
private const val SCRIM_FADE_MILLIS = 250
private const val HandleAlpha = 0.85f

private val SCRIM_COLOR = Color.Black.copy(alpha = 0.45f)
private val SHEET_CORNER_RADIUS = 28.dp
private val HandleWidth = 36.dp
private val HandleHeight = 5.dp
