package dev.ulfrx.recipe.ui.screens.mealplaneditor

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
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.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Plus
import com.composables.icons.lucide.Search
import com.composeunstyled.UnstyledButton
import com.composeunstyled.UnstyledIcon
import dev.ulfrx.recipe.ui.components.recipe.IngredientAmount
import dev.ulfrx.recipe.ui.components.recipe.IngredientDivider
import dev.ulfrx.recipe.ui.theme.RecipeTheme
import org.jetbrains.compose.resources.stringResource
import recipe.composeapp.generated.resources.Res
import recipe.composeapp.generated.resources.meal_plan_editor_add_ingredient
import recipe.composeapp.generated.resources.meal_plan_editor_add_ingredient_cancel
import recipe.composeapp.generated.resources.meal_plan_editor_add_ingredient_empty
import recipe.composeapp.generated.resources.meal_plan_editor_add_ingredient_search_placeholder

/**
 * "Dodaj składnik" affordance — collapsed dashed button by default; expands
 * into a search panel with filtering against [catalog]. Already-used recipe /
 * added ingredient ids are filtered out by [usedIngredientIds] so the user
 * never sees the same ingredient twice. Open/closed and the in-flight query
 * are pure UI state — survived across recompositions via [rememberSaveable]
 * but never lifted into the ViewModel since neither flag matters to confirm.
 */
@Composable
internal fun AddIngredientPanel(
    catalog: List<AddableIngredientUi>,
    usedIngredientIds: Set<String>,
    onPick: (AddableIngredientUi) -> Unit,
    modifier: Modifier = Modifier,
    maxResults: Int = 20,
    keyboardClearance: Dp = 0.dp,
    autoFocusEnabled: Boolean = true,
    keyboardAnimationDurationMillis: Int = DefaultKeyboardAnimationDurationMillis,
    onOpenChange: (Boolean) -> Unit = {},
) {
    var isOpen by rememberSaveable { mutableStateOf(false) }
    var query by rememberSaveable { mutableStateOf("") }
    val focusManager = LocalFocusManager.current
    val panelAnimationDurationMillis =
        keyboardAnimationDurationMillis.coerceAtLeast(MinPanelAnimationDurationMillis)

    LaunchedEffect(isOpen) {
        if (isOpen) {
            onOpenChange(true)
        }
    }

    Column(modifier = modifier.fillMaxWidth()) {
        AnimatedVisibility(
            visible = !isOpen,
            enter =
                fadeIn(animationSpec = tween(durationMillis = panelAnimationDurationMillis)) +
                    expandVertically(animationSpec = tween(durationMillis = panelAnimationDurationMillis)),
            exit =
                fadeOut(animationSpec = tween(durationMillis = panelAnimationDurationMillis)) +
                    shrinkVertically(animationSpec = tween(durationMillis = panelAnimationDurationMillis)),
        ) {
            AddIngredientCollapsedButton(
                onClick = {
                    isOpen = true
                    onOpenChange(true)
                },
            )
        }
        AnimatedVisibility(
            visible = isOpen,
            enter =
                fadeIn(animationSpec = tween(durationMillis = panelAnimationDurationMillis)) +
                    expandVertically(animationSpec = tween(durationMillis = panelAnimationDurationMillis)),
            exit =
                fadeOut(animationSpec = tween(durationMillis = panelAnimationDurationMillis)) +
                    shrinkVertically(animationSpec = tween(durationMillis = panelAnimationDurationMillis)),
        ) {
            AddIngredientSearchCard(
                catalog = catalog,
                usedIngredientIds = usedIngredientIds,
                query = query,
                onSetQuery = { query = it },
                onClose = {
                    focusManager.clearFocus(force = true)
                    isOpen = false
                    onOpenChange(false)
                    query = ""
                },
                onPick = { picked ->
                    focusManager.clearFocus(force = true)
                    onPick(picked)
                    isOpen = false
                    onOpenChange(false)
                    query = ""
                },
                maxResults = maxResults,
                keyboardClearance = keyboardClearance,
                autoFocusEnabled = autoFocusEnabled,
            )
        }
    }
}

@Composable
private fun AddIngredientCollapsedButton(onClick: () -> Unit) {
    val colors = RecipeTheme.colors
    val shape = RoundedCornerShape(CollapsedCornerRadius)
    UnstyledButton(
        onClick = onClick,
        backgroundColor = Color.Transparent,
        contentColor = colors.contentMuted,
        shape = shape,
        contentPadding = PaddingValues(vertical = CollapsedVerticalPadding),
        modifier =
            Modifier
                .fillMaxWidth()
                .border(width = 1.dp, color = colors.borderCard, shape = shape),
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.xs),
        ) {
            UnstyledIcon(
                imageVector = Lucide.Plus,
                contentDescription = null,
                tint = colors.contentMuted,
                modifier = Modifier.size(CollapsedIconSize),
            )
            BasicText(
                text = stringResource(Res.string.meal_plan_editor_add_ingredient),
                style =
                    RecipeTheme.typography.label.copy(
                        color = colors.contentMuted,
                        fontWeight = FontWeight.SemiBold,
                        fontSize = CollapsedTextSize,
                    ),
            )
        }
    }
}

@Composable
private fun AddIngredientSearchCard(
    catalog: List<AddableIngredientUi>,
    usedIngredientIds: Set<String>,
    query: String,
    onSetQuery: (String) -> Unit,
    onClose: () -> Unit,
    onPick: (AddableIngredientUi) -> Unit,
    maxResults: Int,
    keyboardClearance: Dp,
    autoFocusEnabled: Boolean,
) {
    val colors = RecipeTheme.colors
    val shape = RoundedCornerShape(CardCornerRadius)
    val density = LocalDensity.current
    val panelBringIntoViewRequester = remember { BringIntoViewRequester() }
    val focusRequester = remember { FocusRequester() }
    var panelSize by remember { mutableStateOf(IntSize.Zero) }
    var focusRequested by remember { mutableStateOf(false) }
    val results = remember(catalog, usedIngredientIds, query, maxResults) {
        filterCatalog(catalog, usedIngredientIds, query, maxResults)
    }

    LaunchedEffect(panelSize, keyboardClearance, autoFocusEnabled) {
        if (panelSize == IntSize.Zero || !autoFocusEnabled) return@LaunchedEffect
        if (!focusRequested) {
            focusRequested = true
            focusRequester.requestFocus()
            withFrameNanos { }
        }
        val rect =
            with(density) {
                panelSize.panelVisibilityRect(keyboardClearancePx = keyboardClearance.toPx())
            }
        panelBringIntoViewRequester.bringIntoView(rect)
        withFrameNanos { }
        panelBringIntoViewRequester.bringIntoView(rect)
    }

    Column(
        modifier =
            Modifier
                .fillMaxWidth()
                .bringIntoViewRequester(panelBringIntoViewRequester)
                .onSizeChanged { panelSize = it }
                .clip(shape)
                .background(colors.surface)
                .border(width = 1.dp, color = colors.borderCard, shape = shape)
                .padding(RecipeTheme.spacing.sm),
        verticalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
    ) {
        SearchRow(
            query = query,
            onQueryChange = onSetQuery,
            onCancel = onClose,
            focusRequester = focusRequester,
        )
        if (results.isEmpty()) {
            EmptyResultsMessage()
        } else {
            ResultsList(results = results, onPick = onPick)
        }
    }
}

@Composable
private fun SearchRow(
    query: String,
    onQueryChange: (String) -> Unit,
    onCancel: () -> Unit,
    focusRequester: FocusRequester,
) {
    val colors = RecipeTheme.colors

    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
    ) {
        SearchInputField(
            value = query,
            onValueChange = onQueryChange,
            focusRequester = focusRequester,
            modifier = Modifier.weight(1f),
        )
        UnstyledButton(
            onClick = onCancel,
            backgroundColor = Color.Transparent,
            contentColor = colors.contentMuted,
            contentPadding = PaddingValues(horizontal = RecipeTheme.spacing.sm),
        ) {
            BasicText(
                text = stringResource(Res.string.meal_plan_editor_add_ingredient_cancel),
                style =
                    RecipeTheme.typography.label.copy(
                        color = colors.contentMuted,
                        fontWeight = FontWeight.SemiBold,
                        fontSize = CancelTextSize,
                    ),
            )
        }
    }
}

@Composable
private fun SearchInputField(
    value: String,
    onValueChange: (String) -> Unit,
    focusRequester: FocusRequester,
    modifier: Modifier = Modifier,
) {
    val colors = RecipeTheme.colors
    val shape = RoundedCornerShape(SearchInputCornerRadius)
    Box(
        modifier =
            modifier
                .height(SearchInputHeight)
                .clip(shape)
                .background(colors.background)
                .border(width = 1.dp, color = colors.borderCard, shape = shape)
                .padding(horizontal = RecipeTheme.spacing.sm),
        contentAlignment = Alignment.CenterStart,
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
        ) {
            UnstyledIcon(
                imageVector = Lucide.Search,
                contentDescription = null,
                tint = colors.contentMuted,
                modifier = Modifier.size(SearchIconSize),
            )
            BasicTextField(
                value = value,
                onValueChange = onValueChange,
                singleLine = true,
                cursorBrush = SolidColor(colors.accent),
                textStyle =
                    RecipeTheme.typography.body.copy(
                        color = colors.content,
                        fontSize = SearchInputTextSize,
                    ),
                modifier = Modifier.weight(1f).fillMaxHeight().focusRequester(focusRequester),
                decorationBox = { inner ->
                    Box(
                        modifier = Modifier.fillMaxHeight().fillMaxWidth(),
                        contentAlignment = Alignment.CenterStart,
                    ) {
                        if (value.isEmpty()) {
                            BasicText(
                                text = stringResource(Res.string.meal_plan_editor_add_ingredient_search_placeholder),
                                style =
                                    RecipeTheme.typography.body.copy(
                                        color = colors.contentMuted,
                                        fontSize = SearchInputTextSize,
                                    ),
                            )
                        }
                        inner()
                    }
                },
            )
        }
    }
}

@Composable
private fun ResultsList(
    results: List<AddableIngredientUi>,
    onPick: (AddableIngredientUi) -> Unit,
) {
    val colors = RecipeTheme.colors
    val shape = RoundedCornerShape(ResultsCardCornerRadius)
    val scrollState = rememberScrollState()
    Column(
        modifier =
            Modifier
                .fillMaxWidth()
                .heightIn(max = ResultsListMaxHeight)
                .clip(shape)
                .background(colors.background)
                .border(width = 1.dp, color = colors.borderCard, shape = shape)
                .verticalScroll(scrollState),
    ) {
        results.forEachIndexed { index, ingredient ->
            if (index > 0) IngredientDivider()
            ResultRow(ingredient = ingredient, onClick = { onPick(ingredient) })
        }
    }
}

@Composable
private fun ResultRow(
    ingredient: AddableIngredientUi,
    onClick: () -> Unit,
) {
    val colors = RecipeTheme.colors
    UnstyledButton(
        onClick = onClick,
        backgroundColor = Color.Transparent,
        contentColor = colors.content,
        contentPadding =
            PaddingValues(
                horizontal = ResultRowHorizontalPadding,
                vertical = ResultRowVerticalPadding,
            ),
        modifier = Modifier.fillMaxWidth().defaultMinSize(minHeight = ResultRowMinHeight),
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
        ) {
            BasicText(
                text = ingredient.name,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f),
                style =
                    RecipeTheme.typography.body.copy(
                        color = colors.content,
                        fontWeight = FontWeight.SemiBold,
                        fontSize = ResultRowTextSize,
                    ),
            )
            IngredientAmount(amount = ingredient.defaultAmount, unit = ingredient.defaultUnit)
        }
    }
}

@Composable
private fun EmptyResultsMessage() {
    val colors = RecipeTheme.colors
    val shape = RoundedCornerShape(ResultsCardCornerRadius)
    Box(
        modifier =
            Modifier
                .fillMaxWidth()
                .clip(shape)
                .background(colors.background)
                .border(width = 1.dp, color = colors.borderCard, shape = shape)
                .padding(vertical = EmptyMessagePadding),
        contentAlignment = Alignment.Center,
    ) {
        BasicText(
            text = stringResource(Res.string.meal_plan_editor_add_ingredient_empty),
            style =
                RecipeTheme.typography.label.copy(
                    color = colors.contentMuted,
                    fontSize = ResultRowTextSize,
                ),
        )
    }
}

private fun filterCatalog(
    catalog: List<AddableIngredientUi>,
    usedIngredientIds: Set<String>,
    query: String,
    maxResults: Int,
): List<AddableIngredientUi> {
    val needle = query.trim().lowercase()
    return catalog.asSequence()
        .filter { it.ingredientId !in usedIngredientIds }
        .filter { needle.isEmpty() || it.name.lowercase().contains(needle) }
        .take(maxResults)
        .toList()
}

private fun IntSize.panelVisibilityRect(keyboardClearancePx: Float): Rect =
    Rect(
        left = 0f,
        top = 0f,
        right = width.toFloat(),
        bottom = height.toFloat() + keyboardClearancePx,
    )

private val CollapsedCornerRadius = 12.dp
private val CollapsedVerticalPadding = 10.dp
private val CollapsedIconSize = 12.dp
private val CollapsedTextSize = 12.sp

private val CardCornerRadius = 14.dp
private val CancelTextSize = 11.sp
private const val DefaultKeyboardAnimationDurationMillis = 250
private const val MinPanelAnimationDurationMillis = 120

private val SearchInputHeight = 36.dp
private val SearchInputCornerRadius = 10.dp
private val SearchInputTextSize = 13.sp
private val SearchIconSize = 14.dp

private val ResultsListMaxHeight = 200.dp
// Smaller than IngredientCard's 16dp — nested inside the search card, deserves a tighter corner.
private val ResultsCardCornerRadius = 12.dp
private val ResultRowHorizontalPadding = 12.dp
private val ResultRowVerticalPadding = 8.dp
// Smaller than IngredientRow's 48dp min — these rows show only a name, no swap/amount affordances.
private val ResultRowMinHeight = 40.dp
private val ResultRowTextSize = 12.sp
private val EmptyMessagePadding = 14.dp
