"""Construye un workflow ComfyUI de FONDO DE JUEGO EN CAPAS para parallax 2.5D.

Un fondo de parallax (side-scroller / plataformas) se compone de varias capas a
distinta profundidad (cielo, fondo lejano, plano medio, frente) que se desplazan a
velocidades distintas para dar sensacion de profundidad. Generar esas capas en un
solo grafo ComfyUI nativo no es trivial: el camino robusto y barato es
    1. generar UN fondo apaisado con txt2img, y
    2. estimar su MAPA DE PROFUNDIDAD con un preprocessor (DepthAnything V2),
y dejar la separacion en N bandas por rango de profundidad como POST-proceso
(numpy) fuera del grafo. Este builder produce exactamente eso: el fondo + su depth
map, ambos guardados como PNG. El split en capas es una funcion aparte (gap
documentado: `split_parallax_layers`, aun no creada).

Cableado (verificado contra /object_info de ComfyUI 0.26.0):

    CheckpointLoaderSimple ─► ... ─► KSampler ─► VAEDecode ─IMAGE─┬─► SaveImage (fondo)
                                                                  └─► DepthAnythingV2Preprocessor ─► SaveImage (depth)

class_types reales:
  - DepthAnythingV2Preprocessor: inputs image(IMAGE) + ckpt_name(enum de pesos
    depth_anything_v2_*) + resolution(INT). RETURN (IMAGE,). Es el preprocessor de
    controlnet_aux; descarga el peso elegido on-demand al primer uso.
  - SaveImage estandar para fondo y para depth (dos nodos, prefijos distintos).

El parametro `layers` NO genera nodos (el split es post-proceso): se refleja en el
filename_prefix del SaveImage del depth map (`<prefix>_depth_<N>L`) para que, al
bajar los archivos, el nombre conserve cuantas bandas se queria derivar.

Compone comfyui_build_txt2img_workflow. Funcion pura: sin red, sin I/O. No muta el
dict de entrada (copia profunda).
"""
from __future__ import annotations

import copy
import os
import sys

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


def _is_link(v) -> bool:
    return isinstance(v, list) and len(v) == 2 and isinstance(v[0], str) and isinstance(v[1], int)


def comfyui_build_parallax_background_workflow(
    scene: str,
    *,
    style: str = "game background, side-scroller, parallax layers, detailed, no characters",
    layers: int = 3,
    checkpoint: str = "IMG_dreamshaper_8.safetensors",
    negative: str = "character, person, foreground object, text, watermark, blurry, low quality",
    depth_node: str = "DepthAnythingV2Preprocessor",
    depth_model: str = "depth_anything_v2_vitl.pth",
    depth_resolution: int = 512,
    width: int = 1024,
    height: int = 512,
    seed: int = 0,
    steps: int = 28,
    cfg: float = 7.0,
    sampler_name: str = "dpmpp_2m",
    scheduler: str = "karras",
    filename_prefix: str = "parallax_bg",
) -> dict:
    """Construye el dict (API format) del workflow de un fondo de parallax + su depth map.

    Args:
        scene: descripcion de la escena del fondo (ej. "forest at dusk, fantasy",
            "ruined city skyline", "underwater cavern"). No puede estar vacio.
        style: estilo/encuadre que se concatena a la escena. Por defecto fuerza el
            look de fondo apaisado de side-scroller sin personajes. keyword-only.
        layers: numero de capas de profundidad que se derivaran del depth map en
            post-proceso (cielo/lejano/medio/frente...). Debe ser >= 2 (un parallax
            necesita al menos dos planos). NO genera nodos en este grafo: se refleja
            en el filename del depth map para trazabilidad. keyword-only.
        checkpoint: checkpoint base. Default 'IMG_dreamshaper_8.safetensors' (SD1.5,
            holgado en 8GB). keyword-only.
        negative: prompt negativo. Por defecto evita personajes/props en primer
            plano, que rompen un fondo limpio por capas. keyword-only.
        depth_node: class_type del preprocessor de profundidad. Default
            'DepthAnythingV2Preprocessor'. Alternativas instaladas con la misma
            firma (image+ckpt_name+resolution): 'DepthAnythingPreprocessor'. Para
            MiDaS/Zoe (que NO tienen ckpt_name) edita el nodo a mano. keyword-only.
        depth_model: peso del depth_node (enum depth_anything_v2_*). Default vitl
            (buena calidad, cabe en 8GB). Solo aplica a los nodos DepthAnything*.
            keyword-only.
        depth_resolution: resolucion interna del preprocessor de profundidad.
            keyword-only.
        width: ancho en px (multiplo de 8). Default 1024 (formato apaisado tipico de
            parallax). keyword-only.
        height: alto en px (multiplo de 8). Default 512. keyword-only.
        seed, steps, cfg, sampler_name, scheduler: parametros del KSampler.
            keyword-only.
        filename_prefix: prefijo del PNG del fondo en output/. El depth map usa
            '<filename_prefix>_depth_<layers>L'. keyword-only.

    Returns:
        dict en API format listo para comfyui_submit_workflow: txt2img base (fondo
        apaisado) + un SaveImage para el fondo, mas un nodo de profundidad
        (depth_node) que toma el IMAGE del VAEDecode y un segundo SaveImage para el
        depth map. Dos imagenes de salida: el fondo y su mapa de profundidad.

    Raises:
        ValueError: si scene esta vacio, layers < 2, o la base no tiene VAEDecode.
    """
    from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow

    if not scene or not scene.strip():
        raise ValueError(
            "comfyui_build_parallax_background_workflow: 'scene' no puede estar vacio"
        )
    if not isinstance(layers, int) or layers < 2:
        raise ValueError(
            "comfyui_build_parallax_background_workflow: 'layers' debe ser un int >= 2 "
            f"(un parallax necesita >=2 planos), no {layers!r}"
        )

    positive = f"{scene}, {style}".strip().rstrip(",") if style and style.strip() else scene

    wf = comfyui_build_txt2img_workflow(
        checkpoint,
        positive,
        negative,
        steps=steps,
        cfg=cfg,
        width=width,
        height=height,
        seed=seed,
        sampler_name=sampler_name,
        scheduler=scheduler,
        filename_prefix=filename_prefix,
    )
    wf = copy.deepcopy(wf)

    vaedecode_id = next(
        (nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
    )
    if vaedecode_id is None:
        raise ValueError(
            "comfyui_build_parallax_background_workflow: no se encontro VAEDecode en la base"
        )
    if not _is_link(wf[vaedecode_id]["inputs"].get("samples")):
        raise ValueError(
            "comfyui_build_parallax_background_workflow: el VAEDecode no tiene un IMAGE valido"
        )

    numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
    next_id = (max(numeric) + 1) if numeric else len(wf) + 1
    depth_id = str(next_id)
    save_depth_id = str(next_id + 1)

    # Nodo de profundidad sobre el IMAGE del fondo (VAEDecode output 0).
    depth_inputs = {"image": [vaedecode_id, 0], "resolution": depth_resolution}
    # ckpt_name solo lo aceptan los DepthAnything*; para MiDaS/Zoe no se incluye.
    if depth_node.startswith("DepthAnything"):
        depth_inputs["ckpt_name"] = depth_model
    wf[depth_id] = {"class_type": depth_node, "inputs": depth_inputs}

    # SaveImage del depth map: filename refleja el numero de capas a derivar.
    wf[save_depth_id] = {
        "class_type": "SaveImage",
        "inputs": {
            "filename_prefix": f"{filename_prefix}_depth_{layers}L",
            "images": [depth_id, 0],
        },
    }

    return wf


if __name__ == "__main__":
    import json

    wf = comfyui_build_parallax_background_workflow(
        "forest at dusk, fantasy, atmospheric",
        layers=4,
        seed=7,
    )
    print(json.dumps(wf, indent=2))
