#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Gera os SVGs do resumo-voyager a partir de voyagermon/resultados.json e
voyagermon/dados/serie.csv.gz. Saída: docs/pdf-src/graficos/fig5..7_voyager*.svg
Paleta: instância de referência da skill dataviz (slots 1–2, par validado
nesta sessão via validate_palette.mjs). Rode: python3 gen_graficos_voyager.py"""
import csv, gzip, json
from collections import defaultdict
from pathlib import Path

DOCS = Path(__file__).parent
REPO = DOCS.parent
OUT = DOCS / "pdf-src" / "graficos"
OUT.mkdir(parents=True, exist_ok=True)

AZUL, LARANJA = "#2a78d6", "#eb6834"
AZUL_CLARO = "#9ec5f4"
TINTA, TINTA2, GRADE = "#1c1c1c", "#52514e", "#e3e0d9"
PALE = "#f4f1ea"
FONTE = "font-family='Helvetica Neue,Arial,sans-serif'"

RES = json.loads((REPO / "voyagermon" / "resultados.json").read_text())


def br(v, dec=1):
    return f"{v:.{dec}f}".replace(".", ",")


# ------------------------------------------------ fig 5: horas de antena
def fig5():
    masc = RES["link"]["mascaras_B"]
    bruto = sum(c["bruto_f64_B"] for c in RES["codec"].values())
    gz = RES["link"]["gzip9_B"]
    tube = RES["link"]["tube_B"]
    BPS = RES["link"]["bps"]
    h = lambda b: (b + masc) * 8 / BPS / 3600
    linhas = [("Bruto (f64)", bruto, h(bruto)),
              ("gzip -9", gz, h(gz)),
              ("TUBE certificado", tube, h(tube))]
    W, H = 660, 210
    x0, x1 = 150, 610
    hmax = 90.0
    X = lambda v: x0 + v / hmax * (x1 - x0)
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' width='{W}' height='{H}' viewBox='0 0 {W} {H}'>"]
    s.append(f"<text x='{x0}' y='16' {FONTE} font-size='10' fill='{TINTA}' font-weight='700'>"
             f"Horas de antena de 70 m (link de {BPS} bps) para descer 48 anos de dados</text>")
    for v in (0, 20, 40, 60, 80):
        s.append(f"<line x1='{X(v):.1f}' y1='30' x2='{X(v):.1f}' y2='178' stroke='{GRADE}' stroke-width='1'/>")
        s.append(f"<text x='{X(v):.1f}' y='192' {FONTE} font-size='9' fill='{TINTA2}' text-anchor='middle'>{v} h</text>")
    y = 42
    for nome, dados_b, horas in linhas:
        cor = AZUL if "TUBE" not in nome else LARANJA
        # segmento dados + segmento máscara (mesma matiz, passo claro)
        w_dados = (dados_b * 8 / 160 / 3600) / hmax * (x1 - x0)
        w_masc = (masc * 8 / 160 / 3600) / hmax * (x1 - x0)
        s.append(f"<text x='{x0-8}' y='{y+14}' {FONTE} font-size='9.5' fill='{TINTA}' text-anchor='end'>{nome}</text>")
        s.append(f"<rect x='{x0}' y='{y}' width='{max(w_dados,1):.1f}' height='20' rx='4' fill='{cor}'/>")
        s.append(f"<rect x='{x0+w_dados+2:.1f}' y='{y}' width='{w_masc:.1f}' height='20' rx='4' fill='{AZUL_CLARO}'/>")
        s.append(f"<text x='{x0+w_dados+w_masc+8:.1f}' y='{y+14}' {FONTE} font-size='10' "
                 f"fill='{TINTA}' font-weight='700'>{br(horas)} h</text>")
        y += 38
    s.append(f"<rect x='{x0}' y='{y+4}' width='9' height='9' rx='2' fill='{AZUL_CLARO}'/>")
    s.append(f"<text x='{x0+13}' y='{y+12}' {FONTE} font-size='8.5' fill='{TINTA2}'>"
             f"parte clara: a máscara de cobertura — dizer QUANDO a antena mediu custa 38% do pacote final</text>")
    s.append("</svg>")
    (OUT / "fig5_voyager_link.svg").write_text("\n".join(s))


# ------------------------------------- fig 6: a saída da bolha do Sol
def fig6():
    soma, n = defaultdict(float), defaultdict(int)
    with gzip.open(REPO / "voyagermon" / "dados" / "serie.csv.gz", "rt") as f:
        for r in csv.DictReader(f):
            if r["ano"] == "2018" and r["B"]:
                d = int(r["dia"])
                if 283 <= d <= 335:
                    soma[d] += float(r["B"])
                    n[d] += 1
    dias = {d: soma[d] / n[d] for d in sorted(soma) if n[d] >= 4}
    L, EPS = RES["cruzamento"]["L_nT"], 0.05
    W, H = 660, 260
    x0, x1, y0, y1 = 52, 640, 214, 30
    dmin, dmax, vmin, vmax = 283, 335, 0.35, 0.80
    X = lambda d: x0 + (d - dmin) / (dmax - dmin) * (x1 - x0)
    Y = lambda v: y0 - (v - vmin) / (vmax - vmin) * (y0 - y1)
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' width='{W}' height='{H}' viewBox='0 0 {W} {H}'>"]
    for v in (0.4, 0.5, 0.6, 0.7, 0.8):
        s.append(f"<line x1='{x0}' y1='{Y(v):.1f}' x2='{x1}' y2='{Y(v):.1f}' stroke='{GRADE}' stroke-width='1'/>")
        s.append(f"<text x='{x0-6}' y='{Y(v)+3:.1f}' {FONTE} font-size='9' fill='{TINTA2}' text-anchor='end'>{br(v)}</text>")
    for d, rot in ((288, "15 out"), (298, "25 out"), (309, "5 nov"), (319, "15 nov"), (329, "25 nov")):
        s.append(f"<line x1='{X(d):.1f}' y1='{y0}' x2='{X(d):.1f}' y2='{y0+4}' stroke='{GRADE}'/>")
        s.append(f"<text x='{X(d):.1f}' y='{y0+15}' {FONTE} font-size='9' fill='{TINTA2}' text-anchor='middle'>{rot}</text>")
    s.append(f"<text x='14' y='{Y(0.57):.1f}' {FONTE} font-size='8.5' fill='{TINTA2}' "
             f"transform='rotate(-90 14 {Y(0.57):.0f})' text-anchor='middle'>campo magnético (nT)</text>")
    # banda de indecisão L ± ε do instrumento
    s.append(f"<rect x='{x0}' y='{Y(L+EPS):.1f}' width='{x1-x0}' height='{Y(L-EPS)-Y(L+EPS):.1f}' fill='{PALE}'/>")
    s.append(f"<line x1='{x0}' y1='{Y(L):.1f}' x2='{x1}' y2='{Y(L):.1f}' stroke='{TINTA2}' stroke-width='1' stroke-dasharray='5 3'/>")
    s.append(f"<text x='{x1}' y='{Y(L)-5:.1f}' {FONTE} font-size='8.5' fill='{TINTA2}' text-anchor='end'>"
             f"limiar entre os dois lados ± erro do sensor</text>")
    # cruzamento
    s.append(f"<line x1='{X(309):.1f}' y1='{y1}' x2='{X(309):.1f}' y2='{y0}' stroke='{LARANJA}' stroke-width='1' stroke-dasharray='2 3'/>")
    s.append(f"<text x='{X(309)-4:.1f}' y='{y1+8}' {FONTE} font-size='8.5' fill='{LARANJA}' text-anchor='end' font-weight='700'>5 nov 2018:</text>")
    s.append(f"<text x='{X(309)-4:.1f}' y='{y1+18}' {FONTE} font-size='8.5' fill='{LARANJA}' text-anchor='end'>a sonda sai da bolha do Sol</text>")
    for d, v in dias.items():
        if v + EPS < L:
            s.append(f"<circle cx='{X(d):.1f}' cy='{Y(v):.1f}' r='4' fill='{AZUL}' stroke='#fff' stroke-width='2'/>")
        elif v - EPS > L:
            s.append(f"<circle cx='{X(d):.1f}' cy='{Y(v):.1f}' r='4' fill='{LARANJA}' stroke='#fff' stroke-width='2'/>")
        else:
            s.append(f"<circle cx='{X(d):.1f}' cy='{Y(v):.1f}' r='4' fill='none' stroke='{TINTA2}' stroke-width='1.5'/>")
    s.append(f"<line x1='{x0}' y1='{y0}' x2='{x1}' y2='{y0}' stroke='{TINTA2}' stroke-width='1'/>")
    # legenda
    lx = x0 + 8
    s.append(f"<circle cx='{lx}' cy='42' r='4' fill='{AZUL}' stroke='#fff' stroke-width='2'/>")
    s.append(f"<text x='{lx+9}' y='45' {FONTE} font-size='8.5' fill='{TINTA2}'>certamente DENTRO da heliosfera</text>")
    s.append(f"<circle cx='{lx}' cy='58' r='4' fill='{LARANJA}' stroke='#fff' stroke-width='2'/>")
    s.append(f"<text x='{lx+9}' y='61' {FONTE} font-size='8.5' fill='{TINTA2}'>certamente FORA (meio interestelar)</text>")
    s.append(f"<circle cx='{lx}' cy='74' r='4' fill='none' stroke='{TINTA2}' stroke-width='1.5'/>")
    s.append(f"<text x='{lx+9}' y='77' {FONTE} font-size='8.5' fill='{TINTA2}'>indecidível dentro do erro do sensor</text>")
    s.append("</svg>")
    (OUT / "fig6_voyager_heliopausa.svg").write_text("\n".join(s))


# ------------------------- fig 7: dias de indecisão por qualidade de sensor
def fig7():
    pe = RES["cruzamento"]["por_eps"]
    ordem = ["0.02", "0.05", "0.1", "0.13", "0.2"]
    rotulos = ["0,02", "0,05", "0,10", "0,13", "0,20"]
    W, H = 660, 240
    x0, y0, y1 = 46, 196, 30
    bw, gap = 96, 24
    vmax = 45.0
    Y = lambda v: y0 - v / vmax * (y0 - y1)
    s = [f"<svg xmlns='http://www.w3.org/2000/svg' width='{W}' height='{H}' viewBox='0 0 {W} {H}'>"]
    s.append(f"<text x='{x0}' y='16' {FONTE} font-size='10' fill='{TINTA}' font-weight='700'>"
             f"Dias sem veredito sobre a travessia, conforme o erro do sensor (nT)</text>")
    for v in (0, 10, 20, 30, 40):
        s.append(f"<line x1='{x0}' y1='{Y(v):.1f}' x2='{x0+5*(bw+gap):.0f}' y2='{Y(v):.1f}' stroke='{GRADE}' stroke-width='1'/>")
        s.append(f"<text x='{x0-6}' y='{Y(v)+3:.1f}' {FONTE} font-size='9' fill='{TINTA2}' text-anchor='end'>{v}</text>")
    # faixa da acurácia documentada (2 primeiras barras)
    fx0, fx1 = x0 + 2, x0 + 2 * bw + gap + 10
    s.append(f"<rect x='{fx0}' y='{y0+22}' width='{fx1-fx0}' height='14' rx='3' fill='{PALE}'/>")
    s.append(f"<text x='{(fx0+fx1)/2:.0f}' y='{y0+32}' {FONTE} font-size='8' fill='{TINTA2}' "
             f"text-anchor='middle'>acurácia documentada do instrumento real</text>")
    for i, (k, rot) in enumerate(zip(ordem, rotulos)):
        bx = x0 + 8 + i * (bw + gap)
        v = pe.get(k)
        if v is None:
            s.append(f"<rect x='{bx}' y='{Y(vmax):.1f}' width='{bw-16}' height='{y0-Y(vmax):.1f}' rx='4' "
                     f"fill='{LARANJA}' fill-opacity='0.25' stroke='{LARANJA}' stroke-dasharray='4 3'/>")
            s.append(f"<text x='{bx+(bw-16)/2:.0f}' y='{Y(vmax)+26:.0f}' {FONTE} font-size='9' "
                     f"fill='{LARANJA}' text-anchor='middle' font-weight='700'>nunca</text>")
            s.append(f"<text x='{bx+(bw-16)/2:.0f}' y='{Y(vmax)+37:.0f}' {FONTE} font-size='9' "
                     f"fill='{LARANJA}' text-anchor='middle' font-weight='700'>decide</text>")
        else:
            d = v["dias_indecidiveis"]
            s.append(f"<rect x='{bx}' y='{Y(d):.1f}' width='{bw-16}' height='{y0-Y(d):.1f}' rx='4' fill='{AZUL}'/>")
            s.append(f"<text x='{bx+(bw-16)/2:.0f}' y='{Y(d)-5:.1f}' {FONTE} font-size='10' "
                     f"fill='{TINTA}' text-anchor='middle' font-weight='700'>{d}</text>")
        s.append(f"<text x='{bx+(bw-16)/2:.0f}' y='{y0+14}' {FONTE} font-size='9.5' fill='{TINTA2}' text-anchor='middle'>{rot}</text>")
    s.append(f"<line x1='{x0}' y1='{y0}' x2='{x0+5*(bw+gap):.0f}' y2='{y0}' stroke='{TINTA2}' stroke-width='1'/>")
    s.append("</svg>")
    (OUT / "fig7_voyager_epsilon.svg").write_text("\n".join(s))


fig5(); fig6(); fig7()
print("ok:", *(p.name for p in sorted(OUT.glob("fig[567]_voyager*.svg"))))
