package dev.privacyllc.period.feature.insights

import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
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.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.core.data.PredictionAccuracy
import dev.privacyllc.period.designsystem.PeriodTheme
import dev.privacyllc.period.designsystem.art.LearningIllustration
import dev.privacyllc.period.domain.cycle.CycleInsights
import dev.privacyllc.period.domain.cycle.LearningStage
import dev.privacyllc.period.domain.cycle.PeriodRecord
import java.time.LocalDate

/**
 * §27. Its purpose is one sentence: **show the user what the app has learned.**
 *
 * Which means the screen has to be honest about how little that sometimes is.
 * Every figure here is absent rather than approximated when there is not enough
 * history — §27 ends with *do not overstate accuracy*, and the easiest way to
 * overstate is to average two numbers and print a decimal place.
 *
 * Nothing on this screen leaves the device. §46 names `prediction_error=` as a
 * value that must never become an analytics event, and this is exactly the
 * screen that would tempt somebody to send one.
 */
@Composable
fun InsightsScreen(viewModel: InsightsViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    InsightsContent(state)
}

@Composable
private fun InsightsContent(state: InsightsUiState) {
    val i = state.insights

    Column(
        Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
            .padding(16.dp),
    ) {
        LearningBanner(i.stage, i.cycleCount)

        if (i.stage != LearningStage.NO_DATA) {
            Spacer(Modifier.height(16.dp))
            CycleCard(i)
        }

        if (i.recentCycleLengths.isNotEmpty()) {
            Spacer(Modifier.height(12.dp))
            RecentCyclesCard(i.recentCycleLengths)
        }

        Spacer(Modifier.height(12.dp))
        AccuracyCard(state.accuracy)
    }
}

/**
 * §27's learning copy, chosen by what the data supports rather than by mood.
 *
 * "Personalized to your cycle" is a claim. It appears when there are enough
 * confirmed cycles for the forecast to genuinely be hers rather than a default,
 * and not before.
 */
@Composable
private fun LearningBanner(stage: LearningStage, cycleCount: Int) {
    val (title, body) = when (stage) {
        LearningStage.NO_DATA ->
            "Nothing learned yet" to "Log a period and this screen will start filling in."
        LearningStage.LEARNING ->
            "Learning your cycle" to "One cycle recorded. A few more and the predictions get noticeably better."
        LearningStage.GETTING_TO_KNOW ->
            "Getting to know your pattern" to "$cycleCount cycles recorded. The forecast is starting to be yours."
        LearningStage.PERSONALIZED ->
            "Personalized to your cycle" to "$cycleCount cycles recorded. Predictions are based on your own history."
    }

    Column(
        Modifier.fillMaxWidth().padding(vertical = 8.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        if (stage != LearningStage.PERSONALIZED) {
            LearningIllustration(size = 110.dp)
            Spacer(Modifier.height(12.dp))
        }
        Text(title, style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center)
        Spacer(Modifier.height(6.dp))
        Text(
            body,
            style = MaterialTheme.typography.bodyMedium,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
        )
    }
}

@Composable
private fun CycleCard(i: CycleInsights) {
    Card(Modifier.fillMaxWidth()) {
        Column(Modifier.padding(16.dp)) {
            Text("Your cycle", style = MaterialTheme.typography.titleMedium)
            Spacer(Modifier.height(12.dp))
            // Absent rather than approximated. A dash is honest; a number
            // computed from one observation is not.
            Stat("Average cycle", i.averageCycleDays?.let { "%.1f days".format(it) })
            Stat("Typical range", i.typicalCycleRange?.let { "${it.first}–${it.last} days" })
            Stat("Average period", i.averagePeriodDays?.let { "%.1f days".format(it) })
        }
    }
}

@Composable
private fun RecentCyclesCard(lengths: List<Int>) {
    Card(Modifier.fillMaxWidth()) {
        Column(Modifier.padding(16.dp)) {
            Text("Recent cycles", style = MaterialTheme.typography.titleMedium)
            Spacer(Modifier.height(8.dp))
            lengths.forEach {
                Text("$it days", style = MaterialTheme.typography.bodyLarge)
            }
        }
    }
}

/**
 * §16's accuracy figures — the ones the document calls a powerful trust feature.
 *
 * They are also the easiest thing on this screen to lie with, which is why
 * `PredictionAccuracy` refuses to report below three scored forecasts and this
 * card says so plainly instead of showing a mean of one.
 */
@Composable
private fun AccuracyCard(a: PredictionAccuracy) {
    Card(Modifier.fillMaxWidth()) {
        Column(Modifier.padding(16.dp)) {
            Text("Prediction accuracy", style = MaterialTheme.typography.titleMedium)
            Spacer(Modifier.height(8.dp))

            if (!a.hasEnoughToShow) {
                Text(
                    if (a.scoredCount == 0) {
                        "Once a predicted period arrives, the app will start scoring itself here."
                    } else {
                        "${a.scoredCount} prediction(s) scored so far. Accuracy figures appear " +
                            "after ${PredictionAccuracy.MINIMUM_TO_SHOW}, so they mean something."
                    },
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.onSurfaceVariant,
                )
                return@Column
            }

            a.lastSignedErrorDays?.let {
                Stat(
                    "Last prediction",
                    when {
                        it == 0 -> "exact"
                        it < 0 -> "${-it} day(s) early"
                        else -> "$it day(s) late"
                    },
                )
            }
            Stat("Average error", a.meanAbsoluteErrorDays?.let { "%.1f days".format(it) })
            Stat("Within one day", "${a.withinOneDay} of your last ${a.scoredCount}")
            Stat("Within two days", "${a.withinTwoDays} of your last ${a.scoredCount}")
        }
    }
}

@Composable
private fun Stat(label: String, value: String?) {
    Row(
        Modifier.fillMaxWidth().padding(vertical = 4.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
    ) {
        Text(label, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
        Text(value ?: "—", style = MaterialTheme.typography.bodyLarge)
    }
}

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

private fun previewPeriods(vararg gaps: Long): List<PeriodRecord> {
    var cursor = LocalDate.of(2026, 3, 1)
    val out = mutableListOf(cursor)
    gaps.forEach { cursor = cursor.plusDays(it); out += cursor }
    return out.mapIndexed { i, d -> PeriodRecord(i.toLong(), d, d.plusDays(4)) }
}

@Preview(name = "Insights · learning", showBackground = true, heightDp = 800)
@Composable
private fun PreviewLearning() = PeriodTheme {
    InsightsContent(InsightsUiState(insights = CycleInsights.from(previewPeriods(29))))
}

@Preview(name = "Insights · personalized", showBackground = true, heightDp = 900)
@Preview(
    name = "Insights · personalized dark",
    showBackground = true,
    heightDp = 900,
    uiMode = Configuration.UI_MODE_NIGHT_YES,
)
@Composable
private fun PreviewPersonalized() = PeriodTheme {
    InsightsContent(
        InsightsUiState(
            insights = CycleInsights.from(previewPeriods(29, 28, 30, 29, 28, 30)),
            accuracy = PredictionAccuracy.from(listOf(-1, 1, 0, 2, -1, 1)),
        ),
    )
}
