"""comfyui_update_skill_score — acumula el score de un juicio en una *skill* ComfyUI.

Cada vez que el panel `comfyui-judge` puntúa una imagen generada por una skill, esta
función incorpora ese score a la media acumulada de la receta (``score_mean`` / ``score_n``)
mediante **media incremental**, reescribiendo ``recipe.json`` en sitio.

A diferencia de `comfyui_save_skill`, NO crea un snapshot ``versions/vN.json`` ni añade
entrada al ``growth_log``: el score es telemetría acumulada de la versión vigente, no una
versión nueva. Los snapshots y el growth_log los reserva `comfyui_bump_skill_version` para
promociones reales (cuando el score mejora y se sube el semver). Así ``versions/`` refleja
versiones de receta y no se ensucia con una entrada por cada generación.

`library_dir` por defecto ``~/ComfyUI/skills_library``. Slug inexistente → ``{ok: False}``.

Impura: lee y reescribe ``recipe.json`` en disco. Sin red.
"""

import json
import os

DEFAULT_LIBRARY = "~/ComfyUI/skills_library"


def _lib_dir(library_dir):
    return os.path.expanduser(library_dir or DEFAULT_LIBRARY)


def comfyui_update_skill_score(
    slug: str,
    new_score: float,
    *,
    library_dir: str = None,
) -> dict:
    """Acumula ``new_score`` en la media de la skill (media incremental, in-place).

    Args:
        slug: slug de la skill (su carpeta en la librería).
        new_score: score 0-10 del último juicio (p.ej. ``comfyui_judge_image(...)["score"]``).
        library_dir: raíz de la librería. Default ``~/ComfyUI/skills_library``. keyword-only.

    Returns:
        dict ``{ok, slug, score_mean, score_n, prev_score_mean, prev_score_n, error}``.
        En éxito ``ok=True`` y ``score_mean``/``score_n`` son los valores actualizados. Si
        la skill no existe, el score no es numérico, o falla la escritura, ``ok=False`` con
        ``error``; nunca lanza.
    """
    if not slug or not isinstance(slug, str):
        return {"ok": False, "slug": slug, "score_mean": 0.0, "score_n": 0,
                "prev_score_mean": 0.0, "prev_score_n": 0,
                "error": "slug requerido (string no vacío)"}
    try:
        score = float(new_score)
    except (TypeError, ValueError):
        return {"ok": False, "slug": slug, "score_mean": 0.0, "score_n": 0,
                "prev_score_mean": 0.0, "prev_score_n": 0,
                "error": f"new_score debe ser numérico (recibido {new_score!r})"}

    lib = _lib_dir(library_dir)
    recipe_path = os.path.join(lib, slug, "recipe.json")
    if not os.path.isfile(recipe_path):
        return {"ok": False, "slug": slug, "score_mean": 0.0, "score_n": 0,
                "prev_score_mean": 0.0, "prev_score_n": 0,
                "error": f"skill no encontrada: {slug!r} (sin recipe.json)"}

    try:
        with open(recipe_path, encoding="utf-8") as fh:
            recipe = json.load(fh)
    except (OSError, json.JSONDecodeError) as exc:
        return {"ok": False, "slug": slug, "score_mean": 0.0, "score_n": 0,
                "prev_score_mean": 0.0, "prev_score_n": 0,
                "error": f"no se pudo leer la receta: {exc}"}

    try:
        prev_mean = float(recipe.get("score_mean", 0.0) or 0.0)
        prev_n = int(recipe.get("score_n", 0) or 0)
    except (TypeError, ValueError):
        prev_mean, prev_n = 0.0, 0

    new_n = prev_n + 1
    # Media incremental: mean_k = mean_{k-1} + (x_k - mean_{k-1}) / k.
    new_mean = prev_mean + (score - prev_mean) / new_n
    recipe["score_mean"] = round(new_mean, 4)
    recipe["score_n"] = new_n

    try:
        with open(recipe_path, "w", encoding="utf-8") as fh:
            json.dump(recipe, fh, indent=2, ensure_ascii=False)
    except OSError as exc:
        return {"ok": False, "slug": slug, "score_mean": prev_mean, "score_n": prev_n,
                "prev_score_mean": prev_mean, "prev_score_n": prev_n,
                "error": f"fallo de escritura: {exc}"}

    return {"ok": True, "slug": slug, "score_mean": recipe["score_mean"],
            "score_n": new_n, "prev_score_mean": prev_mean, "prev_score_n": prev_n,
            "error": ""}


# Alias con el nombre completo del ID para descubrimiento por convención.
update_skill_score = comfyui_update_skill_score


if __name__ == "__main__":
    import sys

    demo_lib = "/tmp/skills_score_demo"
    sdir = os.path.join(demo_lib, "demo_skill")
    os.makedirs(sdir, exist_ok=True)
    with open(os.path.join(sdir, "recipe.json"), "w", encoding="utf-8") as f:
        json.dump({"schema_version": 1, "slug": "demo_skill", "version": "1.0.0",
                   "base_workflow": "txt2img", "score_mean": 0.0, "score_n": 0}, f, indent=2)

    for s in (7.0, 8.0, 6.0):
        res = comfyui_update_skill_score("demo_skill", s, library_dir=demo_lib)
        print(f"score={s} -> mean={res['score_mean']} n={res['score_n']}", file=sys.stderr)
    print(json.dumps(res, indent=2, ensure_ascii=False))
