"""Construye el workflow ComfyUI de UNA caja de dialogo / bocadillo / panel de texto en API format.

Caja de dialogo de juego (RPG, visual novel, aventura): el contenedor de UI donde
aparece el texto de conversacion. Marco apaisado con borde decorativo y un interior
plano y liso reservado para que el motor de juego renderice el texto encima. Es el
builder hermano de comfyui_build_ui_hud_workflow / comfyui_build_card_art_workflow:
mismo patron (PURO, dict API format) que compone funciones existentes del registry,
no reescribe el grafo.

IMPORTANTE — diferencia con comfyui_build_ui_hud_workflow: ui_hud genera ELEMENTOS
SUELTOS de interfaz (un boton, una barra de vida, un icono, un cursor). Este builder
genera el CONTENEDOR DE DIALOGO completo: un panel apaisado con borde ornamental y
un area interior intencionalmente plana/vacia donde el motor escribe el texto de la
conversacion. No es un elemento del HUD, es el marco de la caja de texto.

Cableado:

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

Compone:
  - comfyui_build_txt2img_workflow  -> base txt2img apaisada (width > height)
  - comfyui_inject_lora             -> LoRA de estilo opcional (consistencia de UI)
  - 'Image Rembg (Remove Background)' (helper local) -> fondo transparente (alpha)

Por que Rembg y NO comfyui_matting_luma_to_alpha: una caja de dialogo es una pieza
SOLIDA con silueta definida (un panel con su borde); rembg recorta limpio el marco
dejando alpha alrededor. La luma-to-alpha es para translucidos sobre negro
(humo/fuego/magia) y aplanaria el panel. 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.

Por que apaisado (width > height): una caja de dialogo ocupa el ancho de la pantalla
y es baja de alto (una/dos lineas de texto). 768x256 (SD1.5) da la proporcion tipica
de panel de conversacion al pie de la pantalla. El interior se mantiene plano para no
competir con el texto del motor.

class_types/inputs verificados contra /object_info del servidor (8GB lowvram) a
traves de los builders que compone: 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 una caja de dialogo: un panel apaisado limpio,
# con el INTERIOR PLANO y VACIO (sin texto, sin imagenes dentro) para que el motor
# escriba el texto encima, sin personajes ni escena que ensucien el contenedor.
_DIALOGUE_NEGATIVE = (
    "blurry, lowres, character, person, face, landscape, scene, "
    "text, letters, words, label, watermark, signature, "
    "busy interior, cluttered, decorated interior, illustration inside, "
    "photo, photorealistic, jpeg artifacts, cropped, out of frame, deformed"
)


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_ui_hud_workflow / comfyui_build_item_icon_workflow:
    el nodo recorta la silueta del panel de dialogo dejando alpha alrededor del
    borde. 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_dialogue_box_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_dialogue_box_workflow(
    box_style: str = "fantasy RPG dialogue box",
    *,
    shape: str = "rounded panel",
    checkpoint: str = "IMG_dreamshaper_8.safetensors",
    width: int = 768,
    height: 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 = "dialogue_box",
) -> dict:
    """Construye el dict (API format) del workflow de una caja de dialogo de juego.

    Args:
        box_style: descriptor del estilo del contenedor de dialogo (ej. "fantasy RPG
            dialogue box", "medieval fantasy dialogue box, wood and gold", "sci-fi
            terminal text box, neon glow", "visual novel text panel, soft pastel").
            Se inserta en un prompt scaffold de caja de dialogo. No puede estar vacio.
        shape: forma del panel (ej. "rounded panel", "rectangular banner", "scroll
            parchment", "stone tablet", "speech bubble"). Mantiene coherentes las
            cajas de un juego. 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 width/height). keyword-only.
        width: ancho del panel en px. Apaisado de caja de dialogo -> width > height.
            768 por defecto. keyword-only.
        height: alto del panel en px. 256 por defecto (panel bajo y ancho).
            keyword-only.
        transparent: si True inyecta Rembg y el PNG sale con alpha (fondo recortado
            alrededor del borde). Si False deja el panel opaco sobre fondo plano,
            recortable luego por el caller/pipeline. keyword-only.
        seed: semilla del KSampler. 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 la
            caja de dialogo (panel limpio, interior plano y vacio, sin texto ni
            personajes/escena). 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 apaisada
        con prompt scaffold de caja de dialogo ('{box_style}, {shape}, game UI
        dialogue box frame, ornate border, empty flat interior for text, plain
        background') + LoRA de estilo opcional + Rembg (si transparent). Es UNA caja;
        un set coherente -> llamar con el mismo box_style/shape/checkpoint/(lora). El
        texto de la conversacion lo renderiza el motor sobre el interior plano, no
        este workflow.

    Raises:
        ValueError: si box_style 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 box_style or not box_style.strip():
        raise ValueError(
            "comfyui_build_dialogue_box_workflow: 'box_style' no puede estar vacio"
        )

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

    # Prompt scaffold de caja de dialogo: panel apaisado con borde decorativo y un
    # interior PLANO y VACIO reservado para el texto que pone el motor de juego.
    positive = (
        f"{box_style.strip()}, {shape}, game UI dialogue box frame, "
        "ornate border, empty flat interior for text, plain background"
    )

    wf = comfyui_build_txt2img_workflow(
        checkpoint,
        positive,
        neg,
        steps=steps,
        cfg=cfg,
        width=width,
        height=height,
        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_dialogue_box_workflow(
        "medieval fantasy dialogue box, wood and gold",
        shape="rounded panel",
        transparent=True,
        seed=42,
    )
    print(
        json.dumps(
            {
                "nodes": list(wf),
                "classes": sorted({n["class_type"] for n in wf.values()}),
            },
            indent=2,
        )
    )
