package dev.privacyllc.period.designsystem.art

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.privacyllc.period.designsystem.R

/**
 * The onboarding and empty-state illustrations, drawn as full-bleed heroes.
 *
 * ## Why these are raster, when PRODUCT_PLAN.md §42 asks for vectors
 *
 * §42's preference list ends *"avoid **unnecessary** raster imagery"*, and it
 * offers polished placeholder vectors as the fallback for artwork that does not
 * exist yet — which is what used to be here. The artwork exists: eight
 * illustrations supplied by the project owner, in a matched light and dark pair.
 *
 * They are gradient landscapes with soft glow and depth. There is no honest
 * `VectorDrawable` of one; converting them would either lose the drawing or
 * produce a path soup larger and slower than the image.
 *
 * [CycleProgressMark] and the calendar markers stay vectors, because they are
 * drawn *from data* — a marker's shape is what a day is, and the progress arc is
 * where the user actually stands in their cycle.
 *
 * ## Why there are density buckets and no plain `drawable/`
 *
 * A file in a density-less `drawable/` is treated as mdpi and pre-scaled at
 * decode by the device's bucket. The previous 552 × 480 set cost roughly 17 MB
 * of heap for one bitmap on an xxxhdpi device, and this artwork is far larger.
 * So each illustration ships at four widths — 617, 822, 1233 and 1644 px, which
 * is a 411 dp screen at hdpi through xxxhdpi — and Android decodes the one it
 * needs. **Never put one of these back in a plain `drawable/`.**
 *
 * The whole set is 2.3 MB across every bucket, but a release is an AAB and Play
 * splits by density, so a device downloads only its own: 130–500 KB.
 *
 * ## How the theme swap works
 *
 * There is no `isSystemInDarkTheme()` branch here on purpose. Each drawable has
 * a `-night` twin at every density, and Android resolves it. Nothing of ours
 * runs on a theme change. The cost is that a missing night asset is invisible in
 * light mode, which is why `OnboardingPreviews.kt` renders one entry per
 * illustration — and why #42 exists.
 *
 * ## Sources
 *
 * `docs/design/dist/` holds the delivered set at full resolution, with a README
 * recording the decisions behind them — no text in the pixels, no step numbers,
 * and a forecast illustration that deliberately names no fields. **Re-read it
 * before regenerating any of these.**
 */

/** Step 1. Overlapping rings over a dawn landscape — the cycle, before it means anything specific. */
@Composable
fun WelcomeIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_welcome, "Overlapping circles above a sunrise", modifier, size)

/** Step 2. A cycle ring with one marked day — the first day being recorded. */
@Composable
fun LastPeriodIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_last_period, "A cycle ring with one marked day", modifier, size)

/** Step 3. The same ring with a span between two marked days — a period with a start and an end. */
@Composable
fun PeriodEndIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_period_end, "A cycle ring with a marked span between two days", modifier, size)

/**
 * Step 4, and the Insights empty state.
 *
 * Earlier cycles fading behind the current one. Used in two places, which is why
 * it carries no step number.
 */
@Composable
fun LearningIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_learning, "Earlier cycle rings fading behind the current one", modifier, size)

/**
 * Step 5, the privacy promise.
 *
 * A shield with the cycle ring inside it. Enclosure reads as protection without
 * reaching for a padlock, which would make a calm product look like a security
 * one.
 */
@Composable
fun PrivacyIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_privacy, "A shield containing a cycle ring", modifier, size)

/** Step 6. A notification with a shield on it — what a reminder shows, and what it withholds. */
@Composable
fun NotificationPrivacyIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_notification_privacy, "A notification card marked with a shield", modifier, size)

/**
 * Step 7, the first forecast.
 *
 * A calendar and a cycle arc running from logged days into dashed predicted
 * ones. It deliberately names no fields: a drawing that said "fertile window" or
 * "confidence: low" would contradict the live forecast beside it, which on a
 * first run declines to estimate fertility at all.
 */
@Composable
fun ForecastIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_forecast, "A calendar with a cycle arc continuing into predicted days", modifier, size)

/**
 * The empty state — no periods logged yet.
 *
 * The ring is empty because nothing has been recorded, not because something
 * failed. It is the same ring the other illustrations fill with cycle days,
 * which is what makes this read as *waiting* rather than *broken*.
 */
@Composable
fun EmptyStateIllustration(modifier: Modifier = Modifier, size: Dp = IllustrationDefaults.Size) =
    Illustration(R.drawable.art_empty_state, "An empty cycle ring above a sunrise", modifier, size)

/**
 * One drawing, full width, fading into the page.
 *
 * **Full-bleed, and no clip.** These are 3:2 scenes composed to reach every
 * edge. An earlier set was composed as cards with their own rounded corners, and
 * the corners had to be clipped off; that is gone, and putting a `clip` back
 * would cut the artwork rather than tidy it.
 *
 * **[size] is a ceiling, not a height.** It is capped at a third of the screen
 * so the primary button below stays reachable. Landscape is the case that makes
 * this necessary: 914 × 411 dp, where a fixed 280 dp hero would be 68% of the
 * screen and every step would break on rotation.
 *
 * The cap reads `LocalConfiguration.screenHeightDp` rather than
 * `BoxWithConstraints`. Inside a `verticalScroll` column a child is measured
 * with an **infinite** height constraint, so `maxHeight` there is `Dp.Infinity`
 * and the cap would silently never apply — a guard that looks right and does
 * nothing.
 *
 * **The fade is drawn, not baked.** A gradient to
 * `MaterialTheme.colorScheme.background` over the bottom third, so the scene
 * settles into the page in whichever theme is showing and the artwork never
 * hard-codes a colour it cannot know.
 */
@Composable
private fun Illustration(
    @DrawableRes id: Int,
    description: String,
    modifier: Modifier,
    size: Dp,
) {
    val cap = (LocalConfiguration.current.screenHeightDp * IllustrationDefaults.MaxScreenFraction).dp
    Box(modifier.fillMaxWidth().height(minOf(size, cap))) {
        Image(
            painter = painterResource(id),
            contentDescription = description,
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.Crop,
        )
        BottomFade()
    }
}

/** The gradient that joins the artwork to the page. */
@Composable
private fun BoxScope.BottomFade() {
    Box(
        Modifier
            .align(Alignment.BottomCenter)
            .fillMaxWidth()
            .fillMaxHeight(IllustrationDefaults.FadeFraction)
            .background(
                Brush.verticalGradient(
                    listOf(Color.Transparent, MaterialTheme.colorScheme.background),
                ),
            ),
    )
}

object IllustrationDefaults {
    /** The hero height a step asks for, before the screen-fraction cap. */
    val Size: Dp = 280.dp

    /** Never taller than this share of the screen — see [Illustration]. */
    const val MaxScreenFraction = 0.32f

    /** How much of the image the fade covers. The artwork keeps this band quiet. */
    const val FadeFraction = 0.34f
}
