"""Centralized path resolution for dev, PyInstaller, and Nuitka builds."""
import os
import sys
from pathlib import Path


def _is_frozen() -> bool:
    """Detect if running as a compiled/frozen executable (Nuitka or PyInstaller)."""
    # PyInstaller / standard frozen
    if getattr(sys, "frozen", False):
        return True
    # Nuitka compiled modules have __compiled__ set at module level
    try:
        __compiled__  # type: ignore[name-defined]  # noqa: B018
        return True
    except NameError:
        pass
    # Nuitka standalone: argv[0] is the EXE, no src/ directory next to it
    exe = Path(sys.argv[0]).resolve()
    if exe.suffix.lower() == ".exe" and not (exe.parent / "src").is_dir():
        return True
    return False


_FROZEN = _is_frozen()

# In frozen builds, set up paths BEFORE any ML library imports.
# ctranslate2 checks for CUDA at first import — DLLs must be findable by then.
if _FROZEN:
    _exe_dir = str(Path(sys.argv[0]).resolve().parent)
    # sys.path for uncompiled packages (transformers source copy)
    if _exe_dir not in sys.path:
        sys.path.insert(0, _exe_dir)
    # DLL search paths for CUDA libraries (cublas, cudnn, cudart in torch/lib/)
    _dll_dirs = [_exe_dir]
    for _sub in ["ctranslate2", os.path.join("torch", "lib")]:
        _d = os.path.join(_exe_dir, _sub)
        if os.path.isdir(_d):
            _dll_dirs.append(_d)
    os.environ["PATH"] = os.pathsep.join(_dll_dirs) + os.pathsep + os.environ.get("PATH", "")
    if hasattr(os, "add_dll_directory"):
        for _d in _dll_dirs:
            try:
                os.add_dll_directory(_d)
            except OSError:
                pass

    # Preload CUDA DLLs by absolute path so they're in-process before
    # ctranslate2._ext.pyd resolves its implicit dependencies.
    # Nuitka's SetDllDirectory() call at startup disables PATH-based DLL
    # search, making os.add_dll_directory() unreliable. ctypes.CDLL() with
    # an absolute path always works regardless of search path configuration.
    # Lightweight CLI subcommands never touch the GPU — skip the multi-GB CUDA
    # DLL preload so register/unregister/version stay fast (matters for the
    # installer's --register-shell hook running on the user's machine).
    _light_cli = any(
        _a in ("--register-shell", "--unregister-shell", "--version")
        for _a in sys.argv[1:]
    )
    if not _light_cli:
        import ctypes
        import glob as _glob

        # All CUDA runtime DLLs ctranslate2/cuDNN may load by name at runtime.
        # Must include nvrtc-builtins (loaded by nvrtc for JIT) and zlibwapi
        # (cuDNN), or GPU inference fails on machines without a CUDA install.
        _cuda_globs = [
            "cudart64_*.dll", "cublas64_*.dll", "cublasLt64_*.dll",
            "cufft64_*.dll", "cufftw64_*.dll", "cusparse64_*.dll",
            "cudnn*.dll", "nvrtc64_*.dll", "nvrtc-builtins64_*.dll",
            "nvJitLink_*.dll", "zlibwapi.dll",
        ]
        _pending: list[str] = []
        for _search_dir in _dll_dirs:
            for _pattern in _cuda_globs:
                _pending.extend(_glob.glob(os.path.join(_search_dir, _pattern)))
        _pending = list(dict.fromkeys(_pending))  # dedupe, preserve order
        # Preload by absolute path (Nuitka's SetDllDirectory disables PATH/search,
        # so each DLL must be in-process before ctranslate2 loads it by name).
        # Retry across passes so inter-DLL dependencies resolve regardless of
        # filename order (e.g. cudnn_ops depends on cudnn_graph).
        for _ in range(6):
            _again: list[str] = []
            for _dll_path in _pending:
                try:
                    ctypes.CDLL(_dll_path)
                except OSError:
                    _again.append(_dll_path)
            if not _again or len(_again) == len(_pending):
                break  # done, or no progress this pass
            _pending = _again


def app_dir() -> Path:
    """Read-only application directory (EXE + assets + DLLs).

    - PyInstaller: sys._MEIPASS
    - Nuitka frozen: directory containing the EXE
    - Dev mode: project root (parent of src/)
    """
    if getattr(sys, "_MEIPASS", None):
        return Path(sys._MEIPASS)
    if _FROZEN:
        return Path(sys.argv[0]).resolve().parent
    return Path(__file__).resolve().parent.parent


def data_dir() -> Path:
    """User-writable directory for models, settings, cache, logs.

    - VOXCAST_DATA_DIR env override: used by the VoxCast Server (Linux
      /var/lib/voxcast, Windows service %ProgramData%) — a service running
      as LocalSystem must never land in LOCALAPPDATA. The desktop client
      never sets it.
    - Installed (frozen): %LOCALAPPDATA%/VoxCast/
    - Dev mode: project root (parent of src/)
    """
    override = os.environ.get("VOXCAST_DATA_DIR")
    if override:
        base = Path(override)
        base.mkdir(parents=True, exist_ok=True)
        return base
    if _FROZEN:
        base = Path(os.environ.get("LOCALAPPDATA", str(Path.home()))) / "VoxCast"
        base.mkdir(parents=True, exist_ok=True)
        return base
    return Path(__file__).resolve().parent.parent
