package dev.privacyllc.period.feature.onboarding

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
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.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.SelectableDates
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
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.semantics.Role
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dev.privacyllc.period.core.datastore.NotificationPrivacy
import dev.privacyllc.period.designsystem.art.ForecastIllustration
import dev.privacyllc.period.designsystem.art.LastPeriodIllustration
import dev.privacyllc.period.designsystem.art.LearningIllustration
import dev.privacyllc.period.designsystem.art.NotificationPrivacyIllustration
import dev.privacyllc.period.designsystem.art.PeriodEndIllustration
import dev.privacyllc.period.designsystem.art.PrivacyIllustration
import dev.privacyllc.period.designsystem.art.WelcomeIllustration
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter

/**
 * PRODUCT_PLAN.md §19 and §56, screen for screen.
 *
 * Two rules shape the whole flow:
 *
 * **The forecast comes before anything is asked of the user.** §19: do not force
 * registration before showing the forecast. There is no account in V1 at all, so
 * this is really about not gating the one thing they came for behind anything —
 * including a permission prompt, which is why none appears here.
 *
 * **Nothing is written until the last step.** Somebody who abandons onboarding
 * halfway has not asked this app to remember anything about them.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnboardingScreen(
    onFinished: () -> Unit,
    viewModel: OnboardingViewModel = hiltViewModel(),
) {
    val state by viewModel.state.collectAsStateWithLifecycle()

    Column(Modifier.fillMaxSize()) {
        LinearProgressIndicator(
            progress = { (Step.entries.indexOf(state.step) + 1f) / Step.entries.size },
            modifier = Modifier.fillMaxWidth(),
        )

        // Vertical padding only. The hero illustration is full-bleed and Compose
        // has no negative padding, so horizontal inset belongs to the content
        // inside each step — see StepBody — rather than to this column.
        Column(
            Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
                .padding(vertical = 24.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            when (state.step) {
                Step.WELCOME -> Welcome(viewModel::toWelcomeNext)
                Step.LAST_PERIOD -> LastPeriod(state, viewModel)
                Step.PERIOD_END -> PeriodEnd(state, viewModel)
                Step.PREVIOUS_HISTORY -> PreviousHistory(state, viewModel)
                Step.PRIVACY_PROMISE -> PrivacyPromise(viewModel::donePrivacyPromise)
                Step.NOTIFICATION_PRIVACY -> NotificationPrivacyStep(state, viewModel)
                Step.FIRST_FORECAST -> FirstForecast(state) { viewModel.complete(onFinished) }
            }

            // These sit outside the step composables, so they carry the inset
            // the parent column no longer applies.
            Column(
                Modifier.padding(horizontal = 24.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
            ) {
                state.message?.let {
                    Spacer(Modifier.height(16.dp))
                    Card { Text(it, Modifier.padding(12.dp), style = MaterialTheme.typography.bodyMedium) }
                    TextButton(onClick = viewModel::messageShown) { Text("OK") }
                }

                if (state.step != Step.WELCOME && state.step != Step.FIRST_FORECAST) {
                    Spacer(Modifier.height(8.dp))
                    TextButton(onClick = viewModel::back) { Text("Back") }
                }
            }
        }
    }
}

/**
 * One onboarding step: a full-bleed hero, then inset content.
 *
 * Every step had hand-rolled this — `Spacer / Illustration(size) / Spacer /
 * Heading / controls` — in three different spacer rhythms with a reason recorded
 * for only two of them. The rhythm lives here now, and a step declares what is
 * different about it rather than restating what is the same.
 *
 * [artHeight] stays per-step and is deliberately not uniform: the steps carrying
 * three or four controls under the art take a smaller hero so the primary button
 * is reachable without scrolling. `Illustration` caps whatever is passed at a
 * third of the screen, which is what makes landscape survivable.
 *
 * The hero is emitted OUTSIDE the inset column, because it is full-bleed and the
 * screen's horizontal padding would otherwise box it in.
 */
@Composable
private fun StepBody(
    artHeight: Dp,
    art: @Composable (Dp) -> Unit,
    title: String,
    body: String? = null,
    content: @Composable ColumnScope.() -> Unit,
) {
    art(artHeight)
    Spacer(Modifier.height(24.dp))
    Column(
        Modifier.fillMaxWidth().padding(horizontal = 24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Heading(title, body)
        content()
    }
}

@Composable
private fun Heading(title: String, body: String? = null) {
    // headlineLarge, not headlineMedium: these titles sit under a hero that now
    // fills a third of the screen, and 28sp under 280dp of artwork reads as a
    // caption rather than a heading.
    Text(title, style = MaterialTheme.typography.headlineLarge, textAlign = TextAlign.Center)
    if (body != null) {
        Spacer(Modifier.height(12.dp))
        Text(
            body,
            style = MaterialTheme.typography.bodyLarge,
            textAlign = TextAlign.Center,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
        )
    }
    Spacer(Modifier.height(28.dp))
}

@Composable
private fun Welcome(onNext: () -> Unit) {
    StepBody(
        artHeight = 280.dp,
        art = { WelcomeIllustration(size = it) },
        title = "Know what's coming.",
        body = "Track your period and get predictions that learn your cycle.",
    ) {
        Button(onClick = onNext, modifier = Modifier.fillMaxWidth()) { Text("Get Started") }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LastPeriod(state: OnboardingUiState, viewModel: OnboardingViewModel) {
    StepBody(
        artHeight = 280.dp,
        art = { LastPeriodIllustration(size = it) },
        title = "When did your last period start?",
    ) {
    DateField(
        label = state.lastPeriodStart?.pretty() ?: "Choose a date",
        selected = state.lastPeriodStart,
        latest = state.today,
        onSelected = viewModel::setLastPeriodStart,
    )

    Spacer(Modifier.height(24.dp))
    Button(
        onClick = viewModel::confirmLastPeriod,
        enabled = state.canContinueFromLastPeriod,
        modifier = Modifier.fillMaxWidth(),
    ) { Text("Continue") }
    }
}

@Composable
private fun PeriodEnd(state: OnboardingUiState, viewModel: OnboardingViewModel) {
    // A shorter hero than the other steps: this one carries three buttons under
    // the date field, and the primary must stay reachable without scrolling.
    // Verified at font scale 2.0, which is how the 104dp version was chosen.
    StepBody(
        artHeight = 200.dp,
        art = { PeriodEndIllustration(size = it) },
        title = "When did it end?",
    ) {
    var picked by remember { mutableStateOf<LocalDate?>(null) }
    DateField(
        label = picked?.pretty() ?: "Choose a date",
        selected = picked,
        earliest = state.lastPeriodStart,
        latest = state.today,
        onSelected = { picked = it },
    )

    Spacer(Modifier.height(24.dp))
    Button(
        onClick = { viewModel.setPeriodEnd(picked) },
        enabled = picked != null,
        modifier = Modifier.fillMaxWidth(),
    ) { Text("Continue") }

    Spacer(Modifier.height(8.dp))
    // Both of these mean "no end date". §24: never invent one.
    OutlinedButton(onClick = { viewModel.setPeriodEnd(null) }, Modifier.fillMaxWidth()) { Text("Still going") }
    Spacer(Modifier.height(8.dp))
    OutlinedButton(onClick = { viewModel.setPeriodEnd(null) }, Modifier.fillMaxWidth()) { Text("I'm not sure") }
    }
}

@Composable
private fun PreviousHistory(state: OnboardingUiState, viewModel: OnboardingViewModel) {
    StepBody(
        artHeight = 240.dp,
        art = { LearningIllustration(size = it) },
        title = "Remember any earlier periods?",
        body = "Adding previous dates helps us learn your cycle faster.",
    ) {
    state.previousStarts.forEach { date ->
        Row(
            Modifier.fillMaxWidth().padding(vertical = 2.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(date.pretty(), style = MaterialTheme.typography.bodyLarge)
            TextButton(onClick = { viewModel.removePreviousStart(date) }) { Text("Remove") }
        }
    }

    Spacer(Modifier.height(8.dp))
    DateField(
        label = "Add Previous Period",
        selected = null,
        latest = state.lastPeriodStart?.minusDays(1) ?: state.today,
        onSelected = viewModel::addPreviousStart,
    )

    Spacer(Modifier.height(24.dp))
    Button(onClick = viewModel::donePreviousHistory, modifier = Modifier.fillMaxWidth()) {
        Text(if (state.previousStarts.isEmpty()) "Skip" else "Continue")
    }
    }
}

@Composable
private fun PrivacyPromise(onNext: () -> Unit) {
    // §4 requires this promise here, in Settings, and on the public privacy
    // page. The wording is deliberate: we never SELL your data. It does not
    // claim no third party ever processes anything, because Play Billing and an
    // ad SDK will, and a promise the implementation cannot keep is worse than a
    // narrower one it can.
    StepBody(
        artHeight = 280.dp,
        art = { PrivacyIllustration(size = it) },
        title = "Your cycle belongs to you.",
        body = "We will never sell your personal or health data.\n\n" +
            "Your period history and fertility information are private. We don't sell them " +
            "to advertisers, data brokers, or third parties.",
    ) {
        Button(onClick = onNext, modifier = Modifier.fillMaxWidth()) { Text("Continue") }
    }
}

@Composable
private fun NotificationPrivacyStep(state: OnboardingUiState, viewModel: OnboardingViewModel) {
    // Also shorter: three option cards and a button follow it.
    StepBody(
        artHeight = 200.dp,
        art = { NotificationPrivacyIllustration(size = it) },
        title = "How should reminders appear?",
    ) {
    Column(Modifier.selectableGroup().fillMaxWidth()) {
        PrivacyOption(
            title = "Discreet",
            example = "\"Quick check-in\"",
            selected = state.notificationPrivacy == NotificationPrivacy.DISCREET,
        ) { viewModel.setNotificationPrivacy(NotificationPrivacy.DISCREET) }

        PrivacyOption(
            title = "Maximum privacy",
            example = "\"Reminder\"",
            selected = state.notificationPrivacy == NotificationPrivacy.MAXIMUM_PRIVACY,
        ) { viewModel.setNotificationPrivacy(NotificationPrivacy.MAXIMUM_PRIVACY) }

        // §28: the user must explicitly choose Direct. It is last and it is
        // never the default, because a lock screen is read by whoever is nearby.
        PrivacyOption(
            title = "Direct",
            example = "\"Your period may start soon\"",
            selected = state.notificationPrivacy == NotificationPrivacy.DIRECT,
        ) { viewModel.setNotificationPrivacy(NotificationPrivacy.DIRECT) }
    }

    Spacer(Modifier.height(24.dp))
    Button(
        onClick = viewModel::finishSetup,
        enabled = !state.saving,
        modifier = Modifier.fillMaxWidth(),
    ) { Text(if (state.saving) "Saving…" else "Continue") }
    }
}

@Composable
    // The whole row is the target, not the radio.
    //
    // Found by tapping the label "Maximum privacy" on a device and watching
    // nothing happen: only the RadioButton was clickable, so the option that
    // decides what a lock screen shows could only be changed by hitting a
    // ~20dp circle. selectable() on the Row with onClick = null on the radio is
    // Material's documented pattern, and it also merges the semantics — TalkBack
    // now announces one selectable option per row instead of a radio button
    // followed by two loose pieces of text.
private fun PrivacyOption(title: String, example: String, selected: Boolean, onSelect: () -> Unit) {
    Card(Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
        Row(
            Modifier
                .fillMaxWidth()
                .selectable(
                    selected = selected,
                    role = Role.RadioButton,
                    onClick = onSelect,
                )
                .padding(12.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            RadioButton(selected = selected, onClick = null)
            Spacer(Modifier.size(8.dp))
            Column {
                Text(title, style = MaterialTheme.typography.titleMedium)
                Text(
                    example,
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
            }
        }
    }
}

@Composable
private fun FirstForecast(state: OnboardingUiState, onDone: () -> Unit) {
    StepBody(
        artHeight = 240.dp,
        art = { ForecastIllustration(size = it) },
        title = "Your first forecast",
    ) {
    val forecast = state.forecast
    if (forecast == null) {
        Text(
            "We'll start predicting once there's a little history to learn from.",
            style = MaterialTheme.typography.bodyLarge,
            textAlign = TextAlign.Center,
        )
    } else {
        Card(Modifier.fillMaxWidth()) {
            Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
                Label("Next period")
                Text(forecast.mostLikelyStartDate.pretty(), style = MaterialTheme.typography.headlineSmall)
                Spacer(Modifier.height(8.dp))
                Label("Expected window")
                Text("${forecast.windowStart.pretty()} – ${forecast.windowEnd.pretty()}")
                Spacer(Modifier.height(8.dp))
                Label("Prediction confidence")
                Text(forecast.confidenceLabel.readable())
            }
        }
        Spacer(Modifier.height(12.dp))
        Text(
            "These are estimates from your cycle history, and they get better as you log more.",
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
        )
    }

    Spacer(Modifier.height(28.dp))
    Button(onClick = onDone, modifier = Modifier.fillMaxWidth()) { Text("Go to Today") }
    }
}

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

/** A date button that opens a Material 3 picker, bounded rather than validated. */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DateField(
    label: String,
    selected: LocalDate?,
    onSelected: (LocalDate) -> Unit,
    earliest: LocalDate? = null,
    latest: LocalDate? = null,
) {
    var open by remember { mutableStateOf(false) }

    OutlinedButton(onClick = { open = true }, modifier = Modifier.fillMaxWidth()) { Text(label) }

    if (open) {
        val pickerState = androidx.compose.material3.rememberDatePickerState(
            initialSelectedDateMillis = selected?.toEpochMillis(),
            selectableDates = object : SelectableDates {
                // A future period start is not offered rather than rejected.
                // Refusing a tap the UI allowed is a worse experience than not
                // allowing it, and there is no honest meaning for the value.
                override fun isSelectableDate(utcTimeMillis: Long): Boolean {
                    val d = Instant.ofEpochMilli(utcTimeMillis).atZone(ZoneOffset.UTC).toLocalDate()
                    if (earliest != null && d < earliest) return false
                    if (latest != null && d > latest) return false
                    return true
                }
            },
        )
        DatePickerDialog(
            onDismissRequest = { open = false },
            confirmButton = {
                TextButton(onClick = {
                    pickerState.selectedDateMillis?.let {
                        onSelected(Instant.ofEpochMilli(it).atZone(ZoneOffset.UTC).toLocalDate())
                    }
                    open = false
                }) { Text("OK") }
            },
            dismissButton = { TextButton(onClick = { open = false }) { Text("Cancel") } },
        ) { DatePicker(state = pickerState) }
    }
}

private fun LocalDate.toEpochMillis(): Long = atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()

private val dateFormat = DateTimeFormatter.ofPattern("d MMMM yyyy")
internal fun LocalDate.pretty(): String = format(dateFormat)

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