"""comfyui_apply_style_preset — traduce un style preset gamedev a los kwargs de un builder.

Toma una receta de estilo (de `comfyui_get_gamedev_style_preset`) y un `subject` del usuario
y produce, de forma PURA, lo que un builder de sujeto del grupo `gamedev-2d` necesita:

  - el `subject` combinado con el prefijo/sufijo del estilo,
  - los kwargs comunes a todos los builders de sujeto (`style`, `checkpoint`, `lora`,
    `lora_strength`, `negative`) listos para hacer `**spread`,
  - la resolucion y el recorte recomendados (`size`, `transparent`),
  - y la spec de post-proceso (`post`, p.ej. pixelize) que el caller aplica al PNG resultante.

Asi el mismo estilo se aplica a CUALQUIER builder de sujeto (item_icon, enemy_creature,
prop_object, structure, ...) sin acoplar este helper a sus firmas, y el preset elige el
checkpoint/lora coherentes ANTES de construir el grafo.

Patron de uso:

    preset = comfyui_get_gamedev_style_preset("gameboy")
    ap = comfyui_apply_style_preset(preset, "knight character")
    wf = comfyui_build_enemy_creature_workflow(
        ap["subject"], size=ap["size"], transparent=ap["transparent"], **ap["builder_kwargs"]
    )
    # tras submit/wait/fetch, si ap["post"].get("pixelize"):
    #     comfyui_pixelize_image(raw_png, dst_png, **ap["post"]["pixelize"])

Funcion pura: sin red, sin I/O. No muta el preset de entrada (copia lo que devuelve).
"""
from __future__ import annotations

import copy

# Claves obligatorias de una receta valida (las que produce comfyui_get_gamedev_style_preset).
_REQUIRED = (
    "name",
    "subject_prefix",
    "subject_suffix",
    "style",
    "negative",
    "checkpoint",
    "lora",
    "lora_strength",
    "size",
    "transparent",
    "post",
)


def _merge_negative(a: str, b: str) -> str:
    """Une dos negativos por comas sin duplicar terminos ni dejar comas sueltas."""
    seen: list[str] = []
    for chunk in (a or "", b or ""):
        for term in chunk.split(","):
            t = term.strip()
            if t and t.lower() not in {s.lower() for s in seen}:
                seen.append(t)
    return ", ".join(seen)


def comfyui_apply_style_preset(
    preset: dict,
    subject: str,
    *,
    style: str | None = None,
    negative: str | None = None,
) -> dict:
    """Aplica un style preset a un subject y devuelve los kwargs listos para un builder.

    Args:
        preset: receta de estilo (dict de comfyui_get_gamedev_style_preset). Debe traer
            los campos del preset; se valida que esten presentes. No se muta.
        subject: lo que el usuario quiere generar (ej. "knight character", "health potion").
            Se combina con el prefijo/sufijo del estilo. No puede estar vacio.
        style: si se pasa, sustituye al `style` del preset (override puntual). None usa el
            del preset. keyword-only.
        negative: negativo extra del caller; se MERGEA con el negativo del estilo (no lo
            reemplaza). None = solo el del estilo. keyword-only.

    Returns:
        dict con:
        - "name" (str): nombre del estilo aplicado.
        - "subject" (str): subject combinado con prefijo/sufijo del estilo.
        - "builder_kwargs" (dict): {style, checkpoint, lora, lora_strength, negative} —
          los kwargs comunes a los builders de sujeto, para hacer **spread.
        - "size" (int): resolucion recomendada por el estilo.
        - "transparent" (bool): recorte a alpha recomendado por el estilo.
        - "post" (dict): post-proceso CPU a aplicar al PNG ({"pixelize": {...}} o {}).

    Raises:
        ValueError: si subject esta vacio o el preset no trae los campos requeridos.
    """
    if not subject or not subject.strip():
        raise ValueError("comfyui_apply_style_preset: 'subject' no puede estar vacio")
    if not isinstance(preset, dict):
        raise ValueError("comfyui_apply_style_preset: 'preset' debe ser un dict")
    missing = [k for k in _REQUIRED if k not in preset]
    if missing:
        raise ValueError(
            f"comfyui_apply_style_preset: preset incompleto, faltan campos {missing}. "
            "Usa comfyui_get_gamedev_style_preset para obtener una receta valida."
        )

    subject_full = (
        f"{preset['subject_prefix']}{subject.strip()}{preset['subject_suffix']}"
    ).strip().strip(",").strip()

    style_final = style if style is not None else preset["style"]
    neg_final = _merge_negative(preset["negative"], negative or "")

    return {
        "name": preset["name"],
        "subject": subject_full,
        "builder_kwargs": {
            "style": style_final,
            "checkpoint": preset["checkpoint"],
            "lora": preset["lora"],
            "lora_strength": preset["lora_strength"],
            "negative": neg_final,
        },
        "size": preset["size"],
        "transparent": preset["transparent"],
        "post": copy.deepcopy(preset["post"]),
    }


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

    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from ml.comfyui_get_gamedev_style_preset import comfyui_get_gamedev_style_preset

    p = comfyui_get_gamedev_style_preset("pixel-art-retro")
    ap = comfyui_apply_style_preset(p, "knight character")
    print(json.dumps(ap, indent=2, ensure_ascii=False))
