/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui.theme

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color

/**
 * The shard's resolved color palette — the fifteen themable tokens of
 * `GET /public/settings`' `theme` map, parsed into Compose colors
 * (THEMING_AND_NAV.md §5.1).
 *
 * **The default value of every field is the shipped constant from
 * [ui/theme/Color.kt], and that is not an approximation.** The app's M5 palette
 * *is* the website's `runic-gateway` preset, value for value, because both were
 * drawn from the same `theme.css`. So [Shipped] renders exactly as the app did
 * before this milestone, and an instance with no `theme_visual` row resolves
 * back to it token by token (§2, AC-1).
 *
 * The palette has two consumers and one resolution: ten of the fifteen tokens
 * have a Material role and are fed into the [androidx.compose.material3.ColorScheme]
 * by [shardColorScheme]; the other five have none, and reach the screens that
 * need them through [LocalShardPalette].
 */
@Immutable
data class ShardPalette(
    /** `--bg-deep` — the page behind everything. */
    val page: Color = ShardPage,
    /** `--bg` — the screen background. */
    val surface: Color = ShardSurface,
    /** `--panel-flat` — top bar, inputs, drawer, list tracks. */
    val elevated: Color = ShardElevated,
    /** `--panel-a` — feature-card gradient, top. No Material role. */
    val cardTop: Color = ShardCardTop,
    /** `--panel-b` — feature-card gradient, bottom. No Material role. */
    val cardBottom: Color = ShardCardBottom,
    /** `--line` — borders and input outlines. */
    val outline: Color = ShardOutline,
    /** `--line-soft` — hairline row dividers. */
    val divider: Color = ShardDivider,
    /** `--ink` — the brightest headings. No Material role. */
    val heading: Color = ShardHeading,
    /** `--head` — heading on a surface. No Material role. */
    val headingDim: Color = ShardHeadingDim,
    /** `--text` — body copy. */
    val body: Color = ShardBody,
    /** `--muted` — secondary text. */
    val muted: Color = ShardMuted,
    /** `--dim` — meta and faint labels. No Material role. */
    val faint: Color = ShardFaint,
    /** `--accent` — links and secondary highlights. */
    val accent: Color = ShardAccent,
    /** `--accent-bright` — the filled CTA surface. */
    val cta: Color = ShardCta,
    /** `--blue` — the neutral/info pill background. */
    val pillBg: Color = ShardPillBg,
) {
    /**
     * Text drawn on the [cta] fill. **Derived, never themed** — it tracks
     * `--bg-deep`, exactly as the server refuses to freeze `--panel-grad` as a
     * literal (§5.1). A value expressed in terms of another token must follow
     * it, or a future light preset inherits a dark one and looks broken.
     */
    val onCta: Color get() = page

    /**
     * The neutral/info pill's foreground. Also derived: `ShardPillFg` and
     * `ShardCta` are the same `--accent-bright` value, so the pill's text
     * follows the CTA fill rather than being a sixteenth token the contract
     * does not have.
     */
    val pillFg: Color get() = cta

    companion object {
        /** The shipped app: the M5 palette, i.e. the `runic-gateway` preset. */
        val Shipped = ShardPalette()

        /**
         * Resolve a `theme` token map into a palette, **field by field** (§2).
         * A token that is missing, blank or unparseable falls back to its
         * shipped value on its own; a bad `--accent` must never discard a good
         * `--bg` beside it (AC-2).
         *
         * [brandAccent] is the pre-feature branding path and must keep working:
         * an instance with a `BRAND_ACCENT_COLOR` but no `theme_visual` row
         * still tints its links and highlights. It seeds `--accent` only — the
         * server resolves `brand.accent` as `theme['--accent'] || env`, so the
         * token always wins where both exist.
         */
        fun resolve(theme: Map<String, String>, brandAccent: Color? = null): ShardPalette {
            if (theme.isEmpty() && brandAccent == null) return Shipped
            fun token(name: String, shipped: Color): Color =
                parseBrandColor(theme[name]) ?: shipped
            return ShardPalette(
                page = token("--bg-deep", ShardPage),
                surface = token("--bg", ShardSurface),
                elevated = token("--panel-flat", ShardElevated),
                cardTop = token("--panel-a", ShardCardTop),
                cardBottom = token("--panel-b", ShardCardBottom),
                outline = token("--line", ShardOutline),
                divider = token("--line-soft", ShardDivider),
                heading = token("--ink", ShardHeading),
                headingDim = token("--head", ShardHeadingDim),
                body = token("--text", ShardBody),
                muted = token("--muted", ShardMuted),
                faint = token("--dim", ShardFaint),
                accent = token("--accent", brandAccent ?: ShardAccent),
                cta = token("--accent-bright", ShardCta),
                pillBg = token("--blue", ShardPillBg),
            )
        }
    }
}

/**
 * The live palette, for the five tokens with no Material role and for the
 * components that draw the card gradient. Everything that *can* go through
 * `MaterialTheme.colorScheme` still should — this is the escape hatch, not the
 * front door.
 *
 * Defaulted to [ShardPalette.Shipped] so previews and any composable outside
 * [RunicGatewayTheme] still draw the shipped palette rather than crashing.
 */
val LocalShardPalette = staticCompositionLocalOf { ShardPalette.Shipped }
