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.geometry.Size
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
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin

/**
 * 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)
}

/**
 * The forecast mark beside the hero number on Today.
 *
 * A ring with one offset dot — the same idea as the launcher icon, so the app's
 * own mark appears inside it rather than only on the home screen.
 *
 * This lives with the markers rather than with the illustrations because it is
 * **drawn from data**: the arc is the user's actual position in their cycle, so
 * it has to be a vector no matter how good the artwork elsewhere gets.
 */
@Composable
fun CycleProgressMark(
    color: Color,
    accent: Color,
    /** 0f at the start of the cycle, 1f at the predicted start of the next. */
    progress: Float,
    modifier: Modifier = Modifier,
    size: Dp = 56.dp,
) {
    Canvas(modifier.size(size)) {
        val r = this.size.minDimension * 0.38f
        val stroke = this.size.minDimension * 0.07f
        drawCircle(color.copy(alpha = 0.25f), r, center, style = Stroke(width = stroke))
        drawArc(
            color = color,
            startAngle = -90f,
            sweepAngle = 360f * progress.coerceIn(0f, 1f),
            useCenter = false,
            topLeft = Offset(center.x - r, center.y - r),
            size = Size(r * 2, r * 2),
            style = Stroke(width = stroke),
        )
        val angle = (-PI / 2 + 2 * PI * progress.coerceIn(0f, 1f)).toFloat()
        drawCircle(accent, stroke * 0.85f, Offset(center.x + r * cos(angle), center.y + r * sin(angle)))
    }
}

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
}
