package dev.privacyllc.period.feature.calendar

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
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.aspectRatio
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.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.text.font.FontWeight
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.ConfirmedPeriodMarker
import dev.privacyllc.period.designsystem.art.FertileWindowMarker
import dev.privacyllc.period.designsystem.art.OvulationMarker
import dev.privacyllc.period.designsystem.art.PredictedPeriodMarker
import dev.privacyllc.period.domain.prediction.CalendarDay
import dev.privacyllc.period.domain.prediction.CalendarMarks
import dev.privacyllc.period.domain.prediction.DayMark
import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter

/**
 * PRODUCT_PLAN.md §26.
 *
 * The rule the whole screen is built around: **states differ in shape, not only
 * in colour**, and predicted never looks like a lighter confirmed. A calendar
 * whose meaning is carried by hue alone says nothing to a colourblind user,
 * nothing in greyscale, and nothing in the screenshot attached to a bug report.
 *
 * The legend is not decoration either. Four shapes need naming once, and a
 * legend is cheaper than four users working it out.
 */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CalendarScreen(viewModel: CalendarViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    val selected by viewModel.selected.collectAsStateWithLifecycle()

    selected?.let { day ->
        DayActionSheet(
            day = day,
            onLogPeriod = { viewModel.logPeriodStart(day.date) },
            onLogSpotting = { viewModel.logSpotting(day.date) },
            onRemove = { viewModel.removeEntry(day) },
            onDismiss = viewModel::clearSelection,
        )
    }

    CalendarContent(
        state = state,
        onPrevious = viewModel::previousMonth,
        onNext = viewModel::nextMonth,
        onSelect = viewModel::select,
        onMessageShown = viewModel::messageShown,
    )
}

@Composable
private fun CalendarContent(
    state: CalendarUiState,
    onPrevious: () -> Unit,
    onNext: () -> Unit,
    onSelect: (CalendarDay) -> Unit,
    onMessageShown: () -> Unit,
) {
    Column(
        Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
            .padding(16.dp),
    ) {
        MonthHeader(state.month, state.canGoForward, onPrevious, onNext)
        Spacer(Modifier.height(8.dp))
        WeekdayRow()
        Spacer(Modifier.height(4.dp))
        MonthGrid(state, onSelect)
        Spacer(Modifier.height(24.dp))
        Legend()

        state.message?.let {
            Spacer(Modifier.height(16.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") }
                }
            }
        }
    }
}

@Composable
private fun MonthHeader(month: YearMonth, canGoForward: Boolean, onPrevious: () -> Unit, onNext: () -> Unit) {
    Row(
        Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        IconButton(onClick = onPrevious) {
            Icon(Icons.Filled.ChevronLeft, contentDescription = "Previous month")
        }
        Text(month.format(monthFormat), style = MaterialTheme.typography.titleLarge)
        IconButton(onClick = onNext, enabled = canGoForward) {
            Icon(Icons.Filled.ChevronRight, contentDescription = "Next month")
        }
    }
}

@Composable
private fun WeekdayRow() {
    Row(Modifier.fillMaxWidth()) {
        listOf("M", "T", "W", "T", "F", "S", "S").forEachIndexed { i, label ->
            Text(
                label,
                modifier = Modifier
                    .weight(1f)
                    // The initials repeat, so a screen reader gets the full name.
                    .clearAndSetSemantics { contentDescription = fullWeekdayNames[i] },
                textAlign = TextAlign.Center,
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
    }
}

private val fullWeekdayNames =
    listOf("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")

@Composable
private fun MonthGrid(state: CalendarUiState, onSelect: (CalendarDay) -> Unit) {
    val cells = List(state.leadingBlanks) { null } + state.days
    cells.chunked(7).forEach { week ->
        Row(Modifier.fillMaxWidth()) {
            week.forEach { day ->
                Box(Modifier.weight(1f).aspectRatio(1f), contentAlignment = Alignment.Center) {
                    if (day != null) DayCell(day, onSelect)
                }
            }
            repeat(7 - week.size) { Spacer(Modifier.weight(1f)) }
        }
    }
}

@Composable
private fun DayCell(day: CalendarDay, onSelect: (CalendarDay) -> Unit) {
    Box(
        Modifier
            .fillMaxSize()
            .padding(3.dp)
            .clip(CircleShape)
            .clickable { onSelect(day) }
            .clearAndSetSemantics { contentDescription = day.accessibilityLabel },
        contentAlignment = Alignment.Center,
    ) {
        // Today is an outline around the whole cell rather than an underline.
        //
        // The underline was the first attempt and it collided with the spotting
        // dot, which also sits low in the cell — on the one day that was both
        // today and spotting, the two marks merged into an unreadable smudge.
        // A ring around the cell cannot overlap anything drawn inside it.
        if (day.isToday) {
            Box(
                Modifier
                    .fillMaxSize()
                    .border(
                        width = 1.5.dp,
                        color = MaterialTheme.colorScheme.onSurfaceVariant,
                        shape = CircleShape,
                    ),
            )
        }
        Marker(day.mark)
        Text(
            "${day.date.dayOfMonth}",
            style = MaterialTheme.typography.bodyMedium,
            fontWeight = if (day.isToday) FontWeight.Bold else FontWeight.Normal,
            color = contentColorFor(day.mark),
        )

    }
}

@Composable
private fun Marker(mark: DayMark) {
    val cycle = PeriodThemeDefaults.cycleColors
    val dp = 40.dp
    when (mark) {
        DayMark.CONFIRMED_PERIOD -> ConfirmedPeriodMarker(cycle.periodConfirmed, size = dp)
        DayMark.PREDICTED_PERIOD -> PredictedPeriodMarker(cycle.periodPredicted, size = dp)
        DayMark.FERTILE_WINDOW -> FertileWindowMarker(cycle.fertileWindow, size = dp)
        // Ovulation is the ring PLUS a small star low in the cell.
        //
        // The star alone was centred, which put it directly behind the numeral —
        // in greyscale the "18" and the mark merged into one smudge. Drawing the
        // ring as well is also semantically right: the ovulation day IS inside
        // the fertile window, and the pair reads as "that window, and this
        // particular day in it".
        DayMark.OVULATION -> {
            FertileWindowMarker(cycle.fertileWindow, size = dp)
            Box(Modifier.size(dp), contentAlignment = Alignment.BottomCenter) {
                OvulationMarker(cycle.ovulation, Modifier.padding(bottom = 1.dp), size = 11.dp)
            }
        }
        DayMark.SPOTTING -> SpottingMarker(cycle.periodConfirmed, dp)
        DayMark.NONE -> Unit
    }
}

/**
 * A small dot low in the cell.
 *
 * Deliberately not a ring of any kind: the other four marks are all circular
 * outlines or fills at cell size, and a fifth circle differing only in diameter
 * is the weakest possible distinction. A small dot sitting below the numeral is
 * a different *kind* of mark, which is what survives greyscale.
 */
@Composable
private fun SpottingMarker(color: Color, size: androidx.compose.ui.unit.Dp) {
    Box(Modifier.size(size), contentAlignment = Alignment.BottomCenter) {
        Box(
            Modifier
                .padding(bottom = 6.dp)
                .size(7.dp)
                .clip(CircleShape)
                .background(color),
        )
    }
}

@Composable
private fun contentColorFor(mark: DayMark): Color = when (mark) {
    // Only the solid fill needs inverted text; every other marker is an outline
    // that the numeral sits inside.
    DayMark.CONFIRMED_PERIOD -> MaterialTheme.colorScheme.onPrimary
    else -> MaterialTheme.colorScheme.onSurface
}

@Composable
private fun Legend() {
    Text("What the marks mean", style = MaterialTheme.typography.titleSmall)
    Spacer(Modifier.height(8.dp))
    listOf(
        DayMark.CONFIRMED_PERIOD to "Period you logged",
        DayMark.PREDICTED_PERIOD to "Period predicted",
        DayMark.SPOTTING to "Spotting",
        DayMark.FERTILE_WINDOW to "Estimated fertile window",
        DayMark.OVULATION to "Estimated ovulation",
    ).forEach { (mark, label) ->
        Row(
            Modifier.fillMaxWidth().padding(vertical = 2.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) { Marker(mark) }
            Spacer(Modifier.size(12.dp))
            Text(label, style = MaterialTheme.typography.bodyMedium)
        }
    }
    Spacer(Modifier.height(8.dp))
    Text(
        stringResource(R.string.fertility_disclaimer),
        style = MaterialTheme.typography.bodySmall,
        color = MaterialTheme.colorScheme.onSurfaceVariant,
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DayActionSheet(
    day: CalendarDay,
    onLogPeriod: () -> Unit,
    onLogSpotting: () -> Unit,
    onRemove: () -> Unit,
    onDismiss: () -> Unit,
) {
    ModalBottomSheet(onDismissRequest = onDismiss, sheetState = rememberModalBottomSheetState()) {
        Column(Modifier.fillMaxWidth().padding(24.dp)) {
            Text(day.date.format(dayFormat), style = MaterialTheme.typography.titleLarge)
            Spacer(Modifier.height(16.dp))

            when (day.mark) {
                DayMark.CONFIRMED_PERIOD, DayMark.SPOTTING ->
                    OutlinedButton(onClick = onRemove, modifier = Modifier.fillMaxWidth()) {
                        Text(if (day.mark == DayMark.SPOTTING) "Remove spotting" else "Remove this period")
                    }
                else -> {
                    androidx.compose.material3.Button(
                        onClick = onLogPeriod,
                        modifier = Modifier.fillMaxWidth(),
                    ) { Text("Period started this day") }
                    Spacer(Modifier.height(8.dp))
                    OutlinedButton(onClick = onLogSpotting, modifier = Modifier.fillMaxWidth()) {
                        Text("Spotting this day")
                    }
                }
            }
            Spacer(Modifier.height(16.dp))
        }
    }
}

private val monthFormat = DateTimeFormatter.ofPattern("MMMM yyyy")
private val dayFormat = DateTimeFormatter.ofPattern("EEEE d MMMM yyyy")

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

@Preview(name = "Calendar · light", showBackground = true, heightDp = 900)
@Preview(
    name = "Calendar · dark",
    showBackground = true,
    heightDp = 900,
    uiMode = Configuration.UI_MODE_NIGHT_YES,
)
@Composable
private fun PreviewCalendar() {
    val month = YearMonth.of(2026, 8)
    val today = LocalDate.of(2026, 8, 18)
    val days = CalendarMarks.forMonth(
        month = month,
        periods = listOf(
            dev.privacyllc.period.domain.cycle.PeriodRecord(
                1, LocalDate.of(2026, 8, 3), LocalDate.of(2026, 8, 7),
            ),
        ),
        spotting = listOf(dev.privacyllc.period.domain.cycle.SpottingRecord(1, LocalDate.of(2026, 8, 11))),
        forecast = dev.privacyllc.period.domain.prediction.Prediction(
            mostLikelyStartDate = LocalDate.of(2026, 8, 31),
            windowStart = LocalDate.of(2026, 8, 29),
            windowEnd = LocalDate.of(2026, 8, 31),
            confidenceScore = 0.7,
            confidenceLabel = dev.privacyllc.period.domain.prediction.ConfidenceLabel.HIGH,
            modelVersion = "preview",
        ),
        today = today,
        fertileWindow = LocalDate.of(2026, 8, 14)..LocalDate.of(2026, 8, 19),
        ovulation = LocalDate.of(2026, 8, 18),
    )
    PeriodTheme {
        CalendarContent(
            state = CalendarUiState(month = month, days = days, today = today),
            onPrevious = {}, onNext = {}, onSelect = {}, onMessageShown = {},
        )
    }
}
