package dev.privacyllc.period.feature.today

import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.SelectableDates
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.privacyllc.period.R
import dev.privacyllc.period.designsystem.PeriodTheme
import dev.privacyllc.period.designsystem.PeriodThemeDefaults
import dev.privacyllc.period.designsystem.art.CycleProgressMark
import dev.privacyllc.period.designsystem.art.EmptyStateIllustration
import dev.privacyllc.period.domain.cycle.PeriodRecord
import dev.privacyllc.period.domain.prediction.ConfidenceLabel
import dev.privacyllc.period.domain.prediction.CycleStatus
import dev.privacyllc.period.domain.prediction.FertilityEstimate
import dev.privacyllc.period.domain.prediction.FertilityLikelihood
import dev.privacyllc.period.domain.prediction.Prediction
import java.time.LocalDate
import java.time.format.DateTimeFormatter

/**
 * The hero screen — PRODUCT_PLAN.md §21 and §22.
 *
 * **The forecast number dominates** (§38). Nothing on this screen competes with
 * it, including anything of ours: the ad slot is at the very bottom, the history
 * is not here at all, and the secondary lines are deliberately quiet.
 *
 * Six states, §22, each a different screen rather than a variant of one. Which
 * one applies is decided by `CycleStatusRules`, in a pure module with twelve
 * tests on its boundaries, because the boundaries are where this screen is most
 * often read: the day a period is due, the day after one ends, the day a
 * forecast slips.
 *
 * The state this screen exists to get right is the last one. Past the forecast,
 * the app **never says late** — nothing is wrong with the user, the model was
 * imprecise, and it says so and shows both forecasts rather than moving the
 * goalposts quietly.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TodayScreen(viewModel: TodayViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val message by viewModel.message.collectAsStateWithLifecycle()
    val prompt by viewModel.prompt.collectAsStateWithLifecycle()

    when (val p = prompt) {
        TodayPrompt.LogPeriod -> LogPeriodSheet(
            today = state.today,
            onToday = { viewModel.confirmStart(state.today) },
            onDate = viewModel::confirmStart,
            onSpotting = { viewModel.logSpotting(state.today) },
            onDismiss = viewModel::dismissPrompt,
        )
        is TodayPrompt.PeriodOrSpotting -> PeriodOrSpottingDialog(
            onPeriod = viewModel::dismissPrompt,
            onSpotting = { viewModel.reclassifyAsSpotting(p.recordId, p.startDate) },
        )
        null -> Unit
    }

    TodayContent(
        state = state,
        message = message,
        onStarted = viewModel::showLogSheet,
        onNotYet = viewModel::notYet,
        onEndedToday = {
            state.periods.maxByOrNull { it.startDate }?.let { viewModel.setEnd(it.id, state.today) }
        },
        onStillGoing = {
            state.periods.maxByOrNull { it.startDate }?.let { viewModel.setEnd(it.id, null) }
        },
        onMessageShown = viewModel::messageShown,
    )
}

@Composable
private fun TodayContent(
    state: TodayUiState,
    message: String?,
    onStarted: () -> Unit,
    onNotYet: () -> Unit,
    onEndedToday: () -> Unit,
    onStillGoing: () -> Unit,
    onMessageShown: () -> Unit,
) {
    Column(
        Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState()),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Column(
            Modifier
                .fillMaxWidth()
                .padding(horizontal = 24.dp, vertical = 32.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            when (val status = state.status) {
                CycleStatus.NoData -> EmptyState()
                is CycleStatus.DuringPeriod -> DuringPeriod(status, onEndedToday, onStillGoing)
                is CycleStatus.BetweenPeriodAndFertile -> BetweenState(status, state, onStarted)
                is CycleStatus.DuringFertileWindow -> FertileState(status, state, onStarted)
                is CycleStatus.PeriodApproaching -> ApproachingState(status, state, onStarted)
                is CycleStatus.PredictedDay -> PredictedDayState(status, state, onStarted, onNotYet)
                is CycleStatus.BeyondForecast -> BeyondForecastState(status, onStarted, onNotYet)
            }

            message?.let {
                Spacer(Modifier.height(20.dp))
                Card(Modifier.fillMaxWidth()) {
                    Row(
                        Modifier.fillMaxWidth().padding(12.dp),
                        horizontalArrangement = Arrangement.SpaceBetween,
                        verticalAlignment = Alignment.CenterVertically,
                    ) {
                        Text(it, style = MaterialTheme.typography.bodyMedium)
                        TextButton(onClick = onMessageShown) { Text("OK") }
                    }
                }
            }
        }

        Spacer(Modifier.weight(1f, fill = false))
        BannerSlot()
    }
}

// ---------------------------------------------------------------------------
// The hero
// ---------------------------------------------------------------------------

/**
 * The number, and it is the whole point of the screen.
 *
 * `displayLarge` is 72sp bold in this theme for exactly this. §38: the number
 * should command attention before secondary details.
 *
 * The unit is a separate, small, wide-tracked line rather than part of the
 * number, so "4" reads instantly and "DAYS" is available if you look.
 */
@Composable
private fun HeroCount(label: String, count: Int, unit: String) {
    Text(
        label.uppercase(),
        style = MaterialTheme.typography.labelMedium,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
        textAlign = TextAlign.Center,
    )
    Text(
        "$count",
        style = MaterialTheme.typography.displayLarge,
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier.clearAndSetSemantics {
            contentDescription = "$label: $count $unit"
        },
    )
    Text(
        unit.uppercase(),
        style = MaterialTheme.typography.labelLarge,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
    )
}

@Composable
private fun ForecastDetail(forecast: Prediction) {
    Spacer(Modifier.height(20.dp))
    Quiet("Most likely")
    Text(forecast.mostLikelyStartDate.pretty(), style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(8.dp))
    Quiet("Expected")
    Text("${forecast.windowStart.short()} – ${forecast.windowEnd.short()}")
    Spacer(Modifier.height(8.dp))
    ConfidenceRow(forecast.confidenceLabel)
}

/**
 * Confidence as dots plus a word.
 *
 * Never dots alone: §43 forbids relying on a visual cue by itself, and "how
 * sure is it" is precisely the thing a screen reader user needs.
 */
@Composable
private fun ConfidenceRow(label: ConfidenceLabel) {
    val filled = when (label) {
        ConfidenceLabel.LOW -> 1
        ConfidenceLabel.MEDIUM -> 2
        ConfidenceLabel.HIGH -> 3
    }
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(6.dp),
        modifier = Modifier.clearAndSetSemantics {
            contentDescription = "Prediction confidence: ${label.readable()}"
        },
    ) {
        Text("●".repeat(filled) + "○".repeat(3 - filled), color = MaterialTheme.colorScheme.primary)
        Text(label.readable(), style = MaterialTheme.typography.bodyMedium)
    }
}

/**
 * §21's fertility block, and §18's vocabulary.
 *
 * The words come from [FertilityLikelihood] rather than from here, because §18
 * forbids "safe" and "unsafe" and the safest place for that prohibition is a
 * type with no such value. This composable only decides *whether* to show a
 * label, never what it says.
 *
 * The disclaimer travels with it. §18 requires it near fertility features, and
 * "near" means on the same screen at the same time — a disclaimer one tab away
 * is a disclaimer nobody read.
 */
@Composable
private fun FertilitySection(estimate: FertilityEstimate?, likelihood: FertilityLikelihood?) {
    likelihood ?: return

    Spacer(Modifier.height(24.dp))
    androidx.compose.material3.HorizontalDivider()
    Spacer(Modifier.height(16.dp))

    Quiet("Fertility today")
    Text(likelihood.label, style = MaterialTheme.typography.titleMedium)

    if (estimate != null) {
        Spacer(Modifier.height(12.dp))
        // "Estimated ovulation", never "you are ovulating today" — §17. The word
        // is load-bearing: this is a health app in a category Play scrutinises,
        // and a sentence that reads as a medical assertion is both a product
        // failure and a compliance one.
        Quiet("Estimated ovulation")
        Text(estimate.ovulationDate.pretty(), style = MaterialTheme.typography.titleMedium)
        Spacer(Modifier.height(8.dp))
        Quiet("Estimated fertile window")
        Text("${estimate.fertileWindowStart.short()} – ${estimate.fertileWindowEnd.short()}")
    } else {
        Spacer(Modifier.height(6.dp))
        Text(
            "Log a few more cycles and the app will be able to estimate ovulation.",
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
        )
    }

    Spacer(Modifier.height(12.dp))
    FertilityDisclaimer()
}

/** §18's exact sentence. Required near fertility features and in About. */
@Composable
private fun FertilityDisclaimer() {
    Text(
        stringResource(R.string.fertility_disclaimer),
        style = MaterialTheme.typography.bodySmall,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
        textAlign = TextAlign.Center,
    )
}

@Composable
private fun Quiet(text: String) = Text(
    text,
    style = MaterialTheme.typography.labelMedium,
    color = MaterialTheme.colorScheme.onSurfaceVariant,
)

@Composable
private fun PrimaryAction(text: String, onClick: () -> Unit) {
    Spacer(Modifier.height(28.dp))
    Button(onClick = onClick, modifier = Modifier.fillMaxWidth()) { Text(text) }
}

// ---------------------------------------------------------------------------
// The six states
// ---------------------------------------------------------------------------

@Composable
private fun EmptyState() {
    Spacer(Modifier.height(24.dp))
    EmptyStateIllustration(size = 140.dp)
    Spacer(Modifier.height(24.dp))
    Text("No periods logged yet", style = MaterialTheme.typography.headlineSmall)
    Spacer(Modifier.height(8.dp))
    Text(
        "Log one and the app will start learning your cycle.",
        style = MaterialTheme.typography.bodyMedium,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
        textAlign = TextAlign.Center,
    )
}

@Composable
private fun DuringPeriod(
    status: CycleStatus.DuringPeriod,
    onEndedToday: () -> Unit,
    onStillGoing: () -> Unit,
) {
    Text("Period", style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(8.dp))
    HeroCount(label = "Day", count = status.dayOfPeriod, unit = "of your period")
    Spacer(Modifier.height(20.dp))
    Quiet("Started")
    Text(status.startedOn.pretty(), style = MaterialTheme.typography.titleMedium)
    status.endedOn?.let {
        Spacer(Modifier.height(8.dp))
        Quiet("Ended")
        Text(it.pretty(), style = MaterialTheme.typography.titleMedium)
    }
    status.typicalDurationDays?.let {
        Spacer(Modifier.height(8.dp))
        Quiet("Typical duration")
        Text("${it.first}–${it.last} days")
    }

    // Once an end date exists the only useful action is undoing it. Offering
    // "Period ended" again would be a button that visibly does nothing, which
    // is how the tap looked before this state carried the end date at all.
    if (status.endedOn == null) {
        PrimaryAction("Period ended", onEndedToday)
        Spacer(Modifier.height(8.dp))
        OutlinedButton(onClick = onStillGoing, modifier = Modifier.fillMaxWidth()) { Text("Still going") }
    } else {
        PrimaryAction("Still going", onStillGoing)
    }
}

@Composable
private fun BetweenState(
    status: CycleStatus.BetweenPeriodAndFertile,
    state: TodayUiState,
    onStarted: () -> Unit,
) {
    CycleRing(state)
    Text("Cycle day ${status.cycleDay}", style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(16.dp))
    HeroCount(label = "Next period in", count = status.daysToNextPeriod, unit = "days")
    // The fertile-window line from §22 appears when Batch 04 estimates it.
    // A placeholder number here would be inventing a fertility estimate.
    status.daysToFertileWindow?.let {
        Spacer(Modifier.height(16.dp))
        Quiet("Estimated fertile window in")
        Text("$it days")
    }
    state.forecast?.let { ForecastDetail(it) }
    FertilitySection(state.fertility, state.fertilityToday)
    PrimaryAction("Started period", onStarted)
}

@Composable
private fun FertileState(
    status: CycleStatus.DuringFertileWindow,
    state: TodayUiState,
    onStarted: () -> Unit,
) {
    Text("Estimated fertile window", style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(16.dp))
    status.daysToOvulation?.let { HeroCount("Estimated ovulation in", it, "days") }
    Spacer(Modifier.height(16.dp))
    Quiet("Next period")
    Text("About ${status.daysToNextPeriod} days")
    state.fertility?.let {
        Spacer(Modifier.height(12.dp))
        Quiet("Estimated fertile window")
        Text("${it.fertileWindowStart.short()} – ${it.fertileWindowEnd.short()}")
    }
    Spacer(Modifier.height(16.dp))
    FertilityDisclaimer()
    state.forecast?.let { ForecastDetail(it) }
    PrimaryAction("Started period", onStarted)
}

@Composable
private fun ApproachingState(
    status: CycleStatus.PeriodApproaching,
    state: TodayUiState,
    onStarted: () -> Unit,
) {
    CycleRing(state)
    Text("Cycle day ${status.cycleDay}", style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(16.dp))
    HeroCount(label = "Period likely in", count = status.daysUntil, unit = if (status.daysUntil == 1) "day" else "days")
    ForecastDetail(status.forecast)
    FertilitySection(state.fertility, state.fertilityToday)
    PrimaryAction(if (status.daysUntil <= 2) "Started early?" else "Started period", onStarted)
}

@Composable
private fun PredictedDayState(
    status: CycleStatus.PredictedDay,
    state: TodayUiState,
    onStarted: () -> Unit,
    onNotYet: () -> Unit,
) {
    CycleRing(state)
    Spacer(Modifier.height(8.dp))
    Text(
        "Your period may start today.",
        style = MaterialTheme.typography.headlineSmall,
        textAlign = TextAlign.Center,
    )
    ForecastDetail(status.forecast)
    PrimaryAction("Started", onStarted)
    Spacer(Modifier.height(8.dp))
    OutlinedButton(onClick = onNotYet, modifier = Modifier.fillMaxWidth()) { Text("Not yet") }
}

/**
 * §22's last state, and the one the tone of the whole product rests on.
 *
 * "Your period is late!" is explicitly ruled out. Late implies a schedule the
 * user failed to keep; the truth is that an estimate was imprecise. So the
 * screen shows what it originally said, what it says now, and asks — which is
 * also the only honest thing to do with a forecast that has moved.
 */
@Composable
private fun BeyondForecastState(
    status: CycleStatus.BeyondForecast,
    onStarted: () -> Unit,
    onNotYet: () -> Unit,
) {
    Spacer(Modifier.height(16.dp))
    Text("Not yet?", style = MaterialTheme.typography.headlineMedium)
    Spacer(Modifier.height(20.dp))
    Quiet("Original forecast")
    Text(status.originalForecast.pretty(), style = MaterialTheme.typography.titleMedium)
    Spacer(Modifier.height(12.dp))
    Quiet("Updated forecast")
    Text(
        "${status.updated.windowStart.short()} – ${status.updated.windowEnd.short()}",
        style = MaterialTheme.typography.titleMedium,
    )
    Spacer(Modifier.height(8.dp))
    ConfidenceRow(status.updated.confidenceLabel)
    PrimaryAction("Started period", onStarted)
    Spacer(Modifier.height(8.dp))
    OutlinedButton(onClick = onNotYet, modifier = Modifier.fillMaxWidth()) { Text("Not yet") }
}

@Composable
private fun CycleRing(state: TodayUiState) {
    CycleProgressMark(
        color = MaterialTheme.colorScheme.primary,
        accent = PeriodThemeDefaults.cycleColors.periodPredicted,
        progress = state.cycleProgress,
        size = 72.dp,
    )
    Spacer(Modifier.height(16.dp))
}

/**
 * The banner slot: reserved, and empty until Batch 07.
 *
 * Reserving it now is the entire point. §48 requires no layout jump when an ad
 * loads and a graceful gap when one fails, and both are properties of the space
 * existing whether or not anything fills it. Retrofitting the reservation later
 * means shipping the jump first and discovering it in a QA round.
 *
 * §33 decides where it may go: never in onboarding, never over controls, never
 * between the steps of a health workflow. Bottom of Today, below everything.
 */
@Composable
private fun BannerSlot() {
    Box(
        Modifier
            .fillMaxWidth()
            .height(BANNER_HEIGHT)
            .padding(top = 8.dp),
        contentAlignment = Alignment.Center,
    ) {
        // Nothing is drawn. An empty reserved space is correct and invisible;
        // a "your ad here" box would be a placeholder for a thing the user is
        // paying to remove.
    }
}

/** Standard AdMob banner height, so Batch 07 changes what is inside and not the layout. */
private val BANNER_HEIGHT = 50.dp

// ---------------------------------------------------------------------------
// Logging — §23, §24, §25
// ---------------------------------------------------------------------------

/**
 * §23, and the whole of it is that this takes two taps.
 *
 * `Started period` opens this; `Yes — today` closes it and the forecast has
 * moved. No symptoms, no mood, no notes, no survey — §23 lists all four as
 * things not to force, and every one of them is a reason somebody stops logging.
 *
 * Spotting is here rather than on a separate screen because it is the answer to
 * the same question the user just asked themselves, and because putting it
 * behind another tap is how it stops being recorded at all.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LogPeriodSheet(
    today: LocalDate,
    onToday: () -> Unit,
    onDate: (LocalDate) -> Unit,
    onSpotting: () -> Unit,
    onDismiss: () -> Unit,
) {
    var pickingDate by remember { mutableStateOf(false) }
    val sheetState = rememberModalBottomSheetState()

    if (pickingDate) {
        val pickerState = rememberDatePickerState(
            selectableDates = object : SelectableDates {
                override fun isSelectableDate(utcTimeMillis: Long): Boolean =
                    java.time.Instant.ofEpochMilli(utcTimeMillis)
                        .atZone(java.time.ZoneOffset.UTC).toLocalDate() <= today
            },
        )
        DatePickerDialog(
            onDismissRequest = { pickingDate = false },
            confirmButton = {
                TextButton(onClick = {
                    pickerState.selectedDateMillis?.let {
                        onDate(
                            java.time.Instant.ofEpochMilli(it)
                                .atZone(java.time.ZoneOffset.UTC).toLocalDate(),
                        )
                    }
                    pickingDate = false
                }) { Text("Log it") }
            },
            dismissButton = { TextButton(onClick = { pickingDate = false }) { Text("Cancel") } },
        ) { DatePicker(state = pickerState) }
        return
    }

    ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState) {
        Column(
            Modifier.fillMaxWidth().padding(24.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            Text("Started today?", style = MaterialTheme.typography.headlineSmall)
            Spacer(Modifier.height(24.dp))
            Button(onClick = onToday, modifier = Modifier.fillMaxWidth()) { Text("Yes — today") }
            Spacer(Modifier.height(8.dp))
            OutlinedButton(onClick = { pickingDate = true }, modifier = Modifier.fillMaxWidth()) {
                Text("Choose another date")
            }
            Spacer(Modifier.height(16.dp))
            TextButton(onClick = onSpotting) { Text("This was spotting, not a period") }
            Spacer(Modifier.height(16.dp))
        }
    }
}

/**
 * §25's question, asked once and only when the length suggests it.
 *
 * The same paragraph that permits this question says not to over-question, so
 * it fires on an entry of two days or fewer, once per record, and never again
 * if dismissed. Both answers are complete — there is no "ask me later", because
 * that is the option that turns one question into three.
 */
@Composable
private fun PeriodOrSpottingDialog(onPeriod: () -> Unit, onSpotting: () -> Unit) {
    androidx.compose.material3.AlertDialog(
        onDismissRequest = onPeriod,
        title = { Text("Was this your period or spotting?") },
        text = {
            Text(
                "That entry was quite short. Spotting is recorded separately and " +
                    "does not change your cycle predictions.",
            )
        },
        confirmButton = { TextButton(onClick = onPeriod) { Text("Period") } },
        dismissButton = { TextButton(onClick = onSpotting) { Text("Spotting") } },
    )
}

// ---------------------------------------------------------------------------

internal fun ConfidenceLabel.readable(): String = when (this) {
    ConfidenceLabel.LOW -> "Low"
    ConfidenceLabel.MEDIUM -> "Medium"
    ConfidenceLabel.HIGH -> "High"
}

private val longFormat = DateTimeFormatter.ofPattern("d MMMM")
private val shortFormat = DateTimeFormatter.ofPattern("d MMM")
private fun LocalDate.pretty(): String = format(longFormat)
private fun LocalDate.short(): String = format(shortFormat)

// ---------------------------------------------------------------------------
// Previews — every state, both themes. See docs/design/README.md on why both.
// ---------------------------------------------------------------------------

private fun previewForecast(on: LocalDate, label: ConfidenceLabel = ConfidenceLabel.HIGH) = Prediction(
    mostLikelyStartDate = on,
    windowStart = on.minusDays(2),
    windowEnd = on.plusDays(2),
    confidenceScore = 0.7,
    confidenceLabel = label,
    modelVersion = "preview",
)

private val previewToday: LocalDate = LocalDate.of(2026, 8, 18)

private fun previewState(
    startsAgo: Long?,
    forecastIn: Long?,
    ended: Long? = null,
    original: LocalDate? = null,
) = TodayUiState(
    loading = false,
    periods = startsAgo?.let {
        listOf(
            PeriodRecord(
                id = 1,
                startDate = previewToday.minusDays(it),
                endDate = ended?.let { e -> previewToday.minusDays(e) },
            ),
        )
    } ?: emptyList(),
    forecast = forecastIn?.let { previewForecast(previewToday.plusDays(it)) },
    today = previewToday,
    originalForecastDate = original,
    fertility = forecastIn?.let { FertilityEstimate.from(previewForecast(previewToday.plusDays(it))) },
)

@Preview(name = "Today · empty", showBackground = true)
@Preview(name = "Today · empty dark", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewEmpty() = PreviewHost(previewState(null, null))

@Preview(name = "Today · during period", showBackground = true)
@Composable
private fun PreviewDuring() = PreviewHost(previewState(startsAgo = 2, forecastIn = 27))

@Preview(name = "Today · between", showBackground = true)
@Composable
private fun PreviewBetween() = PreviewHost(previewState(startsAgo = 12, forecastIn = 17, ended = 8))

@Preview(name = "Today · approaching", showBackground = true)
@Preview(name = "Today · approaching dark", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun PreviewApproaching() = PreviewHost(previewState(startsAgo = 24, forecastIn = 4, ended = 20))

@Preview(name = "Today · predicted day", showBackground = true)
@Composable
private fun PreviewPredictedDay() = PreviewHost(previewState(startsAgo = 28, forecastIn = 0, ended = 24))

@Preview(name = "Today · beyond forecast", showBackground = true)
@Composable
private fun PreviewBeyond() = PreviewHost(
    previewState(startsAgo = 33, forecastIn = 2, ended = 29, original = previewToday.minusDays(3)),
)

@Composable
private fun PreviewHost(state: TodayUiState) {
    PeriodTheme {
        TodayContent(
            state = state,
            message = null,
            onStarted = {}, onNotYet = {}, onEndedToday = {}, onStillGoing = {}, onMessageShown = {},
        )
    }
}
