"""selo_garantia: o selo redondo de garantia, com serrilha, fita e o prazo em dias.

O selo entra girando e crescendo, a fita cai atrás e o número do prazo dá um pulinho. Serve para a
parte da garantia: "são 7 dias", "30 dias para testar", "garantia de 1 ano".

Tempo: `delay` até o selo entrar; o número pulsa logo depois.
"""

from __future__ import annotations

import json
import math
from functools import lru_cache

from motion import engine as E

TEMPLATE = {
    "id": "selo_garantia",
    "label_pt": "Selo de garantia",
    "description_pt": "Selo redondo com serrilha e fita, com o prazo em dias no meio e uma linha embaixo. "
                      "Entra girando e o número dá um pulinho.",
    "use_when_pt": "Quando a fala fala de garantia, prazo para testar ou devolução: 7 dias, 30 dias, "
                   "risco zero.",
    "notes_pt": "O número é o que aparece grande: deixe a palavra em `unidade` curta (dias, meses).",
    "category": "enfase",
    "version": 1,
    "params": {
        "numero": {"type": "text", "default": "7", "max_len": 4, "required_text": True,
                   "label_pt": "O número do prazo (até 4 caracteres)"},
        "unidade": {"type": "text", "default": "dias", "max_len": 10,
                    "label_pt": "A unidade embaixo do número (dias, meses...)"},
        "titulo": {"type": "text", "default": "GARANTIA", "max_len": 16,
                   "label_pt": "Palavra em cima do número, dentro do selo"},
        "legenda": {"type": "text", "default": "", "max_len": 34,
                    "label_pt": "Linha embaixo do selo (opcional). *Palavra* entre asteriscos fica na cor de destaque"},
        "delay": {"type": "number", "default": 0.15, "min": 0, "max": 30,
                  "label_pt": "Segundos até o selo entrar"},
        "fita": {"type": "bool", "default": True, "label_pt": "Fitas atrás do selo"},
    },
    "default_duration": 2.4,
    "default_region": "centro",
    "sfx": [{"at": 0.15, "name": "pop"}, {"at": 0.55, "name": "success", "volume": 0.8}],
    "example": {"numero": "30", "unidade": "dias", "titulo": "GARANTIA", "legenda": "para testar *sem risco*"},
}

NUM_FONT = "sans_black"
TXT_FONT = "text_bold"
ENTRA = 0.42           # entrada do selo (gira e cresce)
PULO = 0.30            # pulinho do número
PONTAS = 18            # dentes da serrilha
PRE = 0.16             # o selo começa a crescer um pouco antes, para o quadro 0 nunca sair vazio


@lru_cache(maxsize=64)
def _layout(params_json: str, box: E.Box) -> dict:
    p = json.loads(params_json)
    legenda = p["legenda"].strip()
    plain = " ".join(w for w, _ in E.parse_accent(legenda)) if legenda else ""
    leg_size = leg_cap = 0.0
    if legenda:
        leg_size = E.fit_text(plain, TXT_FONT, box.w * 0.9, 52.0, 22.0)
        leg_cap = E.measure("H", TXT_FONT, leg_size).cap
    espaco = box.h - (leg_cap * 2.2 if legenda else 0.0)
    r = max(90.0, min(box.w * 0.42, espaco * 0.42))
    num_size = E.fit_text(p["numero"], NUM_FONT, r * 1.25, r * 1.12, 40.0)
    uni_size = min(r * 0.30, E.fit_text(p["unidade"], TXT_FONT, r * 1.3, r * 0.30, 16.0))
    tit_size = min(r * 0.24, E.fit_text(p["titulo"], TXT_FONT, r * 1.3, r * 0.24, 14.0))
    cy = box.cy - (leg_cap * 1.1 if legenda else 0.0)
    return {"r": r, "cx": box.cx, "cy": cy, "num_size": num_size, "uni_size": uni_size,
            "tit_size": tit_size, "leg_size": leg_size, "leg_cap": leg_cap, "legenda": legenda,
            "num_cap": E.measure("H", NUM_FONT, num_size).cap}


def _serrilha(cx: float, cy: float, r: float) -> list[tuple[float, float]]:
    """Contorno do selo: dentes suaves em volta do círculo."""
    pts = []
    passos = PONTAS * 6
    for i in range(passos):
        a = 2 * math.pi * i / passos
        raio = r * (1.0 + 0.055 * math.cos(PONTAS * a))
        pts.append((cx + raio * math.cos(a), cy + raio * math.sin(a)))
    return pts


def render(frame_index: int, fps: int, params: dict, box: E.Box) -> E.Canvas:
    cv = E.Canvas(box)
    t = frame_index / fps + 1.0 / fps
    L = _layout(json.dumps(params, sort_keys=True), box)
    inicio = max(0.0, float(params["delay"]) - PRE)
    if t < inicio:
        return cv
    ent = E.eased(t, inicio, ENTRA + PRE, "out_back")
    if ent <= 0:
        return cv
    opac = min(1.0, E.eased(t, inicio, (ENTRA + PRE) * 0.5, "out_quad") * 1.4)
    giro = -26.0 * (1.0 - E.clamp01(ent))
    cx, cy, r = L["cx"], L["cy"], L["r"]
    with cv.group(opacity=opac, scale=0.35 + 0.65 * min(1.15, ent), pivot=(cx, cy), rotate=giro):
        if params["fita"]:
            larg = r * 0.42
            for lado in (-1, 1):
                x0 = cx + lado * r * 0.36
                pts = [(x0 - larg / 2, cy + r * 0.2), (x0 + larg / 2, cy + r * 0.2),
                       (x0 + larg / 2 + lado * r * 0.14, cy + r * 1.25),
                       (x0 + lado * r * 0.07, cy + r * 1.0),
                       (x0 - larg / 2 + lado * r * 0.14, cy + r * 1.25)]
                cv.polygon(pts, fill="danger", shadow=E.Shadow(blur=12.0, dy=6.0, opacity=0.35))
        cv.polygon(_serrilha(cx, cy, r), fill="accent",
                   shadow=E.Shadow(blur=16.0, dy=8.0, opacity=0.4))
        cv.ring(cx, cy, r * 0.86, max(3.0, r * 0.035), color="on_accent", opacity=0.55)
        cv.text(cx, cy - r * 0.34, params["titulo"], TXT_FONT, L["tit_size"], color="on_accent",
                anchor="mc", opacity=0.9, upper=True)
        bate = inicio + (ENTRA + PRE) * 0.7
        pulo = 1.0 + 0.10 * (1.0 - E.clamp01((t - bate) / PULO)) if bate <= t < bate + PULO else 1.0
        with cv.group(scale=pulo, pivot=(cx, cy + r * 0.05)):
            cv.text(cx, cy + L["num_cap"] * 0.5 + r * 0.02, params["numero"], NUM_FONT, L["num_size"],
                    color="on_accent", anchor="ms")
        cv.text(cx, cy + r * 0.56, params["unidade"], TXT_FONT, L["uni_size"], color="on_accent",
                anchor="mc", opacity=0.95)
    if L["legenda"]:
        la = E.eased(t, inicio + (ENTRA + PRE) * 0.6, 0.3, "out_cubic")
        if la > 0:
            y = cy + r * 1.35 + L["leg_cap"]
            palavras = E.parse_accent(L["legenda"])
            larguras = [E.measure(w, TXT_FONT, L["leg_size"]).width for w, _ in palavras]
            espaco = (E.measure("a a", TXT_FONT, L["leg_size"]).width
                      - E.measure("aa", TXT_FONT, L["leg_size"]).width)
            total = sum(larguras) + espaco * max(0, len(palavras) - 1)
            x = cx - total / 2.0
            for (palavra, destaque), larg in zip(palavras, larguras):
                cor = "accent" if destaque else "text"
                cv.text(x, y + (1.0 - la) * 12.0, palavra, TXT_FONT, L["leg_size"], color=cor,
                        opacity=la, anchor="ls", stroke=3.0, stroke_color=(0, 0, 0, 140))
                x += larg + espaco
    return cv


def sfx(params: dict, fps: int, frames: int) -> list[dict]:
    d = float(params["delay"])
    return [{"frame": E.frame_of(d, fps), "name": "pop", "why": "entrada do selo"},
            {"frame": E.frame_of(d + ENTRA * 0.7, fps), "name": "success", "volume": 0.8,
             "why": "o prazo aparece"}]


def settle(params: dict, fps: int) -> float:
    return float(params["delay"]) + ENTRA + PULO + 0.05


def suggested_duration(params: dict) -> float:
    return round(max(TEMPLATE["default_duration"], float(params["delay"]) + ENTRA + PULO + 1.2), 2)
