"""
Ortak yardımcılar — defansif tip dönüşümleri, proje dizini yönetimi,
sürüm/restore, view URL helper'ları, HuggingFace download utilities.

Tüm modüller (audio, llm, character, visual, project, endpoints) buradan
import eder. Gövde büyük ölçüde `_legacy` modülünde; bu dosya konsolide
re-export sağlar + Sprint 1+ yeni utilities barındırır.
"""
import os
import urllib.request


from ._legacy import (
    CATEGORY,
    PROJECT_BASE_DIR,
    _safe_int,
    _safe_float,
    _safe_bool,
    _ffmpeg_bin,
    _slug,
    _ensure_project_dir,
    _load_project_meta,
    _save_project_meta,
    _list_project_versions,
    _restore_project_version,
    _rel_to_output,
    _view_url,
    _fetch_ollama_models,
)


def hf_download(repo_id, filename, dest_dir, progress_cb=None, sha256=None,
                revision="main", resume=True):
    """HuggingFace dosya indirici (Sprint 5'te SHA-check + resume + progress doluyor).

    Sprint 1 stub: basit urlretrieve. PuLID install kodu Sprint 5'te bu fonksiyona
    refaktör edilecek (bkz. _legacy.py:7185-7225 antelopev2/eva-clip indirme blokları).

    Args:
        repo_id: "InstantX/InstantID" gibi
        filename: "ip-adapter.bin"
        dest_dir: hedef klasör (yoksa oluşturulur)
        progress_cb: callable(downloaded_bytes, total_bytes) — Sprint 5
        sha256: hex hash — Sprint 5
        revision: branch/tag

    Dönüş: dest_path (str)
    Hata: RuntimeError
    """
    os.makedirs(dest_dir, exist_ok=True)
    dest_path = os.path.join(dest_dir, os.path.basename(filename))
    if os.path.isfile(dest_path) and os.path.getsize(dest_path) > 1024:
        return dest_path  # cached

    url = f"https://huggingface.co/{repo_id}/resolve/{revision}/{filename}"
    try:
        urllib.request.urlretrieve(url, dest_path)
    except Exception as e:
        if os.path.isfile(dest_path):
            try:
                os.remove(dest_path)
            except Exception:
                pass
        raise RuntimeError(f"hf_download başarısız: {repo_id}/{filename} → {e}")
    return dest_path


def get_models_dir(subdir=None):
    """ComfyUI models dizinini ya da alt-klasörünü döner (oluşturur)."""
    try:
        import folder_paths
        base = folder_paths.models_dir
    except Exception:
        base = os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", "models")
        base = os.path.abspath(base)
    if subdir:
        base = os.path.join(base, subdir)
    os.makedirs(base, exist_ok=True)
    return base


def get_custom_nodes_dir():
    """ComfyUI custom_nodes dizinini döner."""
    # nodes/common.py -> custom_nodes/<plugin>/nodes/common.py
    here = os.path.dirname(os.path.abspath(__file__))
    return os.path.dirname(os.path.dirname(here))


def _try_apply_teacache(model, model_type="flux", rel_l1_thresh=0.40,
                         start_percent=0.0, end_percent=1.0,
                         cache_device="cuda"):
    """Sprint 14: TeaCache wrapper — 2-3x hızlandırma, kalite kaybı yok.

    model_type: "flux" | "wan" | "ltx" | "hunyuan" | "sdxl"
    rel_l1_thresh:
      - 0.25-0.30: konservatif (1.5x hız, neredeyse kayıpsız)
      - 0.40: dengeli (2x hız, hafif fark)
      - 0.50+: agresif (2.5-3x hız, fark görülür)

    Kurulu değilse model'i değiştirmeden döner. (model, status_str).
    """
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM
    except Exception:
        return model, "TeaCache: ComfyUI nodes erişilemedi"

    # ComfyUI-TeaCache node isimleri
    candidates = [
        "TeaCache",
        "TeaCacheForImgGen",
        "TeaCacheForVidGen",
        "ApplyTeaCacheOnModel",
    ]
    cls = next((_NCM.get(n) for n in candidates if _NCM.get(n)), None)
    if not cls:
        return model, ("TeaCache node yok. Install: ComfyUI Manager > 'ComfyUI-TeaCache' "
                       "(welltop-cn) — 2-3x hız")
    try:
        node = cls()
        method = getattr(node, "apply", None) or getattr(node, "execute", None) or \
                 getattr(node, getattr(cls, "FUNCTION", "apply"), None)
        if not method:
            return model, "TeaCache: apply metodu yok"
        # Defansif signature dene
        try:
            patched = method(model=model, rel_l1_thresh=rel_l1_thresh,
                              model_type=model_type, start_percent=start_percent,
                              end_percent=end_percent, cache_device=cache_device)[0]
        except TypeError:
            try:
                patched = method(model=model, rel_l1_thresh=rel_l1_thresh)[0]
            except TypeError:
                patched = method(model, rel_l1_thresh)[0]
        return patched, f"✓ TeaCache uygulandı ({model_type}, thresh={rel_l1_thresh}, ~2x hız)"
    except Exception as e:
        return model, f"TeaCache hata: {e}"


def _try_apply_wavespeed(model, residual_diff_threshold=0.15,
                          start_percent=0.0, end_percent=1.0):
    """Sprint 14: WaveSpeed FBCache wrapper — 1.5-3x hızlandırma.

    residual_diff_threshold:
      - 0.08-0.12: konservatif (1.5x)
      - 0.15: dengeli (2x)
      - 0.20+: agresif (2.5-3x)

    Kurulu değilse fallback.
    """
    try:
        from nodes import NODE_CLASS_MAPPINGS as _NCM
    except Exception:
        return model, "WaveSpeed: ComfyUI nodes erişilemedi"

    candidates = [
        "ApplyFBCacheOnModel",
        "FBCache",
        "WaveSpeedFBCache",
    ]
    cls = next((_NCM.get(n) for n in candidates if _NCM.get(n)), None)
    if not cls:
        return model, ("WaveSpeed node yok. Install: ComfyUI Manager > "
                       "'Comfy-WaveSpeed' (chengzeyi) — 1.5-3x hız")
    try:
        node = cls()
        method = getattr(node, "patch", None) or getattr(node, "apply", None) or \
                 getattr(node, getattr(cls, "FUNCTION", "patch"), None)
        if not method:
            return model, "WaveSpeed: patch metodu yok"
        try:
            patched = method(model=model, residual_diff_threshold=residual_diff_threshold,
                              start=start_percent, end=end_percent)[0]
        except TypeError:
            try:
                patched = method(model, residual_diff_threshold)[0]
            except TypeError:
                patched = method(model)[0]
        return patched, (f"✓ WaveSpeed FBCache uygulandı "
                         f"(thresh={residual_diff_threshold}, ~2x hız)")
    except Exception as e:
        return model, f"WaveSpeed hata: {e}"


def apply_acceleration(model, accelerator="auto", model_type="flux",
                        intensity="balanced"):
    """Sprint 14: Dispatcher — TeaCache/WaveSpeed otomatik veya seçili.

    accelerator: "auto" | "teacache" | "wavespeed" | "none"
    intensity:   "conservative" | "balanced" | "aggressive"

    auto: TeaCache öncelikli (daha yaygın), yoksa WaveSpeed, yoksa no-op.
    Dönüş: (patched_model, method_used, status_str)
    """
    if accelerator == "none":
        return model, "none", "Hızlandırma kapalı"

    thresh_map_tc = {"conservative": 0.25, "balanced": 0.40, "aggressive": 0.55}
    thresh_map_ws = {"conservative": 0.10, "balanced": 0.15, "aggressive": 0.22}

    if accelerator == "auto":
        # TeaCache dene, sonra WaveSpeed
        order = ["teacache", "wavespeed"]
    else:
        order = [accelerator]

    statuses = []
    for m in order:
        if m == "teacache":
            patched, status = _try_apply_teacache(
                model, model_type=model_type,
                rel_l1_thresh=thresh_map_tc.get(intensity, 0.40))
            statuses.append(status)
            if status.startswith("✓"):
                return patched, "teacache", " | ".join(statuses)
        elif m == "wavespeed":
            patched, status = _try_apply_wavespeed(
                model, residual_diff_threshold=thresh_map_ws.get(intensity, 0.15))
            statuses.append(status)
            if status.startswith("✓"):
                return patched, "wavespeed", " | ".join(statuses)

    return model, "none", " | ".join(statuses) or "Hızlandırma bulunamadı"


def _should_use_schnell(vram_mb_threshold=14_000, user_speed_mode=False):
    """Sprint 5 (W16): Flux Dev → Schnell otomatik fallback dispatcher.

    True dönerse caller, model seçiminde flux1-dev yerine flux1-schnell tercih
    etmelidir. Karar kriterleri:
      - Kullanıcı explicit "hız modu" toggle açtıysa → True
      - VRAM toplamı eşik (default 14GB) altındaysa → True
      - CUDA yoksa veya algılanamadıysa → False (kullanıcı seçimini sürdür)

    Caller log'lamalı: "[FikoYT] Auto Schnell switch (vram=12GB)" gibi.
    """
    if user_speed_mode:
        return True
    try:
        import torch
        if not torch.cuda.is_available():
            return False
        total = torch.cuda.get_device_properties(0).total_memory
        total_mb = int(total / 1_048_576)
        return total_mb < vram_mb_threshold
    except Exception:
        return False


__all__ = [
    "CATEGORY",
    "PROJECT_BASE_DIR",
    "_safe_int",
    "_safe_float",
    "_safe_bool",
    "_ffmpeg_bin",
    "_slug",
    "_ensure_project_dir",
    "_load_project_meta",
    "_save_project_meta",
    "_list_project_versions",
    "_restore_project_version",
    "_rel_to_output",
    "_view_url",
    "_fetch_ollama_models",
    "hf_download",
    "get_models_dir",
    "get_custom_nodes_dir",
    "_should_use_schnell",
    "_try_apply_teacache",
    "_try_apply_wavespeed",
    "apply_acceleration",
]
