"""comfyui_flatten_alpha_on_color — aplana un PNG con alpha sobre un fondo solido.

Compone una imagen con canal alpha (tipicamente un sprite ya recortado, fondo
transparente) sobre un fondo de color SOLIDO y opaco, produciendo un PNG RGB sin
transparencia. Sirve para preparar la entrada de modelos 3D / multivista (SV3D,
Stable Zero123, Hunyuan3D), que esperan el sujeto sobre un fondo limpio y opaco
(normalmente blanco), no sobre alpha 0.

Por que importa: el `LoadImage` de ComfyUI hace `convert("RGB")` y DESCARTA el
canal alpha, mostrando los RGB crudos que habia debajo de la transparencia. Esos
RGB suelen ser basura (negro residual, halos, restos del fondo original) -> la
reconstruccion 3D sale mala. Aplanar antes garantiza un fondo controlado.

La funcion es impura solo por la lectura/escritura de disco. Sin red, sin GPU,
sin servidor ComfyUI. Solo Pillow.
"""

import os

_RESAMPLE = {
    "lanczos": "LANCZOS",
    "nearest": "NEAREST",
    "bilinear": "BILINEAR",
    "bicubic": "BICUBIC",
    "area": "BOX",
}


def comfyui_flatten_alpha_on_color(
    image_path: str,
    *,
    out_path: str | None = None,
    color: tuple = (255, 255, 255),
    size: int | None = None,
    resample: str = "lanczos",
) -> dict:
    """Aplana un PNG con alpha sobre un fondo de color solido -> PNG RGB.

    Args:
        image_path: ruta del PNG de entrada (idealmente con alpha; un sprite ya
            recortado). Se expande `~`. Si no existe devuelve ok=False sin lanzar.
        out_path: ruta del PNG RGB de salida; si None se escribe junto al original
            con sufijo "_flat" antes de la extension. Se crea el directorio
            destino si falta. keyword-only.
        color: color de fondo (r, g, b) en 0..255. Si llega (r, g, b, a) se ignora
            el alpha entrante (el fondo siempre es opaco). Por defecto blanco.
            keyword-only.
        size: si no es None, redimensiona el resultado a size x size (cuadrado),
            util para encajar la base al tamano nativo del modelo 3D (SV3D 576,
            Zero123 256). keyword-only.
        resample: filtro de redimension ("lanczos" por defecto, "nearest",
            "bilinear", "bicubic", "area"). String desconocido -> LANCZOS.
            keyword-only.

    Returns:
        dict con:
        - ok (bool): True si se compuso y guardo.
        - out_path (str): ruta del PNG RGB generado (vacio si error).
        - size (list[int]): [w, h] final del resultado (ausente si error temprano).
        - error (str): mensaje de error; cadena vacia si todo OK.
    """
    out = {"ok": False, "out_path": "", "size": [0, 0], "error": ""}

    try:
        from PIL import Image
    except ImportError as exc:
        out["error"] = f"falta dependencia (PIL): {exc}"
        return out

    src_path = os.path.expanduser(image_path)
    if not os.path.isfile(src_path):
        out["error"] = f"imagen no existe: {src_path!r}"
        return out

    # Color de fondo opaco: usa solo (r, g, b), descarta cualquier alpha entrante.
    rgb = tuple(int(c) for c in color[:3])
    if len(rgb) != 3:
        out["error"] = f"color debe tener al menos 3 componentes (r,g,b), recibido {color!r}"
        return out
    bg_rgba = rgb + (255,)

    resample_const = getattr(Image, _RESAMPLE.get(str(resample).lower(), "LANCZOS"))

    try:
        with Image.open(src_path) as src:
            img = src.convert("RGBA")
        canvas = Image.new("RGBA", img.size, bg_rgba)
        flat = Image.alpha_composite(canvas, img).convert("RGB")

        if size is not None:
            flat = flat.resize((int(size), int(size)), resample_const)

        if out_path is None:
            base, ext = os.path.splitext(src_path)
            dst_path = base + "_flat" + (ext if ext.lower() == ".png" else ".png")
        else:
            dst_path = os.path.expanduser(out_path)

        dst_dir = os.path.dirname(os.path.abspath(dst_path))
        os.makedirs(dst_dir, exist_ok=True)
        flat.save(dst_path, "PNG")
    except (OSError, ValueError) as exc:
        out["error"] = str(exc)
        return out
    except Exception as exc:  # pragma: no cover - Pillow puede lanzar tipos varios
        out["error"] = str(exc)
        return out

    out.update(ok=True, out_path=dst_path, size=list(flat.size), error="")
    return out


if __name__ == "__main__":
    import json
    import sys

    if len(sys.argv) < 2:
        print("uso: comfyui_flatten_alpha_on_color.py <sprite_con_alpha> [out] [size]",
              file=sys.stderr)
        sys.exit(2)
    src = sys.argv[1]
    dst = sys.argv[2] if len(sys.argv) > 2 else None
    sz = int(sys.argv[3]) if len(sys.argv) > 3 else None
    print(json.dumps(comfyui_flatten_alpha_on_color(src, out_path=dst, size=sz), indent=2))
