"""Render de prova da lumo-mockup — o smoke test da skill.

Monta um mockup completo aplicando o que a skill manda: formas determinísticas,
placeholder sequencial fixo, chassi do produto, e o assert anti-vazamento em
cada widget.
"""
import json
import math
import os
import re
import sys

import vl_convert as vlc

SKILL = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(SKILL, "scripts"))
from placeholder import (  # noqa: E402
    TODOS, acima, contagem, escala_de, placeholder,
)

vlc.register_font_directory(f"{SKILL}/assets/fonts")
theme = json.load(open(f"{SKILL}/references/horus-theme.json"))
VL = "6.4"

# Escalas desta tela, escolhidas uma vez e reusadas (coerência, §2c).
E_RECEITA = escala_de("faturamento mensal")      # centenas-de-milhares
P_RECEITA = placeholder(E_RECEITA)               # R$ 123.456,78
P_TOTAL = placeholder(acima(E_RECEITA))          # R$ 1.234.567,89
P_TICKET = placeholder(escala_de("ticket medio"))
P_QTDE = contagem(escala_de("contagem cliente"))


# --- formas determinísticas (ficha-para-vega §4.3) ---------------------------
def pareto(n=5, decay=0.62):
    return [round(decay ** i, 4) for i in range(n)]


def tendencia_alta(n=8, start=0.25, slope=0.055):
    return [round(start + slope * i, 4) for i in range(n)]


def sazonal(n=12, base=0.5, amp=0.35):
    return [round(base + amp * math.sin(2 * math.pi * i / n), 4) for i in range(n)]


# --- geometria do grid: o spec nasce do tamanho do card ----------------------
PAGINA_MAX = 1440
PAGINA_PAD = 20
COLUNAS = 12
GUTTER = 4          # GridStack margin: 2
CELL_H = 40         # GridStack cellHeight
CARD_PAD = 16
HEADER_H = 30       # h3 + margin


def dim(col, row, com_titulo=True):
    """Largura e altura ÚTEIS de um card que ocupa `col` colunas por `row` linhas.

    O gráfico não é escalado para caber no card: ele é DESENHADO no tamanho do
    card. Escalar um SVG com preserveAspectRatio deixa faixa vazia nas laterais
    sempre que a proporção do card difere da do spec, que é o defeito que faz o
    mockup não parecer o produto (no Horus BI o Highcharts redesenha no
    container). Como o grid é determinístico, dá para calcular e passar as
    dimensões certas ao spec.
    """
    util = PAGINA_MAX - 2 * PAGINA_PAD
    largura_col = (util - (COLUNAS - 1) * GUTTER) / COLUNAS
    w = col * largura_col + (col - 1) * GUTTER - 2 * CARD_PAD
    h = row * CELL_H + (row - 1) * GUTTER - 2 * CARD_PAD - (HEADER_H if com_titulo else 0)
    return int(w), int(h)


def encaixa(spec, alvo_h, tentativas=4):
    """Ajusta `height` até o SVG inteiro caber na altura útil do card.

    Em Vega-Lite, `height` é a altura da ÁREA DE PLOT: eixo, legenda e rótulo
    entram POR FORA dela. Pedir height=286 pode render um SVG de 329px, que
    estoura o card e corta o título. Como o excedente varia por widget (legenda
    de 2 linhas custa mais que um eixo simples), medir e reajustar é mais
    robusto do que descontar um valor fixo chutado.
    """
    for _ in range(tentativas):
        svg = vlc.vegalite_to_svg(spec, config=theme, vl_version=VL)
        m = re.search(r'<svg[^>]*height="(\d+)"', svg)
        real = int(m.group(1))
        if real <= alvo_h:
            return spec
        spec = {**spec, "height": spec["height"] - (real - alvo_h)}
    return spec


def render(spec, nome, categorias=()):
    """Assert anti-vazamento.

    Todo <text> com dígito precisa ser (a) um placeholder da tabela de escalas
    ou (b) um nome de categoria que a ficha declarou. Qualquer outro dígito é
    magnitude vazando.
    """
    svg = vlc.vegalite_to_svg(spec, config=theme, vl_version=VL)
    ok_set = set(TODOS) | set(categorias)
    vazou = [t for t in re.findall(r"<text[^>]*>([^<]*)</text>", svg)
             if re.search(r"\d", t) and t.strip() not in ok_set]
    print(f"  [{'ok ' if not vazou else 'VAZOU'}] {nome:<26}" + (f" -> {vazou}" if vazou else ""))
    assert not vazou, f"{nome} vazou magnitude: {vazou}"
    return svg


def label(**kw):
    """O layer de data label: valor CONSTANTE, nunca field (ficha-para-vega §2)."""
    m = {"type": "text", "fontSize": 10, "color": "#000"}
    m.update(kw)
    return m


print("=== escalas desta tela ===")
print(f"  receita  {P_RECEITA}   total {P_TOTAL}")
print(f"  ticket   {P_TICKET}       qtde  {P_QTDE}\n")
print("=== widgets ===")

# 1) magnitude — Barline vertical
meses = ["Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"]
w_mag = {
    "data": {"values": [{"c": m, "v": v} for m, v in zip(meses, tendencia_alta())]},
    **dict(zip(("width", "height"), dim(6, 8))),
    "layer": [
        {"mark": {"type": "bar", "width": 26},
         "encoding": {
             "x": {"field": "c", "type": "nominal", "sort": None,
                   "axis": {"title": None, "labelAngle": 0}},
             "y": {"field": "v", "type": "quantitative",
                   "axis": {"title": None, "labels": False},
                   "scale": {"domainMax": 0.95}}}},
        {"mark": label(dy=-9),
         "encoding": {"x": {"field": "c", "type": "nominal", "sort": None},
                      "y": {"field": "v", "type": "quantitative",
                            "scale": {"domainMax": 0.95}},
                      "text": {"value": P_RECEITA}}},
    ],
}
svg_mag = render(encaixa(w_mag, dim(6, 8)[1]), "magnitude/Barline")

# 2) ranking — Barline horizontal
lojas = ["Loja Sul", "Loja Centro", "Loja Norte", "Loja Leste", "Loja Oeste"]
w_rank = {
    "data": {"values": [{"c": l, "v": v} for l, v in zip(lojas, pareto())]},
    **dict(zip(("width", "height"), dim(6, 8))),
    "layer": [
        {"mark": {"type": "bar", "height": 18},
         "encoding": {
             "y": {"field": "c", "type": "nominal", "sort": "-x", "axis": {"title": None}},
             "x": {"field": "v", "type": "quantitative",
                   "axis": {"title": None, "labels": False},
                   "scale": {"domainMax": 1.75}}}},
        {"mark": label(dx=5, align="left"),
         "encoding": {"y": {"field": "c", "type": "nominal", "sort": "-x"},
                      "x": {"field": "v", "type": "quantitative",
                            "scale": {"domainMax": 1.75}},
                      "text": {"value": P_RECEITA}}},
    ],
}
svg_rank = render(encaixa(w_rank, dim(6, 8)[1]), "ranking/Barline")

# 3) parte-todo — Pie donut, exatamente 4 categorias (a trava)
canais = ["Balcão", "E-commerce", "Representante", "Distribuidor"]
w_donut = {
    "data": {"values": [{"c": c, "v": v} for c, v in zip(canais, pareto(4))]},
    **dict(zip(("width", "height"), dim(4, 8))),
    "mark": {"type": "arc", "innerRadius": 52, "outerRadius": 86},
    "encoding": {
        "theta": {"field": "v", "type": "quantitative"},
        "color": {"field": "c", "type": "nominal", "sort": canais,
                  "legend": {"orient": "bottom", "columns": 2}},
    },
}
svg_donut = render(encaixa(w_donut, dim(4, 8)[1]), "parte-todo/Pie")

# 4) desvio — realizado (barra) vs meta (linha). NUNCA linha x linha:
#    duas linhas leem como "duas séries temporais", não como "medido contra
#    referência". IBCS: realizado = preenchido, meta = linha/marcador.
meses12 = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
           "Jul", "Ago", "Set", "Out", "Nov", "Dez"]
pontos = [{"m": m, "real": v, "meta": round(v * 0.82, 4)}
          for m, v in zip(meses12, sazonal())]
w_tend = {
    "data": {"values": pontos},
    **dict(zip(("width", "height"), dim(8, 8))),
    "layer": [
        {"mark": {"type": "bar", "width": 24},
         "encoding": {
             "x": {"field": "m", "type": "nominal", "sort": None,
                   "axis": {"title": None, "labelAngle": 0}},
             "y": {"field": "real", "type": "quantitative",
                   "axis": {"title": None, "labels": False},
                   "scale": {"domainMax": 0.95}},
             "color": {"datum": "Realizado",
                       "legend": {"title": None, "orient": "top"}}}},
        {"mark": {"type": "line", "point": True, "strokeWidth": 2},
         "encoding": {
             "x": {"field": "m", "type": "nominal", "sort": None},
             "y": {"field": "meta", "type": "quantitative",
                   "scale": {"domainMax": 0.95}},
             "color": {"datum": "Meta"}}},
    ],
}
svg_tend = render(encaixa(w_tend, dim(8, 8)[1]), "desvio/Barline combo")

svgs = {"mag": svg_mag, "rank": svg_rank, "donut": svg_donut, "tend": svg_tend}

print("\n=== resumo ===")
for nome, svg in svgs.items():
    print(f"  {nome:<10} {len(svg):>6} bytes   Poppins={'Poppins' in svg}"
          f"   placeholder={P_RECEITA in svg}")


# ---------- página com o chassi do produto ----------
def kpi(over, valor, sub, icone):
    return f'''
      <div class="hb-card" style="grid-column:span 3; grid-row:span 3">
        <div class="hb-kpi"><div class="hb-kpi__icon">{icone}</div><div>
          <div class="hb-kpi__over">{over}</div>
          <div class="hb-kpi__val">{valor}</div>
          <div class="hb-kpi__sub">{sub}</div>
        </div></div>
      </div>'''


def card(n, titulo, svg, col, row, extra=""):
    return f'''
      <div class="hb-card" style="grid-column:span {col}; grid-row:span {row}">
        <span class="wf-n">{n}</span><h3>{titulo}</h3>
        <div class="hb-actions">&#128269; &#8599;</div>
        <div class="hb-body" style="{extra}">{svg}</div>
      </div>'''


linhas = "".join(
    f"<tr><td class='ord'>{i}</td><td>Fornecedor {chr(64 + i)}</td>"
    f"<td class='num'>{P_RECEITA}</td><td class='num'>{P_QTDE}</td></tr>"
    for i in range(1, 9)
)
tabela = f'''
  <table class="hb-table">
    <thead><tr><th class="ord">#</th><th>Fornecedor</th>
      <th class="num">Valor Total</th><th class="num">Qtde NF</th></tr></thead>
    <tbody>{linhas}</tbody>
    <tfoot><tr><td></td><td>Total</td>
      <td class="num">{P_TOTAL}</td><td class="num">{contagem("milhares")}</td></tr></tfoot>
  </table>'''

HTML = f"""<!doctype html><html lang="pt-BR"><head><meta charset="utf-8">
<title>Mockup — Visão Comercial</title><style>
@font-face {{ font-family:Poppins; font-weight:400;
  src:url('file://{SKILL}/assets/fonts/Poppins-Regular.ttf') format('truetype') }}
@font-face {{ font-family:Poppins; font-weight:500;
  src:url('file://{SKILL}/assets/fonts/Poppins-Medium.ttf') format('truetype') }}
:root {{ --hb-page:#F8FAFC; --hb-card:#fff; --hb-card-border:rgba(241,245,249,.5);
  --hb-title:#334155; --hb-action:#CBD5E1; --hb-axis:#949DAC; --hb-cell-border:#F1F5F9; }}
* {{ box-sizing:border-box; margin:0 }}
body {{ font:14px/1.45 Poppins,-apple-system,system-ui,sans-serif; color:var(--hb-title);
  background:var(--hb-page); padding:20px; max-width:1440px; margin:0 auto }}
.hb-filters {{ display:flex; gap:6px; margin-bottom:8px }}
.hb-filter {{ background:#fff; border:1px solid #E2E8F0; border-radius:4px;
  padding:4px 8px; font-size:12px }}
.hb-filter b {{ display:block; font-size:10px; font-weight:400; color:var(--hb-axis) }}
.hb-grid {{ display:grid; grid-template-columns:repeat(12,1fr); grid-auto-rows:40px; gap:4px }}
.hb-card {{ position:relative; background:var(--hb-card); border:2px solid var(--hb-card-border);
  border-radius:8px; box-shadow:0 1px 2px 0 rgb(0 0 0/.05); padding:16px;
  display:flex; flex-direction:column; overflow:hidden }}
.hb-card h3 {{ font-size:14px; font-weight:500; letter-spacing:.025em; margin-bottom:8px;
  padding-right:56px }}
.hb-actions {{ position:absolute; top:16px; right:16px; display:flex; gap:8px;
  color:var(--hb-action); font-size:13px }}
.hb-body {{ flex:1; display:flex; align-items:center; justify-content:center; min-height:0 }}
.hb-body svg {{ display:block }}
.hb-kpi {{ display:grid; grid-template-columns:auto 1fr; align-items:center; gap:12px }}
.hb-kpi__icon {{ font-size:26px; opacity:.85 }}
.hb-kpi__over {{ font-size:12px; text-transform:uppercase; letter-spacing:.025em; opacity:.7 }}
.hb-kpi__val {{ font-weight:500; font-size:26px }}
.hb-kpi__sub {{ font-size:12px; opacity:.8 }}
.hb-table {{ width:100%; border-collapse:collapse; font-size:12px }}
.hb-table th {{ font-weight:500; text-align:left; border-bottom:1px solid #E2E8F0; padding:4px 6px }}
.hb-table td {{ border-bottom:1px solid var(--hb-cell-border); padding:4px 6px }}
.hb-table .num {{ text-align:right }}
.hb-table .ord {{ width:28px; color:var(--hb-axis) }}
.hb-table tfoot td {{ font-weight:600; border-top:1px solid #E2E8F0; border-bottom:0 }}
.wf-banner {{ border:1px solid #E2E8F0; background:#fff; padding:10px 14px; text-align:center;
  font-weight:500; letter-spacing:.02em; margin-bottom:16px; font-size:12px }}
.wf-overlay {{ border-left:3px solid #6567ea; background:#fff; padding:10px 16px;
  margin-bottom:16px; font-size:13px; line-height:1.4 }}
.wf-n {{ position:absolute; top:-1px; left:-1px; width:18px; height:18px; background:#6567ea;
  color:#fff; border-radius:8px 0 8px 0; font-size:10px; font-weight:500;
  display:flex; align-items:center; justify-content:center }}
</style></head><body>
<div class="wf-banner">DADOS ILUSTRATIVOS &mdash; A FORMA REFLETE A PROPOSTA, OS VALORES N&Atilde;O REFLETEM O CLIENTE</div>
<div class="wf-overlay">Esta tela existe para o comercial decidir, toda segunda, de qual canal puxar
meta quando o m&ecirc;s abre abaixo do ritmo, e n&atilde;o para reportar o fechado.</div>

<!-- A DATA é filtro como os outros e vem SEMPRE primeiro (chassi-horus-bi.md §8) -->
<div class="hb-filters">
  <div class="hb-filter"><b>Per&iacute;odo</b>&#128197; Abril 1, 2026 - Abril 30, 2026 &#9662;</div>
  <div class="hb-filter"><b>Filial</b>Todas selecionadas</div>
  <div class="hb-filter"><b>Vendedor</b>Todos selecionados</div>
</div>

<div class="hb-grid">
  {kpi("Receita L&iacute;quida &mdash; m&ecirc;s", P_RECEITA, "&#9650; vs m&ecirc;s anterior", "&#128181;")}
  {kpi("Ticket M&eacute;dio &mdash; m&ecirc;s", P_TICKET, "&#9660; vs m&ecirc;s anterior", "&#127991;")}
  {kpi("Clientes Atendidos", P_QTDE, "&#9650; vs m&ecirc;s anterior", "&#128101;")}
  {kpi("Margem Bruta", "12,3%", "a confirmar na virada", "&#128200;")}
  {card(5, "Faturamento por Per&iacute;odo", svgs["mag"], 6, 8)}
  {card(6, "Receita L&iacute;quida por Loja", svgs["rank"], 6, 8)}
  {card(7, "Participa&ccedil;&atilde;o por Canal", svgs["donut"], 4, 8)}
  {card(8, "Realizado vs Meta", svgs["tend"], 8, 8)}
  <div class="hb-card" style="grid-column:span 12; grid-row:span 8"><span class="wf-n">9</span>
    <h3>Detalhamento por Fornecedor</h3>
    <div class="hb-actions">&#128269; &#8599;</div>
    <div class="hb-body" style="align-items:flex-start">{tabela}</div>
  </div>
</div></body></html>"""

SAIDA = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mockup.html")
open(SAIDA, "w").write(HTML)
print("\n", SAIDA, len(HTML), "bytes")
