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

import androidx.compose.ui.graphics.Color
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test

/**
 * The token → palette resolution (THEMING_AND_NAV.md §5.1) and its
 * forgiving-on-read rule (§2, AC-2).
 */
class ShardPaletteTest {

    /** AC-1, the palette half: no theme row resolves to the shipped M5 palette. */
    @Test
    fun `no tokens resolve to the shipped palette`() {
        assertSame(ShardPalette.Shipped, ShardPalette.resolve(emptyMap()))
    }

    /** Every one of the fifteen has a home, and lands in the right one. */
    @Test
    fun `all fifteen tokens map to their palette field`() {
        val p = ShardPalette.resolve(
            mapOf(
                "--bg" to "#010101",
                "--bg-deep" to "#020202",
                "--panel-a" to "#030303",
                "--panel-b" to "#040404",
                "--panel-flat" to "#050505",
                "--line" to "#060606",
                "--line-soft" to "#070707",
                "--accent" to "#080808",
                "--accent-bright" to "#090909",
                "--ink" to "#0a0a0a",
                "--head" to "#0b0b0b",
                "--text" to "#0c0c0c",
                "--muted" to "#0d0d0d",
                "--dim" to "#0e0e0e",
                "--blue" to "#0f0f0f",
            ),
        )
        assertEquals(Color(0xFF010101), p.surface)
        assertEquals(Color(0xFF020202), p.page)
        assertEquals(Color(0xFF030303), p.cardTop)
        assertEquals(Color(0xFF040404), p.cardBottom)
        assertEquals(Color(0xFF050505), p.elevated)
        assertEquals(Color(0xFF060606), p.outline)
        assertEquals(Color(0xFF070707), p.divider)
        assertEquals(Color(0xFF080808), p.accent)
        assertEquals(Color(0xFF090909), p.cta)
        assertEquals(Color(0xFF0A0A0A), p.heading)
        assertEquals(Color(0xFF0B0B0B), p.headingDim)
        assertEquals(Color(0xFF0C0C0C), p.body)
        assertEquals(Color(0xFF0D0D0D), p.muted)
        assertEquals(Color(0xFF0E0E0E), p.faint)
        assertEquals(Color(0xFF0F0F0F), p.pillBg)
    }

    /**
     * AC-2. One valid token applies; four malformed ones each fall back on their
     * own, and none of them costs the good one beside it.
     */
    @Test
    fun `a malformed token costs only itself`() {
        val p = ShardPalette.resolve(
            mapOf(
                "--accent" to "#c9973f",   // the one valid token
                "--bg" to "not a color",
                "--ink" to "",
                "--line" to "#12",
                "--text" to "rgb(1, 2, 3)",
            ),
        )
        assertEquals(Color(0xFFC9973F), p.accent)
        assertEquals(ShardPalette.Shipped.surface, p.surface)
        assertEquals(ShardPalette.Shipped.heading, p.heading)
        assertEquals(ShardPalette.Shipped.outline, p.outline)
        assertEquals(ShardPalette.Shipped.body, p.body)
    }

    /** A token the app does not know is not an error — it is simply not read. */
    @Test
    fun `unknown tokens are ignored`() {
        val p = ShardPalette.resolve(mapOf("--mode-live" to "#ff0000", "--radius-card" to "2px"))
        assertEquals(ShardPalette.Shipped, p)
    }

    /**
     * The pre-feature branding path: a `BRAND_ACCENT_COLOR` with no theme row
     * still tints links and highlights, and nothing else moves.
     */
    @Test
    fun `brand accent seeds only the accent token`() {
        val p = ShardPalette.resolve(emptyMap(), brandAccent = Color(0xFFC9973F))
        assertEquals(Color(0xFFC9973F), p.accent)
        assertEquals(ShardPalette.Shipped.copy(accent = Color(0xFFC9973F)), p)
    }

    /**
     * The server resolves `brand.accent` as `theme['--accent'] || env`, so the two
     * can only disagree if a client is holding a stale brand — the token wins.
     */
    @Test
    fun `the accent token beats the brand accent`() {
        val p = ShardPalette.resolve(mapOf("--accent" to "#4f8ef7"), brandAccent = Color(0xFFC9973F))
        assertEquals(Color(0xFF4F8EF7), p.accent)
    }

    /** An unparseable brand accent is the same as none. */
    @Test
    fun `a malformed brand accent falls back to the shipped accent`() {
        assertEquals(ShardPalette.Shipped, ShardPalette.resolve(emptyMap(), parseBrandColor("nope")))
    }

    /**
     * The derived pair (§5.1): expressed in terms of another token, so they must
     * follow it rather than being frozen at the shipped literal.
     */
    @Test
    fun `derived colors track the tokens they are expressed in`() {
        val p = ShardPalette.resolve(mapOf("--bg-deep" to "#120c07", "--accent-bright" to "#e8c374"))
        assertEquals(Color(0xFF120C07), p.onCta)
        assertEquals(Color(0xFFE8C374), p.pillFg)
    }

    /** And on the shipped palette they are exactly today's two constants. */
    @Test
    fun `derived colors are the shipped constants by default`() {
        assertEquals(ShardOnCta, ShardPalette.Shipped.onCta)
        assertEquals(ShardPillFg, ShardPalette.Shipped.pillFg)
    }
}
