package dev.privacyllc.period.designsystem.art

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
 * The four calendar states, drawn so they differ in **shape**.
 *
 * PRODUCT_PLAN.md §26 and §43 both require it and for the same reason: a
 * calendar whose states differ only in colour carries no information for a
 * colourblind user, none in a greyscale screenshot, and none in the bug report
 * somebody files about it.
 *
 *   confirmed  ●   solid disc
 *   predicted  ◌   dotted outline
 *   fertile    ○   continuous ring
 *   ovulation  ✦   small four-pointed mark
 *
 * Colour still carries meaning — it is just never the only thing that does.
 *
 * Each one is a named composable rather than a drawable resource because that is
 * what "behind a replaceable name" means in Compose: swapping the drawing is a
 * change in one function body, and every call site follows.
 */

/** A period the user confirmed. Solid, because it is the only state that is certain. */
@Composable
fun ConfirmedPeriodMarker(
    color: Color,
    modifier: Modifier = Modifier,
    size: Dp = MarkerDefaults.Size,
) {
    Canvas(modifier.size(size)) { drawCircle(color = color, radius = this.size.minDimension / 2f) }
}

/**
 * A period the app predicts.
 *
 * Dotted, and never a lighter version of the confirmed marker: §26 is explicit
 * that predicted and confirmed must never look identical, and a difference in
 * opacity alone fails exactly when the screen is dim or the user is not looking
 * closely.
 */
@Composable
fun PredictedPeriodMarker(
    color: Color,
    modifier: Modifier = Modifier,
    size: Dp = MarkerDefaults.Size,
) {
    Canvas(modifier.size(size)) {
        val stroke = this.size.minDimension * MarkerDefaults.StrokeFraction
        drawCircle(
            color = color,
            radius = (this.size.minDimension - stroke) / 2f,
            style = Stroke(
                width = stroke,
                pathEffect = PathEffect.dashPathEffect(
                    floatArrayOf(this.size.minDimension / 9f, this.size.minDimension / 9f),
                ),
            ),
        )
    }
}

/** The estimated fertile window. A continuous ring — open, unlike the solid confirmed disc. */
@Composable
fun FertileWindowMarker(
    color: Color,
    modifier: Modifier = Modifier,
    size: Dp = MarkerDefaults.Size,
) {
    Canvas(modifier.size(size)) {
        val stroke = this.size.minDimension * MarkerDefaults.StrokeFraction
        drawCircle(color = color, radius = (this.size.minDimension - stroke) / 2f, style = Stroke(width = stroke))
    }
}

/** Estimated ovulation. Its own shape entirely, because it is a day rather than a span. */
@Composable
fun OvulationMarker(
    color: Color,
    modifier: Modifier = Modifier,
    size: Dp = MarkerDefaults.Size,
) {
    Canvas(modifier.size(size)) { drawFourPointStar(color) }
}

internal fun DrawScope.drawFourPointStar(color: Color) {
    val r = size.minDimension / 2f
    val c = Offset(size.width / 2f, size.height / 2f)
    val waist = r * 0.34f
    val path = Path().apply {
        moveTo(c.x, c.y - r)
        quadraticTo(c.x + waist, c.y - waist, c.x + r, c.y)
        quadraticTo(c.x + waist, c.y + waist, c.x, c.y + r)
        quadraticTo(c.x - waist, c.y + waist, c.x - r, c.y)
        quadraticTo(c.x - waist, c.y - waist, c.x, c.y - r)
        close()
    }
    drawPath(path, color)
}

object MarkerDefaults {
    val Size: Dp = 32.dp
    /** Thick enough to read at 32dp on a dense screen, thin enough to stay a ring rather than a disc. */
    const val StrokeFraction = 0.14f
}
