"""comfyui_export_skill_template — exporta una *skill* (receta) a un node-template/workflow ComfyUI.

Una **skill** es una receta versionada (`recipe.json` del grupo `comfyui-skill`). Esta función
impura la "congela" en dos artefactos cargables:

1. **Template API format** en `<skill_dir>/exports/<slug>.template.json` — el dict
   `{node_id: {class_type, inputs}}` que produce `comfyui_build_skill_workflow` con un
   `subject` placeholder. Es el node-template reproducible de la skill (apto para
   `comfyui_submit_workflow` o `app.loadApiJson`).
2. **UI graph posicionado** (solo con `ui_graph=True`) en la carpeta nativa de la UI
   `~/ComfyUI/user/default/workflows/<slug>.json` — el grafo con `nodes`/`links`/`pos`
   (`app.graph.serialize()`), que aparece en el menú **Workflows** del navegador y se abre como
   GRAFO visual. Se genera cargando el API format en la UI (litegraph lo auto-posiciona) y
   extrayendo el grafo serializado, vía CDP. También se guarda una copia en
   `<skill_dir>/exports/<slug>.ui.json`.

El UI graph requiere una pestaña de ComfyUI abierta en el navegador (CDP). Si el navegador no
está disponible, la función NO falla: deja escrito el template API format y devuelve un `error`
explicando cómo cargarlo a mano. La carpeta nativa de la UI solo acepta el UI graph format
(`nodes`/`links`), no el API format — por eso la copia a esa carpeta solo ocurre con `ui_graph=True`.

Función impura: hace red (CDP WebSocket, solo si `ui_graph=True`) y escribe en disco.
"""
from __future__ import annotations

import json
import os
import sys

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


class SkillTemplateError(RuntimeError):
    """Error tipado al exportar una skill a template (skill inexistente, receta inválida)."""


def _load_recipe(slug: str, library_dir: str):
    """Lee `recipe.json` de la skill. Devuelve (recipe|None, skill_dir, recipe_path)."""
    skill_dir = os.path.join(os.path.expanduser(library_dir), slug)
    recipe_path = os.path.join(skill_dir, "recipe.json")
    if not os.path.isfile(recipe_path):
        return None, skill_dir, recipe_path
    with open(recipe_path, encoding="utf-8") as fh:
        return json.load(fh), skill_dir, recipe_path


def export_skill_template(
    slug: str,
    *,
    ui_graph: bool = False,
    port: int = 9222,
    library_dir: str = "~/ComfyUI/skills_library",
    ui_workflows_dir: str = "~/ComfyUI/user/default/workflows",
    subject_placeholder: str = "{subject}",
    seed: int = 0,
    server_url_substr: str = "8188",
    timeout_s: float = 20.0,
) -> dict:
    """Exporta una skill a un node-template (API format) y, opcionalmente, a un grafo de UI.

    Args:
        slug: slug de la skill (carpeta `<library_dir>/<slug>/` con `recipe.json`).
        ui_graph: si True, además del template API carga el workflow en la UI de ComfyUI vía
            CDP, extrae el UI graph posicionado (`app.graph.serialize()`) y lo guarda en la
            carpeta nativa de la UI (cargable desde el menú Workflows del navegador). Default False.
        port: puerto de remote debugging del Chrome diario (solo si `ui_graph=True`). Default 9222.
        library_dir: raíz de la librería de skills. Default `~/ComfyUI/skills_library`.
        ui_workflows_dir: carpeta nativa de workflows de la UI de ComfyUI. Default
            `~/ComfyUI/user/default/workflows`.
        subject_placeholder: texto que sustituye `{subject}` en el scaffold del prompt del
            template. Default `"{subject}"` (se mantiene literal para que el usuario lo edite).
        seed: semilla que se fija en el template. Default 0.
        server_url_substr: substring de la URL de la pestaña de ComfyUI (default "8188").
        timeout_s: timeout de la conexión CDP en segundos.

    Returns:
        dict {ok, template_path, ui_workflow_path, error}. Campos extra: `ui_export_path` (copia
        del UI graph en `exports/`). `ok` True si el template API se escribió. Con `ui_graph=True`
        y CDP no disponible, `ok` sigue True (template escrito), `ui_workflow_path` queda None y
        `error` explica el fallback. Skill sin `recipe.json` → `ok` False.
    """
    from ml.comfyui_build_skill_workflow import SkillWorkflowError, build_skill_workflow

    recipe, skill_dir, recipe_path = _load_recipe(slug, library_dir)
    if recipe is None:
        return {
            "ok": False,
            "template_path": None,
            "ui_workflow_path": None,
            "error": f"skill sin recipe.json: {recipe_path}",
        }

    # 1. receta -> API format (subject placeholder, seed fija).
    try:
        api_workflow = build_skill_workflow(recipe, subject_placeholder, seed=seed)
    except SkillWorkflowError as exc:
        return {
            "ok": False,
            "template_path": None,
            "ui_workflow_path": None,
            "error": f"build_skill_workflow: {exc}",
        }

    # 2. escribe exports/<slug>.template.json (API format) — respeta export_template_path si existe.
    export_rel = recipe.get("export_template_path") or os.path.join("exports", f"{slug}.template.json")
    template_path = os.path.join(skill_dir, export_rel)
    os.makedirs(os.path.dirname(template_path), exist_ok=True)
    with open(template_path, "w", encoding="utf-8") as fh:
        json.dump(api_workflow, fh, ensure_ascii=False, indent=2)

    result = {
        "ok": True,
        "template_path": template_path,
        "ui_workflow_path": None,
        "error": "",
    }

    if not ui_graph:
        return result

    # 3. ui_graph=True: carga el API en la UI (litegraph posiciona) y extrae el UI graph serializado.
    from browser.comfyui_export_workflow_ui import comfyui_export_workflow_ui
    from browser.comfyui_load_workflow_ui import comfyui_load_workflow_ui

    ui_path = os.path.join(os.path.expanduser(ui_workflows_dir), f"{slug}.json")

    loaded = comfyui_load_workflow_ui(
        api_workflow,
        port=port,
        server_url_substr=server_url_substr,
        filename=f"{slug}.json",
        timeout_s=timeout_s,
    )
    if not loaded.get("ok"):
        result["error"] = (
            f"ui_graph no disponible (CDP/navegador): {loaded.get('error')}. "
            f"Template API escrito en {template_path}; cárgalo a mano arrastrándolo a la UI "
            f"de ComfyUI o con comfyui_load_workflow_ui."
        )
        return result

    exported = comfyui_export_workflow_ui(
        port=port,
        server_url_substr=server_url_substr,
        api_format=False,
        save_path=ui_path,
        timeout_s=timeout_s,
    )
    if not exported.get("ok"):
        result["error"] = (
            f"export_workflow_ui falló: {exported.get('error')}. Template API escrito en "
            f"{template_path}."
        )
        return result

    result["ui_workflow_path"] = exported.get("saved_to") or ui_path

    # Copia del UI graph posicionado junto al template API, en exports/.
    ui_export_path = os.path.join(
        skill_dir, os.path.dirname(export_rel) or "exports", f"{slug}.ui.json"
    )
    os.makedirs(os.path.dirname(ui_export_path), exist_ok=True)
    with open(ui_export_path, "w", encoding="utf-8") as fh:
        json.dump(exported.get("workflow") or {}, fh, ensure_ascii=False, indent=2)
    result["ui_export_path"] = ui_export_path

    return result


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


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Exporta una skill ComfyUI a template/grafo.")
    parser.add_argument("slug", nargs="?", default="portrait_cinematic_sd15")
    parser.add_argument("--ui-graph", action="store_true", help="además genera el UI graph vía CDP")
    parser.add_argument("--port", type=int, default=9222)
    parser.add_argument("--library-dir", default="~/ComfyUI/skills_library")
    args = parser.parse_args()

    out = export_skill_template(
        args.slug, ui_graph=args.ui_graph, port=args.port, library_dir=args.library_dir
    )
    print(json.dumps(out, ensure_ascii=False, indent=2))
