package dev.ulfrx.recipe.ui.screens.recipedetail

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.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
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.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.composables.core.BottomSheetScope
import com.composables.core.DragIndication
import com.composables.core.ModalBottomSheet
import com.composables.core.Scrim
import com.composables.core.Sheet
import com.composables.core.SheetDetent
import com.composables.core.rememberModalBottomSheetState
import com.composables.icons.lucide.Calendar
import com.composables.icons.lucide.Lucide
import com.composeunstyled.UnstyledButton
import com.composeunstyled.UnstyledIcon
import dev.ulfrx.recipe.ui.components.glass.GlassBackdropSource
import dev.ulfrx.recipe.ui.components.glass.GlassSurface
import dev.ulfrx.recipe.ui.components.glass.LocalGlassBackdropState
import dev.ulfrx.recipe.ui.components.glass.rememberGlassBackdropState
import dev.ulfrx.recipe.ui.components.recipe.IngredientRow
import dev.ulfrx.recipe.ui.components.recipe.NutritionSummary
import dev.ulfrx.recipe.ui.components.recipe.RecipeIngredientSlotUi
import dev.ulfrx.recipe.ui.components.recipe.RecipeNutritionUi
import dev.ulfrx.recipe.ui.components.recipe.scaledBy
import dev.ulfrx.recipe.ui.theme.RecipeTheme
import org.jetbrains.compose.resources.stringResource
import recipe.composeapp.generated.resources.Res
import recipe.composeapp.generated.resources.nutrition_label
import recipe.composeapp.generated.resources.recipe_detail_handle_a11y
import recipe.composeapp.generated.resources.recipe_detail_plan_button
import recipe.composeapp.generated.resources.recipe_detail_section_ingredients
import recipe.composeapp.generated.resources.recipe_detail_section_steps
import recipe.composeapp.generated.resources.recipe_detail_step_number_format

@Composable
fun RecipeDetailSheet(
    viewModel: RecipeDetailViewModel,
    onPlanRecipe: (recipeId: String) -> Unit,
) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val sheetState =
        rememberModalBottomSheetState(
            initialDetent = SheetDetent.Hidden,
            detents = listOf(SheetDetent.Hidden, SheetDetent.FullyExpanded),
        )
    val ready = state as? RecipeDetailState.Ready
    val hasReadyRecipe = ready != null

    LaunchedEffect(hasReadyRecipe) {
        sheetState.targetDetent = if (hasReadyRecipe) SheetDetent.FullyExpanded else SheetDetent.Hidden
    }

    // Only caller of dismiss(): a drag that settles the sheet at Hidden while the VM still holds
    // the recipe. Programmatic closes must set targetDetent = Hidden and let this fire — calling
    // dismiss() directly would clear the recipe mid-animation and blank the closing sheet.
    // Keys are the sheet's settled state, NOT hasReadyRecipe — keying on the latter would fire
    // the effect at open time (before the sheet leaves Hidden) and immediately dismiss the recipe.
    LaunchedEffect(sheetState.isIdle, sheetState.currentDetent) {
        if (sheetState.isIdle && sheetState.currentDetent == SheetDetent.Hidden && hasReadyRecipe) {
            viewModel.dismiss()
        }
    }

    ModalBottomSheet(state = sheetState) {
        Scrim(
            scrimColor = SCRIM_COLOR,
            enter = fadeIn(tween(SCRIM_FADE_MILLIS)),
            exit = fadeOut(tween(SCRIM_FADE_MILLIS)),
        )
        Sheet(
            modifier = Modifier.fillMaxWidth(),
            backgroundColor = RecipeTheme.colors.background,
            shape = RoundedCornerShape(topStart = SHEET_CORNER_RADIUS, topEnd = SHEET_CORNER_RADIUS),
        ) {
            ready?.let {
                RecipeDetailContent(
                    ready = it,
                    onServingsChange = viewModel::setServings,
                    onSelectSubstitution = viewModel::selectSubstitution,
                    onPlanRecipe = onPlanRecipe,
                )
            }
        }
    }
}

@Composable
private fun BottomSheetScope.RecipeDetailContent(
    ready: RecipeDetailState.Ready,
    onServingsChange: (Int) -> Unit,
    onSelectSubstitution: (String, String) -> Unit,
    onPlanRecipe: (String) -> Unit,
) {
    val colors = RecipeTheme.colors
    val spacing = RecipeTheme.spacing
    val scrollState = rememberScrollState()
    val bottomInset = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
    val handleLabel = stringResource(Res.string.recipe_detail_handle_a11y)

    val backdrop = rememberGlassBackdropState()

    val detail = ready.recipe
    val servings = ready.servings

    CompositionLocalProvider(LocalGlassBackdropState provides backdrop) {
        Box(modifier = Modifier.fillMaxWidth().fillMaxHeight(SHEET_HEIGHT_FRACTION)) {
            GlassBackdropSource(state = backdrop, modifier = Modifier.fillMaxSize()) {
                Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
                    RecipeDetailHero(
                        title = detail.title,
                        cookingMinutes = detail.cookingMinutes,
                        servings = servings,
                        onServingsChange = onServingsChange,
                    )

                    Column(modifier = Modifier.fillMaxWidth().padding(horizontal = spacing.lg)) {
                        Spacer(Modifier.height(spacing.xl))
                        NutritionSection(nutrition = detail.nutrition.scaledBy(servings))

                        Spacer(Modifier.height(spacing.xl))
                        IngredientsSection(
                            ingredients = detail.ingredients,
                            servings = servings,
                            substitutions = ready.substitutions,
                            onSelectSubstitution = onSelectSubstitution,
                        )

                        Spacer(Modifier.height(spacing.xl))
                        StepsSection(steps = detail.steps)

                        Spacer(Modifier.height(bottomInset + spacing.xxl))
                    }
                }
            }

            DragIndication(
                modifier =
                    Modifier
                        .align(Alignment.TopCenter)
                        .padding(top = spacing.sm)
                        .semantics { contentDescription = handleLabel }
                        .clip(RoundedCornerShape(percent = 50))
                        .background(colors.surface.copy(alpha = 0.85f))
                        .width(HANDLE_WIDTH)
                        .height(HANDLE_HEIGHT),
            )

            PlanButton(
                modifier =
                    Modifier
                        .align(Alignment.TopEnd)
                        .padding(top = spacing.xl, end = spacing.lg),
                onClick = { onPlanRecipe(detail.id) },
            )
        }
    }
}

@Composable
private fun Section(
    title: String,
    content: @Composable () -> Unit,
) {
    SectionTitle(text = title)
    Spacer(Modifier.height(RecipeTheme.spacing.lg))
    content()
}

@Composable
private fun SectionTitle(text: String) {
    BasicText(
        text = text.uppercase(),
        style =
            RecipeTheme.typography.label.copy(
                color = RecipeTheme.colors.contentMuted,
                fontSize = SECTION_HEADER_TEXT_SIZE,
                letterSpacing = SECTION_HEADER_TRACKING,
                fontWeight = FontWeight.Bold,
            ),
    )
}

@Composable
private fun NutritionSection(nutrition: RecipeNutritionUi) {
    Section(title = stringResource(Res.string.nutrition_label)) {
        NutritionSummary(nutrition = nutrition)
    }
}

@Composable
private fun IngredientsSection(
    ingredients: List<RecipeIngredientSlotUi>,
    servings: Int,
    substitutions: Map<String, String>,
    onSelectSubstitution: (slotId: String, optionId: String) -> Unit,
) {
    val colors = RecipeTheme.colors
    val cardShape = RoundedCornerShape(INGREDIENTS_CARD_CORNER)
    Section(title = stringResource(Res.string.recipe_detail_section_ingredients)) {
        Column(
            modifier =
                Modifier
                    .fillMaxWidth()
                    .clip(cardShape)
                    .background(colors.surface)
                    .border(width = CARD_BORDER_WIDTH, color = colors.borderCard, shape = cardShape),
        ) {
            ingredients.forEachIndexed { index, slot ->
                if (index > 0) IngredientDivider()
                IngredientRow(
                    slot = slot.scaledBy(servings),
                    selectedOptionId = substitutions[slot.id] ?: slot.default.id,
                    onSelect =
                        if (slot.alternatives.isNotEmpty()) {
                            { choice -> onSelectSubstitution(slot.id, choice.id) }
                        } else {
                            null
                        },
                )
            }
        }
    }
}

@Composable
private fun IngredientDivider() {
    Box(
        modifier =
            Modifier
                .fillMaxWidth()
                .padding(horizontal = INGREDIENT_DIVIDER_INSET)
                .height(INGREDIENT_DIVIDER_THICKNESS)
                .background(RecipeTheme.colors.separator),
    )
}

@Composable
private fun StepsSection(steps: List<String>) {
    Section(title = stringResource(Res.string.recipe_detail_section_steps)) {
        Column(verticalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm)) {
            steps.forEachIndexed { index, step ->
                StepRow(number = index + 1, text = step)
            }
        }
    }
}

@Composable
private fun StepRow(
    number: Int,
    text: String,
) {
    val colors = RecipeTheme.colors
    Row(horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm)) {
        BasicText(
            text = stringResource(Res.string.recipe_detail_step_number_format, number),
            style =
                RecipeTheme.typography.body.copy(
                    color = colors.contentMuted,
                    fontWeight = FontWeight.Bold,
                    fontSize = STEP_NUMBER_TEXT_SIZE,
                ),
            modifier = Modifier.width(STEP_NUMBER_WIDTH),
        )
        BasicText(
            text = text,
            style =
                RecipeTheme.typography.body.copy(
                    color = colors.content,
                    fontWeight = FontWeight.Normal,
                    fontSize = STEP_TEXT_SIZE,
                    lineHeight = STEP_LINE_HEIGHT,
                ),
            modifier = Modifier.weight(1f),
        )
    }
}

@Composable
private fun PlanButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    val colors = RecipeTheme.colors
    GlassSurface(
        modifier = modifier.height(PLAN_BUTTON_HEIGHT),
        cornerRadius = PLAN_BUTTON_HEIGHT / 2,
        tint = colors.surfaceGlass,
    ) {
        UnstyledButton(
            onClick = onClick,
            backgroundColor = Color.Transparent,
            contentColor = colors.content,
            contentPadding = PaddingValues(horizontal = RecipeTheme.spacing.lg),
            modifier = Modifier.fillMaxHeight(),
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.xs),
            ) {
                UnstyledIcon(
                    imageVector = Lucide.Calendar,
                    contentDescription = null,
                    tint = colors.content,
                    modifier = Modifier.size(PLAN_BUTTON_ICON_SIZE),
                )
                BasicText(
                    text = stringResource(Res.string.recipe_detail_plan_button),
                    style =
                        RecipeTheme.typography.label.copy(
                            color = colors.content,
                            fontWeight = FontWeight.SemiBold,
                        ),
                )
            }
        }
    }
}

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

private val SCRIM_COLOR = Color.Black.copy(alpha = 0.45f)
private val SHEET_CORNER_RADIUS = 28.dp
private val HANDLE_WIDTH = 36.dp
private val HANDLE_HEIGHT = 5.dp
private val INGREDIENTS_CARD_CORNER = 16.dp
private val INGREDIENT_DIVIDER_INSET = 12.dp
private val INGREDIENT_DIVIDER_THICKNESS = 1.dp
private val CARD_BORDER_WIDTH = 1.dp
private val STEP_NUMBER_WIDTH = 20.dp
private val PLAN_BUTTON_HEIGHT = 36.dp
private val PLAN_BUTTON_ICON_SIZE = 14.dp

private val SECTION_HEADER_TEXT_SIZE = 11.sp
private val SECTION_HEADER_TRACKING = 1.sp
private val STEP_NUMBER_TEXT_SIZE = 11.sp
private val STEP_TEXT_SIZE = 13.sp
private val STEP_LINE_HEIGHT = 19.sp
