#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""gen-detalhe.py — GERADOR dos tres usos internos do Grafismo v2:
textura de fundo, divisor editorial e marcador de secao.

Fecha a pendencia 3 da §17 do `marca-seed.md` (aberta desde a v5.4 para estes
tres usos). Nasceu do incidente de 2026-08-22: uma sessao de producao (Cowork,
post 4:5 do Diagnostico 360°) inventou a curva a mao porque nao havia geometria
de referencia para consumir.

╔══════════════════════════════════════════════════════════════════════════════╗
║ ESTE ARQUIVO NAO DESENHA NADA NOVO. O desenho e' O APROVADO:                 ║
║   · textura  = a CENA Z3 do rodape, inteira, a 10% de opacidade             ║
║   · divisor  = a IMAGEM do rodape (1080x85) repetida emendada e CORTADA     ║
║                no limite direito                                             ║
║   · marcador = o risco aprovado (bezier unica 56x8, amarelo, 2px) que ja    ║
║                sai em toda peca — gen-zero-formatos.py, `class="risco"`     ║
║                                                                              ║
║ As funcoes vem de gen-zero-formatos.py por EXTRACAO DE AST — nao por copia. ║
║ Canon §7.7: "NAO RECRIE O TRACADO A MAO".                                   ║
╚══════════════════════════════════════════════════════════════════════════════╝

OS VEREDITOS DO RAFAEL SANT'ANA QUE ESTE ARQUIVO EXECUTA (2026-08-22):

  textura   "essa aplicacao entao esta errada, vc tem que criar essas linhas
             igual as do rodape que ja estao aprovadas" — a textura e' a cena
             INTEIRA (5 camadas, 4 tons, oclusao por <mask>), nunca linhas
             paralelas: "paralelismo e' ritmo; serra e' profundidade" (§7.1).

  divisor   "era pra vc pegar o desenho original e duplica-lo. o desenho
             original nao pode ser alterado" + "independente do tamanho da
             largura, vc vai fazer a quantidade de copias que precisar em
             sequencia emendadas, e o que passar do limite do lado direito vc
             corta" + piso de leitura: A REGRA JA FEITA da §7.2 ("a serra
             precisa de ~15% da altura da peca para LER como serra") — sem
             teto superior; ao crescer, cresce proporcionalmente (§7.4).

  emenda    "o ponto A vc vai terminar o desenho ate tocar na linha, e o
             ponto B voce vai terminar a linha ate tocar a parte de baixo"
             — que e' a §7.3 aplicada a junta: A = toque exato (a mascara de
             oclusao aprovada corta a ponta do FUNDO da copia da esquerda
             exatamente na crista da copia seguinte — NATURAL, nada se
             constroi); B = morrer abaixo da moldura (mergulho na zona que a
             moldura da peca aprovada ja cortava).

  marcador  "prevalece o veredito que vc achou ja estabelecido" — o §78.3-6
             do seed-componentes.md reconcilia o amarelo do risco de
             proposito, e o §7.4-c ja lhe dera veredito proprio. O desenho
             aprovado fica INTOCADO, inclusive a cor.

  cor       turquesa-600 #098475 sobre branco · turquesa-300 #66D1C2 sobre
             turquesa-800 (escadas completas da §7.5 na textura e no divisor).

  sangria   "as pontas sangrando vao existir em producao de materiais para
             impressao (...) material para aplicacao apenas digital nao
             precisa" — sangria e' do TIPO DE ARQUIVO (§7.4-e, GATE x
             PRODUCAO), nao do sub-uso. Medido: o transbordo natural de 6%
             do desenho ja cobre os 36px de sangria em todo formato impresso.

ALTERNATIVAS DESCARTADAS (registro completo na v5.12 do marca-seed.md §18):
  quatro politicas parametricas de amplitude (todas trocavam n=7 por n=3 —
  redesenhavam a curva) · duas linhas continuas defasadas (so 17,5% de oclusao
  real — le como fita trancada) · cadeia de arcos (fatiava o desenho) · cena
  reduzida por F (o piso de traco da serra exige F>=58,64) · copias com
  profundidade alternada (linha listrada — §7.2 exclui oclusao do regime) ·
  descida-A construida (a natural ja existe; construida abria talho ou no').

LICAO DE INSTRUMENTO (medida, tres rodadas de lupa headless): a composicao
roda PURA sobre o desenho intocado. Modificar fundo_v antes de compoe() desloca
os retangulos de SUPRESSAO DE FRAGMENTO — no aprovado eles cortam a linha em
cruzamentos (corte invisivel); deslocados, cortavam no meio do ar.
"""
import argparse
import ast
import math
import pathlib
import re

RAIZ = pathlib.Path(__file__).resolve().parents[2]
APROVADO = RAIZ / '06-validacao' / 'geradores' / 'gen-zero-formatos.py'
DEST = RAIZ / '03-assets' / 'grafismos'

# ══════════════════════════════════════════════════════════════════════════════
# 1. EXTRACAO DO TRACADO APROVADO, por AST
# ══════════════════════════════════════════════════════════════════════════════
_FN = {'_d', '_lerp', 'cordilheira', 'filetes', 'segs_para_d', 'cena_z3',
       'amostra_segs', 'perfil', 'compoe', 'svg_cena', 'faixa_de'}
_VR = {'VAR', 'LARG', 'SELA', 'ASS', 'LONGE_', 'M_AM', 'FRAG_LEN',
       'FRAG_FOLGA'}


def _extrai(origem=APROVADO):
    arv = ast.parse(origem.read_text(encoding='utf-8'))
    nos = [n for n in arv.body
           if (isinstance(n, ast.FunctionDef) and n.name in _FN)
           or (isinstance(n, ast.Assign)
               and any(isinstance(t, ast.Name) and t.id in _VR
                       for t in n.targets))]
    ns = {'math': math}
    exec(compile(ast.Module(body=nos, type_ignores=[]), str(origem), 'exec'), ns)
    faltando = (_FN | _VR) - set(ns)
    if faltando:
        raise SystemExit('ERRO: nao extraiu de %s: %s'
                         % (origem.name, ', '.join(sorted(faltando))))
    return ns


AP = _extrai()

# o `d` do risco aprovado, lido DA FONTE (nunca redigitado)
_RISCO_D = re.search(
    r'class="risco" viewBox="0 0 56 8"[^\']*\'\s*\'<path d="([^"]+)"',
    APROVADO.read_text(encoding='utf-8')).group(1)

# ══════════════════════════════════════════════════════════════════════════════
# 2. CONSTANTES DO APROVADO E DO CANON
# ══════════════════════════════════════════════════════════════════════════════
W_APROV, F_APROV = 1080.0, 85.0     # a peca de referencia: post 1080, F=faixa_de(1080)
RAZAO_NATURAL = 12.7                # §7.4: w/F da cena aprovada
ESCADA = {'claro': ('#098475', '#11B0A0', '#66D1C2', '#9CE4D8'),
          'escuro': ('#66D1C2', '#11B0A0', '#098475', '#006C62')}
OPAC_TEXTURA = 0.10                 # §7.6
SANGRIA_MM = 3.0                    # §7.4-e
DPI = 300.0
F_TEXTURA_REF = 80.0                # veredito: F=80 em peca de 1080 (textura)


def pesos_de(F):
    """A lei do peso do aprovado (§7.4): fator = F/86, grampo 1,5–4,5."""
    fator = max(1.5 / 3.5, min(4.5 / 3.5, F / 86.0))
    return [round(p * fator, 2) for p in (3.5, 3.0, 2.5, 2.2)]


def sangria_px(dpi=DPI):
    """§7.4-e: 3mm, arredondando px para CIMA (36px a 300dpi)."""
    return math.ceil(SANGRIA_MM / 25.4 * dpi)


def _translada_segs(segs, dx):
    out = []
    for s in segs:
        pts = tuple((p[0] + dx, p[1]) for p in s[1:])
        out.append((s[0],) + pts)
    return out


# ══════════════════════════════════════════════════════════════════════════════
# 3. DIVISOR — a imagem do rodape, emendada e cortada
# ══════════════════════════════════════════════════════════════════════════════
def svg_divisor(span, altura=None, tema='claro', uid='dv'):
    """A imagem APROVADA do rodape (1080x85), repetida emendada, cortada.

    `altura` define a escala (k = altura/85). Sem `altura`, usa a PROPORCAO
    NATURAL da cena (§7.4, w/F = 12,7): altura = span/12,7 — que preenche o
    span com exatamente uma imagem. Copias entram na quantidade necessaria e
    o viewBox corta a ultima no limite direito (veredito do corte). PISO de
    leitura: §7.2 — sem teto superior; escala sempre proporcional.

    A EMENDA (§7.3 aplicada, medida em lupa 11x):
      A · toque exato NATURAL — a mascara de oclusao aprovada corta a ponta
          direita do FUNDO da copia da esquerda exatamente na crista do morro
          da copia seguinte. Nada se constroi.
      B · a ponta esquerda do FUNDO das copias internas ganha um MERGULHO ate
          abaixo da faixa, na zona que a moldura da peca aprovada ja cortava
          — o desenho visivel aprovado nao muda.
    """
    F = F_APROV
    if altura is None:
        altura = span / RAZAO_NATURAL
    k = altura / F
    vb_w = span / k                        # largura visivel em px aprovados
    r = max(1, math.ceil(vb_w / W_APROV))
    pesos = pesos_de(F)

    # composicao PURA, uma vez, sobre o desenho intocado (ver licao no topo)
    fundo_v0, morros, frente = AP['cena_z3'](W_APROV, F)
    capas0, supress0, _ = AP['compoe'](W_APROV, F, fundo_v0, morros, frente)
    i_fundo = next(i for i, c in enumerate(capas0) if c['tom'] == 3)

    capas_tudo, supress_tudo = [], []
    for j in range(r):
        dx = j * W_APROV
        base = len(capas_tudo)
        for i, c in enumerate(capas0):
            segs = _translada_segs(c['segs'], dx)
            if i == i_fundo and j > 0:     # regra B: mergulho da ponta esquerda
                a = segs[0][1]
                segs.insert(0, ('L', (a[0] - 18.0, F + 8.0), a))
            capas_tudo.append(dict(c, segs=segs))
        for si, sx0, sx1, sy0, sy1 in supress0:
            supress_tudo.append((si + base, sx0 + dx, sx1 + dx, sy0, sy1))

    cena = AP['svg_cena'](vb_w, F, capas_tudo, supress_tudo,
                          ESCADA[tema], pesos, uid)
    interno = cena.split('>', 1)[1].rsplit('</svg>', 1)[0]
    svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%.0f" '
           'height="%.2f" viewBox="0 0 %.2f %.0f" '
           'preserveAspectRatio="xMidYMid slice" role="img" '
           'aria-hidden="true" focusable="false" data-regime="serra-cena" '
           'data-uso="divisor" data-cena="Z3" data-aprovado="post-1080-F85" '
           'data-copias="%d" data-escala="%.5f" '
           'data-emenda="toque-exato+mergulho" data-corte="direita" '
           'data-piso="§7.2" data-sangra="nao">%s</svg>'
           % (span, altura, vb_w, F, r, k, interno))
    return svg, dict(span=span, altura=altura, k=k, r=r, vb_w=vb_w,
                     planos=len(capas_tudo), mascaras=cena.count('<mask'),
                     pesos_visuais=[round(x * k, 2) for x in pesos])


# ══════════════════════════════════════════════════════════════════════════════
# 4. TEXTURA — a cena aprovada inteira, a 10%
# ══════════════════════════════════════════════════════════════════════════════
def svg_textura(w=1080, tema='claro', papel='gate', dpi=DPI, uid='tx'):
    """A cena Z3 inteira a <=10% de opacidade, atras de conteudo.

    MESMO DESENHO, DOIS RECORTES (§7.4-e): papel='gate' recorta na area de
    corte; papel='producao' cresce a caixa PARA FORA em sangria_px por lado —
    o transbordo natural de 6% do desenho ja cobre os 36px exigidos (medido:
    cartao 63,78px · pasta A4 148,80px). Nunca dois desenhos.
    """
    F = w * F_TEXTURA_REF / W_APROV
    fundo_v, morros, frente = AP['cena_z3'](float(w), float(F))
    capas, supress, _ = AP['compoe'](float(w), float(F), fundo_v, morros,
                                     frente)
    cena = AP['svg_cena'](float(w), float(F), capas, supress,
                          ESCADA[tema], pesos_de(F), uid)
    s = sangria_px(dpi) if papel == 'producao' else 0
    cx_w, cx_h = w + 2 * s, F + s
    meta = (' data-papel="producao" data-corte-px="%.0fx%.0f" data-sangria="%d"'
            % (w, F, s)) if papel == 'producao' else \
        ' data-papel="gate" data-sangria="0"'
    interno = cena.split('>', 1)[1].rsplit('</svg>', 1)[0]
    svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%.0f" height="%.0f" '
           'viewBox="%d 0 %.0f %.0f" role="img" aria-hidden="true" '
           'focusable="false" data-regime="serra-cena" data-uso="textura" '
           'data-cena="Z3"%s opacity="%s">%s</svg>'
           % (cx_w, cx_h, -s, cx_w, cx_h, meta, OPAC_TEXTURA, interno))
    return svg, dict(w=w, F=F, sangria=s, planos=len(capas),
                     mascaras=cena.count('<mask'), pesos=pesos_de(F))


# ══════════════════════════════════════════════════════════════════════════════
# 5. MARCADOR — o risco aprovado, standalone
# ══════════════════════════════════════════════════════════════════════════════
def svg_marcador():
    """O risco aprovado (gen-zero-formatos.py, class='risco'), com os
    atributos que la vivem no CSS (.risco path) inlined para o SVG valer
    sozinho. O `d` e' lido da fonte por regex — nunca redigitado. Amarelo
    mantido por veredito (§78.3-6 prevalece)."""
    return ('<svg xmlns="http://www.w3.org/2000/svg" width="56" height="8" '
            'viewBox="0 0 56 8" role="img" aria-hidden="true" '
            'focusable="false" data-regime="detalhe" data-uso="marcador" '
            'data-aprovado="gen-zero-formatos.risco" data-sangra="nao">'
            '<path d="%s" fill="none" stroke="#FAD61D" stroke-width="2" '
            'stroke-linecap="round"/></svg>' % _RISCO_D)


# ══════════════════════════════════════════════════════════════════════════════
# 6. CLI
# ══════════════════════════════════════════════════════════════════════════════
def medir():
    print('extraido de %s: %d funcoes + %d constantes · risco d="%s"'
          % (APROVADO.name, len(_FN), len(_VR), _RISCO_D))
    print('sangria §7.4-e: %.0fmm a %.0fdpi -> %dpx (arredonda p/ cima)\n'
          % (SANGRIA_MM, DPI, sangria_px()))
    print('=== divisor: proporcao natural e emenda ===')
    for span in (680, 1080, 2160):
        _, m = svg_divisor(span)
        print('  span %4d: altura natural %.1fpx · %d copia(s) · '
              'tracos visuais %s'
              % (span, m['altura'], m['r'], m['pesos_visuais']))
    _, m = svg_divisor(680, altura=40)
    print('  span  680 com altura=40: %d copias, corte a direita, '
          'escala %.4f' % (m['r'], m['k']))
    print('\n=== textura: transbordo x sangria ===')
    for rot, w, dpi in (('post 1080 (tela)', 1080, None),
                        ('cartao @300dpi', 1063, 300.0),
                        ('pasta A4 @300dpi', 2480, 300.0)):
        _, m = svg_textura(w, papel='gate')
        tr = w * 0.06
        prec = sangria_px(dpi) if dpi else 0
        vd = 'n/a (tela)' if not dpi else ('COBRE' if tr >= prec
                                           else 'NAO COBRE')
        print('  %-18s transborda %6.2fpx · precisa %2dpx · %s'
              % (rot, tr, prec, vd))


def emitir():
    DEST.mkdir(parents=True, exist_ok=True)
    escritos = []

    def grava(nome, svg):
        p = DEST / nome
        p.write_text(svg, encoding='utf-8', newline='\n')
        escritos.append((p, len(svg)))

    for tema in ('claro', 'escuro'):
        grava('grafismo-textura-%s.svg' % tema, svg_textura(tema=tema)[0])
        grava('grafismo-divisor-%s.svg' % tema, svg_divisor(680, tema=tema)[0])
    grava('grafismo-marcador.svg', svg_marcador())
    for p, n in escritos:
        print('  %6d B  %s' % (n, p.relative_to(RAIZ)))
    print('%d assets oficiais em %s/' % (len(escritos),
                                         DEST.relative_to(RAIZ)))


if __name__ == '__main__':
    ap = argparse.ArgumentParser(description=__doc__.split('\n')[0])
    ap.add_argument('--medir', action='store_true',
                    help='imprime as contas; nao escreve arquivo')
    ap.add_argument('--emitir', action='store_true',
                    help='escreve os SVGs oficiais em 03-assets/grafismos/')
    a = ap.parse_args()
    if a.emitir:
        emitir()
    else:
        medir()
