package dev.ulfrx.recipe.ui.components.calendar

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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.ulfrx.recipe.ui.theme.RecipeTheme
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.datetime.LocalDate

/**
 * Reusable calendar surface for planner, pantry, and shopping. One swipe-able
 * paged carousel of week strips or month grids, plus an optional chevron to
 * toggle between the two modes.
 *
 * The composable is **controlled** — anchor/selection/mode live in the
 * caller's state. The pager is local UI state and is re-keyed when [mode]
 * changes (so the new origin date can be picked up safely).
 *
 * @param selectedDate Currently selected day. Defaults to the only highlight
 *   used by [isSelectedOverride]'s default impl. Tapping the topbar pill jumps
 *   here.
 * @param today Used for the "today" outline ring; also the date the topbar
 *   jumps to when tapped.
 * @param mode Whether to render week strips or month grids.
 * @param onSelectDate Called when the user taps a day cell.
 * @param onModeChange Called when the user taps the expand chevron.
 * @param onVisibleAnchorChange Called when the user swipes to a new period.
 *   Receives an anchor inside the now-visible period. The caller usually
 *   updates [selectedDate] in response (see PlannerViewModel for the pattern).
 * @param dayState Per-day visual modifiers (dimmed for adjacent-month days is
 *   added automatically by the month grid).
 * @param isSelectedOverride Custom selection predicate. Pass for range
 *   selection; defaults to `date == selectedDate`.
 * @param expandable When true, renders the chevron and supports mode toggle.
 *   Popup variants (pantry/shopping) set this to false.
 */
@Composable
fun SwipeableCalendar(
    selectedDate: LocalDate,
    today: LocalDate,
    mode: CalendarMode,
    onSelectDate: (LocalDate) -> Unit,
    onModeChange: (CalendarMode) -> Unit,
    onVisibleAnchorChange: (LocalDate) -> Unit,
    modifier: Modifier = Modifier,
    dayState: (LocalDate) -> DayState = { DayState() },
    isSelectedOverride: ((LocalDate) -> Boolean)? = null,
    expandable: Boolean = true,
    locale: CalendarLocale = CalendarLocale.PL,
    contentPadding: PaddingValues = PaddingValues(horizontal = 12.dp),
) {
    val isSelected: (LocalDate) -> Boolean =
        isSelectedOverride ?: { it == selectedDate }
    val currentOnAnchorChange by rememberUpdatedState(onVisibleAnchorChange)

    Column(
        modifier = modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(RecipeTheme.spacing.sm),
    ) {
        // Re-key the pager block on mode so we can pick a fresh origin from
        // the currently-selected date. The pager state is local; the caller
        // never needs to scroll it manually.
        key(mode) {
            val origin = remember { selectedDate }
            val initialPage = remember { INITIAL_PAGE }
            val pagerState = rememberPagerState(initialPage = initialPage) { PAGE_COUNT }

            CalendarTopbar(
                mode = mode,
                anchor = origin.plusPeriods(pagerState.currentPage - initialPage, mode),
                today = today,
                selectedDate = selectedDate,
                locale = locale,
                onJumpToToday = { onSelectDate(today) },
                expandable = expandable,
                onToggleMode = {
                    onModeChange(
                        if (mode == CalendarMode.Month) CalendarMode.Week else CalendarMode.Month,
                    )
                },
                modifier = Modifier.padding(contentPadding),
            )

            // Bring the pager onto the page that contains [selectedDate]
            // whenever it changes externally (e.g., tap "today" on the topbar
            // or a fresh selection from the page we're already on).
            LaunchedEffect(selectedDate) {
                val target = initialPage + periodsBetween(origin, selectedDate, mode)
                if (target != pagerState.currentPage) {
                    pagerState.animateScrollToPage(target)
                }
            }

            // Report swipe-driven anchor changes upward so the caller can keep
            // its own selection in sync (e.g., planner auto-follows the week).
            LaunchedEffect(pagerState) {
                snapshotFlow { pagerState.settledPage }
                    .distinctUntilChanged()
                    .collect { page ->
                        if (page == initialPage) return@collect
                        val anchor = origin.plusPeriods(page - initialPage, mode)
                        if (!isInVisiblePeriod(selectedDate, anchor, mode)) {
                            currentOnAnchorChange(anchor)
                        }
                    }
            }

            Column(modifier = Modifier.fillMaxWidth().padding(contentPadding)) {
                WeekdayHeader(locale = locale)
                HorizontalPager(
                    state = pagerState,
                    pageSpacing = 0.dp,
                    flingBehavior =
                        PagerDefaults.flingBehavior(
                            state = pagerState,
                        ),
                    modifier = Modifier.fillMaxWidth(),
                ) { page ->
                    val pageAnchor = origin.plusPeriods(page - initialPage, mode)
                    Box(modifier = Modifier.fillMaxWidth()) {
                        when (mode) {
                            CalendarMode.Week -> {
                                WeekStrip(
                                    anchor = pageAnchor,
                                    today = today,
                                    dayState = dayState,
                                    isSelected = isSelected,
                                    onSelect = onSelectDate,
                                )
                            }

                            CalendarMode.Month -> {
                                MonthGrid(
                                    anchor = pageAnchor,
                                    today = today,
                                    dayState = dayState,
                                    isSelected = isSelected,
                                    onSelect = onSelectDate,
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

// Centered start lets the pager scroll forward and backward freely while
// keeping page indices small enough for the underlying lazy list. 100k pages
// in either direction is ~1900 years — far beyond any reasonable navigation.
private const val PAGE_COUNT: Int = 200_000
private const val INITIAL_PAGE: Int = PAGE_COUNT / 2
