#!/usr/bin/env python3
"""Проверка контраста токенов VidConf по WCAG 2.1 (относительная светимость sRGB).

Источник значений — design/tokens.css (продублированы явно ниже, без парсинга
CSS, чтобы список пар был читаемым и явным). Покрывает все три темы проекта:
светлую оболочку (дефолт), тёмную оболочку ([data-theme='dark'] /
prefers-color-scheme, фаза 6a) и комнату конференции ([data-theme='room']).

Пороги:
  - «текст» (text)      — обычный текст, надписи на кнопках/бейджах: >= 4.5:1
  - «нетекст» (non-text) — иконки-индикаторы, точки статусов, декоративные
    рамки/обводки, несущие смысл без текста (правило WCAG 1.4.11): >= 3:1

Запуск: `python3 design/tools/contrast.py`
Печатает таблицу пар с ratio и PASS/FAIL, exit code 1 при хотя бы одном провале.
"""
from __future__ import annotations

import sys
from dataclasses import dataclass


def _linearize(channel: float) -> float:
    c = channel / 255.0
    return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4


def relative_luminance(hex_color: str) -> float:
    """Относительная светимость sRGB-цвета (формула WCAG 2.1)."""
    h = hex_color.lstrip("#")
    r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
    r_lin, g_lin, b_lin = _linearize(r), _linearize(g), _linearize(b)
    return 0.2126 * r_lin + 0.7152 * g_lin + 0.0722 * b_lin


def contrast_ratio(hex_a: str, hex_b: str) -> float:
    """Контраст пары цветов по WCAG: (L_light + 0.05) / (L_dark + 0.05)."""
    l_a, l_b = relative_luminance(hex_a), relative_luminance(hex_b)
    l_light, l_dark = max(l_a, l_b), min(l_a, l_b)
    return (l_light + 0.05) / (l_dark + 0.05)


@dataclass(frozen=True)
class Pair:
    name: str
    fg: str
    bg: str
    kind: str  # "текст" | "нетекст"
    threshold: float


# ---------------------------------------------------------------------------
# Явный список пар. Значения продублированы из design/tokens.css.
# ---------------------------------------------------------------------------
PAIRS: list[Pair] = [
    # ---- Светлая тема (оболочка): CTA-кнопка (пастельная мята, осветлена
    # на 50% по фидбеку гейта утверждения R1-итерации, 2026-07-16) ----
    Pair("CTA primary: текст на заливке default", "#2E3454", "#D4F2E3", "текст", 4.5),
    Pair("CTA primary: текст на заливке hover", "#2E3454", "#C7EBD9", "текст", 4.5),
    Pair("CTA primary: текст на заливке active", "#2E3454", "#BAE4CE", "текст", 4.5),
    # ---- Светлая тема: точки-индикаторы статуса (лобби, фильтр-чипы) ----
    Pair("Точка статуса «свободна» (success-dot) на карточке", "#46916B", "#FFFFFF", "нетекст", 3.0),
    Pair("Точка статуса «занята» (danger-dot) на карточке", "#C65366", "#FFFFFF", "нетекст", 3.0),
    Pair("Точка статуса «забронирована» (warning-dot) на карточке", "#B58A26", "#FFFFFF", "нетекст", 3.0),
    # ---- Светлая тема: регрессия — текст статусов на белой поверхности
    # (та же база измерения, что и в DESIGN_SYSTEM.md §1.1: «на белом») ----
    Pair("Текст статуса «свободна» на белой поверхности", "#0A7A4C", "#FFFFFF", "текст", 4.5),
    Pair("Текст статуса «занята» на белой поверхности", "#C7364F", "#FFFFFF", "текст", 4.5),
    Pair("Текст статуса «забронирована» на белой поверхности", "#7F560D", "#FFFFFF", "текст", 4.5),
    # ---- Светлая тема: регрессия — базовая типографика (не менялась) ----
    Pair("Основной текст (ink-900) на альт.-фоне", "#2E3454", "#F7F7F8", "текст", 4.5),
    Pair("Брендовый индиго (ink-700) на фоне страницы", "#3B4D95", "#F1F1F1", "текст", 4.5),
    # ---- Логотип «объектив» (§1.4 DESIGN_SYSTEM.md) — монохром ink-700,
    # проверяем как графику (нетекст, >=3:1) на обеих светлых поверхностях ----
    Pair("Логотип «объектив» (ink-700) на фоне страницы", "#3B4D95", "#F1F1F1", "нетекст", 3.0),
    Pair("Логотип «объектив» (ink-700) на белой поверхности", "#3B4D95", "#FFFFFF", "нетекст", 3.0),
    # ---- Аватары с инициалами (§1.5 DESIGN_SYSTEM.md) — текст всегда
    # светлый на индиго-заливке, никогда тёмный (регрессия гейта, 2026-07-16) ----
    Pair("Инициалы аватара (белые) на заливке ink-700 (основной)", "#FFFFFF", "#3B4D95", "текст", 4.5),
    Pair("Инициалы аватара (белые) на заливке ink-500 (второстепенный)", "#FFFFFF", "#535F94", "текст", 4.5),
    # ---- Тёмная тема (комната): микрофон вкл/выкл — пастельные индикаторы ----
    Pair("Микрофон «вкл» (иконка) на тулбаре/панели", "#7FDDA8", "#2A2A2A", "нетекст", 3.0),
    Pair("Микрофон «вкл» (иконка) на плитке участника", "#7FDDA8", "#363636", "нетекст", 3.0),
    Pair("Микрофон «выкл/mute» (иконка) на тулбаре/панели", "#EB93A1", "#2A2A2A", "нетекст", 3.0),
    Pair("Микрофон «выкл/mute» (иконка) на плитке участника", "#EB93A1", "#363636", "нетекст", 3.0),
    # ---- Тёмная тема: кнопка «Выйти» (заливка danger, белый текст) ----
    Pair("Кнопка «Выйти»: белый текст на заливке default", "#FFFFFF", "#B85468", "текст", 4.5),
    Pair("Кнопка «Выйти»: белый текст на заливке hover", "#FFFFFF", "#B9495F", "текст", 4.5),
    # ---- Тёмная тема: регрессия — текст на плитке участника (не менялся) ----
    Pair("Имя участника (текст) на плитке", "#E8E8E8", "#363636", "текст", 4.5),
    Pair("Вторичный текст (время/статус) на плитке", "#B0B0B0", "#363636", "текст", 4.5),
    # ---- Тёмная тема: рамка активного спикера (декоративная, крупный элемент) ----
    Pair("Рамка активного спикера (speaker-ring) на поверхности", "#D6A83D", "#2A2A2A", "нетекст", 3.0),

    # ---- Тёмная тема оболочки (фаза 6a, 2026-07-18, design/mockups/dark/) ----
    # CTA-кнопка: бренд-мята переиспользована из --color-room-mic-on,
    # текст — accent-text (тёмный ink, буквальный hex из room.html) ----
    Pair("Тёмная оболочка — CTA primary: текст на заливке default", "#10331F", "#7FDDA8", "текст", 4.5),
    Pair("Тёмная оболочка — CTA primary: текст на заливке hover", "#10331F", "#75CA9A", "текст", 4.5),
    Pair("Тёмная оболочка — CTA primary: текст на заливке active", "#10331F", "#6CB78C", "текст", 4.5),
    # ---- Тёмная оболочка: базовая типографика ----
    Pair("Тёмная оболочка — основной текст (ink-900) на фоне страницы", "#E8E8E8", "#1E1E1E", "текст", 4.5),
    Pair("Тёмная оболочка — основной текст (ink-900) на поверхности (карточки)", "#E8E8E8", "#2A2A2A", "текст", 4.5),
    Pair("Тёмная оболочка — вторичный текст (ink-500) на альт.-фоне", "#B0B0B0", "#242424", "текст", 4.5),
    # ---- Тёмная оболочка: брендовая мята (ink-700 = accent = --color-room-mic-on) ----
    Pair("Тёмная оболочка — бренд-мята (ink-700, заголовки/ссылки) на фоне страницы", "#7FDDA8", "#1E1E1E", "текст", 4.5),
    Pair("Тёмная оболочка — бренд-мята (ink-700) на поверхности", "#7FDDA8", "#2A2A2A", "текст", 4.5),
    # ---- Тёмная оболочка: текст статусов на поверхности (карточки конференций) ----
    Pair("Тёмная оболочка — текст статуса «занята»/ошибка (danger) на поверхности", "#EB93A1", "#2A2A2A", "текст", 4.5),
    Pair("Тёмная оболочка — текст статуса «забронирована» (warning) на поверхности", "#D6A83D", "#2A2A2A", "текст", 4.5),
    # ---- Тёмная оболочка: сплошные danger-кнопки (.btn-danger — «Отменить
    # все повторения» в календаре, «Удалить» в админке); отдельный токен
    # --color-danger-solid(-hover), см. DESIGN_SYSTEM.md §1.2b ----
    Pair("Тёмная оболочка — btn-danger: белый текст на заливке default", "#FFFFFF", "#B85468", "текст", 4.5),
    Pair("Тёмная оболочка — btn-danger: белый текст на заливке hover", "#FFFFFF", "#B9495F", "текст", 4.5),
    # ---- Тёмная оболочка: аватар (патч категории 1, README dark/) — белый
    # текст на мятной заливке нечитаем, заменён на accent-text ----
    Pair("Тёмная оболочка — инициалы аватара (accent-text) на заливке ink-700", "#10331F", "#7FDDA8", "текст", 4.5),
    # ---- Тёмная оболочка: third-tier текст (ink-400, эйбрау-подписи в
    # [скобках]) — только крупный/жирный текст, аналог light ink-400 ⚠︎ ----
    Pair("Тёмная оболочка — third-tier (ink-400, ⚠︎ крупный текст) на фоне страницы", "#5D9A78", "#1E1E1E", "нетекст", 3.0),
    Pair("Тёмная оболочка — third-tier (ink-400, ⚠︎ крупный текст) на поверхности", "#5D9A78", "#2A2A2A", "нетекст", 3.0),
    # ---- Тёмная оболочка: кольцо :focus-visible (переиспользован --color-room-focus-ring) ----
    Pair("Тёмная оболочка — кольцо :focus-visible на поверхности", "#D6A83D", "#2A2A2A", "нетекст", 3.0),
]


def main() -> int:
    rows = []
    all_pass = True
    for pair in PAIRS:
        ratio = contrast_ratio(pair.fg, pair.bg)
        ok = ratio >= pair.threshold
        all_pass = all_pass and ok
        rows.append((pair, ratio, ok))

    name_width = max(len(p.name) for p, _, _ in rows)
    header = (
        f"{'Пара':<{name_width}}  {'fg':>7}  {'bg':>7}  {'Тип':<8}  "
        f"{'Порог':>6}  {'Ratio':>7}  Итог"
    )
    print(header)
    print("-" * len(header))
    for pair, ratio, ok in rows:
        status = "PASS" if ok else "FAIL"
        print(
            f"{pair.name:<{name_width}}  {pair.fg:>7}  {pair.bg:>7}  "
            f"{pair.kind:<8}  {pair.threshold:>5.1f}:1  {ratio:>6.2f}:1  {status}"
        )
    print("-" * len(header))

    passed = sum(1 for _, _, ok in rows if ok)
    print(f"Итого: {passed}/{len(rows)} пар прошли проверку.")

    if not all_pass:
        print("ПРОВАЛ: есть пары с контрастом ниже порога WCAG.", file=sys.stderr)
        return 1

    print("OK: все пары соответствуют порогам WCAG 2.1.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
