package com.pixelized.headache.ui.page.summary.yearly

import android.annotation.SuppressLint
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.pixelized.headache.ui.theme.HeadacheTheme
import com.pixelized.headache.ui.theme.color.HeadacheColorPalette
import com.pixelized.headache.utils.extention.capitalize
import java.util.Date
import java.util.Locale

@Stable
data object YearSummaryMonthDefault {

    @Stable
    val daySize: Dp = 16.dp

    @Stable
    val pillSize: Dp = 2.dp

    @Stable
    val pillSpace: Dp = 1.dp

    @SuppressLint("ConstantLocale")
    @Stable
    val formatter = SimpleDateFormat("MMMM", Locale.getDefault())
}

@Stable
data class YearSummaryMonthUio(
    val date: Date,
    val weeks: List<List<DayUio>>,
) {
    @Stable
    data class DayUio(
        val number: Int,
        val headache: Boolean,
        val pills: List<Color>,
    )
}

@Composable
fun YearSummaryMonth(
    modifier: Modifier = Modifier,
    formatter: DateFormat = YearSummaryMonthDefault.formatter,
    defaultTextStyle: TextStyle,
    headacheTextStyle: TextStyle,
    daySize: Dp = YearSummaryMonthDefault.daySize,
    pillSize: Dp = YearSummaryMonthDefault.pillSize,
    pillSpace: Dp = YearSummaryMonthDefault.pillSpace,
    uio: YearSummaryMonthUio,
    onMonth: (YearSummaryMonthUio) -> Unit,
) {
    val textMeasurer: TextMeasurer = rememberTextMeasurer()
    val pill: Float = with(LocalDensity.current) { pillSize.toPx() }
    val space: Float = with(LocalDensity.current) { pillSpace.toPx() }

    Column(
        modifier = Modifier
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = ripple(bounded = false),
                onClick = { onMonth(uio) }
            )
            .then(other = modifier),
    ) {
        Text(
            modifier = Modifier.align(alignment = Alignment.Start),
            style = MaterialTheme.typography.titleMedium,
            text = remember(uio.date) { formatter.format(uio.date) }.capitalize(),
        )
        Box(
            modifier = Modifier
                .size(width = daySize * 7, height = daySize * 6)
                .drawWithCache {
                    val cellSize = Size(
                        width = size.width / 7f,
                        height = size.height / 6f,
                    )
                    val pillSize = Size(
                        width = pill,
                        height = pill,
                    )

                    onDrawBehind {
                        var localCellOffset: Offset = Offset.Zero
                        var localCellSize: Size = Size.Zero

                        uio.weeks.forEachIndexed { row, week ->
                            week.forEachIndexed { column, day ->
                                // Compute current cell offset and size
                                localCellOffset = Offset(
                                    x = when (row) {
                                        0 -> cellSize.width * (column + 7 - week.size)
                                        else -> cellSize.width * column
                                    },
                                    y = cellSize.height * row,
                                )
                                localCellSize = Size(
                                    width = cellSize.width,
                                    height = cellSize.height,
                                )

                                // draw background
                                if (day.headache) {
                                    drawRect(
                                        topLeft = Offset(
                                            x = localCellOffset.x - 1f,
                                            y = localCellOffset.y + pillSize.height + space,
                                        ),
                                        size = Size(
                                            width = localCellSize.width - 0f,
                                            height = localCellSize.height - (pillSize.height + space) * 2,
                                        ),
                                        color = HeadacheColorPalette.Calendar.Headache,
                                    )
                                }

                                // draw day number
                                val label = "${day.number}"
                                val measure = textMeasurer.measure(label, defaultTextStyle)

                                drawText(
                                    textMeasurer = textMeasurer,
                                    text = label,
                                    style = when {
                                        day.headache -> headacheTextStyle
                                        else -> defaultTextStyle
                                    },
                                    topLeft = Offset(
                                        x = localCellOffset.x + (localCellSize.width - measure.size.width) / 2,
                                        y = localCellOffset.y + (localCellSize.height - measure.size.height) / 2,
                                    ),
                                )

                                // draw pills
                                day.pills.forEachIndexed { index, pill ->
                                    drawRect(
                                        topLeft = Offset(
                                            x = localCellOffset.x + (localCellSize.width - (pillSize.width + space) * day.pills.size) / 2f + index * (pillSize.width + space),
                                            y = localCellOffset.y + localCellSize.height - pillSize.height,
                                        ),
                                        size = pillSize,
                                        color = pill
                                    )
                                }
                            }
                        }
                    }
                },
        )
    }
}

@Composable
@Preview
private fun YearSummaryMonthPreview(
    @PreviewParameter(MonthPreviewProvider::class) preview: YearSummaryMonthUio,
) {
    HeadacheTheme {
        Surface {
            YearSummaryMonth(
                modifier = Modifier.padding(all = 8.dp),
                defaultTextStyle = MaterialTheme.typography.labelSmall,
                headacheTextStyle = MaterialTheme.typography.labelSmall.copy(color = Color.White),
                uio = preview,
                onMonth = { },
            )
        }
    }
}

object MonthPreviewProviderHelper {
    private var day: Int = 1

    fun day(): () -> YearSummaryMonthUio.DayUio = {
        YearSummaryMonthUio.DayUio(
            number = day++,
            headache = false,
            pills = emptyList(),
        )
    }

    fun headache(
        spifen: Boolean = false,
        eletriptan: Boolean = false,
    ): () -> YearSummaryMonthUio.DayUio = {
        YearSummaryMonthUio.DayUio(
            number = day++,
            headache = true,
            pills = listOfNotNull(
                if (spifen) HeadacheColorPalette.Pill.Spifen400 else null,
                if (eletriptan) HeadacheColorPalette.Pill.Eletriptan40 else null,
            ),
        )
    }

    // inline to avoid un sequential call to "day()" between default and overridden function parameters.
    @Suppress("NOTHING_TO_INLINE")
    fun week(
        mon: (() -> YearSummaryMonthUio.DayUio)? = day(),
        tue: (() -> YearSummaryMonthUio.DayUio)? = day(),
        wen: (() -> YearSummaryMonthUio.DayUio)? = day(),
        thu: (() -> YearSummaryMonthUio.DayUio)? = day(),
        fry: (() -> YearSummaryMonthUio.DayUio)? = day(),
        sat: (() -> YearSummaryMonthUio.DayUio)? = day(),
        sun: (() -> YearSummaryMonthUio.DayUio)? = day(),
    ): List<YearSummaryMonthUio.DayUio> = listOfNotNull(
        mon?.invoke(),
        tue?.invoke(),
        wen?.invoke(),
        thu?.invoke(),
        fry?.invoke(),
        sat?.invoke(),
        sun?.invoke(),
    )

    fun month(
        month: Int,
        year: Int,
        vararg weeks: List<YearSummaryMonthUio.DayUio>,
    ): YearSummaryMonthUio {
        day = 1
        return YearSummaryMonthUio(
            date = java.util.Calendar.getInstance().apply {
                set(java.util.Calendar.YEAR, year)
                set(java.util.Calendar.MONTH, month)
            }.time,
            weeks = weeks.toList(),
        )
    }
}

private class MonthPreviewProvider() : PreviewParameterProvider<YearSummaryMonthUio> {
    override val values: Sequence<YearSummaryMonthUio>
        get() = with(MonthPreviewProviderHelper) {
            sequenceOf(
                month(
                    month = Calendar.MARCH,
                    year = 2025,
                    week(
                        mon = null,
                        tue = null,
                        wen = null,
                        thu = null,
                        fry = null,
                        sat = headache(spifen = true)
                    ),
                    week(sat = headache(spifen = true)),
                    week(
                        wen = headache(spifen = true, eletriptan = true),
                        thu = headache(spifen = true, eletriptan = true),
                        fry = headache(spifen = true, eletriptan = true),
                        sat = headache(),
                        sun = headache(spifen = true, eletriptan = true)
                    ),
                    week(
                        mon = headache(),
                        tue = headache(),
                        sat = headache(spifen = true, eletriptan = true),
                    ),
                    week(thu = headache(), fry = headache(), sat = headache()),
                    week(
                        mon = headache(spifen = true, eletriptan = true),
                        tue = null, wen = null, thu = null, fry = null, sat = null, sun = null
                    ),
                ),
                month(
                    month = java.util.Calendar.JULY,
                    year = 2025,
                    week(mon = null, sun = headache(spifen = true)),
                    week(wen = headache(spifen = true)),
                    week(sat = headache()),
                    week(
                        wen = headache(spifen = true),
                        thu = headache(),
                        sat = headache(spifen = true),
                    ),
                    week(wen = headache(), fry = null, sat = null, sun = null),
                ),
                month(
                    month = java.util.Calendar.AUGUST,
                    year = 2025,
                    week(mon = null, tue = null, wen = null, thu = null),
                    week(wen = headache(), fry = headache()),
                    week(fry = headache(spifen = true)),
                    week(tue = headache(spifen = true), fry = headache()),
                    week(
                        tue = headache(spifen = true, eletriptan = true),
                        wen = headache(),
                        sat = headache(spifen = true),
                    ),
                ),
                month(
                    month = java.util.Calendar.SEPTEMBER,
                    year = 2025,
                    week(
                        tue = headache(spifen = true, eletriptan = true),
                        wen = headache(spifen = true),
                        fry = headache(),
                        sat = headache(),
                        sun = headache(),
                    ),
                    week(
                        sun = headache(spifen = true),
                    ),
                    week(
                        wen = headache(spifen = true),
                        tue = headache(),
                    ),
                    week(),
                    week(wen = null, thu = null, fry = null, sat = null, sun = null),
                ),
            )
        }
}