"""Construye el workflow ComfyUI de UN arte de carta coleccionable (TCG) en API format.

Ilustracion central de una carta de juego coleccionable (criatura, personaje o
hechizo): formato vertical de carta (~512x768) con la figura en composicion
centrada y dramatica, dejando aire alrededor para que el motor/post anada el
marco, el titulo y los stats. Es el builder hermano de
comfyui_build_portrait_avatar_workflow / comfyui_build_item_icon_workflow /
comfyui_build_ui_hud_workflow: mismo patron (PURO, dict API format) que compone
funciones existentes del registry, no reescribe el grafo.

IMPORTANTE — alcance del builder: genera SOLO la ILUSTRACION de la carta. El marco
decorativo, la barra de titulo, el cuadro de texto y los stats (ataque/defensa/
coste) NO los pinta este workflow: son composicion del motor de juego o de un paso
de post sobre la ilustracion. El prompt scaffold empuja a "full art illustration"
y el negativo por defecto rechaza "card frame / border / text / stats / UI" para
mantener la ilustracion limpia y recortable.

Cableado segun los argumentos:

    [hires] CheckpointLoaderSimple -> [LoraLoader si lora] -> KSampler (base) ->
            VAEDecode -> UpscaleModelLoader + UltimateSDUpscale -> SaveImage
    [plano] CheckpointLoaderSimple -> [LoraLoader si lora] -> KSampler ->
            VAEDecode -> SaveImage

Compone:
  - comfyui_build_hires_fix_workflow (si hires) -> base vertical + 2a pasada de
    detalle por tiles (UltimateSDUpscale + Remacri); el arte de carta luce el
    detalle fino de la ilustracion.
  - comfyui_build_txt2img_workflow (si no hires) -> base txt2img vertical simple.
  - comfyui_inject_lora -> LoRA de estilo opcional (fantasy / anime / realista)
    para coherencia de estilo entre cartas de un mismo set.

Por que vertical (width < height): una carta TCG es mas alta que ancha; 512x768
(SD1.5) o 768x1152 (SDXL) da el encuadre de retrato de carta. La figura va centrada
con margen para el chrome de la carta.

Por que hires opcional: el arte de carta se mira de cerca (ilustracion "full art"),
asi que el detalle importa; hires re-difunde la imagen por tiles y anade detalle
real. En 8GB lowvram con SDXL puede ser pesado: bajar a SD1.5 o poner hires=False
si la GPU se queda corta.

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, UpscaleModelLoader,
UltimateSDUpscale).

Funcion pura: sin red, sin I/O. No muta dicts de entrada (los builders/inyectores
que compone trabajan sobre copias). Determinista para los mismos argumentos.
"""
from __future__ import annotations

import os
import sys

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

# Negativo por defecto pensado para arte de carta: una figura central bien formada,
# ilustracion limpia, SIN el chrome de la carta (marco/borde/titulo/stats/UI) ni
# texto/marcas, que son trabajo del motor/post, no de la ilustracion.
_CARD_NEGATIVE = (
    "blurry, lowres, deformed, disfigured, bad anatomy, extra limbs, "
    "extra fingers, mutated hands, ugly, card frame, card border, border, "
    "title bar, text box, text, label, stats, numbers, UI, watermark, "
    "signature, cropped, out of frame, jpeg artifacts"
)


def comfyui_build_card_art_workflow(
    subject: str,
    *,
    card_style: str = "fantasy trading card art",
    checkpoint: str = "IMG_juggernaut_xl_v11.safetensors",
    width: int = 512,
    height: int = 768,
    hires: bool = True,
    seed: int = 0,
    lora: str | None = None,
    lora_strength: float = 1.0,
    negative: str | None = None,
    steps: int = 28,
    cfg: float = 7.0,
    sampler_name: str = "dpmpp_2m",
    scheduler: str = "karras",
    upscale_by: float = 1.5,
    hires_denoise: float = 0.4,
    upscale_model: str = "4x_foolhardy_Remacri.pth",
    filename_prefix: str = "card_art",
) -> dict:
    """Construye el dict (API format) del workflow de un arte de carta coleccionable.

    Args:
        subject: descripcion de la figura central de la carta (ej. "a fire dragon
            breathing flames", "an elven archer drawing a glowing bow", "a frost
            elemental made of ice"). Se inserta en un prompt scaffold de carta. No
            puede estar vacio.
        card_style: descriptor de estilo de la ilustracion que mantiene coherentes
            las cartas de un set (ej. "fantasy trading card art", "anime trading
            card art", "realistic painted card art", "dark gothic card art"). Pasa
            el MISMO card_style + checkpoint + (lora) a todas las cartas del set
            para coherencia visual. keyword-only.
        checkpoint: checkpoint del servidor. 'IMG_juggernaut_xl_v11.safetensors' (SDXL,
            mejor detalle a alta resolucion) por defecto; en 8GB lowvram puede ser
            pesado con hires: si la GPU se queda corta, usa
            'IMG_dreamshaper_8.safetensors' (SD1.5) y/o hires=False. keyword-only.
        width: ancho del lienzo en px. Vertical de carta -> width < height. 512 por
            defecto. keyword-only.
        height: alto del lienzo en px. 768 por defecto (retrato de carta). Para
            SDXL nativo, 768x1152 luce mejor (subir ambos). keyword-only.
        hires: si True encadena la 2a pasada de detalle (UltimateSDUpscale + Remacri,
            re-difusion por tiles) sobre la base vertical para una ilustracion mas
            detallada (full art). False deja la imagen tal cual sale del VAEDecode.
            keyword-only.
        seed: semilla del KSampler (y de la pasada hires). Misma seed + mismo
            subject -> misma ilustracion. keyword-only.
        lora: LoRA de estilo opcional en models/loras (ej.
            'SD15_detail_tweaker.safetensors', 'SDXL_anime_style.safetensors'). None =
            sin LoRA. Encadena estilo coherente entre cartas. keyword-only.
        lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
            keyword-only.
        negative: prompt negativo. None usa el negativo por defecto pensado para
            arte de carta (figura limpia, sin marco/titulo/stats/texto). keyword-only.
        steps: pasos del KSampler (y de la pasada hires). keyword-only.
        cfg: CFG del KSampler (y de la pasada hires). keyword-only.
        sampler_name: sampler del KSampler. keyword-only.
        scheduler: scheduler del KSampler. keyword-only.
        upscale_by: factor de ampliacion de la pasada hires sobre la base (1.5 ->
            512x768 pasa a 768x1152). Solo se usa si hires=True. keyword-only.
        hires_denoise: fuerza de re-difusion de la pasada hires (0.4 por defecto:
            anade detalle sin alterar la composicion). Solo si hires=True.
            keyword-only.
        upscale_model: modelo de upscale en models/upscale_models/ que usa la pasada
            hires ('4x_foolhardy_Remacri.pth'). Solo si hires=True. keyword-only.
        filename_prefix: prefijo del PNG generado en output/. keyword-only.

    Returns:
        dict en API format listo para comfyui_submit_workflow: base vertical
        (hires-fix si hires, txt2img si no) con prompt scaffold de carta
        ('{subject}, {card_style}, dramatic lighting, detailed illustration,
        centered composition, full art, ...') + LoRA de estilo opcional. Es UNA
        carta; un set coherente -> llamar por subject con el mismo
        card_style/checkpoint/(lora). El marco/titulo/stats los pone el motor/post,
        no este workflow.

    Raises:
        ValueError: si subject esta vacio, o si los builders/inyectores que compone
            no encuentran los nodos donde enganchar (propagado).
    """
    if not subject or not subject.strip():
        raise ValueError(
            "comfyui_build_card_art_workflow: 'subject' no puede estar vacio"
        )

    subject = subject.strip()
    lora_strength = max(0.0, min(2.0, float(lora_strength)))
    neg = _CARD_NEGATIVE if negative is None else negative

    # Prompt scaffold de carta: figura central, iluminacion dramatica, full art
    # vertical, dejando aire para el chrome de la carta (que pone el motor/post).
    positive = (
        f"{subject}, {card_style}, dramatic lighting, detailed illustration, "
        "centered composition, full art, vertical card illustration, "
        "intricate detail, high quality"
    )

    if hires:
        from ml.comfyui_build_hires_fix_workflow import (
            comfyui_build_hires_fix_workflow,
        )

        wf = comfyui_build_hires_fix_workflow(
            checkpoint,
            positive,
            neg,
            first_pass=(width, height),
            upscale_by=upscale_by,
            denoise=hires_denoise,
            steps=steps,
            cfg=cfg,
            seed=seed,
            upscale_model=upscale_model,
            sampler_name=sampler_name,
            scheduler=scheduler,
            filename_prefix=filename_prefix,
        )
    else:
        from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow

        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
        )

    return wf


if __name__ == "__main__":
    import json

    wf = comfyui_build_card_art_workflow(
        "a fire dragon breathing flames",
        card_style="fantasy trading card art",
        checkpoint="IMG_dreamshaper_8.safetensors",
        hires=True,
        seed=7,
    )
    print(
        json.dumps(
            {
                "nodes": list(wf),
                "classes": sorted({n["class_type"] for n in wf.values()}),
            },
            indent=2,
        )
    )
