package dev.ulfrx.recipe.ui.screens.shell

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import dev.ulfrx.recipe.navigation.DockDestination
import dev.ulfrx.recipe.ui.screens.shell.dock.DockBar
import dev.ulfrx.recipe.ui.screens.shell.dock.FloatingSearchButton
import dev.ulfrx.recipe.ui.screens.shell.search.SearchPillRow
import dev.ulfrx.recipe.ui.screens.shell.search.SearchState
import dev.ulfrx.recipe.ui.theme.RecipeTheme
import org.jetbrains.compose.resources.stringResource
import recipe.composeapp.generated.resources.Res
import recipe.composeapp.generated.resources.search_placeholder

/**
 * Search-side inputs for [ShellBottomChrome] — the live [SearchState] plus the
 * lambdas the chrome calls back into. Bundled into one holder so the chrome's
 * parameter list doesn't grow with the VM, and so a `@Preview` can construct
 * one with no-op lambdas to render any of the three states without a real VM.
 *
 * Data class on purpose: structural equality means Compose can skip-recompose
 * the chrome when [AppShell] re-emits an identical handler bag (lambdas built
 * from the same VM method references compare equal).
 */
data class SearchHandlers(
    val state: SearchState,
    val onOpen: () -> Unit,
    val onQueryChange: (String) -> Unit,
    val onClose: () -> Unit,
    val onFocus: () -> Unit,
    val onUnfocus: () -> Unit,
)

/**
 * Bottom chrome for [AppShell]. Owns the dock ↔ search-pill swap and the
 * three-state geometry choreography (insets, horizontal-padding curve, height
 * lock, AnimatedContent transition tuning).
 *
 * Modes — driven by [search].state:
 *  - **A (closed)** — `[DockBar (full)] [FloatingSearchButton]`
 *  - **B (open, unfocused)** — `[collapsed dock icon] [search pill]`
 *  - **C (open, focused)** — `[search pill (full width)] [X button]`
 *
 * Geometry contract (kept here so [AppShell] doesn't need to know any of it):
 *  - The chrome band is height-locked to the dock's 63 dp so the body above
 *    doesn't shift when search opens/closes; the (shorter) search pill is
 *    centred vertically inside that band.
 *  - Horizontal padding animates with state (xl → xl+2 → 8 dp). The narrow C
 *    inset makes the focused input read as a width extension of the keyboard
 *    above it.
 *  - Bottom inset uses navBar/2 (Apple Music pattern — chrome sits close to
 *    the bottom and the home indicator visually overlaps the substrate). When
 *    the IME is up the IME inset wins via `max`.
 */
@Composable
fun ShellBottomChrome(
    activeTab: DockDestination,
    onTabSelect: (DockDestination) -> Unit,
    search: SearchHandlers,
    modifier: Modifier = Modifier,
) {
    val bottomInset =
        with(LocalDensity.current) {
            val imePx = WindowInsets.ime.getBottom(this)
            val navPx = WindowInsets.navigationBars.getBottom(this)
            maxOf(imePx, navPx / 2).toDp()
        }
    val horizontalPadding by animateDpAsState(
        targetValue =
            when {
                !search.state.isOpen -> RecipeTheme.spacing.xl
                !search.state.isFocused -> RecipeTheme.spacing.xl + 2.dp
                else -> 8.dp
            },
        animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
        label = "chrome horizontal padding",
    )
    Row(
        modifier =
            modifier
                .fillMaxWidth()
                .padding(
                    start = horizontalPadding,
                    end = horizontalPadding,
                    top = RecipeTheme.spacing.sm,
                    bottom = bottomInset + RecipeTheme.spacing.xs,
                ),
        horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        AnimatedContent(
            targetState = search.state.isOpen,
            modifier = Modifier.fillMaxWidth().height(DockBandHeight),
            contentAlignment = Alignment.Center,
            // Exit is instant (no fade-out): the outgoing chrome cell — dock
            // OR search pill row — may still be playing its press animation
            // (the user's finger triggered the tap that switched states). If
            // we also fade it out, the half-faded pressed-up button overlaps
            // visually with the incoming pill, which reads as "two things on
            // screen at once". Instant exit keeps the hand-off clean while
            // the press animation finishes off-screen on the removed branch.
            transitionSpec = {
                fadeIn(tween(durationMillis = 200, easing = FastOutSlowInEasing)) togetherWith
                    ExitTransition.None
            },
            label = "AppShell bottom chrome",
        ) { searchOpen ->
            if (searchOpen) {
                SearchPillRow(
                    query = search.state.query,
                    isFocused = search.state.isFocused,
                    placeholder = stringResource(Res.string.search_placeholder),
                    activeTab = activeTab,
                    onQueryChange = search.onQueryChange,
                    onClose = search.onClose,
                    onFocusGained = search.onFocus,
                    onFocusLost = search.onUnfocus,
                )
            } else {
                DockRow(
                    activeTab = activeTab,
                    onTabSelect = onTabSelect,
                    onSearchTap = search.onOpen,
                )
            }
        }
    }
}

@Composable
private fun DockRow(
    activeTab: DockDestination,
    onTabSelect: (DockDestination) -> Unit,
    onSearchTap: () -> Unit,
) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        DockBar(
            destinations = DockDestination.entries,
            active = activeTab,
            collapsed = false,
            onTabSelect = onTabSelect,
            modifier = Modifier.weight(1f),
            height = DockBandHeight,
        )
        Box(modifier = Modifier.size(DockBandHeight)) {
            FloatingSearchButton(onClick = onSearchTap)
        }
    }
}
