"""Extrae el grafo de nodos de un workflow template oficial de ComfyUI por su nombre.

Funcion impura: lee disco (el .json del template instalado) ejecutando la API oficial
del paquete comfyui-workflow-templates dentro del interprete de ComfyUI.

Dado el nombre de un template (su template_id, p.ej. "image_sdxl" o
"api_bfl_flux2_max_sofa_swap"), devuelve:
  - graph: el dict completo del .json (formato UI: nodes/links con posiciones).
  - class_types: la lista de tipos de nodo (class_type) que usa, aplanando los
    subgrafos de `definitions` si los hay.
  - format: "ui_graph" (lo normal en los templates) o "api".
  - assets: rutas en disco de los ficheros del template (json + previews .webp).

Opcionalmente (to_api=True) intenta convertir el grafo UI a API format reutilizando
comfyui_import_workflow_json del registry. Esa conversion necesita un servidor ComfyUI
vivo para mapear los widgets a sus claves de input; si no lo hay, se devuelve el grafo
UI + class_types igualmente y se reporta el motivo en api_error (KISS: no se fuerza la
conversion de grafos complejos).

El paquete vive en el venv de ComfyUI (no en el del registry), por eso esta funcion no
lo importa: localiza el interprete de ComfyUI y le pasa un script que usa la API oficial.
"""
import json
import os
import subprocess
import sys
import tempfile

_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
if _THIS_DIR not in sys.path:
    sys.path.insert(0, _THIS_DIR)


# Script que corre DENTRO del python de ComfyUI. Resuelve un template por id, vuelca su
# grafo + metadata como JSON. Si no existe, devuelve sugerencias cercanas.
_EXTRACT_SCRIPT = r"""
import json, sys, difflib, re
try:
    import comfyui_workflow_templates_core as core
except Exception as exc:
    print(json.dumps({"__err__": "import", "msg": str(exc)}))
    sys.exit(0)

_UUID_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")

TID = json.loads({tid_json!r})
m = core.load_manifest()
if TID not in m.templates:
    near = [k for k in m.templates if TID.lower() in k.lower()][:8]
    if not near:
        near = difflib.get_close_matches(TID, list(m.templates.keys()), n=8, cutoff=0.6)
    print(json.dumps({"__err__": "not_found", "suggestions": near}))
    sys.exit(0)

entry = m.templates[TID]
json_asset = next((a.filename for a in entry.assets if a.filename.endswith(".json")), None)
if not json_asset:
    print(json.dumps({"__err__": "no_json"}))
    sys.exit(0)

path = core.get_asset_path(TID, json_asset)
with open(path, encoding="utf-8") as fh:
    graph = json.load(fh)

# Detecta formato y extrae class_types.
fmt = "unknown"
class_types = set()
has_subgraphs = False
if isinstance(graph, dict) and isinstance(graph.get("nodes"), list):
    fmt = "ui_graph"
    for n in graph["nodes"]:
        t = n.get("type") if isinstance(n, dict) else None
        if t and not _UUID_RE.match(str(t)):
            class_types.add(t)
    defs = graph.get("definitions")
    if isinstance(defs, dict) and isinstance(defs.get("subgraphs"), list):
        for sg in defs["subgraphs"]:
            for n in (sg.get("nodes") or []) if isinstance(sg, dict) else []:
                if isinstance(n, dict) and n.get("type"):
                    has_subgraphs = True
                    if not _UUID_RE.match(str(n["type"])):
                        class_types.add(n["type"])
elif isinstance(graph, dict):
    fmt = "api"
    for v in graph.values():
        if isinstance(v, dict) and v.get("class_type"):
            class_types.add(v["class_type"])

print(json.dumps({
    "graph": graph,
    "class_types": sorted(class_types),
    "format": fmt,
    "has_subgraphs": has_subgraphs,
    "bundle": entry.bundle,
    "version": entry.version,
    "assets": core.resolve_all_assets(TID),
    "json_path": path,
}))
"""


def _find_comfyui_python(explicit: str | None) -> str | None:
    """Localiza un interprete de ComfyUI con el paquete instalado (ver list_templates)."""
    candidates = []
    if explicit:
        candidates.append(os.path.expanduser(explicit))
    env = os.environ.get("COMFYUI_PYTHON")
    if env:
        candidates.append(os.path.expanduser(env))
    candidates += [
        os.path.expanduser("~/ComfyUI/.venv/bin/python"),
        os.path.expanduser("~/ComfyUI/venv/bin/python"),
        os.path.expanduser("~/comfyui/.venv/bin/python"),
        sys.executable,
    ]
    for c in candidates:
        if c and os.path.isfile(c):
            return c
    return None


def comfyui_extract_template(
    name: str,
    comfyui_python: str | None = None,
    to_api: bool = False,
    server: str = "127.0.0.1:8188",
) -> dict:
    """Extrae el grafo y los class_types de un template oficial de ComfyUI por nombre.

    Args:
        name: template_id exacto del template (p.ej. "image_sdxl"). Usa
            comfyui_list_templates para ver los nombres disponibles.
        comfyui_python: ruta al interprete python de ComfyUI con el paquete
            comfyui-workflow-templates. Si None, se autodetecta.
        to_api: si True, intenta convertir el grafo UI a API format reutilizando
            comfyui_import_workflow_json (requiere un servidor ComfyUI vivo en
            `server`). Si la conversion falla, se devuelve el grafo UI igualmente y
            el motivo va en api_error.
        server: host:port del servidor ComfyUI para la conversion to_api.

    Returns:
        dict {ok, name, format, class_types, has_subgraphs, n_nodes, graph,
        api_workflow, api_error, bundle, version, assets, error}:
          - graph: el dict del template en formato UI (o API si ya lo estaba).
          - class_types: lista ordenada de tipos de nodo del grafo (incluye los de
            subgrafos de `definitions`).
          - api_workflow: dict en API format si to_api tuvo exito, si no {}.
        Nunca lanza. Nombre inexistente -> ok=False con error legible + sugerencias.
    """
    py = _find_comfyui_python(comfyui_python)
    base = {
        "ok": False,
        "name": name,
        "format": "",
        "class_types": [],
        "has_subgraphs": False,
        "n_nodes": 0,
        "graph": {},
        "api_workflow": {},
        "api_error": "",
        "bundle": "",
        "version": "",
        "assets": [],
        "error": "",
    }
    if not py:
        base["error"] = (
            "no se encontro un interprete de ComfyUI. Pasa comfyui_python=... o "
            "define COMFYUI_PYTHON. Instala el paquete con: "
            "pip install comfyui-workflow-templates"
        )
        return base

    script = _EXTRACT_SCRIPT.replace("{tid_json!r}", repr(json.dumps(name)))
    try:
        proc = subprocess.run(
            [py, "-c", script],
            capture_output=True,
            text=True,
            timeout=60,
        )
    except Exception as exc:  # noqa: BLE001
        base["error"] = f"fallo al ejecutar el interprete de ComfyUI ({py}): {exc}"
        return base

    if proc.returncode != 0:
        base["error"] = f"el interprete de ComfyUI fallo: {proc.stderr.strip()[:500]}"
        return base

    try:
        data = json.loads(proc.stdout.strip().splitlines()[-1])
    except Exception as exc:  # noqa: BLE001
        base["error"] = f"salida no parseable del interprete de ComfyUI: {exc}"
        return base

    err = data.get("__err__")
    if err == "import":
        base["error"] = (
            f"el paquete comfyui-workflow-templates no esta instalado en {py} "
            f"({data.get('msg', '')}). Instalalo con: "
            "pip install comfyui-workflow-templates"
        )
        return base
    if err == "not_found":
        sug = data.get("suggestions", [])
        hint = f" ¿Quizas: {', '.join(sug)}?" if sug else ""
        base["error"] = f"template '{name}' no existe en el paquete.{hint}"
        return base
    if err == "no_json":
        base["error"] = f"el template '{name}' no tiene asset .json."
        return base

    graph = data.get("graph", {})
    fmt = data.get("format", "")
    nodes = graph.get("nodes") if isinstance(graph, dict) else None
    n_nodes = len(nodes) if isinstance(nodes, list) else (
        len(graph) if fmt == "api" and isinstance(graph, dict) else 0
    )

    out = {
        "ok": True,
        "name": name,
        "format": fmt,
        "class_types": data.get("class_types", []),
        "has_subgraphs": data.get("has_subgraphs", False),
        "n_nodes": n_nodes,
        "graph": graph,
        "api_workflow": {},
        "api_error": "",
        "bundle": data.get("bundle", ""),
        "version": data.get("version", ""),
        "assets": data.get("assets", []),
        "error": "",
    }

    if to_api:
        if fmt == "api":
            out["api_workflow"] = graph
        else:
            out["api_workflow"], out["api_error"] = _convert_to_api(graph, server)

    return out


def _convert_to_api(graph: dict, server: str) -> tuple[dict, str]:
    """Convierte un grafo UI a API format via comfyui_import_workflow_json del registry.

    Requiere un servidor ComfyUI vivo para mapear widgets. Devuelve (workflow, "")
    si tuvo exito o ({}, motivo) si fallo. No lanza.
    """
    try:
        from comfyui_import_workflow_json import comfyui_import_workflow_json
    except Exception as exc:  # noqa: BLE001
        return {}, f"no se pudo importar comfyui_import_workflow_json: {exc}"

    tmp = None
    try:
        with tempfile.NamedTemporaryFile(
            "w", suffix=".json", delete=False, encoding="utf-8"
        ) as fh:
            json.dump(graph, fh)
            tmp = fh.name
        res = comfyui_import_workflow_json(tmp, server=server)
        if res.get("ok"):
            return res.get("workflow", {}), ""
        return {}, (
            res.get("error", "conversion fallida")
            + f" (requiere un servidor ComfyUI vivo en {server})"
        )
    except Exception as exc:  # noqa: BLE001
        return {}, f"conversion to_api fallida: {exc}"
    finally:
        if tmp and os.path.exists(tmp):
            try:
                os.unlink(tmp)
            except OSError:
                pass


if __name__ == "__main__":
    import argparse

    ap = argparse.ArgumentParser(description="Extrae el grafo de un template ComfyUI")
    ap.add_argument("name", help="template_id (ver comfyui_list_templates)")
    ap.add_argument("--comfyui-python", default=None)
    ap.add_argument("--to-api", action="store_true")
    ap.add_argument("--server", default="127.0.0.1:8188")
    ap.add_argument("--full", action="store_true", help="incluye el grafo entero")
    args = ap.parse_args()

    res = comfyui_extract_template(
        args.name,
        args.comfyui_python,
        to_api=args.to_api,
        server=args.server,
    )
    if args.full or not res["ok"]:
        print(json.dumps(res, indent=2, ensure_ascii=False))
    else:
        slim = {k: v for k, v in res.items() if k != "graph"}
        slim["graph_keys"] = list(res["graph"].keys()) if isinstance(res["graph"], dict) else []
        print(json.dumps(slim, indent=2, ensure_ascii=False))
