"""carimbo: uma palavra que cai carimbada na tela, torta, com a moldura batendo junto.

O carimbo vem de cima grande e desfocado, bate (a tela treme de leve), e fica. Serve para fechar uma
frase com uma palavra só: APROVADO, ESGOTADO, TEM UM CUSTO. Pode ter uma linha menor embaixo.

Tempo: `delay` até a batida; a partir daí fica parado até o fim do item.
"""

from __future__ import annotations

import json
from functools import lru_cache

from motion import engine as E

TEMPLATE = {
    "id": "carimbo",
    "label_pt": "Carimbo",
    "description_pt": "Uma palavra cai carimbada na tela, torta e com moldura, e a tela treme junto. "
                      "Pode ter uma linha menor embaixo.",
    "use_when_pt": "Para fechar uma frase com uma palavra só, no instante em que ela é falada: "
                   "APROVADO, ESGOTADO, TEM UM CUSTO, VAGAS ENCERRADAS.",
    "notes_pt": "Palavra curta lê melhor (até 22 letras). Não use como legenda: é um golpe só, na palavra "
                "que importa.",
    "category": "enfase",
    "version": 1,
    "params": {
        "texto": {"type": "text", "default": "APROVADO", "max_len": 22, "required_text": True,
                  "label_pt": "A palavra do carimbo (até 22 letras)"},
        "subtexto": {"type": "text", "default": "", "max_len": 28,
                     "label_pt": "Linha menor embaixo, dentro da moldura (opcional)"},
        "delay": {"type": "number", "default": 0.12, "min": 0, "max": 30,
                  "label_pt": "Segundos até a batida do carimbo"},
        "angulo": {"type": "number", "default": -7.0, "min": -20, "max": 20,
                   "label_pt": "Inclinação do carimbo em graus (negativo inclina para a esquerda)"},
        "estilo": {"type": "choice", "choices": ["moldura", "cheio"], "default": "moldura",
                   "label_pt": "Moldura vazada (padrão) ou retângulo pintado na cor de destaque"},
        "tremer": {"type": "bool", "default": True,
                   "label_pt": "A moldura treme quando o carimbo bate"},
    },
    "default_duration": 2.0,
    "default_region": "centro",
    "sfx": [{"at": 0.12, "name": "bass_drop", "volume": 0.9}],
    "example": {"texto": "TEM UM CUSTO", "subtexto": "servidor, IA, suporte", "angulo": -5},
}

FONT = "sans_black"
SUB_FONT = "text_bold"
MAX_SIZE = 150.0
MIN_SIZE = 44.0
QUEDA = 0.20           # segundos da queda até bater
TREMOR = 0.22          # segundos de tremida depois da batida
ESCALA_INICIAL = 2.6


@lru_cache(maxsize=64)
def _layout(params_json: str, box: E.Box) -> dict:
    p = json.loads(params_json)
    texto = p["texto"].strip()
    sub = p["subtexto"].strip()
    pad_x, pad_y = 46.0, 30.0
    disp = max(220.0, box.w * 0.86) - 2 * pad_x
    size = min(MAX_SIZE, E.fit_text(texto.upper(), FONT, disp, MAX_SIZE, MIN_SIZE))
    cap = E.measure("H", FONT, size).cap
    largura = E.measure(texto.upper(), FONT, size).width
    sub_size = sub_cap = 0.0
    sub_w = 0.0
    if sub:
        sub_size = min(size * 0.34, E.fit_text(sub, SUB_FONT, disp, size * 0.34, 20.0))
        sub_cap = E.measure("H", SUB_FONT, sub_size).cap
        sub_w = E.measure(sub, SUB_FONT, sub_size).width
    conteudo_h = cap + (sub_cap + size * 0.28 if sub else 0.0)
    w = min(box.w - 8.0, max(largura, sub_w) + 2 * pad_x)
    h = conteudo_h + 2 * pad_y
    return {"size": size, "cap": cap, "w": w, "h": h, "pad_y": pad_y,
            "sub_size": sub_size, "sub_cap": sub_cap, "texto": texto.upper(), "sub": sub,
            "borda": max(7.0, size * 0.085), "raio": max(14.0, size * 0.16)}


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)
    bate = float(params["delay"])
    if t < bate - QUEDA:
        return cv
    queda = E.clamp01((t - (bate - QUEDA)) / QUEDA)          # 0 no alto, 1 na batida
    escala = ESCALA_INICIAL + (1.0 - ESCALA_INICIAL) * (queda ** 2.2)   # desaba no fim (in_quart)
    opac = min(1.0, queda * 1.8)
    tremida = 0.0
    if params["tremer"] and 0 <= t - bate < TREMOR:
        f = E.clamp01((t - bate) / TREMOR)
        tremida = (1.0 - f) * 5.0 * (1 if int((t - bate) * fps) % 2 == 0 else -1)
    x = box.cx - L["w"] / 2.0
    y = box.cy - L["h"] / 2.0
    cheio = params["estilo"] == "cheio"
    with cv.group(opacity=opac, scale=escala, pivot=(box.cx, box.cy),
                  rotate=float(params["angulo"]) + tremida * 0.25, dx=tremida):
        if cheio:
            cv.rect(x, y, L["w"], L["h"], radius=L["raio"], fill="accent",
                    shadow=E.Shadow(blur=18.0, dy=8.0, opacity=0.45))
        else:
            if box.theme.style != "vidro":     # papel, pop, neon: the stamp lands on a card, not on the video
                cv.card(x, y, L["w"], L["h"], radius=L["raio"], border=False)
            cv.rect(x, y, L["w"], L["h"], radius=L["raio"], fill=None, stroke="accent",
                    stroke_width=L["borda"], shadow=E.Shadow(blur=14.0, dy=6.0, opacity=0.4))
        cor = "on_accent" if cheio else "accent"
        base = y + L["pad_y"] + L["cap"]
        cv.text(box.cx, base, L["texto"], FONT, L["size"], color=cor, anchor="ms",
                stroke=0.0, shadow=E.Shadow(blur=10.0, dy=4.0, opacity=0.35))
        if L["sub"]:
            cv.text(box.cx, base + L["size"] * 0.28 + L["sub_cap"], L["sub"], SUB_FONT, L["sub_size"],
                    color=cor, opacity=0.9, anchor="ms")
    return cv


def sfx(params: dict, fps: int, frames: int) -> list[dict]:
    return [{"frame": E.frame_of(float(params["delay"]), fps), "name": "bass_drop", "volume": 0.9,
             "why": "batida do carimbo"}]


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


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


def ink(params: dict, box: E.Box) -> tuple[float, float, float, float]:
    L = _layout(json.dumps(params, sort_keys=True), box)
    return (box.cx - L["w"] / 2.0, box.cy - L["h"] / 2.0, box.cx + L["w"] / 2.0, box.cy + L["h"] / 2.0)
