import __main__
import inspect
import sys
import os
import functools
import importlib.util


if 'imp_cache' not in __main__.__dict__:
    __main__.__dict__['imp_cache'] = {}
CACHE = __main__.__dict__['imp_cache']


#Importation de module à partir d'un tuple ou d'une liste ou d'un str
def load(data, func=None, globals_val=None):
    # Pour supporter l'import des modules locaux
    if "path" not in globals():
        if not func:
            frame = inspect.currentframe()
            frame = frame.f_back # remonte d'un niveau dans la pile d'appels
            path = frame.f_globals["__file__"] # récupère le chemin du fichier appelant
            del frame
        else:
            path = inspect.getfile(func)

        globals()['path'] = getattr(__import__('os', fromlist=['object']), 'path').dirname(path)
        if globals()['path'] not in sys.path:
            sys.path.append(globals()['path'])

    # Détermination du scope (globals) et du package parent pour les imports relatifs
    if not func:
        if globals_val:
            calling_globals = globals_val
        else:
            current_frame = inspect.currentframe()
            calling_frame = current_frame.f_back
            calling_globals = calling_frame.f_globals
        func_dict = calling_globals
    else:
        calling_globals = func.__globals__
        func_dict = calling_globals

    # On récupère le nom du package actuel pour résoudre les imports relatifs (ex: 'nexus_project.package')
    package_context = calling_globals.get('__package__')

    if isinstance(data, (tuple, list)):
        for mod in data:
            if isinstance(mod, dict):
                # --- RÉSOLUTION DES CHEMINS RELATIFS ---
                module_name = mod['module']
                if module_name.startswith('.'):
                    if not package_context:
                        # Si pas de package (ex: script lancé en direct), on ne peut pas résoudre le "."
                        print(f"Warning: Relative import '{module_name}' ignored (no package context).")
                        continue
                    # Transforme '.package' en 'nexus_project.package'
                    module_name = importlib.util.resolve_name(module_name, package_context)
                # ----------------------------------------

                if 'submodule' in mod and mod['submodule'] is not None:
                    for sub in mod['submodule']:
                        try:
                            if 'as' in mod:
                                name = module_name + "." + sub + mod['as']
                                if name in CACHE:
                                    func_dict[mod['as']] = CACHE[name]
                                else:
                                    funct_in = getattr(__import__(module_name, fromlist=['object']), sub)
                                    CACHE[name] = funct_in
                                    func_dict[mod['as']] = funct_in
                            else:
                                name = module_name + "." + sub
                                if name in CACHE:
                                    func_dict[sub] = CACHE[name]
                                else:
                                    funct_in = getattr(__import__(module_name, fromlist=['object']), sub)
                                    CACHE[name] = funct_in
                                    func_dict[sub] = funct_in
                        except:
                            # Fallback si le submodule est en fait un fichier/module physique
                            if 'as' in mod:
                                name = module_name + "." + sub + mod['as']
                                if name in CACHE:
                                    func_dict[mod['as']] = CACHE[name]
                                else:
                                    funct = __import__(module_name + "." + sub, fromlist=['object'])
                                    CACHE[name] = funct
                                    func_dict[mod['as']] = funct
                            else:
                                name = module_name + "." + sub
                                if name in CACHE:
                                    func_dict[sub] = CACHE[name]
                                else:
                                    funct = __import__(module_name + "." + sub, fromlist=['object'])
                                    CACHE[name] = funct
                                    func_dict[sub] = funct
                else:
                    if 'as' in mod:
                        name = module_name + "." + mod['as']
                        if name in CACHE:
                            func_dict[mod['as']] = CACHE[name]
                        else:
                            funct = __import__(module_name, fromlist=['object'])
                            CACHE[name] = funct
                            func_dict[mod['as']] = funct
                    else:
                        name = module_name
                        if name in CACHE:
                            func_dict[module_name] = CACHE[name]
                        else:
                            funct = __import__(module_name, fromlist=['object'])
                            CACHE[name] = funct
                            func_dict[module_name] = funct
            else:
                # Traitement str simple dans une liste
                funct = get(mod)
                if funct is not None:
                    CACHE[mod] = funct
                    func_dict[mod] = funct
                
    elif isinstance(data, str):
        if data in CACHE:
            func_dict[data] = CACHE[data]
        else:
            funct = get(data)
            if funct is not None:
                CACHE[data] = funct
                func_dict[data] = funct

#Décharger un module
def unload(modulename, uncache=False):
    if uncache:
        if modulename in CACHE:
            del CACHE[modulename]

    current_frame = inspect.currentframe()
    calling_frame = current_frame.f_back
    calling_globals = calling_frame.f_globals
    if modulename in calling_globals:
        del calling_globals[modulename]
    del sys.modules[modulename]

#Rechargement d'un module
def reload(modulename):
    unload(modulename, uncache=True)
    current_frame = inspect.currentframe()
    calling_frame = current_frame.f_back
    load(modulename, globals_val = calling_frame.f_globals)

#Chargement d'un module sans l'importer dans le code
def get(modulename):
    funct = None

    cache_mod = None
    if modulename in sys.modules:
        cache_mod = sys.modules[modulename]
        del sys.modules[modulename]

    try:
        funct = __import__(modulename, fromlist=['object'])
    except:
        try:
            if "." in modulename:
                modulename = modulename.split(".")
                funct = __import__(".".join(modulename[:-1]), fromlist=['object'])

                funct = getattr(funct, modulename[-1])
        except:
            pass
    finally:
        if cache_mod:
            sys.modules[modulename] = cache_mod
        return funct


#Chargement d'un module à partir d'un fichier spécifique sans l'importer dans le code
def get_from_file(filepath):
    # Le nom du module est le nom du fichier sans l'extension .py
    modulename = os.path.splitext(os.path.basename(filepath))[0]

    cache_mod = None
    if modulename in sys.modules:
        cache_mod = sys.modules[modulename]
        del sys.modules[modulename]

    # Sauvegarde du sys.path original
    original_sys_path = sys.path.copy()

    # Détermine le répertoire contenant le fichier
    module_dir = os.path.dirname(filepath)
    # Ajoute ce répertoire au sys.path
    sys.path.insert(0, module_dir)

    try:
    
        # Importer le module
        module = __import__(modulename)
        
        # Retourner le module importé
        return module
    except Exception as e:
        print(f"Error loading module {modulename} from {filepath}: {e}")
        return None
    finally:
        # Restaurer le sys.path original
        sys.path = original_sys_path

        if cache_mod:
            sys.modules[modulename] = cache_mod


#Décorateur pour l'importation de module
def loader(*data):
    def inner(func):
        @functools.wraps(func)  # Conserve les métadonnées de la fonction originale
        def wrapper(*args, **kwargs):
            load(data, func)  # Appelle la fonction load avec les données et la fonction cible
            result = func(*args, **kwargs)  # Exécute la fonction originale
            return result  # Retourne directement le résultat sans modification
        return wrapper
    return inner
