"""Construye el workflow ComfyUI de UN prop/objeto de escenario de juego (API format).

Prop/objeto de mundo (barril, cofre, antorcha, planta, mueble, roca, fuente,
estatua, decoracion...): UN objeto inanimado aislado, a escala de escena y con la
perspectiva del juego (isometrica o lateral), centrado, fondo limpio y uniforme
recortable a alpha, estilo consistente para poblar un nivel. Es el builder hermano
de comfyui_build_item_icon_workflow / comfyui_build_enemy_creature_workflow /
comfyui_build_ui_hud_workflow: mismo patron (PURO, dict API format) que compone
funciones existentes del registry, no reescribe el grafo.

Diferencia con item_icon (clave para no duplicar): un *item icon* es un icono PLANO
de inventario (encuadre cuadrado, sujeto presentado de frente, pensado para una
casilla de UI). Un *prop* es un objeto de MUNDO: se coloca en el escenario, conserva
la perspectiva del juego (isometrica/lateral) y la escala de la escena para encajar
con los tiles y el resto de decorado. Por eso el style por defecto empuja
"game prop, isometric or side view, scene prop" en vez de "game icon, clean".

Cableado:

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

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

Por que Rembg y NO comfyui_matting_luma_to_alpha: un prop es un objeto SOLIDO con
silueta definida (barril, cofre, mueble); rembg recorta limpio la silueta dejando
alpha, listo para soltar sobre el tilemap. La luma-to-alpha es para translucidos
sobre negro (humo/fuego/magia), donde aplanaria el objeto. Si el prop es etereo o
translucido (cristal magico, llama de antorcha sola) y se quiere conservar la
translucidez, recortar fuera del workflow (transparent=False) y componer con
luma-to-alpha en un paso aparte. Para el atrezzo tipico (barril, cofre, roca,
planta) rembg es lo correcto.

Por que un solo objeto centrado y fondo plano: un prop se inserta como sprite/objeto
suelto en el motor; el scaffold empuja a "single object, centered, plain background,
game asset" y el negativo por defecto rechaza "person, character, creature, multiple
objects, cropped, out of frame" para mantener UN objeto entero, inanimado y
recortable.

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 props/objetos de escenario: UN objeto inanimado
# entero, fondo limpio, sin personas/criaturas, sin texto/marcas ni recortes. No
# filtra ningun tipo de objeto (barril, cofre, planta, roca, fuente... son validos).
_PROP_NEGATIVE = (
    "person, people, character, creature, animal, hands, face, "
    "multiple objects, cluttered, blurry, lowres, deformed, bad perspective, "
    "text, watermark, signature, logo, photo, photorealistic, "
    "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_item_icon_workflow / enemy_creature: el nodo
    recorta la silueta del objeto 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_prop_object_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_prop_object_workflow(
    prop: str,
    *,
    style: str = "game prop, isometric or side view",
    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 = "prop_object",
) -> dict:
    """Construye el dict (API format) del workflow de un prop/objeto de escenario.

    Args:
        prop: descripcion del objeto de escenario (ej. "wooden barrel",
            "treasure chest", "lit torch", "potted plant", "stone fountain",
            "wooden table", "mossy rock"). Se inserta en un prompt scaffold de prop.
            No puede estar vacio.
        style: descriptor de estilo/perspectiva que mantiene consistentes los props
            del set y los situa en el MUNDO, no en la UI (ej. "game prop, isometric
            or side view", "top-down RPG prop", "side-scroller platformer prop",
            "low poly stylized prop"). Pasa el MISMO style + checkpoint + (lora) a
            todos los props del nivel 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 soltar sobre el tilemap). Si False deja el objeto
            opaco sobre fondo plano, recortable luego por el caller/pipeline.
            keyword-only.
        seed: semilla del KSampler. Misma seed + mismo prop/style -> mismo objeto.
            keyword-only.
        lora: LoRA de estilo opcional en models/loras (ej.
            'SD15_isometric_game_assets.safetensors', 'SDXL_stylized_props.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
            props (un objeto inanimado entero, sin personas/criaturas, fondo limpio,
            sin texto/recorte). 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 prop ('{prop}, {style}, game asset, single object,
        centered, plain background, ...') + LoRA de estilo opcional + Image Rembg (si
        transparent). Es UN objeto; para poblar un nivel -> llamar por cada prop con
        el mismo style/checkpoint/(lora). Montar el set con comfyui_build_grid si se
        quiere un contact-sheet del atrezzo.

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

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

    # Prompt scaffold de prop: un objeto de mundo entero, centrado, fondo plano,
    # listo como asset de juego suelto (sprite/objeto) recortable.
    positive = (
        f"{prop}, {style}, game asset, single object, centered, plain background, "
        "scene prop, world object, no characters, 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_prop_object_workflow(
        "wooden treasure chest",
        style="game prop, isometric or side view",
        transparent=True,
        seed=7,
    )
    print(
        json.dumps(
            {
                "nodes": list(wf),
                "classes": sorted({n["class_type"] for n in wf.values()}),
            },
            indent=2,
        )
    )
