"""Construye el workflow ComfyUI de UN nodo de arbol de habilidades / talentos (API format).

Nodo de skill tree de juego (RPG, ARPG, MOBA, roguelike): el icono de una
habilidad/talento DENTRO de un marco decorativo (circular, hexagonal, rombo,
escudo) que la UI de progresion pinta en la rejilla de talentos, con variante de
ESTADO visual (desbloqueado = brillante/iluminado / bloqueado = gris/apagado).
Centrado, fondo limpio uniforme, recortable a alpha, estilo de UI consistente entre
nodos del mismo arbol. Es el builder hermano de comfyui_build_status_effect_icon /
comfyui_build_ui_hud_workflow: mismo patron (PURO, dict API format) que compone
funciones existentes del registry, no reescribe el grafo.

DISTINTO de item_icon, status_effect_icon y ui_hud:
  - item_icon         -> objeto de inventario (espada, pocion): ilustracion de un
                         objeto suelto, SIN marco.
  - status_effect_icon-> simbolo de estado compacto que se superpone al retrato/barra
                         del personaje, SIN marco; optimizado para 16-32 px.
  - ui_hud            -> chrome grande/suelto de la interfaz (botones, barras, marcos
                         vacios); no es el nodo de talento completo.
  - skill_tree_node   -> ESTO: el nodo ENMARCADO de la pantalla de talentos = icono de
                         skill + marco + estado (unlocked/locked). El marco y el estado
                         son la firma del asset.

Por que importa el estado: una pantalla de talentos muestra el MISMO nodo en dos
apariencias — disponible/comprado (brillante, saturado, resaltado) y bloqueado (gris,
desaturado, atenuado). Generar el par (state="unlocked" / state="locked") con el mismo
skill/frame/ui_style/seed da las dos caras coherentes del mismo talento; el motor
intercambia el sprite segun el progreso del jugador.

Cableado:

    CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
                          -> CLIPTextEncode (prompt scaffold de nodo de skill tree) ...
                          -> VAEDecode -> [Image Rembg opcional] -> SaveImage

Compone:
  - comfyui_build_txt2img_workflow  -> base txt2img cuadrada
  - comfyui_inject_lora             -> LoRA de estilo opcional (consistencia del arbol)
  - 'Image Rembg (Remove Background)' (helper local) -> fondo transparente

Por que Rembg y NO comfyui_matting_luma_to_alpha: un nodo de skill tree es una pieza
SOLIDA con silueta definida (el marco perfila el borde); rembg recorta limpio la
silueta del marco dejando alpha. La luma-to-alpha es para translucidos sobre negro
(humo/fuego/runas brillantes) y aplanaria el marco. Si el caller prefiere recortar
fuera del workflow (transparent=False) deja la imagen opaca sobre fondo plano,
recortable luego por el pipeline o el caller.

El mismo frame + ui_style + checkpoint + (lora) en todos los nodos del arbol hace que
la rejilla de talentos combine visualmente: es la clave de un skill tree coherente,
igual que en los iconos de inventario y los elementos del HUD.

class_types/inputs verificados contra /object_info del servidor (8GB lowvram):
CheckpointLoaderSimple, CLIPTextEncode, EmptyLatentImage, KSampler, VAEDecode,
SaveImage, LoraLoader, 'Image Rembg (Remove Background)' (transparency BOOLEAN).

Funcion pura: sin red, sin I/O. No muta dicts de entrada (copia profunda en el
helper de rembg). Determinista para los mismos argumentos.
"""
from __future__ import annotations

import copy
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

# Negativo por defecto pensado para nodos de skill tree: un icono enmarcado limpio,
# centrado y recortable, sin escena ni personaje ni texto/marcas que ensucien la
# silueta del marco. NO rechaza "frame/border" (el marco es parte del asset), pero si
# empuja contra escenas recargadas, multiples nodos y fondos sucios.
_SKILL_TREE_NEGATIVE = (
    "blurry, lowres, busy background, cluttered, multiple nodes, multiple icons, "
    "detailed scene, landscape, full body character, person, face, text, letters, "
    "words, numbers, watermark, signature, photo, photorealistic, realistic, "
    "jpeg artifacts, cropped, out of frame, deformed"
)

# Pistas visuales por estado: refuerzan en el prompt el aspecto del nodo segun este
# disponible/comprado (unlocked) o bloqueado (locked) en la pantalla de talentos. El
# nombre del estado tambien se inserta literal en el prompt para que sea explicito.
_STATE_HINTS = {
    "unlocked": "glowing, illuminated, vibrant, saturated, highlighted, active",
    "locked": "greyed out, desaturated, dimmed, dark, muted, inactive",
}


def _inject_rembg(workflow: dict, model: str) -> dict:
    """Inserta 'Image Rembg (Remove Background)' (transparency=True) entre VAEDecode y SaveImage.

    Mismo helper que usan comfyui_build_status_effect_icon_workflow / comfyui_build_ui_hud_workflow:
    el nodo recorta la silueta del nodo enmarcado dejando alpha. Repunta
    SaveImage.images a la salida del Rembg.
    """
    wf = copy.deepcopy(workflow)
    vaedecode_id = next(
        (nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
    )
    save_id = next((nid for nid, n in wf.items() if n.get("class_type") == "SaveImage"), None)
    if vaedecode_id is None or save_id is None:
        raise ValueError(
            "comfyui_build_skill_tree_node_workflow: no se encontro VAEDecode/SaveImage para Rembg"
        )
    numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
    rembg_id = str((max(numeric) + 1) if numeric else len(wf) + 1)
    wf[rembg_id] = {
        "class_type": "Image Rembg (Remove Background)",
        "inputs": {
            "images": [vaedecode_id, 0],
            "transparency": True,
            "model": model,
            "post_processing": False,
            "only_mask": False,
            "alpha_matting": False,
            "alpha_matting_foreground_threshold": 240,
            "alpha_matting_background_threshold": 10,
            "alpha_matting_erode_size": 10,
            "background_color": "none",
        },
    }
    wf[save_id]["inputs"]["images"] = [rembg_id, 0]
    return wf


def comfyui_build_skill_tree_node_workflow(
    skill: str,
    *,
    frame: str = "hexagonal",
    state: str = "unlocked",
    ui_style: str = "fantasy skill tree node",
    checkpoint: str = "IMG_dreamshaper_8.safetensors",
    size: int = 256,
    transparent: bool = True,
    seed: int = 0,
    lora: str | None = None,
    lora_strength: float = 1.0,
    rembg_model: str = "u2net",
    negative: str | None = None,
    steps: int = 28,
    cfg: float = 7.0,
    sampler_name: str = "dpmpp_2m",
    scheduler: str = "karras",
    filename_prefix: str = "skill_tree_node",
) -> dict:
    """Construye el dict (API format) del workflow de un nodo de arbol de habilidades.

    Args:
        skill: nombre de la habilidad/talento que va dentro del nodo (ej. "fireball",
            "double jump", "critical strike", "healing aura", "stealth", "lightning
            bolt"). Se inserta en un prompt scaffold de nodo de skill tree. No puede
            estar vacio.
        frame: forma del marco del nodo (ej. "hexagonal", "circular", "diamond",
            "shield", "square ornate"). Define la silueta enmarcada que perfila el
            nodo en la rejilla de talentos. Pasa el MISMO frame a todos los nodos del
            arbol para coherencia. keyword-only.
        state: estado visual del nodo: "unlocked" (disponible/comprado: brillante,
            saturado, resaltado) o "locked" (bloqueado: gris, desaturado, atenuado).
            Genera el par con el mismo skill/frame/ui_style/seed para tener las dos
            caras del mismo talento. Cualquier otro valor se inserta literal (el state
            hint cae al de "unlocked"). keyword-only.
        ui_style: descriptor de estilo de UI que mantiene consistentes los nodos de un
            arbol (ej. "fantasy skill tree node", "sci-fi tech tree, neon", "dark
            souls talent node", "minimal flat skill node"). Pasa el MISMO ui_style +
            frame + checkpoint + (lora) a todos los nodos para coherencia visual.
            keyword-only.
        checkpoint: checkpoint del servidor. 'IMG_dreamshaper_8.safetensors' (SD1.5,
            holgado en 8GB lowvram) por defecto; 'IMG_juggernaut_xl_v11.safetensors' para
            SDXL (mas VRAM, subir size). keyword-only.
        size: lado del cuadrado en px (width = height = size). 256 por defecto: los
            nodos de talento se muestran a tamano reducido en la rejilla. keyword-only.
        transparent: si True inyecta Rembg y el PNG sale con alpha (fondo recortado,
            la silueta del marco). Si False deja el nodo opaco sobre fondo plano,
            recortable luego por el caller/pipeline. keyword-only.
        seed: semilla del KSampler. Fija el mismo seed en el par unlocked/locked del
            mismo talento para que coincidan. keyword-only.
        lora: LoRA de estilo opcional en models/loras (ej.
            'SD15_detail_tweaker.safetensors'). None = sin LoRA. keyword-only.
        lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
            keyword-only.
        rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se
            usa si transparent=True. keyword-only.
        negative: prompt negativo. None usa el negativo por defecto pensado para nodos
            de skill tree (icono enmarcado limpio, fondo limpio, sin escena/personaje/
            texto). keyword-only.
        steps, cfg, sampler_name, scheduler, filename_prefix: parametros de
            generacion. keyword-only.

    Returns:
        dict en API format listo para comfyui_submit_workflow: txt2img base cuadrada
        con prompt scaffold de nodo de skill tree ('{skill} skill icon inside a {frame}
        {ui_style} frame, {state} ({state hint}), centered, plain background, game
        UI, ...') + LoRA de estilo opcional + Rembg (si transparent). Es UN nodo; un
        arbol completo -> llamar por skill (y por state) con el mismo frame/ui_style/
        checkpoint/lora y montar la rejilla en el motor.

    Raises:
        ValueError: si skill esta vacio, o si la base no tiene VAEDecode/SaveImage
            donde inyectar el Rembg (propagado por el helper).
    """
    from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow

    if not skill or not skill.strip():
        raise ValueError(
            "comfyui_build_skill_tree_node_workflow: 'skill' no puede estar vacio"
        )

    lora_strength = max(0.0, min(2.0, float(lora_strength)))
    neg = _SKILL_TREE_NEGATIVE if negative is None else negative

    skill_s = skill.strip()
    frame_s = (frame or "hexagonal").strip()
    state_s = (state or "unlocked").strip()
    ui_style_s = (ui_style or "fantasy skill tree node").strip()
    # Pista de aspecto segun estado; estado desconocido -> aspecto de unlocked.
    state_hint = _STATE_HINTS.get(state_s.lower(), _STATE_HINTS["unlocked"])

    # Prompt scaffold de nodo de skill tree: icono de skill DENTRO de un marco, con el
    # estado (desbloqueado brillante / bloqueado gris), centrado, fondo plano,
    # recortable. skill/frame/state/ui_style quedan reflejados literalmente.
    positive = (
        f"{skill_s} skill icon inside a {frame_s} {ui_style_s} frame, "
        f"{state_s} ({state_hint}), centered, plain background, game UI, "
        "skill tree talent node, clean, high detail"
    )

    wf = comfyui_build_txt2img_workflow(
        checkpoint,
        positive,
        neg,
        steps=steps,
        cfg=cfg,
        width=size,
        height=size,
        seed=seed,
        sampler_name=sampler_name,
        scheduler=scheduler,
        filename_prefix=filename_prefix,
    )

    if lora:
        from ml.comfyui_inject_lora import comfyui_inject_lora

        wf = comfyui_inject_lora(
            wf, lora, strength_model=lora_strength, strength_clip=lora_strength
        )

    if transparent:
        wf = _inject_rembg(wf, rembg_model)

    return wf


if __name__ == "__main__":
    import json

    wf = comfyui_build_skill_tree_node_workflow(
        "fireball",
        frame="hexagonal",
        state="unlocked",
        ui_style="fantasy skill tree node",
        transparent=True,
        seed=42,
    )
    print(
        json.dumps(
            {
                "nodes": list(wf),
                "classes": sorted({n["class_type"] for n in wf.values()}),
            },
            indent=2,
        )
    )
