"""
Construtor de elementos Excalidraw com sistema de design Imperatriz aplicado.

Formato Excalidraw v2 (referencia oficial):
- Cena: {type, version, source, elements, appState, files}
- Elemento base: {id, type, x, y, width, height, angle, strokeColor, backgroundColor,
                  fillStyle, strokeWidth, strokeStyle, roughness, opacity, groupIds,
                  frameId, roundness, seed, version, versionNonce, isDeleted,
                  boundElements, updated, link, locked}

Tipos de elemento suportados:
- rectangle, diamond, ellipse (shapes)
- text (texto livre ou bound a shape)
- arrow, line (conectores)
- frame, magicframe (containers nomeados)
- freedraw (desenho a mao livre)
"""

import json
import random
import string
import time
from pathlib import Path

from helpers.paleta import (
    GAP_BLOCOS,
    GRID,
    PADDING_INTERNO,
    FONT_HELVETICA,
    FONT_VIRGIL,
    FONT_CASCADIA,
    ROUGHNESS,
    TAMANHOS,
    cor_sombra,
    get_paleta,
)


def gerar_id(n=16):
    """Gera id alfanumerico estilo Excalidraw."""
    alfabeto = string.ascii_letters + string.digits + "-_"
    return "".join(random.choices(alfabeto, k=n))


def gerar_seed():
    """Seed pra randomizar 'desenho a mao' (mantem consistencia visual)."""
    return random.randint(1, 2**31 - 1)


def snap(valor, grid=GRID):
    """Snapa valor ao grid (multiplo mais proximo). Garante alinhamento premium."""
    return round(valor / grid) * grid


def _base_element(tipo, x, y, w, h, paleta, **overrides):
    """Esqueleto comum a todos os elementos. Auto-snap a grid de 20px."""
    el = {
        "id": gerar_id(),
        "type": tipo,
        "x": snap(x),
        "y": snap(y),
        "width": snap(w) if w >= 20 else w,
        "height": snap(h) if h >= 20 else h,
        "angle": 0,
        "strokeColor": paleta["traco"],
        "backgroundColor": "transparent",
        "fillStyle": "solid",
        "strokeWidth": 2,
        "strokeStyle": "solid",
        "roughness": ROUGHNESS["architect"],
        "opacity": 100,
        "groupIds": [],
        "frameId": None,
        "roundness": None,
        "seed": gerar_seed(),
        "version": 1,
        "versionNonce": gerar_seed(),
        "isDeleted": False,
        "boundElements": [],
        "updated": int(time.time() * 1000),
        "link": None,
        "locked": False,
    }
    el.update(overrides)
    return el


def retangulo(x, y, w, h, paleta, fill=None, borda=None, arredondado=True, **kw):
    """Card retangular premium. fill=None => transparente."""
    el = _base_element("rectangle", x, y, w, h, paleta, **kw)
    if fill:
        el["backgroundColor"] = fill
    if borda:
        el["strokeColor"] = borda
    if arredondado:
        el["roundness"] = {"type": 3}
    return el


def losango(x, y, w, h, paleta, fill=None, **kw):
    el = _base_element("diamond", x, y, w, h, paleta, **kw)
    if fill:
        el["backgroundColor"] = fill
    el["roundness"] = {"type": 2}
    return el


def elipse(x, y, w, h, paleta, fill=None, **kw):
    el = _base_element("ellipse", x, y, w, h, paleta, **kw)
    if fill:
        el["backgroundColor"] = fill
    el["roundness"] = {"type": 2}
    return el


def texto(x, y, conteudo, paleta, tamanho=18, fonte=FONT_HELVETICA,
          alinhamento="left", vertical="top", cor=None, container_id=None,
          largura=None, **kw):
    """
    Texto livre ou ancorado a um container (containerId).
    Quando container_id e dado, o texto vira label DO container e move junto.
    """
    if cor is None:
        cor = paleta["traco"]
    linhas = conteudo.split("\n")
    line_height = 1.25
    # Estimativa visual de largura/altura (Excalidraw recalcula no carregamento)
    char_w = tamanho * 0.6
    if largura is None:
        largura = max(len(l) for l in linhas) * char_w + 4
    altura = tamanho * line_height * len(linhas) + 4

    el = _base_element("text", x, y, int(largura), int(altura), paleta, **kw)
    el.update({
        "text": conteudo,
        "fontSize": tamanho,
        "fontFamily": fonte,
        "textAlign": alinhamento,
        "verticalAlign": vertical,
        "baseline": int(tamanho * 0.9),
        "containerId": container_id,
        "originalText": conteudo,
        "lineHeight": line_height,
        "autoResize": True,
        "strokeColor": cor,
    })
    return el


def seta(x1, y1, x2, y2, paleta, curvada=False, elbow=False,
         start_binding=None, end_binding=None, espessura=2, cor=None, **kw):
    """
    Conector entre 2 pontos. start/end_binding={'elementId': id, 'focus': 0, 'gap': 4}
    'elbow=True' => seta em angulo reto (premium B2B).
    """
    if cor is None:
        cor = paleta["neutro_medio"]
    w = abs(x2 - x1)
    h = abs(y2 - y1)
    el = _base_element("arrow", x1, y1, w, h, paleta, **kw)
    pontos = [[0, 0], [x2 - x1, y2 - y1]]
    el.update({
        "points": pontos,
        "lastCommittedPoint": None,
        "startBinding": start_binding,
        "endBinding": end_binding,
        "startArrowhead": None,
        "endArrowhead": "arrow",
        "elbowed": elbow,
        "strokeColor": cor,
        "strokeWidth": espessura,
    })
    if curvada:
        # Curva via ponto de controle no meio
        mx, my = (x2 - x1) / 2, (y2 - y1) / 2 - 30
        el["points"] = [[0, 0], [mx, my], [x2 - x1, y2 - y1]]
    return el


def linha(x1, y1, x2, y2, paleta, tracejada=False, espessura=1, cor=None, **kw):
    if cor is None:
        cor = paleta["neutro_medio"]
    el = _base_element("line", x1, y1, abs(x2 - x1), abs(y2 - y1), paleta, **kw)
    el.update({
        "points": [[0, 0], [x2 - x1, y2 - y1]],
        "lastCommittedPoint": None,
        "startBinding": None,
        "endBinding": None,
        "startArrowhead": None,
        "endArrowhead": None,
        "strokeColor": cor,
        "strokeWidth": espessura,
        "strokeStyle": "dashed" if tracejada else "solid",
    })
    return el


def frame(x, y, w, h, nome, paleta, **kw):
    """Container nomeado (vira 'capitulo' do diagrama, exportavel separado)."""
    el = _base_element("frame", x, y, w, h, paleta, **kw)
    el.update({
        "name": nome,
        "strokeColor": paleta["neutro_medio"],
        "strokeStyle": "dashed",
    })
    return el


def envelopa_em_frame(elementos, nome, paleta, margem=80):
    """
    Calcula bounding box dos elementos e envelopa todos num Frame nomeado.
    Atribui frameId em cada elemento (Excalidraw os trata como grupo).
    Retorna lista com [frame, ...elementos].
    """
    if not elementos:
        return [frame(0, 0, 800, 600, nome, paleta)]

    xs, ys, x2s, y2s = [], [], [], []
    for el in elementos:
        if "x" not in el or "y" not in el:
            continue
        # Frames nao tem largura util pro bbox
        if el.get("type") == "frame":
            continue
        xs.append(el["x"])
        ys.append(el["y"])
        x2s.append(el["x"] + el.get("width", 0))
        y2s.append(el["y"] + el.get("height", 0))

    if not xs:
        return [frame(0, 0, 800, 600, nome, paleta)] + elementos

    fx = snap(min(xs) - margem)
    fy = snap(min(ys) - margem)
    fw = snap(max(x2s) - fx + margem)
    fh = snap(max(y2s) - fy + margem)

    frame_el = frame(fx, fy, fw, fh, nome, paleta)
    for el in elementos:
        if el.get("type") != "frame":
            el["frameId"] = frame_el["id"]

    return [frame_el] + elementos


# ============================================================
# COMPOSITES — blocos premium prontos
# ============================================================

def card_premium(x, y, w, h, paleta, titulo, fill=None, sombra=True,
                 borda_dupla=False, tamanho_titulo=None):
    """
    Card premium completo: shape + sombra falsa + label centralizada.
    Retorna lista de elementos (sombra atras, shape, [borda interna], texto).
    """
    elementos = []
    grupo = gerar_id()

    # Sombra falsa (6px direita+baixo, cinza translucido)
    if sombra:
        sombra_el = retangulo(
            x + 6, y + 6, w, h, paleta,
            fill=cor_sombra(paleta["traco"], 15),
            arredondado=True,
        )
        sombra_el["strokeColor"] = "transparent"
        sombra_el["groupIds"] = [grupo]
        elementos.append(sombra_el)

    # Shape principal
    if fill is None:
        fill = paleta["background"]
    card = retangulo(x, y, w, h, paleta, fill=fill, arredondado=True)
    card["groupIds"] = [grupo]
    elementos.append(card)

    # Borda dupla (retangulo interno 8px margem)
    if borda_dupla:
        interno = retangulo(
            x + 8, y + 8, w - 16, h - 16, paleta,
            fill="transparent",
            arredondado=True,
        )
        interno["strokeWidth"] = 1
        interno["groupIds"] = [grupo]
        elementos.append(interno)

    # Texto label centralizado no card
    if tamanho_titulo is None:
        tamanho_titulo = TAMANHOS["label_bloco"]
    txt = texto(
        x + PADDING_INTERNO, y + PADDING_INTERNO,
        titulo, paleta,
        tamanho=tamanho_titulo,
        fonte=FONT_HELVETICA,
        alinhamento="center",
        vertical="middle",
        container_id=card["id"],
        largura=w - 2 * PADDING_INTERNO,
    )
    txt["x"] = x
    txt["y"] = y
    txt["width"] = w
    txt["height"] = h
    txt["groupIds"] = [grupo]
    card["boundElements"] = [{"type": "text", "id": txt["id"]}]
    elementos.append(txt)

    return elementos


def highlighter(x, y, w, h, paleta, cor_destaque=None):
    """Marca-texto translucido amarelo (ou cor accent) — fundo de palavra-chave."""
    if cor_destaque is None:
        cor_destaque = paleta["accent"]
    el = retangulo(x, y, w, h, paleta, fill=cor_sombra(cor_destaque, 40))
    el["strokeColor"] = "transparent"
    el["roughness"] = ROUGHNESS["artist"]  # quebra a regularidade, parece marca-texto
    return el


def post_it(x, y, w, h, paleta, conteudo, rotacao=4):
    """Sticky note amarelo com rotacao leve + sombra (look 3D)."""
    elementos = []
    grupo = gerar_id()

    sombra_el = retangulo(
        x + 8, y + 8, w, h, paleta,
        fill=cor_sombra(paleta["traco"], 20),
    )
    sombra_el["strokeColor"] = "transparent"
    sombra_el["angle"] = rotacao * 0.0174533
    sombra_el["groupIds"] = [grupo]
    elementos.append(sombra_el)

    note = retangulo(x, y, w, h, paleta, fill="#FFE082", arredondado=False)
    note["angle"] = rotacao * 0.0174533
    note["strokeColor"] = cor_sombra("#FFE082", 80)
    note["groupIds"] = [grupo]
    elementos.append(note)

    txt = texto(
        x, y, conteudo, paleta,
        tamanho=TAMANHOS["annotation"],
        fonte=FONT_VIRGIL,
        alinhamento="center",
        vertical="middle",
        container_id=note["id"],
    )
    txt["width"] = w
    txt["height"] = h
    txt["angle"] = rotacao * 0.0174533
    txt["groupIds"] = [grupo]
    note["boundElements"] = [{"type": "text", "id": txt["id"]}]
    elementos.append(txt)

    return elementos


def container_secao(x, y, w, h, nome, paleta, cor_accent=None):
    """Bloco com label flutuante no canto superior — agrupa secao logica."""
    if cor_accent is None:
        cor_accent = paleta["accent"]
    elementos = []
    grupo = gerar_id()

    bg = retangulo(x, y, w, h, paleta, fill=cor_sombra(cor_accent, 8))
    bg["strokeStyle"] = "dashed"
    bg["strokeColor"] = paleta["neutro_medio"]
    bg["groupIds"] = [grupo]
    elementos.append(bg)

    lbl = texto(
        x + 16, y - 14, nome.upper(), paleta,
        tamanho=12, fonte=FONT_HELVETICA, alinhamento="left",
        cor=paleta["neutro_medio"],
    )
    lbl["groupIds"] = [grupo]
    elementos.append(lbl)

    return elementos


def conector_inteligente(el_origem, el_destino, paleta, label=None, elbow=True):
    """
    Cria seta com binding nos centros dos elementos.
    Excalidraw ajusta automaticamente quando ancorada.
    """
    cx_o = el_origem["x"] + el_origem["width"] / 2
    cy_o = el_origem["y"] + el_origem["height"] / 2
    cx_d = el_destino["x"] + el_destino["width"] / 2
    cy_d = el_destino["y"] + el_destino["height"] / 2

    seta_el = seta(
        cx_o, cy_o, cx_d, cy_d, paleta,
        elbow=elbow,
        start_binding={"elementId": el_origem["id"], "focus": 0, "gap": 4},
        end_binding={"elementId": el_destino["id"], "focus": 0, "gap": 4},
    )

    # Atualiza boundElements dos shapes pra refletir a conexao
    el_origem.setdefault("boundElements", []).append({"type": "arrow", "id": seta_el["id"]})
    el_destino.setdefault("boundElements", []).append({"type": "arrow", "id": seta_el["id"]})

    elementos = [seta_el]
    if label:
        # Highlighter atras do label pra texto nao brigar com a linha
        meio_x = (cx_o + cx_d) / 2
        meio_y = (cy_o + cy_d) / 2
        h_lbl = highlighter(meio_x - 40, meio_y - 10, 80, 20, paleta, cor_destaque=paleta["background"])
        h_lbl["backgroundColor"] = paleta["background"]
        h_lbl["opacity"] = 100
        elementos.append(h_lbl)
        lbl = texto(
            meio_x - 40, meio_y - 8, label, paleta,
            tamanho=12, alinhamento="center",
        )
        elementos.append(lbl)

    return elementos


# ============================================================
# CENA — empacotamento final
# ============================================================

def cena_excalidraw(elementos, paleta, modo_dark=False):
    """Empacota lista de elementos como cena Excalidraw v2 valida."""
    return {
        "type": "excalidraw",
        "version": 2,
        "source": "https://excalidraw.com",
        "elements": elementos,
        "appState": {
            "viewBackgroundColor": paleta["background"],
            "gridSize": GRID,
            "gridStep": 5,
            "theme": "dark" if modo_dark else "light",
        },
        "files": {},
    }


def cena_obsidian_md(elementos, paleta, modo_dark=False, titulo="Diagrama"):
    """
    Empacota como .excalidraw.md (formato do plugin Obsidian de zsviczian).
    Permite uso nativo no vault + backlinks + embed.
    """
    cena = cena_excalidraw(elementos, paleta, modo_dark)
    cena_json = json.dumps(cena, ensure_ascii=False, indent=2)

    md = f"""---

excalidraw-plugin: parsed
tags: [excalidraw, imperatriz]

---
==⚠  Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==


# Text Elements

%%
# Drawing
```json
{cena_json}
```
%%
"""
    return md


def salvar(cena, caminho, formato="excalidraw"):
    """Salva cena no disco. formato: 'excalidraw' (JSON) ou 'md' (Obsidian)."""
    p = Path(caminho).expanduser()
    p.parent.mkdir(parents=True, exist_ok=True)
    if formato == "md":
        p.write_text(cena, encoding="utf-8")
    else:
        p.write_text(json.dumps(cena, ensure_ascii=False, indent=2), encoding="utf-8")
    return p


def library_excalidraw(items, nome="Imperatriz Library"):
    """
    Empacota grupos de elementos como .excalidrawlib.
    items: lista de {nome, elementos}
    """
    library_items = []
    for it in items:
        library_items.append({
            "status": "published",
            "name": it["nome"],
            "id": gerar_id(),
            "created": int(time.time() * 1000),
            "elements": it["elementos"],
        })
    return {
        "type": "excalidrawlib",
        "version": 2,
        "source": "imperatriz-skill",
        "libraryItems": library_items,
    }
