"""Construye el workflow ComfyUI de UN sprite en VISTA CENITAL (top-down) (API format).

Sprite de personaje/objeto visto DESDE ARRIBA, estilo RPG clasico / roguelike
(Zelda, juegos cenitales): la camara mira el suelo en picado y el sujeto se ve
desde lo alto, centrado, sobre fondo limpio uniforme recortable a alpha, listo para
colocar en un mapa de tiles top-down. Opcion de `direction` (south/north/east/west)
para el sprite de movimiento: el conjunto de 4 direcciones de un personaje se obtiene
llamando con la misma `subject`/`style`/`seed` variando solo `direction`, y montando
los PNG con `comfyui_build_grid`.

DISTINTO de comfyui_build_sprite_sheet_workflow: ese es vista LATERAL/frontal (sprite
de plataformas, cuerpo entero de perfil/de frente con OpenPose). Este es vista CENITAL
(personaje proyectado en el suelo, visto desde arriba). El scaffold empuja a
"top-down view, overhead view" y el negativo por defecto rechaza "side view, front
view, isometric, perspective" precisamente para no caer en la vista lateral del builder
hermano.

Es el builder hermano de comfyui_build_enemy_creature_workflow /
comfyui_build_prop_object_workflow / comfyui_build_item_icon_workflow: mismo patron
(PURO, dict API format) que compone funciones existentes del registry, no reescribe el
grafo.

Cableado:

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

Compone:
  - comfyui_build_txt2img_workflow  -> base txt2img cuadrada
  - comfyui_inject_lora             -> LoRA de estilo top-down/RPG opcional (consistencia)
  - 'Image Rembg (Remove Background)' (helper local) -> fondo transparente (alpha)

Por que Rembg y NO comfyui_matting_luma_to_alpha: un sprite cenital es un sujeto
SOLIDO con silueta definida; rembg recorta limpio dejando alpha. La luma-to-alpha es
para translucidos sobre negro (humo/fuego/magia), donde aplanaria el sprite. Para el
sprite top-down tipico (personaje, objeto del mapa) rembg es lo correcto.

Por que `direction` y no meterlo en `subject`: separar el sujeto de la direccion de
encare deja generar el set de movimiento del MISMO personaje — la misma `subject` +
`style` + `seed` con distintos `direction` (south/north/east/west) da las 4 vistas de
caminata coherentes. Con `direction` vacio el prompt usa solo "facing" neutro.

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 sprites cenitales: UNA figura entera vista desde
# arriba, fondo limpio, sin texto/marcas ni recortes. Rechaza explicitamente las vistas
# que pertenecen al builder LATERAL (side/front/3/4/isometric/perspective) para que la
# vista cenital salga de verdad.
_TOPDOWN_NEGATIVE = (
    "blurry, lowres, deformed, disfigured, bad anatomy, extra limbs, mutated, ugly, "
    "side view, front view, 3/4 view, isometric, perspective, vanishing point, "
    "multiple characters, crowd, text, watermark, signature, logo, "
    "cropped, cut off, out of frame, jpeg artifacts"
)


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_enemy_creature_workflow / item_icon: el nodo
    recorta la silueta del sprite 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_topdown_sprite_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_topdown_sprite_workflow(
    subject: str,
    *,
    direction: str = "south",
    style: str = "top-down game sprite, RPG",
    checkpoint: str = "IMG_dreamshaper_8.safetensors",
    size: int = 512,
    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 = "topdown_sprite",
) -> dict:
    """Construye el dict (API format) del workflow de UN sprite en vista cenital (top-down).

    Args:
        subject: descripcion del personaje/objeto (ej. "a knight character",
            "a wizard", "a treasure chest", "a slime"). Se inserta en un prompt
            scaffold cenital. No puede estar vacio.
        direction: direccion de encare para el sprite de movimiento. Tipico
            "south" (mirando hacia la camara/abajo, el frame por defecto de un
            sprite RPG), "north", "east", "west" (tambien diagonales como
            "south-east" si el motor las usa). Las 4 vistas de caminata del MISMO
            personaje = misma subject/style/seed variando solo direction. None/""
            = encare neutro sin direccion. keyword-only.
        style: descriptor de estilo que mantiene consistentes los sprites del set
            (ej. "top-down game sprite, RPG", "pixel art top-down", "Zelda-like
            overhead sprite", "roguelike tile character"). Pasa el MISMO style +
            checkpoint + (lora) a todos los sprites 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 a 768/1024). keyword-only.
        size: lado del cuadrado en px (width = height = size). 512 SD1.5 por
            defecto. keyword-only.
        transparent: si True inyecta Image Rembg y el PNG sale con alpha (fondo
            recortado, listo para colocar sobre un mapa de tiles). Si False deja el
            sprite opaco sobre fondo plano, recortable luego por el caller/pipeline.
            keyword-only.
        seed: semilla del KSampler. Misma seed + misma subject/style -> misma
            figura; variar solo `direction` da las vistas coherentes de movimiento.
            keyword-only.
        lora: LoRA de estilo/top-down opcional en models/loras (ej.
            'SD15_topdown_rpg.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
            vista cenital (una figura entera vista desde arriba, fondo limpio, sin
            vistas laterales/frontales/isometricas que delatarian otra perspectiva).
            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 cenital ('{subject}, top-down view, overhead view,
        {direction} facing, {style}, centered, plain background, game asset, ...') +
        LoRA de estilo opcional + Image Rembg (si transparent). Es UN sprite; las 4
        direcciones de un personaje -> misma subject/style/seed variando `direction`;
        montar el set con comfyui_build_grid si se quiere un atlas/contact-sheet.

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

    subject = subject.strip()
    direction = (direction or "").strip()
    lora_strength = max(0.0, min(2.0, float(lora_strength)))
    neg = _TOPDOWN_NEGATIVE if negative is None else negative

    # Prompt scaffold cenital: una figura entera vista desde arriba, centrada, fondo
    # plano, lista como asset top-down recortable. La direccion (si se da) fija el
    # encare para el sprite de movimiento.
    facing = f"{direction} facing, " if direction else ""
    positive = (
        f"{subject}, top-down view, overhead view, {facing}{style}, "
        "centered, plain background, game asset, single sprite, "
        "bird's-eye view, full body from above"
    )

    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_topdown_sprite_workflow(
        "a knight character",
        direction="south",
        style="top-down game sprite, RPG",
        transparent=True,
        seed=5,
    )
    print(
        json.dumps(
            {
                "nodes": list(wf),
                "classes": sorted({n["class_type"] for n in wf.values()}),
            },
            indent=2,
        )
    )
