"""comfyui_export_asset_to_godot — lleva un asset generado a un proyecto Godot 4.

Pipeline del puente ComfyUI -> Godot (docs/comfyui-godot-integration.md): copia un
asset salido de `~/ComfyUI/output/` a la subcarpeta correcta de un proyecto Godot
(`res://assets/{sprites,tilesets,vfx,audio/{sfx,music},models}/` segun `kind`),
escribe el archivo `.import` adecuado al tipo (textura / audio / escena glTF) con
los settings clave, asegura el filtro Nearest del proyecto si el asset es pixelart,
y lanza un reimport headless si el binario de Godot esta disponible.

Compone las funciones puras del registry:
    godot_map_asset_dir_py_core    (kind -> subcarpeta)
    godot_clean_asset_name_py_core (nombre de origen -> snake_case seguro)

Y orquesta el I/O especifico del puente (copia, .import, project.godot, reimport).

Seguridad: NO toca ningun proyecto salvo el `godot_project` que se le pasa
explicitamente. Aborta si ese directorio no es un proyecto Godot (sin
`project.godot`). Idempotente: preserva el `uid://` de un `.import` ya existente
(no rompe referencias de escenas en uso). Impuro: lee/escribe disco + subprocess
(reimport).
"""

from __future__ import annotations

import os
import re
import shutil
import subprocess
import sys

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

from core.godot_clean_asset_name import godot_clean_asset_name
from core.godot_map_asset_dir import godot_map_asset_dir

# Tipos que importan como textura 2D.
_TEXTURE_KINDS = {"sprite", "pixelart", "tileset", "vfx"}
# Candidatos de binario Godot 4 (autodeteccion).
_GODOT_CANDIDATES = [
    "godot", "godot4",
    os.path.expanduser("~/godot/Godot_v4.7-stable_linux.x86_64"),
]
_UID_RE = re.compile(r'^uid="(uid://[^"]+)"', re.MULTILINE)


def _find_godot_bin(godot_bin: str | None) -> str | None:
    """Resuelve el binario de Godot: arg explicito -> PATH -> rutas conocidas."""
    if godot_bin:
        return godot_bin if (os.path.isfile(godot_bin) or shutil.which(godot_bin)) else None
    for cand in _GODOT_CANDIDATES:
        found = cand if os.path.isfile(cand) else shutil.which(cand)
        if found:
            return found
    return None


def _existing_uid(import_path: str) -> str | None:
    """Lee el uid:// de un .import ya presente (idempotencia), si lo hay."""
    if not os.path.isfile(import_path):
        return None
    try:
        with open(import_path, encoding="utf-8") as fh:
            m = _UID_RE.search(fh.read())
        return m.group(1) if m else None
    except OSError:
        return None


def _texture_import(res_path: str, pixelart: bool, uid: str | None) -> str:
    """Genera el .import de una textura 2D (Lossless, mipmaps off)."""
    uid_line = f'uid="{uid}"\n' if uid else ""
    return (
        "[remap]\n\n"
        'importer="texture"\n'
        f"{uid_line}"
        'type="CompressedTexture2D"\n\n'
        "[deps]\n\n"
        f'source_file="{res_path}"\n\n'
        "[params]\n\n"
        "compress/mode=0\n"            # 0 = Lossless (correcto para pixelart/2D)
        "mipmaps/generate=false\n"
        "process/fix_alpha_border=true\n"
        "detect_3d/compress_to=1\n"
    )


def _audio_import(res_path: str, kind: str, uid: str | None) -> str:
    """Genera el .import de audio: wav (loop segun kind) u ogg (loop segun kind)."""
    loop = kind == "music"
    ext = os.path.splitext(res_path)[1].lower()
    uid_line = f'uid="{uid}"\n' if uid else ""
    if ext == ".ogg":
        return (
            "[remap]\n\n"
            'importer="oggvorbisstr"\n'
            f"{uid_line}"
            'type="AudioStreamOggVorbis"\n\n'
            "[deps]\n\n"
            f'source_file="{res_path}"\n\n'
            "[params]\n\n"
            f"loop={'true' if loop else 'false'}\n"
        )
    return (
        "[remap]\n\n"
        'importer="wav"\n'
        f"{uid_line}"
        'type="AudioStreamWAV"\n\n'
        "[deps]\n\n"
        f'source_file="{res_path}"\n\n'
        "[params]\n\n"
        f"edit/loop_mode={1 if loop else 0}\n"   # 0=Disabled (sfx), 1=Forward (musica)
        "force/mono=false\n"
    )


def _scene_import(res_path: str, uid: str | None) -> str:
    """Genera el .import de una malla glTF/GLB (escena PackedScene)."""
    uid_line = f'uid="{uid}"\n' if uid else ""
    return (
        "[remap]\n\n"
        'importer="scene"\n'
        f"{uid_line}"
        'importer_version=1\n'
        'type="PackedScene"\n\n'
        "[deps]\n\n"
        f'source_file="{res_path}"\n\n'
        "[params]\n\n"
        "nodes/root_type=\"\"\n"
    )


def _ensure_pixelart_filter(project_godot: str) -> bool:
    """Asegura default_texture_filter=0 (Nearest) en project.godot. Idempotente.

    Returns True si el archivo quedo con el filtro Nearest (ya lo tenia o se anadio).
    """
    key = "rendering/textures/canvas_textures/default_texture_filter"
    try:
        with open(project_godot, encoding="utf-8") as fh:
            text = fh.read()
    except OSError:
        return False

    if re.search(rf"^{re.escape(key)}=0\s*$", text, re.MULTILINE):
        return True  # ya esta en Nearest
    # Reemplaza un valor existente distinto de 0, o anade la clave a [rendering].
    if re.search(rf"^{re.escape(key)}=", text, re.MULTILINE):
        text = re.sub(rf"^{re.escape(key)}=.*$", f"{key}=0", text, flags=re.MULTILINE)
    elif re.search(r"^\[rendering\]\s*$", text, re.MULTILINE):
        text = re.sub(r"^\[rendering\]\s*$", f"[rendering]\n\n{key}=0", text,
                      count=1, flags=re.MULTILINE)
    else:
        text = text.rstrip() + f"\n\n[rendering]\n\n{key}=0\n"
    try:
        with open(project_godot, "w", encoding="utf-8") as fh:
            fh.write(text)
        return True
    except OSError:
        return False


def comfyui_export_asset_to_godot(
    asset_path: str,
    kind: str,
    godot_project: str,
    *,
    name: str | None = None,
    reimport: bool = True,
    godot_bin: str | None = None,
) -> dict:
    """Exporta un asset generado a un proyecto Godot con su .import correcto.

    Args:
        asset_path: ruta del asset de origen (p.ej. en ~/ComfyUI/output/).
        kind: tipo de asset: "sprite", "pixelart", "tileset", "vfx", "sfx",
            "music" o "model".
        godot_project: ruta raiz del proyecto Godot destino (debe contener
            project.godot).
        name: nombre base deseado para el archivo destino (sin extension); si
            None se deriva del origen (snake_case sin sufijo _NNNNN_). keyword-only.
        reimport: si True intenta un reimport headless con el binario de Godot.
            keyword-only.
        godot_bin: ruta del binario de Godot; None autodetecta (PATH y rutas
            conocidas). keyword-only.

    Returns:
        dict con:
        - ok (bool)
        - dest_res_path (str): ruta "res://assets/.../<n>.<ext>".
        - dest_abs_path (str): ruta absoluta en disco del asset copiado.
        - import_path (str): ruta absoluta del .import escrito.
        - import_written (bool)
        - pixelart_filter_set (bool): True si se aseguro Nearest en project.godot.
        - reimported (bool): True si el reimport headless salio con exit 0.
        - warnings (list[str])
        - error (str): vacio si OK.
    """
    out = {
        "ok": False, "dest_res_path": "", "dest_abs_path": "", "import_path": "",
        "import_written": False, "pixelart_filter_set": False, "reimported": False,
        "warnings": [], "error": "",
    }

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

    project_godot = os.path.join(godot_project, "project.godot")
    if not os.path.isfile(project_godot):
        out["error"] = f"no es un proyecto Godot (falta project.godot): {godot_project!r}"
        return out

    try:
        subdir = godot_map_asset_dir(kind)
    except ValueError as exc:
        out["error"] = str(exc)
        return out

    clean = godot_clean_asset_name(asset_path, override=name)
    dest_dir_abs = os.path.join(godot_project, "assets", subdir)
    dest_abs = os.path.join(dest_dir_abs, clean)
    res_path = f"res://assets/{subdir}/{clean}"
    import_abs = dest_abs + ".import"

    # 1. Copiar el asset.
    try:
        os.makedirs(dest_dir_abs, exist_ok=True)
        shutil.copy2(asset_path, dest_abs)
    except OSError as exc:
        out["error"] = f"no se pudo copiar el asset: {exc}"
        return out
    out["dest_abs_path"] = dest_abs
    out["dest_res_path"] = res_path

    # 2. Escribir el .import segun el tipo (preservando uid existente).
    kind_l = kind.strip().lower()
    uid = _existing_uid(import_abs)
    if kind_l in _TEXTURE_KINDS:
        content = _texture_import(res_path, pixelart=(kind_l == "pixelart"), uid=uid)
        if kind_l in ("tileset", "vfx"):
            out["warnings"].append(
                f"{kind_l}: textura copiada; Godot no deriva el TileSet/SpriteFrames "
                "solo (crear el .tres en editor o por script)."
            )
    elif kind_l in ("sfx", "music"):
        content = _audio_import(res_path, kind_l, uid=uid)
    elif kind_l == "model":
        content = _scene_import(res_path, uid=uid)
    else:  # no deberia pasar (map_asset_dir ya valido), defensa.
        out["error"] = f"kind sin importer: {kind!r}"
        return out

    try:
        with open(import_abs, "w", encoding="utf-8") as fh:
            fh.write(content)
        out["import_written"] = True
        out["import_path"] = import_abs
    except OSError as exc:
        out["error"] = f"no se pudo escribir el .import: {exc}"
        return out

    # 3. Filtro Nearest del proyecto si es pixelart (mecanismo Godot 4: global).
    if kind_l == "pixelart":
        if _ensure_pixelart_filter(project_godot):
            out["pixelart_filter_set"] = True
        else:
            out["warnings"].append(
                "no se pudo asegurar default_texture_filter=0 en project.godot"
            )

    # 4. Reimport headless (best-effort). Sin binario -> deja el .import y anota.
    if reimport:
        gbin = _find_godot_bin(godot_bin)
        if gbin is None:
            out["warnings"].append(
                "Godot CLI no encontrado: .import escrito, reimport pendiente "
                "(abre el editor o pasa godot_bin)."
            )
        else:
            try:
                proc = subprocess.run(
                    [gbin, "--headless", "--path", godot_project, "--import"],
                    capture_output=True, text=True, timeout=180,
                )
                out["reimported"] = proc.returncode == 0
                if proc.returncode != 0:
                    tail = (proc.stderr or proc.stdout or "").strip().splitlines()[-3:]
                    out["warnings"].append(
                        f"reimport exit {proc.returncode}: {' / '.join(tail)}"
                    )
            except subprocess.TimeoutExpired:
                out["warnings"].append("reimport headless excedio el timeout (180s)")
            except OSError as exc:
                out["warnings"].append(f"reimport headless fallo: {exc}")

    out["ok"] = True
    return out


if __name__ == "__main__":
    import json

    if len(sys.argv) < 4:
        print("uso: comfyui_export_asset_to_godot.py <asset> <kind> <godot_project> [name]",
              file=sys.stderr)
        sys.exit(2)
    a, k, p = sys.argv[1], sys.argv[2], sys.argv[3]
    nm = sys.argv[4] if len(sys.argv) > 4 else None
    print(json.dumps(comfyui_export_asset_to_godot(a, k, p, name=nm), indent=2))
