"""Construye un workflow ComfyUI img2vid (SVD) en "API format" (dict de nodos numerados).

Implementa la plantilla canonica de Stable Video Diffusion de ComfyUI: a partir de
una imagen estatica genera un clip corto de video. El checkpoint `VIDEO_svd.safetensors`
es todo-en-uno (UNet + VAE + CLIP image encoder), cargado con
ImageOnlyCheckpointLoader (da MODEL, CLIP_VISION y VAE de una sola pieza).

Cadena de nodos:
  ImageOnlyCheckpointLoader (MODEL, CLIP_VISION, VAE) + LoadImage (imagen base) ->
  SVD_img2vid_Conditioning (positive, negative, latent) ->
  VideoLinearCFGGuidance (interpola cfg de min_cfg a cfg a lo largo del clip) ->
  KSampler (denoise 1.0) -> VAEDecode (secuencia de frames) -> SaveAnimatedWEBP.

A diferencia de los modelos txt2video (LTX/Wan), SVD no usa prompts de texto: el
condicionamiento sale de la imagen via el CLIP_VISION del propio checkpoint. El
movimiento se controla con motion_bucket_id (mas alto = mas movimiento) y fps.

Funcion pura: sin red, sin I/O. Determinista para los mismos argumentos.
"""


def comfyui_build_img2vid_workflow(
    image: str,
    *,
    ckpt: str = "VIDEO_svd.safetensors",
    width: int = 1024,
    height: int = 576,
    video_frames: int = 14,
    motion_bucket_id: int = 127,
    fps: int = 6,
    augmentation_level: float = 0.0,
    steps: int = 20,
    cfg: float = 2.5,
    min_cfg: float = 1.0,
    seed: int = 0,
    sampler_name: str = "euler",
    scheduler: str = "karras",
    filename_prefix: str = "comfy_svd",
) -> dict:
    """Construye el dict del workflow img2vid (SVD) para VIDEO_svd.safetensors.

    Args:
        image: nombre del archivo de imagen base dentro de la carpeta input/ del
            servidor ComfyUI (lo que carga el nodo LoadImage). Es el frame inicial
            del que SVD deriva el clip.
        ckpt: nombre del checkpoint SVD tal como lo ve el servidor. Por defecto
            "VIDEO_svd.safetensors" (todo-en-uno: UNet + VAE + CLIP image encoder).
            keyword-only.
        width: ancho del video en px (multiplo de 8; SVD base entrena a 1024).
            keyword-only.
        height: alto del video en px (multiplo de 8; SVD base entrena a 576).
            keyword-only.
        video_frames: numero de frames del clip. VIDEO_svd.safetensors es el modelo de
            14 frames; el variante xt llega a 25. keyword-only.
        motion_bucket_id: intensidad de movimiento (1-255 util; 127 por defecto).
            Mas alto = mas movimiento. keyword-only.
        fps: frames por segundo con que se condiciona y se guarda el clip.
            keyword-only.
        augmentation_level: ruido anadido a la imagen base (0.0 = fiel a la base;
            subirlo da mas libertad/movimiento a costa de fidelidad). keyword-only.
        steps: pasos de sampling del KSampler. keyword-only.
        cfg: guidance scale del ultimo frame (VideoLinearCFGGuidance interpola de
            min_cfg al primer frame hasta cfg al ultimo). SVD usa cfg baja (~2.5).
            keyword-only.
        min_cfg: guidance scale del primer frame para VideoLinearCFGGuidance.
            keyword-only.
        seed: semilla del sampler (0 = determinista; cambiar para variar el clip).
            keyword-only.
        sampler_name: algoritmo del KSampler. Por defecto "euler". keyword-only.
        scheduler: scheduler del KSampler. Por defecto "karras". keyword-only.
        filename_prefix: prefijo del archivo de salida (.webp animado).
            keyword-only.

    Returns:
        dict en API format listo para comfyui_submit_workflow. Las claves son
        node_ids (string) y cada valor tiene class_type + inputs. Devuelve 7 nodos:
        ImageOnlyCheckpointLoader, LoadImage, SVD_img2vid_Conditioning,
        VideoLinearCFGGuidance, KSampler, VAEDecode y SaveAnimatedWEBP. El denoise
        del KSampler se fija a 1.0 (img2vid genera desde latente vacio condicionado,
        no es img2img).
    """
    return {
        "15": {
            "class_type": "ImageOnlyCheckpointLoader",
            "inputs": {"ckpt_name": ckpt},
        },
        "23": {
            "class_type": "LoadImage",
            "inputs": {"image": image},
        },
        "12": {
            "class_type": "SVD_img2vid_Conditioning",
            "inputs": {
                "clip_vision": ["15", 1],
                "init_image": ["23", 0],
                "vae": ["15", 2],
                "width": width,
                "height": height,
                "video_frames": video_frames,
                "motion_bucket_id": motion_bucket_id,
                "fps": fps,
                "augmentation_level": augmentation_level,
            },
        },
        "14": {
            "class_type": "VideoLinearCFGGuidance",
            "inputs": {"model": ["15", 0], "min_cfg": min_cfg},
        },
        "3": {
            "class_type": "KSampler",
            "inputs": {
                "seed": seed,
                "steps": steps,
                "cfg": cfg,
                "sampler_name": sampler_name,
                "scheduler": scheduler,
                "denoise": 1.0,
                "model": ["14", 0],
                "positive": ["12", 0],
                "negative": ["12", 1],
                "latent_image": ["12", 2],
            },
        },
        "8": {
            "class_type": "VAEDecode",
            "inputs": {"samples": ["3", 0], "vae": ["15", 2]},
        },
        "30": {
            "class_type": "SaveAnimatedWEBP",
            "inputs": {
                "images": ["8", 0],
                "filename_prefix": filename_prefix,
                "fps": float(fps),
                "lossless": False,
                "quality": 90,
                "method": "default",
            },
        },
    }


if __name__ == "__main__":
    import json

    wf = comfyui_build_img2vid_workflow(
        "example.png",
        motion_bucket_id=127,
        fps=6,
        video_frames=14,
        seed=42,
    )
    print(json.dumps(wf, indent=2))
