#!/usr/bin/env python3
"""Validador dos temas do Relógio. Um tema só passa se a MEDIDA deixar.

Duas perguntas, e as duas têm piso numérico:

1. LEGIBILIDADE — contraste WCAG do texto sobre o painel e sobre o fundo.
   Piso 4.5:1 para texto corrido, 3:1 para o mostrador (que é grande).

2. SEPARAÇÃO DOS ESTADOS — as três cores de meta (no alvo / entrando / passou)
   precisam se distinguir ENTRE SI, inclusive para quem não separa verde de
   vermelho. Simula protanopia e deuteranopia (matrizes de Viénot 1999) e mede
   ΔE em OKLab. Piso 8, o mesmo que o ecossistema já usava.

   Aqui a cor não é o único portador — todo estado vem com palavra ao lado — mas
   um par que colide vira decoração inútil, e decoração inútil ocupa espaço que
   podia informar.
"""
import math, json, sys

def hex2rgb(h):
    h = h.lstrip('#')
    return tuple(int(h[i:i+2], 16) / 255 for i in (0, 2, 4))

def lin(c):   return c/12.92 if c <= 0.04045 else ((c+0.055)/1.055) ** 2.4

def lum(rgb): 
    r, g, b = (lin(c) for c in rgb)
    return 0.2126*r + 0.7152*g + 0.0722*b

def contraste(a, b):
    la, lb = lum(hex2rgb(a)), lum(hex2rgb(b))
    hi, lo = max(la, lb), min(la, lb)
    return (hi + 0.05) / (lo + 0.05)

# --- OKLab (Björn Ottosson) --------------------------------------------------
def oklab(rgb):
    r, g, b = (lin(c) for c in rgb)
    l = 0.4122214708*r + 0.5363325363*g + 0.0514459929*b
    m = 0.2119034982*r + 0.6806995451*g + 0.1073969566*b
    s = 0.0883024619*r + 0.2817188376*g + 0.6299787005*b
    l, m, s = (v ** (1/3) if v > 0 else -((-v) ** (1/3)) for v in (l, m, s))
    return (0.2104542553*l + 0.7936177850*m - 0.0040720468*s,
            1.9779984951*l - 2.4285922050*m + 0.4505937099*s,
            0.0259040371*l + 0.7827717662*m - 0.8086757660*s)

def dE(c1, c2):
    a, b = oklab(hex2rgb(c1)), oklab(hex2rgb(c2))
    # ΔE em OKLab escalado por 100, como a casa já reportava
    return 100 * math.dist(a, b)

# --- simulação de daltonismo (Viénot, Brettel & Mollon 1999) -----------------
M_PROT = [[0.1121, 0.8853, -0.0005], [0.1127, 0.8897, -0.0001], [0.0045, 0.0085, 1.0000]]
M_DEUT = [[0.2920, 0.7054, -0.0003], [0.2934, 0.7089, 0.0000], [-0.0209, 0.4912, 0.5310]]

def simular(hexcor, M):
    r, g, b = (lin(c) for c in hex2rgb(hexcor))
    out = []
    for row in M:
        v = row[0]*r + row[1]*g + row[2]*b
        v = max(0.0, min(1.0, v))
        v = 12.92*v if v <= 0.0031308 else 1.055*(v ** (1/2.4)) - 0.055
        out.append(int(round(max(0, min(1, v)) * 255)))
    return '#%02x%02x%02x' % tuple(out)

PISO_TEXTO, PISO_GRANDE, PISO_DE = 4.5, 3.0, 8.0

def validar(nome, t):
    falhas, avisos = [], []
    # 1. legibilidade
    pares = [('texto/painel', t['texto'], t['painel'], PISO_TEXTO),
             ('texto/fundo',  t['texto'], t['fundo'],  PISO_TEXTO),
             ('fraco/painel', t['fraco'], t['painel'], PISO_GRANDE)]
    for rot, a, b, piso in pares:
        c = contraste(a, b)
        if c < piso: falhas.append(f'{rot} {c:.2f}:1 < {piso}')
    # os estados aparecem como borda e texto pequeno sobre o painel
    for k in ('boa', 'entrando', 'longa'):
        c = contraste(t[k], t['painel'])
        if c < PISO_GRANDE: falhas.append(f'{k}/painel {c:.2f}:1 < {PISO_GRANDE}')
    # 2. separação dos estados entre si, inclusive simulada
    estados = [('boa', t['boa']), ('entrando', t['entrando']), ('longa', t['longa'])]
    for visao, M in (('normal', None), ('protanopia', M_PROT), ('deuteranopia', M_DEUT)):
        for i in range(len(estados)):
            for j in range(i+1, len(estados)):
                (n1, c1), (n2, c2) = estados[i], estados[j]
                if M: c1, c2 = simular(c1, M), simular(c2, M)
                d = dE(c1, c2)
                if d < PISO_DE:
                    falhas.append(f'{n1}<->{n2} em {visao}: ΔE {d:.1f} < {PISO_DE}')
    return falhas

if __name__ == '__main__':
    temas = json.load(open(sys.argv[1], encoding='utf-8'))
    ok = True
    for nome, t in temas.items():
        f = validar(nome, t)
        print(('  PASSA  ' if not f else '  REPROVA') + f'  {nome}')
        for x in f: print('            ·', x); 
        if f: ok = False
    print()
    print('todos passam' if ok else 'HÁ TEMA REPROVADO')
    sys.exit(0 if ok else 1)
