"""Regenera o wireframe canônico do bundle de escopo, pela lumo-mockup.

Fonte: llms/lumo-scope/references/exemplo-canonico/escopo/dashboards/vendas-executivo.md
Saída: .../escopo/wireframes/vendas-executivo.html
"""
import json
import math
import os
import re
import sys

import vl_convert as vlc

# Caminhos relativos a este arquivo: o gabarito precisa rodar de qualquer clone.
AQUI = os.path.dirname(os.path.abspath(__file__))
SKILL = os.path.abspath(os.path.join(AQUI, "..", "..", "..", "..", "..", "lumo-mockup"))
OUT = os.path.join(AQUI, "vendas-executivo.html")
sys.path.insert(0, os.path.join(SKILL, "scripts"))
from placeholder import TODOS, do_formato, escala_de, placeholder  # noqa: E402

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

# encoding.formato da ficha -> placeholder (widgets 2,4,5 pedem "R$ mil")
E_REC = escala_de("faturamento mensal")
P_MIL = do_formato("R$ mil", E_REC)          # R$ 123,5 mil
P_TICKET = do_formato("R$", escala_de("ticket medio"))


def dim(col, row, com_titulo=True):
    util = 1440 - 40
    lc = (util - 11 * 4) / 12
    w = col * lc + (col - 1) * 4 - 32
    h = row * 40 + (row - 1) * 4 - 32 - (30 if com_titulo else 0)
    return int(w), int(h)


def encaixa(spec, alvo, n=4):
    for _ in range(n):
        svg = vlc.vegalite_to_svg(spec, config=theme, vl_version=VL)
        real = int(re.search(r'<svg[^>]*height="(\d+)"', svg).group(1))
        if real <= alvo:
            return spec
        spec = {**spec, "height": spec["height"] - (real - alvo)}
    return spec


def render(spec, nome, cats=()):
    svg = vlc.vegalite_to_svg(spec, config=theme, vl_version=VL)
    ok = set(TODOS) | set(cats)
    vazou = [t for t in re.findall(r"<text[^>]*>([^<]*)</text>", svg)
             if re.search(r"\d", t) and t.strip() not in ok]
    print(f"  [{'ok ' if not vazou else 'VAZOU'}] {nome:<28}" + (f" -> {vazou}" if vazou else ""))
    assert not vazou, f"{nome}: {vazou}"
    return svg


def pareto(n, d=0.62):
    return [round(d ** i, 4) for i in range(n)]


def plato(n=10, c=1.0, s=0.15, d=0.72):
    return [round(c - (c - s) * d ** i, 4) for i in range(n)]


print("=== widgets da ficha ===")

# widget 2 — tendencia-inline: sparkline dentro do card do KPI
w2, h2 = dim(4, 3, com_titulo=False)
spark = render({
    "data": {"values": [{"i": i, "v": v} for i, v in enumerate(plato())]},
    "width": 150, "height": 30,
    "mark": {"type": "line", "point": False, "strokeWidth": 2},
    "encoding": {"x": {"field": "i", "type": "ordinal", "axis": None},
                 "y": {"field": "v", "type": "quantitative", "axis": None}},
}, "2 tendencia-inline/Kpi")

# widget 4 — ranking por vendedor. Nome de vendedor identifica pessoa fisica,
# entao vai placeholder semantico (forma-sem-magnitude §4).
vend = [f"Vendedor {c}" for c in "ABCDE"]
w4, h4 = dim(12, 6)
rank = render(encaixa({
    "data": {"values": [{"c": c, "v": v} for c, v in zip(vend, pareto(5))]},
    "width": w4, "height": h4,
    "layer": [
        {"mark": {"type": "bar", "height": 20},
         "encoding": {
             "y": {"field": "c", "type": "nominal", "sort": "-x", "axis": {"title": None}},
             "x": {"field": "v", "type": "quantitative",
                   "axis": {"title": None, "labels": False},
                   "scale": {"domainMax": 1.5}}}},
        {"mark": {"type": "text", "dx": 5, "align": "left", "fontSize": 10, "color": "#000"},
         "encoding": {"y": {"field": "c", "type": "nominal", "sort": "-x"},
                      "x": {"field": "v", "type": "quantitative", "scale": {"domainMax": 1.5}},
                      "text": {"value": P_MIL}}},
    ],
}, h4), "4 ranking/Barline")

# widget 5 — parte-todo com 5 lojas. A regra manda Barline a partir de 5, e a
# ficha pede legenda: barra empilhada 100%, nao donut. Loja e canal publico,
# entao nome real (forma-sem-magnitude §4).
lojas = ["Loja Sul", "Loja Centro", "Loja Norte", "Loja Leste", "Loja Oeste"]
vals = pareto(5)
tot = sum(vals)
acc, dados = 0.0, []
for l, v in zip(lojas, vals):
    dados.append({"l": l, "x0": acc / tot, "x1": (acc + v) / tot, "meio": (acc + v / 2) / tot})
    acc += v
w5, h5 = dim(12, 6)
stack = render(encaixa({
    "data": {"values": dados},
    "width": w5, "height": h5,
    "layer": [
        {"mark": {"type": "bar", "height": 46},
         "encoding": {
             "x": {"field": "x0", "type": "quantitative",
                   "axis": {"title": None, "labels": False}, "scale": {"domain": [0, 1]}},
             "x2": {"field": "x1"},
             "color": {"field": "l", "type": "nominal", "sort": lojas,
                       "legend": {"orient": "bottom", "columns": 3, "title": None}}}},
        {"mark": {"type": "text", "fontSize": 10, "color": "#fff"},
         "encoding": {"x": {"field": "meio", "type": "quantitative", "scale": {"domain": [0, 1]}},
                      "text": {"value": P_MIL}}},
    ],
}, h5), "5 parte-todo/Barline empilhado", cats=lojas)

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


kpi2 = f'''<div class="hb-kpi"><div class="hb-kpi__icon">&#128181;</div><div>
    <div class="hb-kpi__over">Receita L&iacute;quida &mdash; m&ecirc;s corrente</div>
    <div class="hb-kpi__val">{P_MIL}</div>
    <div class="hb-kpi__spark">{spark}</div>
  </div></div>'''

kpi3 = f'''<div class="hb-kpi"><div class="hb-kpi__icon">&#127991;</div><div>
    <div class="hb-kpi__over">Ticket M&eacute;dio &mdash; m&ecirc;s corrente</div>
    <div class="hb-kpi__val">{P_TICKET}</div>
    <div class="hb-kpi__sub">&#9660; vs m&ecirc;s anterior</div>
  </div></div>'''

filtros = '''<div class="hb-filters">
    <div class="hb-filter"><b>Per&iacute;odo</b>&#128197; Abril 2026 &#9662;</div>
    <div class="hb-filter"><b>Loja</b>Todas selecionadas</div>
  </div>'''

HTML = f"""<!doctype html><html lang="pt-BR"><head><meta charset="utf-8">
<title>Mockup &mdash; Vendas Executivo (Vale Sul)</title><style>
/* Poppins e a fonte do produto. Aqui ela vem por nome, com fallback de sistema:
   este arquivo e gabarito de leitura no repositorio, nao a peca que vai ao
   cliente. No render de verdade a skill embarca o .ttf (assets/fonts). */
:root {{ --hb-page:#F8FAFC; --hb-card:#fff; --hb-card-border:rgba(241,245,249,.5);
  --hb-title:#334155; --hb-action:#CBD5E1; --hb-axis:#949DAC; }}
* {{ 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 }}
.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-kpi {{ display:grid; grid-template-columns:auto 1fr; align-items:center; gap:12px; width:100% }}
.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-kpi__spark {{ margin-top:2px; opacity:.9 }}
.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; z-index:2 }}
.wf-conf {{ position:absolute; bottom:8px; left:16px; font-size:10px; color:var(--hb-axis) }}
.wf-layout {{ display:grid; grid-template-columns:1fr 280px; gap:20px; align-items:start }}
.wf-annot {{ border-left:1px solid #E2E8F0; padding-left:16px; font-size:12px }}
.wf-annot li {{ margin-bottom:10px }}
</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 Marcos decidir, na segunda de manh&atilde;, de qual loja
remanejar meta quando o m&ecirc;s abre concentrado &mdash; e n&atilde;o para reportar o faturamento fechado.</div>
<div class="wf-layout">
 <div>
  <div class="hb-grid">
    {card(1, "", filtros, 4, 3, "align-items:center")}
    {card(2, "", kpi2, 4, 3)}
    {card(3, "", kpi3, 4, 3)}
    {card(4, "Ranking de Receita L&iacute;quida por Vendedor &mdash; m&ecirc;s corrente", rank, 12, 6)}
    {card(5, "Receita L&iacute;quida por Loja &mdash; m&ecirc;s corrente", stack, 12, 6)}
  </div>
 </div>
 <aside class="wf-annot"><h3>Anota&ccedil;&otilde;es</h3><ol>
   <li><b>Filtros:</b> per&iacute;odo primeiro, como todo filtro do produto; loja em seguida.</li>
   <li><b>Receita L&iacute;quida:</b> tend&ecirc;ncia di&aacute;ria embutida como sparkline no pr&oacute;prio card, em vez de widget separado.</li>
   <li><b>Ticket M&eacute;dio:</b> m&eacute;trica de raz&atilde;o &mdash; recomposta dos componentes, nunca somada nem mediada.</li>
   <li><b>Ranking por vendedor:</b> nome de vendedor identifica pessoa f&iacute;sica, ent&atilde;o vai placeholder sem&acirc;ntico. Crit&eacute;rio de corte do top-N ainda em valida&ccedil;&atilde;o.</li>
   <li><b>Receita por Loja:</b> 5 lojas estouram o limite de 4 fatias do donut &mdash; reconduzido para barra empilhada, com nome real de loja (canal p&uacute;blico).</li>
 </ol></aside>
</div>
</body></html>"""

open(OUT, "w").write(HTML)
print(f"\n{OUT}\n{len(HTML)} bytes")
