"""Magogi design tokens — the single visual source of truth for the GUI.

Every colour, scale, and font name the VManager GUI renders with lives
here; no other module (Python or ``.ui``) may carry a raw hex value.
Values are the locked Magogi brand tokens
(``mgf-brand/brand/assets/system/design-tokens.json``, LOCK v1
2026-07-04), digested in
``docs/plans/desktop-ui-redesign/00-brand-understanding.md``.

Semantic colours exist in two theme instances, :data:`LIGHT` and
:data:`DARK`, with an identical field set by construction (one frozen
dataclass). Upstream ``color-mix()`` recipes are reproduced via
:func:`mgf.brand_qt._mix.mix` rather than transcribed by hand.

This module is deliberately Qt-free (kit-ready: see
``docs/plans/desktop-ui-redesign/03-target-design.md`` §D1/D2).
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from mgf.brand_qt._mix import BLACK, WHITE, mix

#: Theme mode vocabulary. Lives here — the Qt-free core — so settings
#: models can annotate with it without importing PySide6 (PAPER-09);
#: :mod:`mgf.brand_qt.theme` re-exports it unchanged.
Mode = Literal["dark", "light", "system"]


@dataclass(frozen=True)
class RawPalette:
    """The 12 locked brand hexes (10 core + 2 functional state hues)."""

    indigo: str = "#1e2a4a"  # Magogi Indigo — primary/ink, headings, structure
    gold: str = "#f2a93b"  # Keystone Gold — THE accent ("one flame per room")
    teal: str = "#2bb7a3"  # Federation Teal — secondary, success, connections
    ink: str = "#11151f"  # body text on light; dark-theme background
    slate: str = "#5b6678"  # captions, borders, secondary text
    mist: str = "#e9ecf2"  # surfaces, dividers; dark-theme text
    paper: str = "#fbfbfd"  # light background
    error: str = "#e5484d"
    warning: str = "#e8a317"
    info: str = "#3e63dd"
    busy: str = "#2e86c8"  # functional only: running/checking/syncing — never a CTA
    derived: str = "#7e5bb0"  # functional only: derived/authority/telemetry


RAW = RawPalette()


@dataclass(frozen=True)
class SemanticColors:
    """Theme-resolved semantic roles. LIGHT and DARK share this exact field set."""

    bg_canvas: str
    bg_surface: str
    bg_surface_raised: str
    bg_muted: str
    bg_inverse: str
    text_primary: str
    text_secondary: str
    text_inverse: str
    text_accent: str
    border_subtle: str
    border_strong: str
    link: str
    link_hover: str
    focus_ring: str
    action_primary_bg: str
    action_primary_text: str
    action_secondary_text: str
    status_error_bg: str
    status_error_text: str
    status_error_tint: str
    status_error_strong: str
    status_warning_bg: str
    status_warning_text: str
    status_warning_tint: str
    status_warning_strong: str
    status_info_bg: str
    status_info_text: str
    status_info_tint: str
    status_info_strong: str
    status_success_bg: str
    status_success_text: str
    status_success_tint: str
    status_success_strong: str
    state_busy: str
    state_busy_tint: str
    state_busy_strong: str
    state_derived: str
    state_derived_tint: str
    state_derived_strong: str


LIGHT = SemanticColors(
    bg_canvas=RAW.paper,
    bg_surface="#ffffff",
    bg_surface_raised="#ffffff",
    bg_muted=RAW.mist,
    bg_inverse=RAW.indigo,
    text_primary=RAW.ink,
    text_secondary=RAW.slate,
    text_inverse=RAW.paper,
    text_accent=RAW.indigo,
    border_subtle=RAW.mist,
    border_strong=RAW.slate,
    # Raw teal fails AA on Paper (2.42:1) — links/focus use darkened teal.
    link=mix(RAW.teal, 64, BLACK),
    link_hover=mix(RAW.teal, 50, BLACK),
    focus_ring=mix(RAW.teal, 64, BLACK),
    action_primary_bg=RAW.gold,
    action_primary_text=RAW.ink,
    action_secondary_text=RAW.ink,
    status_error_bg=RAW.error,
    status_error_text=RAW.ink,
    status_error_tint=mix(RAW.error, 16, WHITE),
    status_error_strong=mix(RAW.error, 75, BLACK),
    status_warning_bg=RAW.warning,
    status_warning_text=RAW.ink,
    status_warning_tint=mix(RAW.warning, 22, WHITE),
    # 62 → 60: the lone light-theme strong-on-tint pair under WCAG-AA
    # (4.40:1); 60 lands 4.63:1 (PAPER-07 — kit-owned derivation, the
    # locked RAW palette is untouched).
    status_warning_strong=mix(RAW.warning, 60, BLACK),
    status_info_bg=RAW.info,
    status_info_text=RAW.paper,
    status_info_tint=mix(RAW.info, 16, WHITE),
    status_info_strong=mix(RAW.info, 72, BLACK),
    status_success_bg=RAW.teal,
    status_success_text=RAW.ink,
    status_success_tint=mix(RAW.teal, 18, WHITE),
    status_success_strong=mix(RAW.teal, 64, BLACK),
    state_busy=RAW.busy,
    state_busy_tint=mix(RAW.busy, 14, WHITE),
    state_busy_strong=mix(RAW.busy, 72, BLACK),
    state_derived=RAW.derived,
    state_derived_tint=mix(RAW.derived, 16, WHITE),
    state_derived_strong=mix(RAW.derived, 66, BLACK),
)

DARK = SemanticColors(
    bg_canvas=RAW.ink,
    bg_surface=RAW.indigo,
    bg_surface_raised=RAW.indigo,
    bg_muted="rgba(233,236,242,0.10)",
    bg_inverse=RAW.paper,
    text_primary=RAW.mist,
    text_secondary=mix(RAW.slate, 55, WHITE),
    text_inverse=RAW.ink,
    text_accent=RAW.gold,
    border_subtle="rgba(233,236,242,0.25)",
    border_strong=mix(RAW.slate, 35, WHITE),
    link=mix(RAW.teal, 85, WHITE),
    link_hover=mix(RAW.teal, 78, WHITE),
    # Gold fails on Paper (1.93:1) so it is never the light ring — but it IS the dark ring.
    focus_ring=RAW.gold,
    action_primary_bg=RAW.gold,
    action_primary_text=RAW.ink,
    action_secondary_text=RAW.mist,
    status_error_bg=RAW.error,
    status_error_text=RAW.ink,
    status_error_tint=mix(RAW.error, 28, RAW.ink),
    status_error_strong=mix(RAW.error, 32, WHITE),
    status_warning_bg=RAW.warning,
    status_warning_text=RAW.ink,
    status_warning_tint=mix(RAW.warning, 26, RAW.ink),
    status_warning_strong=mix(RAW.warning, 82, WHITE),
    status_info_bg=RAW.info,
    status_info_text=RAW.paper,
    status_info_tint=mix(RAW.info, 30, RAW.ink),
    # 82 → 69: dark info strong-on-tint measured 3.63:1; 69 lands
    # 4.60:1 with the tint surface unchanged (PAPER-07).
    status_info_strong=mix(RAW.info, 69, WHITE),
    status_success_bg=RAW.teal,
    status_success_text=RAW.ink,
    status_success_tint=mix(RAW.teal, 26, RAW.ink),
    status_success_strong=mix(RAW.teal, 82, WHITE),
    state_busy=RAW.busy,
    # 30 → 23: dark busy strong-on-tint measured 4.13:1; the darker
    # tint lands 4.60:1 with the text tone unchanged (PAPER-07).
    state_busy_tint=mix(RAW.busy, 23, RAW.ink),
    state_busy_strong=mix(RAW.busy, 82, WHITE),
    state_derived=RAW.derived,
    state_derived_tint=mix(RAW.derived, 30, RAW.ink),
    # 82 → 70: dark derived strong-on-tint measured 3.68:1; 70 lands
    # 4.59:1 with the tint surface unchanged (PAPER-07).
    state_derived_strong=mix(RAW.derived, 70, WHITE),
)

# --- scales (03-target-design §4) --------------------------------------------

# Quarter-rem web scale at 16px/rem → px. Keys mirror the web token names.
SPACE: dict[int, int] = {
    0: 0,
    1: 4,
    2: 8,
    3: 12,
    4: 16,
    5: 20,
    6: 24,
    7: 28,
    8: 32,
    9: 36,
    10: 40,
    11: 44,
    12: 48,
    16: 64,
    24: 96,
}

RADIUS: dict[str, int] = {"sm": 8, "md": 12, "lg": 16, "tile": 20, "pill": 999}

# Type scale in points, anchored at the Brand Book's 10.5pt body.
TEXT_PT: dict[str, float] = {
    "xs": 8.0,
    "sm": 9.0,
    "base": 10.5,
    "md": 12.0,
    "lg": 13.0,
    "xl": 16.0,
    "xxl": 21.0,
    "xxxl": 29.0,
}

WEIGHT: dict[str, int] = {"regular": 400, "medium": 500, "semibold": 600, "bold": 700}

DURATION_MS: dict[str, int] = {"fast": 120, "normal": 180, "slow": 260, "sweep": 640}

#: chip/legend state → theme-invariant base hue (dots, borders).
STATE_HUES: dict[str, str] = {
    "success": RAW.teal,
    "warning": RAW.warning,
    "error": RAW.error,
    "info": RAW.info,
    "busy": RAW.busy,
    "derived": RAW.derived,
    "neutral": RAW.slate,
    "quarantine": RAW.derived,
}

# --- derived button-hover shades (theme-invariant, mix(base 88%, black)) ------

BUTTON_PRIMARY_HOVER = mix(RAW.indigo, 88, BLACK)
BUTTON_ACCENT_HOVER = mix(RAW.gold, 88, BLACK)

# --- evidence-viz colours (fixed order — never reorder) -----------------------

VIZ_CATEGORICAL: tuple[str, ...] = (
    RAW.gold,
    RAW.teal,
    RAW.indigo,
    RAW.slate,
    RAW.busy,
    RAW.derived,
)

VIZ_SEQ_INDIGO: tuple[str, ...] = ("#dbdde2", "#b3b7c1", "#81889a", "#505972", "#1e2a4a")

VIZ_DIVERGING: tuple[str, ...] = (
    "#2bb7a3",
    "#73cfc2",
    "#b9e7e1",
    "#e9ecf2",
    "#fbe3be",
    "#f6c67e",
    "#f2a93b",
)

# --- fonts (families as registered by design.fonts in chunk C1) ---------------

FONT_DISPLAY = "Space Grotesk"
FONT_BODY = "Inter"
FONT_MONO = "JetBrains Mono"

__all__ = [
    "BUTTON_ACCENT_HOVER",
    "BUTTON_PRIMARY_HOVER",
    "DARK",
    "DURATION_MS",
    "FONT_BODY",
    "FONT_DISPLAY",
    "FONT_MONO",
    "LIGHT",
    "RADIUS",
    "RAW",
    "SPACE",
    "STATE_HUES",
    "TEXT_PT",
    "VIZ_CATEGORICAL",
    "VIZ_DIVERGING",
    "VIZ_SEQ_INDIGO",
    "WEIGHT",
    "Mode",
    "RawPalette",
    "SemanticColors",
]
