using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Manager pour charger et gérer les paramètres généraux du projet
/// </summary>
public class GeneralConfigManager : MonoBehaviour
{
    public static GeneralConfigManager Instance { get; private set; }
    
    [Header("Configuration")]
    public string configFileName = "general-config.json";
    
    [Header("Debug")]
    public bool showDebugLogs = false;
    
    // IMPORTANT: ne pas limiter ces logs à UNITY_EDITOR.
    // Quand ça casse en WebGL (cache/404/CORS), on a besoin des URLs + codes HTTP dans la console navigateur.
    private void LogVerbose(string message) { if (showDebugLogs) Debug.Log(message); }

    // URL de fallback hardcodée pour WebGL si le fichier local n'est pas trouvé
    private const string FALLBACK_REMOTE_CONFIG_URL = "https://ujsa.studioplc.fr/datas/json/general-config.json";
    
    private GeneralConfig generalConfig;
    private bool isConfigLoaded = false;
    private bool isLoadingInProgress = false; // 🔒 NOUVEAU : Empêche les chargements multiples

    private static string AppendQueryParam(string url, string key, string value)
    {
        if (string.IsNullOrEmpty(url)) return url;
        string sep = url.Contains("?") ? "&" : "?";
        return url + sep + key + "=" + UnityWebRequest.EscapeURL(value ?? "");
    }

    #if UNITY_WEBGL && !UNITY_EDITOR
    private static string GetWebGLPageBaseDirectory()
    {
        // Application.absoluteURL ressemble à "https://site/chemin/index.html?x=y"
        // On récupère le dossier ("https://site/chemin/")
        string abs = Application.absoluteURL ?? "";
        if (string.IsNullOrEmpty(abs)) return "";

        int q = abs.IndexOf('?');
        if (q >= 0) abs = abs.Substring(0, q);
        int h = abs.IndexOf('#');
        if (h >= 0) abs = abs.Substring(0, h);

        int lastSlash = abs.LastIndexOf('/');
        if (lastSlash < 0) return "";
        return abs.Substring(0, lastSlash + 1);
    }

    private static List<string> BuildWebGLLocalConfigBaseUrls(string configFileName)
    {
        var bases = new List<string>();

        // 1) Unity-provided streamingAssetsPath (souvent le bon)
        string sa = (Application.streamingAssetsPath ?? "").Replace("\\", "/");
        if (!string.IsNullOrEmpty(sa))
        {
            sa = sa.TrimEnd('/');
            bases.Add(sa + "/" + configFileName);
        }

        // 2) Base dérivée de la page (robuste aux déploiements où StreamingAssets est à la racine)
        string pageBase = GetWebGLPageBaseDirectory();
        if (!string.IsNullOrEmpty(pageBase))
        {
            bases.Add(pageBase.TrimEnd('/') + "/StreamingAssets/" + configFileName);
            bases.Add(pageBase.TrimEnd('/') + "/Build/StreamingAssets/" + configFileName);
        }

        // 3) Quelques variantes "souvent vues" (si les fichiers sont servis depuis un sous-dossier)
        //    On évite d'en faire trop : juste les plus probables.
        if (!string.IsNullOrEmpty(pageBase))
        {
            bases.Add(pageBase.TrimEnd('/') + "/" + configFileName);
        }

        // De-dupe + nettoyer
        bases = bases
            .Where(u => !string.IsNullOrEmpty(u))
            .Select(u => u.Replace("\\", "/"))
            .Distinct()
            .ToList();

        return bases;
    }
    #endif

    private IEnumerator TryDownloadText(string url, System.Action<string> onSuccess, System.Action<long, string, string> onFail)
    {
        using (UnityWebRequest req = UnityWebRequest.Get(url))
        {
            // Aide certains proxys/serveurs à retourner le bon contenu
            try { req.SetRequestHeader("Accept", "application/json"); } catch { /* ignore */ }
            req.timeout = 10;
            yield return req.SendWebRequest();

            if (req.result == UnityWebRequest.Result.Success)
            {
                onSuccess?.Invoke(req.downloadHandler.text);
                yield break;
            }

            // Sur certains serveurs, on récupère quand même un body (HTML 404, proxy, etc.).
            string body = "";
            try { body = req.downloadHandler?.text ?? ""; } catch { /* ignore */ }
            onFail?.Invoke(req.responseCode, req.error, body);
        }
    }
    
    void Awake()
    {
        // Singleton pattern
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
    }
    
    void Start()
    {
        StartCoroutine(LoadGeneralConfig());
    }
    
    /// <summary>
    /// Charge la configuration générale depuis le fichier JSON
    /// </summary>
    private IEnumerator LoadGeneralConfig()
    {
        // 🔒 EMPÊCHER LES CHARGEMENTS MULTIPLES
        if (isLoadingInProgress)
        {
            if (showDebugLogs)
            {
                Debug.LogWarning("[GeneralConfigManager] Chargement déjà en cours - Tentative ignorée");
            }
            yield break;
        }
        
        if (isConfigLoaded)
        {
            if (showDebugLogs)
            {
                LogVerbose("[GeneralConfigManager] Configuration déjà chargée - Pas besoin de recharger");
            }
            yield break;
        }
        
        isLoadingInProgress = true;
        
        // IMPORTANT WebGL: Application.streamingAssetsPath est une URL HTTP.
        // Sur Windows, Path.Combine injecte des '\' qui cassent l'URL (=> échec de chargement).
        // On construit donc un chemin/URL en normalisant les séparateurs.
        string streamingBase = (Application.streamingAssetsPath ?? "").Replace("\\", "/");
        if (!streamingBase.EndsWith("/")) streamingBase += "/";
        string filePath = streamingBase + configFileName;
        
        if (showDebugLogs)
        {
            LogVerbose($"[GeneralConfigManager] Chargement de la configuration depuis: {filePath}");
            LogVerbose($"[GeneralConfigManager] Platform: {Application.platform}");
        }
        
        // DÉTECTION MAC AU RUNTIME (plus fiable que les defines de compilation)
        bool isMacOrIOS = Application.platform == RuntimePlatform.OSXEditor || 
                         Application.platform == RuntimePlatform.OSXPlayer ||
                         Application.platform == RuntimePlatform.IPhonePlayer;
        
        // FIX MAC : Utiliser File.ReadAllText sur Mac/iOS au lieu de UnityWebRequest
        // IMPORTANT : Aussi dans l'éditeur Mac car UnityWebRequest échoue avec file:///
        if (isMacOrIOS)
        {
            LogVerbose("[GeneralConfigManager] 🍎 Détection Mac/iOS - Utilisation de File.ReadAllText");
            
            if (System.IO.File.Exists(filePath))
            {
                try
                {
                    string jsonContent = System.IO.File.ReadAllText(filePath);
                    
                    if (showDebugLogs)
                    {
                        LogVerbose($"[GeneralConfigManager] Fichier lu: {jsonContent.Length} caractères");
                    }
                    
                    generalConfig = JsonUtility.FromJson<GeneralConfig>(jsonContent);
                    isConfigLoaded = true;
                    
                    // Debug des buttonStyles (toujours afficher pour le debug)
                    LogVerbose($"[GeneralConfigManager] 🎨 (Mac/iOS) buttonStyles: {(generalConfig.buttonStyles != null ? "OK" : "NULL")}");
                    if (generalConfig.buttonStyles != null)
                    {
                        LogVerbose($"[GeneralConfigManager] 🎨 validationPurple: {(generalConfig.buttonStyles.validationPurple != null ? $"OK (w={generalConfig.buttonStyles.validationPurple.width})" : "NULL")}");
                        LogVerbose($"[GeneralConfigManager] 🎨 validationBeige: {(generalConfig.buttonStyles.validationBeige != null ? $"OK (w={generalConfig.buttonStyles.validationBeige.width})" : "NULL")}");
                    }
                    LogVerbose($"[GeneralConfigManager] 🔘 connexionPanel: {(generalConfig.connexionPanel != null ? "OK" : "NULL")}");
                    if (generalConfig.connexionPanel != null)
                    {
                        LogVerbose($"[GeneralConfigManager] 🔘 connexionPanel.buttons: {(generalConfig.connexionPanel.buttons != null ? "OK" : "NULL")}");
                        if (generalConfig.connexionPanel.buttons != null)
                        {
                            LogVerbose($"[GeneralConfigManager] 🔘 continueButton.style: '{generalConfig.connexionPanel.buttons.continueButton?.style ?? "NULL"}'");
                            LogVerbose($"[GeneralConfigManager] 🔘 seroniConnectButton.style: '{generalConfig.connexionPanel.buttons.seroniConnectButton?.style ?? "NULL"}'");
                        }
                    }
                    
                    if (showDebugLogs)
                    {
                        LogVerbose($"[GeneralConfigManager] Configuration chargée avec succès (Mac/iOS)!");
                        LogVerbose($"[GeneralConfigManager] Projet: {generalConfig.projectName} v{generalConfig.version}");
                        LogVerbose($"[GeneralConfigManager] Map par défaut: {generalConfig.defaultMapId}");
                    }
                    
                    isLoadingInProgress = false; // 🔓 DÉVERROUILLER - Succès Mac/iOS
                    yield break; // Important : sortir de la coroutine
                }
                    catch (System.Exception e)
                    {
                        isLoadingInProgress = false; // 🔓 DÉVERROUILLER - Erreur Mac/iOS
                        Debug.LogError($"[GeneralConfigManager] Erreur lors du parsing JSON (Mac/iOS): {e.Message}");
                        LoadDefaultConfig();
                        yield break;
                    }
                }
                else
                {
                    isLoadingInProgress = false; // 🔓 DÉVERROUILLER - Fichier introuvable Mac/iOS
                    Debug.LogError($"[GeneralConfigManager] Fichier introuvable sur Mac/iOS: {filePath}");
                    Debug.LogError($"[GeneralConfigManager] Assurez-vous que {configFileName} est dans le dossier StreamingAssets");
                    LoadDefaultConfig();
                    yield break;
                }
        }
        
        // Code normal pour Windows/Android/autres plateformes (ou éditeur Mac)
        if (showDebugLogs)
        {
            LogVerbose("[GeneralConfigManager] Utilisation de UnityWebRequest");
        }
        
        // WebGL: StreamingAssets est servi via HTTP et peut être agressivement caché par le navigateur/CDN.
        // Si le cache sert une ancienne version, on observe exactement le symptôme "Editor OK / WebGL pas à jour".
        // On ajoute donc un cache-buster stable basé sur la version de l'app,
        // MAIS certains serveurs/CDN/config Apache cassés peuvent mal gérer les querystrings.
        // Donc on tente plusieurs variantes (avec version, avec timestamp, sans query) avant de conclure à un échec.
        var candidateUrls = new System.Collections.Generic.List<string>();

        #if UNITY_WEBGL && !UNITY_EDITOR
        // WebGL: selon le déploiement, StreamingAssets peut être servi depuis /StreamingAssets
        // ou /Build/StreamingAssets. On teste plusieurs bases plausibles.
        string appVersion = string.IsNullOrEmpty(Application.version) ? "0" : Application.version;
        var localBases = BuildWebGLLocalConfigBaseUrls(configFileName);
        foreach (var baseLocalUrl in localBases)
        {
            candidateUrls.Add(AppendQueryParam(baseLocalUrl, "v", appVersion)); // stable
            candidateUrls.Add(AppendQueryParam(baseLocalUrl, "t", System.DateTime.UtcNow.Ticks.ToString())); // ultra-no-cache
            candidateUrls.Add(baseLocalUrl); // plain (au cas où ?v casse le serveur)
        }
        #else
        // Sur non-WebGL, on préfère le schéma file:/// pour UnityWebRequest si on tombe ici.
        // (Le code Mac utilise déjà File.ReadAllText, mais sur Windows/Android on reste compatible.)
        string baseLocalUrl = filePath;
        if (!baseLocalUrl.Contains("://") && !baseLocalUrl.Contains(":///"))
        {
            baseLocalUrl = "file:///" + baseLocalUrl.Replace("\\", "/");
        }
        candidateUrls.Add(baseLocalUrl);
        #endif

        string lastBody = "";
        long lastCode = 0;
        string lastError = "";
        bool loaded = false;
        string downloadedJson = null;

        for (int i = 0; i < candidateUrls.Count; i++)
        {
            string u = candidateUrls[i];
            LogVerbose($"[GeneralConfigManager] 🌐 Tentative {(i + 1)}/{candidateUrls.Count}: {u}");
            yield return TryDownloadText(
                u,
                onSuccess: (text) =>
                {
                    downloadedJson = text;
                    loaded = true;
                },
                onFail: (code, err, body) =>
                {
                    lastCode = code;
                    lastError = err ?? "";
                    lastBody = body ?? "";
                }
            );

            if (loaded) break;
        }

        if (loaded)
        {
            LogVerbose($"[GeneralConfigManager] 🎯 Texte reçu: {(downloadedJson != null ? downloadedJson.Length : 0)} caractères");
            if (!string.IsNullOrEmpty(downloadedJson))
            {
                LogVerbose($"[GeneralConfigManager] 🎯 Premiers 120 chars: {downloadedJson.Substring(0, Mathf.Min(120, downloadedJson.Length))}");
            }

            try
            {
                LogVerbose($"[GeneralConfigManager] 🎯 Début parsing...");
                generalConfig = JsonUtility.FromJson<GeneralConfig>(downloadedJson);

                // IMPORTANT: JsonUtility peut "parser" du HTML ou une réponse vide sans exception,
                // et produire un objet partiellement nul => symptômes: apiUrls null / baseUrl vide.
                // Si la config est invalide, on force une nouvelle tentative (autre URL / fallback).
                bool looksValid =
                    generalConfig != null &&
                    generalConfig.apiUrls != null &&
                    !string.IsNullOrEmpty(generalConfig.apiUrls.baseUrl);

                if (!looksValid)
                {
                    string snippet = (downloadedJson ?? "");
                    snippet = snippet.Substring(0, Mathf.Min(180, snippet.Length)).Replace("\n", " ").Replace("\r", " ");
                    Debug.LogError($"[GeneralConfigManager] ❌ Contenu local invalide (apiUrls/baseUrl manquants). Souvent causé par cache/rewrite HTML. Snippet: {(string.IsNullOrEmpty(snippet) ? "<empty>" : snippet)}");

                    // Continuer vers fallback distant (sans marquer la config comme chargée)
                    generalConfig = null;
                }
                else
                {
                    LogVerbose($"[GeneralConfigManager] 🎯 Parsing terminé. Config null? {generalConfig == null}");
                    isConfigLoaded = true;
                    isLoadingInProgress = false; // 🔓 DÉVERROUILLER

                    LogVerbose($"[GeneralConfigManager] ✅ Configuration chargée avec SUCCÈS!");

                    // Debug des buttonStyles
                    LogVerbose($"[GeneralConfigManager] 🎨 buttonStyles: {(generalConfig.buttonStyles != null ? "OK" : "NULL")}");
                    if (generalConfig.buttonStyles != null)
                    {
                        LogVerbose($"[GeneralConfigManager] 🎨 validationPurple: {(generalConfig.buttonStyles.validationPurple != null ? "OK" : "NULL")}");
                        LogVerbose($"[GeneralConfigManager] 🎨 validationBeige: {(generalConfig.buttonStyles.validationBeige != null ? "OK" : "NULL")}");
                    }
                    LogVerbose($"[GeneralConfigManager] 🔘 connexionPanel.buttons: {(generalConfig.connexionPanel?.buttons != null ? "OK" : "NULL")}");

                    // Debug de apiUrls
                    LogVerbose($"[GeneralConfigManager] 🌐 apiUrls: {(generalConfig.apiUrls != null ? "OK" : "NULL")}");
                    if (generalConfig.apiUrls != null)
                    {
                        LogVerbose($"[GeneralConfigManager] 🌐 apiUrls.baseUrl: '{(string.IsNullOrEmpty(generalConfig.apiUrls.baseUrl) ? "VIDE" : generalConfig.apiUrls.baseUrl)}'");
                    }

                    if (showDebugLogs)
                    {
                        LogVerbose($"[GeneralConfigManager] Projet: {generalConfig.projectName} v{generalConfig.version}");
                        LogVerbose($"[GeneralConfigManager] Map par défaut: {generalConfig.defaultMapId}");
                    }

                    yield break;
                }
            }
            catch (System.Exception e)
            {
                isLoadingInProgress = false; // 🔓 DÉVERROUILLER même en cas d'erreur
                Debug.LogError($"[GeneralConfigManager] ❌ Erreur parsing: {e.Message}");
                Debug.LogError($"[GeneralConfigManager] ❌ Stack: {e.StackTrace}");
                // Continuer vers fallback distant
            }
        }
        else
        {
            // NE PAS DÉVERROUILLER ICI - On va essayer le fallback
            string snippet = "";
            if (!string.IsNullOrEmpty(lastBody))
            {
                snippet = lastBody.Substring(0, Mathf.Min(180, lastBody.Length)).Replace("\n", " ").Replace("\r", " ");
            }
            Debug.LogError($"[GeneralConfigManager] ❌ Échec local: {lastError} | Code: {lastCode} | Body: {(string.IsNullOrEmpty(snippet) ? "<empty>" : snippet)}");
            LogVerbose($"[GeneralConfigManager] 🔄 Tentative fallback URL: {FALLBACK_REMOTE_CONFIG_URL}");
        }

        // Tentative de chargement depuis l'URL distante
        {
            var fallbackCandidates = new System.Collections.Generic.List<string>();
            #if UNITY_WEBGL && !UNITY_EDITOR
            string cacheBustVersion = string.IsNullOrEmpty(Application.version) ? "0" : Application.version;
            fallbackCandidates.Add(AppendQueryParam(FALLBACK_REMOTE_CONFIG_URL, "v", cacheBustVersion));
            fallbackCandidates.Add(AppendQueryParam(FALLBACK_REMOTE_CONFIG_URL, "t", System.DateTime.UtcNow.Ticks.ToString()));
            fallbackCandidates.Add(FALLBACK_REMOTE_CONFIG_URL);
            #else
            fallbackCandidates.Add(FALLBACK_REMOTE_CONFIG_URL);
            #endif

            string remoteJson = null;
            bool remoteLoaded = false;
            long remoteCode = 0;
            string remoteErr = "";
            string remoteBody = "";

            for (int i = 0; i < fallbackCandidates.Count; i++)
            {
                string u = fallbackCandidates[i];
                LogVerbose($"[GeneralConfigManager] 🌐 Fallback distant {(i + 1)}/{fallbackCandidates.Count}: {u}");
                yield return TryDownloadText(
                    u,
                    onSuccess: (text) =>
                    {
                        remoteJson = text;
                        remoteLoaded = true;
                    },
                    onFail: (code, err, body) =>
                    {
                        remoteCode = code;
                        remoteErr = err ?? "";
                        remoteBody = body ?? "";
                    }
                );
                if (remoteLoaded) break;
            }

            if (remoteLoaded)
            {
                try
                {
                    generalConfig = JsonUtility.FromJson<GeneralConfig>(remoteJson);
                    isConfigLoaded = true;
                    isLoadingInProgress = false; // 🔓 DÉVERROUILLER - Succès du fallback
                    LogVerbose($"[GeneralConfigManager] ✅ Configuration chargée depuis URL de secours: {FALLBACK_REMOTE_CONFIG_URL}");
                    yield break;
                }
                catch (System.Exception e)
                {
                    isLoadingInProgress = false; // 🔓 DÉVERROUILLER - Échec du parsing
                    Debug.LogError($"[GeneralConfigManager] Erreur parsing config distante: {e.Message}");
                    LoadDefaultConfig();
                    yield break;
                }
            }

            // Échec du fallback
            isLoadingInProgress = false; // 🔓 DÉVERROUILLER
            string remoteSnippet = "";
            if (!string.IsNullOrEmpty(remoteBody))
            {
                remoteSnippet = remoteBody.Substring(0, Mathf.Min(180, remoteBody.Length)).Replace("\n", " ").Replace("\r", " ");
            }
            Debug.LogError($"[GeneralConfigManager] ❌ Échec chargement distant: {remoteErr} | Code: {remoteCode} | Body: {(string.IsNullOrEmpty(remoteSnippet) ? "<empty>" : remoteSnippet)}");

            // Dernier filet de sécurité: fallback embarqué via Resources (fonctionne même si StreamingAssets est mal servi en WebGL).
            if (TryLoadFromResourcesFallback())
            {
                LogVerbose("[GeneralConfigManager] ✅ Configuration chargée depuis Resources (fallback ultime)");
                yield break;
            }

            LoadDefaultConfig();
            yield break;
        }
    }

    private bool TryLoadFromResourcesFallback()
    {
        try
        {
            // On cherche plusieurs emplacements possibles dans Resources
            TextAsset ta =
                Resources.Load<TextAsset>("Configs/general-config") ??
                Resources.Load<TextAsset>("general-config");

            if (ta == null || string.IsNullOrEmpty(ta.text))
            {
                Debug.LogError("[GeneralConfigManager] ❌ Fallback Resources: TextAsset introuvable (Configs/general-config ou general-config)");
                return false;
            }

            generalConfig = JsonUtility.FromJson<GeneralConfig>(ta.text);
            isConfigLoaded = true;
            return generalConfig != null;
        }
        catch (System.Exception e)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ Fallback Resources: erreur parsing: {e.Message}");
            return false;
        }
    }
    
    /// <summary>
    /// Charge une configuration par défaut en cas d'erreur
    /// Essaie de charger le JSON de manière synchrone avant de créer une config vide
    /// </summary>
    private void LoadDefaultConfig()
    {
        // Dernier essai : charger le JSON de manière synchrone
        string streamingBase = (Application.streamingAssetsPath ?? "").Replace("\\", "/");
        if (!streamingBase.EndsWith("/")) streamingBase += "/";
        string filePath = streamingBase + configFileName;
        
        if (System.IO.File.Exists(filePath))
        {
            try
            {
                string jsonContent = System.IO.File.ReadAllText(filePath);
                generalConfig = JsonUtility.FromJson<GeneralConfig>(jsonContent);
                isConfigLoaded = true;
                Debug.LogWarning("[GeneralConfigManager] Configuration chargée depuis LoadDefaultConfig (dernier essai)");
                return;
            }
            catch (System.Exception e)
            {
                Debug.LogError($"[GeneralConfigManager] Erreur lors du dernier essai de chargement: {e.Message}");
            }
        }
        
        // WebGL: dernier filet de sécurité via Resources (évite un menu bloqué si StreamingAssets/remote sont indisponibles)
        if (TryLoadFromResourcesFallback())
        {
            Debug.LogWarning("[GeneralConfigManager] Configuration chargée via Resources (LoadDefaultConfig fallback)");
            return;
        }

        // Si le chargement échoue, créer une configuration vide (sans valeurs en dur)
        // Les scripts devront gérer le cas où les valeurs sont null/vides
        Debug.LogError("[GeneralConfigManager] ❌ Impossible de charger general-config.json - Configuration vide créée");
        Debug.LogError("[GeneralConfigManager] ❌ Le fichier general-config.json est OBLIGATOIRE pour le fonctionnement du jeu");
        
        generalConfig = new GeneralConfig
        {
            // Configuration minimale sans URLs en dur
            // Les scripts devront vérifier si les valeurs sont null/vides
        };
        
        isConfigLoaded = true;
    }
    
    /// <summary>
    /// Attend que la configuration soit chargée (coroutine)
    /// </summary>
    public IEnumerator WaitForConfigLoaded()
    {
        float timeout = 10f; // 10 secondes max
        float elapsed = 0f;
        
        while (!isConfigLoaded && elapsed < timeout)
        {
            yield return new WaitForSeconds(0.1f);
            elapsed += 0.1f;
        }
        
        if (!isConfigLoaded)
        {
            Debug.LogError("[GeneralConfigManager] ⏱️ Timeout : La configuration n'a pas pu être chargée en 10 secondes");
        }
    }
    
    /// <summary>
    /// Vérifie si la configuration est chargée
    /// </summary>
    public bool IsConfigLoaded()
    {
        return isConfigLoaded;
    }

    /// <summary>
    /// Indique si la configuration est exploitable pour les appels API.
    /// Important: IsConfigLoaded peut être true même si on est tombé sur une config vide (fallback).
    /// </summary>
    public bool HasValidApiUrls()
    {
        try
        {
            var cfg = GetConfig();
            return cfg != null &&
                   cfg.apiUrls != null &&
                   !string.IsNullOrEmpty(cfg.apiUrls.baseUrl);
        }
        catch
        {
            return false;
        }
    }
    
    /// <summary>
    /// Obtient la configuration générale (attendre que soit chargée)
    /// </summary>
    public GeneralConfig GetConfig()
    {
        if (!isConfigLoaded)
        {
            Debug.LogWarning("[GeneralConfigManager] Configuration pas encore chargée, retour de la config par défaut");
            LoadDefaultConfig();
        }
        return generalConfig;
    }
    
    // Cache pour la police par défaut
    private TMPro.TMP_FontAsset cachedDefaultFont;
    
    /// <summary>
    /// Obtient la police par défaut configurée
    /// </summary>
    public TMPro.TMP_FontAsset GetDefaultFont()
    {
        if (cachedDefaultFont != null) return cachedDefaultFont;
        
        string fontName = GetConfig()?.defaultFontName ?? "Lato SDF";
        
        // Essayer de charger depuis Resources/Fonts/
        cachedDefaultFont = Resources.Load<TMPro.TMP_FontAsset>($"Fonts/{fontName}");
        
        if (cachedDefaultFont == null)
        {
            // Essayer directement avec le nom
            cachedDefaultFont = Resources.Load<TMPro.TMP_FontAsset>(fontName);
        }
        
        if (cachedDefaultFont == null)
        {
            Debug.LogWarning($"[GeneralConfigManager] Police '{fontName}' non trouvée, utilisation de la police par défaut TMP");
        }
        else
        {
            LogVerbose($"[GeneralConfigManager] Police par défaut chargée: {fontName}");
        }
        
        return cachedDefaultFont;
    }
    
    /// <summary>
    /// Obtient la map par défaut
    /// </summary>
    public string GetDefaultMapId()
    {
        return GetConfig().defaultMapId;
    }
    
    /// <summary>
    /// Obtient l'URL du registre des maps
    /// </summary>
    public string GetMapsRegistryUrl()
    {
        return ResolveStreamingAssetsPath(GetConfig().mapsRegistryUrl);
    }
    
    /// <summary>
    /// Convertit un chemin STREAMING_ASSETS/ en URL file:/// complète
    /// </summary>
    private string ResolveStreamingAssetsPath(string path)
    {
        if (string.IsNullOrEmpty(path))
            return path;
            
        if (path.StartsWith("STREAMING_ASSETS/"))
        {
            string relativePath = path.Substring("STREAMING_ASSETS/".Length);
            string fullPath = System.IO.Path.Combine(Application.streamingAssetsPath, relativePath);

            // WebGL: Application.streamingAssetsPath est déjà une URL accessible par le serveur (pas de file:///)
            // Éditeur/Standalone: on peut utiliser file:///
            fullPath = fullPath.Replace("\\", "/");

            #if UNITY_WEBGL && !UNITY_EDITOR
            return fullPath; // ex: /StreamingAssets/json/… servi par le serveur WebGL
            #else
            return "file:///" + fullPath;
            #endif
        }
        
        return path;
    }
    
    /// <summary>
    /// Force le rechargement de la configuration
    /// </summary>
    public void ReloadConfig()
    {
        isConfigLoaded = false;
        isLoadingInProgress = false; // permettre un retry même si un chargement précédent a "coincé"
        StartCoroutine(LoadGeneralConfig());
    }
    
    // ==========================================
    // MÉTHODES UTILITAIRES POUR LES CHEMINS D'ASSETS
    // ==========================================
    
    /// <summary>
    /// Construit l'URL complète pour une vidéo de popup
    /// </summary>
    /// <param name="fileName">Nom du fichier (peut inclure un sous-dossier comme "focus/video.mp4")</param>
    /// <returns>URL complète</returns>
    public string GetPopupVideoUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.popupVideoPath))
        {
            Debug.LogWarning("[GeneralConfigManager] popupVideoPath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.popupVideoPath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour une image décorative
    /// </summary>
    /// <param name="fileName">Nom du fichier (peut inclure un sous-dossier comme "personnages/image.png")</param>
    /// <returns>URL complète</returns>
    public string GetDecoratorImageUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.decoratorImagePath))
        {
            Debug.LogWarning("[GeneralConfigManager] decoratorImagePath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.decoratorImagePath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour une vidéo générale
    /// </summary>
    /// <param name="fileName">Nom du fichier (peut inclure un sous-dossier comme "focus/video.mp4")</param>
    /// <returns>URL complète</returns>
    public string GetVideoUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
        
        // 🔧 NORMALISATION : Ajouter underscore manquant avant intro/success/fail/outro
        // Ex: Q07intro.mp4 → Q07_intro.mp4 (le serveur utilise toujours un underscore)
        // IMPORTANT : Normaliser AVANT de vérifier si c'est une URL complète
        fileName = NormalizeVideoFileName(fileName);
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.videoPath))
        {
            Debug.LogWarning("[GeneralConfigManager] videoPath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.videoPath + fileName;
    }
    
    /// <summary>
    /// Normalise les noms de fichiers vidéo pour ajouter l'underscore manquant
    /// Ex: Q07intro.mp4 → Q07_intro.mp4
    /// </summary>
    private string NormalizeVideoFileName(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return fileName;
        
        // Liste des suffixes vidéo courants qui nécessitent un underscore
        string[] videoSuffixes = { "intro", "success", "fail", "outro", "focus" };
        
        foreach (string suffix in videoSuffixes)
        {
            // Pattern: QxxSUFFIX (sans underscore) où xx peut être 01-99 ou juste un chiffre
            // Ex: Q07intro → Q07_intro, Q7intro → Q7_intro, Q1J2intro → Q1J2_intro
            string pattern = $"(Q\\d+[A-Z]?\\d*)({suffix})";
            string replacement = $"$1_{suffix}";
            
            // Vérifier si le pattern matche ET qu'il n'y a pas déjà un underscore
            if (System.Text.RegularExpressions.Regex.IsMatch(fileName, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))
            {
                // Ne remplacer que si pas déjà un underscore avant le suffix
                if (!System.Text.RegularExpressions.Regex.IsMatch(fileName, $"(Q\\d+[A-Z]?\\d*)_({suffix})", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
                {
                    string normalized = System.Text.RegularExpressions.Regex.Replace(
                        fileName, 
                        pattern, 
                        replacement, 
                        System.Text.RegularExpressions.RegexOptions.IgnoreCase
                    );
                    
                    if (normalized != fileName)
                    {
                        Debug.Log($"[GeneralConfigManager] 🔧 Normalisation vidéo: '{fileName}' → '{normalized}'");
                        return normalized;
                    }
                }
            }
        }
        
        return fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour une image de fond
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetBackgroundImageUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.backgroundImagePath))
        {
            Debug.LogWarning("[GeneralConfigManager] backgroundImagePath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.backgroundImagePath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour une vidéo de fond
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetBackgroundVideoUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.backgroundVideoPath))
        {
            Debug.LogWarning("[GeneralConfigManager] backgroundVideoPath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.backgroundVideoPath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour un fichier UI
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetUIUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.uiPath))
        {
            Debug.LogWarning("[GeneralConfigManager] uiPath non défini dans la config");
            return fileName;
        }
        
        return config.assetsPaths.uiPath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour un crosshair (curseur de visée)
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetCrosshairUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Détecter et ignorer les URLs placeholder (via.placeholder.com)
        if (fileName.Contains("via.placeholder.com"))
        {
            Debug.LogWarning($"[GeneralConfigManager] ⚠️ URL placeholder détectée pour crosshair: '{fileName}'. Ignorée, utilisation du nom de fichier par défaut.");
            // Retourner une chaîne vide pour déclencher un fallback ou une erreur
            return "";
        }
            
        // Si le fichier contient déjà une URL complète valide, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.crosshairPath))
        {
            // Fallback vers gameAssetsPath si crosshairPath n'est pas défini
            Debug.LogWarning("[GeneralConfigManager] crosshairPath non défini, utilisation de gameAssetsPath");
            return GetGameAssetsUrl(fileName);
        }
        
        return config.assetsPaths.crosshairPath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour un asset de jeu (gun, crosshair, impact, etc.)
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetGameAssetsUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.gameAssetsPath))
        {
            // Fallback vers uiPath si gameAssetsPath n'est pas défini
            Debug.LogWarning("[GeneralConfigManager] gameAssetsPath non défini, utilisation de uiPath");
            return GetUIUrl(fileName);
        }
        
        string basePath = config.assetsPaths.gameAssetsPath;
        
        // Si le chemin de base est STREAMING_ASSETS/, le résoudre d'abord
        if (basePath.StartsWith("STREAMING_ASSETS/"))
        {
            basePath = ResolveStreamingAssetsPath(basePath);
        }
        
        return basePath + fileName;
    }
    
    /// <summary>
    /// Construit l'URL complète pour un fichier de dialogue
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetDialogueUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.dialoguePath))
        {
            Debug.LogWarning("[GeneralConfigManager] dialoguePath non défini dans la config");
            return fileName;
        }
        
        string fullUrl = config.assetsPaths.dialoguePath + fileName;
        return ResolveStreamingAssetsPath(fullUrl);
    }
    
    /// <summary>
    /// Construit l'URL complète pour un fichier de questions
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetQuestionsUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.questionsPath))
        {
            Debug.LogWarning("[GeneralConfigManager] questionsPath non défini dans la config");
            return fileName;
        }
        
        string fullUrl = config.assetsPaths.questionsPath + fileName;
        return ResolveStreamingAssetsPath(fullUrl);
    }
    
    /// <summary>
    /// Construit l'URL complète pour un fichier de configuration de map
    /// </summary>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetMapConfigUrl(string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
            
        var config = GetConfig();
        if (config.assetsPaths == null || string.IsNullOrEmpty(config.assetsPaths.mapsPath))
        {
            Debug.LogWarning("[GeneralConfigManager] mapsPath non défini dans la config");
            return fileName;
        }
        
        string fullUrl = config.assetsPaths.mapsPath + fileName;
        return ResolveStreamingAssetsPath(fullUrl);
    }
    
    /// <summary>
    /// Méthode générique pour construire une URL d'asset
    /// </summary>
    /// <param name="assetType">Type d'asset (popupVideo, decoratorImage, video, backgroundImage, backgroundVideo, ui, dialogue, questions, maps)</param>
    /// <param name="fileName">Nom du fichier</param>
    /// <returns>URL complète</returns>
    public string GetAssetUrl(string assetType, string fileName)
    {
        if (string.IsNullOrEmpty(fileName))
            return "";
            
        // Si le fichier contient déjà une URL complète, le retourner tel quel
        if (fileName.StartsWith("http://") || fileName.StartsWith("https://"))
            return fileName;
        
        switch (assetType.ToLower())
        {
            case "popupvideo":
                return GetPopupVideoUrl(fileName);
            case "decoratorimage":
                return GetDecoratorImageUrl(fileName);
            case "video":
                return GetVideoUrl(fileName);
            case "backgroundimage":
                return GetBackgroundImageUrl(fileName);
            case "backgroundvideo":
                return GetBackgroundVideoUrl(fileName);
            case "ui":
                return GetUIUrl(fileName);
            case "dialogue":
                return GetDialogueUrl(fileName);
            case "questions":
                return GetQuestionsUrl(fileName);
            case "maps":
                return GetMapConfigUrl(fileName);
            default:
                Debug.LogWarning($"[GeneralConfigManager] Type d'asset inconnu: {assetType}");
                return fileName;
        }
    }
    
    // ==========================================
    // GETTERS POUR LES URLs D'API
    // ==========================================
    
    /// <summary>
    /// Obtient l'URL de base de l'API
    /// </summary>
    public string GetApiBaseUrl()
    {
        var config = GetConfig();
        if (config == null)
        {
            Debug.LogError("[GeneralConfigManager] ❌ Config est null");
            return "";
        }
        
        if (config.apiUrls == null)
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls est null dans general-config.json");
            return "";
        }
        
        if (string.IsNullOrEmpty(config.apiUrls.baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.baseUrl est vide dans general-config.json");
            return "";
        }
        
        return config.apiUrls.baseUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API de questions
    /// </summary>
    public string GetQuestionsApiUrl(int answersCount, int count = 3)
    {
        var config = GetConfig();
        string baseUrl = GetApiBaseUrl();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.questionsApi))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.questionsApi non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        string apiPath = config.apiUrls.questionsApi;
        return $"{baseUrl}{apiPath}?type=qcu&answers={answersCount}&count={count}";
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API de questions pour les jeux de type trous
    /// </summary>
    public string GetTrousQuestionsApiUrl()
    {
        var config = GetConfig();
        string baseUrl = GetApiBaseUrl();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.questionsApi))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.questionsApi non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        
        // Récupérer le count depuis la configuration (défaut: 1)
        int count = 1;
        if (config.trousGame != null && config.trousGame.count > 0)
        {
            count = config.trousGame.count;
        }
        
        string apiPath = config.apiUrls.questionsApi;
        return $"{baseUrl}{apiPath}?type=text_blank&count={count}";
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour récupérer la configuration de la scène main
    /// Format: {baseUrl}/api/ujsa/projects/{slug}
    /// </summary>
    public string GetMainSceneConfigApiUrl()
    {
        var config = GetConfig();
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(config.slug))
        {
            Debug.LogError("[GeneralConfigManager] ❌ slug non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        string projectSlug = config.slug;
        string apiUrl = $"{baseUrl}/api/ujsa/projects/{projectSlug}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API main scene construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour récupérer la configuration d'une quest (map)
    /// Format: {baseUrl}/api/ujsa/quests/{questId}
    /// </summary>
    public string GetQuestConfigApiUrl(int questId)
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        if (questId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ questId invalide: {questId}");
            return "";
        }
        
        string apiUrl = $"{baseUrl}/api/ujsa/quests/{questId}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API quest construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour récupérer tous les projets avec leurs quêtes
    /// Format: {baseUrl}/api/ujsa/projects/{projectSlug}
    /// </summary>
    public string GetProjectsApiUrl()
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        var config = GetConfig();
        if (config == null)
        {
            Debug.LogError("[GeneralConfigManager] ❌ config non chargée");
            return "";
        }
        
        string projectSlug = config.slug;
        if (string.IsNullOrEmpty(projectSlug))
        {
            Debug.LogError("[GeneralConfigManager] ❌ projectSlug non défini dans general-config.json");
            return "";
        }
        
        string apiUrl = $"{baseUrl}/api/ujsa/projects/{projectSlug}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API projects construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour récupérer la configuration d'un jeu
    /// Format: {baseUrl}/api/ujsa/games/{gameId}?difficulty={difficulty}
    /// </summary>
    public string GetGameConfigApiUrl(int gameId, string difficulty = "Débutant")
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        if (gameId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ gameId invalide: {gameId}");
            return "";
        }
        
        // Encoder le paramètre difficulty pour l'URL (gère les accents)
        string encodedDifficulty = UnityEngine.Networking.UnityWebRequest.EscapeURL(difficulty);
        string apiUrl = $"{baseUrl}/api/ujsa/games/{gameId}?difficulty={encodedDifficulty}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API game construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL de préconnexion
    /// </summary>
    public string GetAuthPreconnectUrl()
    {
        var config = GetConfig();
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            return "";
        }
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.authPreconnect))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.authPreconnect non défini dans general-config.json");
            return "";
        }
        return baseUrl + config.apiUrls.authPreconnect;
    }
    
    /// <summary>
    /// Obtient l'URL de connexion
    /// </summary>
    public string GetAuthLoginUrl()
    {
        var config = GetConfig();
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            return "";
        }
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.authLogin))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.authLogin non défini dans general-config.json");
            return "";
        }
        return baseUrl + config.apiUrls.authLogin;
    }
    
    /// <summary>
    /// Obtient l'URL de vérification du token
    /// </summary>
    public string GetAuthVerifyTokenUrl()
    {
        var config = GetConfig();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.authVerifyToken))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.authVerifyToken non défini dans general-config.json");
            return "";
        }
        
        // Si l'URL est déjà absolue, ne pas ajouter baseUrl
        if (config.apiUrls.authVerifyToken.StartsWith("http://") || config.apiUrls.authVerifyToken.StartsWith("https://"))
        {
            return config.apiUrls.authVerifyToken;
        }
        
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            return "";
        }
        return baseUrl + config.apiUrls.authVerifyToken;
    }
    
    /// <summary>
    /// Obtient l'URL d'inscription
    /// </summary>
    public string GetRegistrationUrl()
    {
        var config = GetConfig();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.registrationUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.registrationUrl non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        return config.apiUrls.registrationUrl;
    }

    public string GetForgotPasswordUrl()
    {
        var config = GetConfig();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.forgotPasswordUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.forgotPasswordUrl non défini dans general-config.json");
            return "";
        }
        return config.apiUrls.forgotPasswordUrl;
    }

    /// <summary>
    /// Obtient l'URL de reset de mot de passe
    /// Format: {baseUrl}/api/auth/reset-password
    /// </summary>
    public string GetAuthResetPasswordUrl()
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }

        return baseUrl.TrimEnd('/') + "/api/auth/reset-password";
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour envoyer les réponses d'un joueur pour un jeu
    /// Format: {baseUrl}/api/ujsa/games/{gameId}/answers
    /// </summary>
    public string GetGameAnswersApiUrl(int gameId)
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        var config = GetConfig();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.gameAnswersApi))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.gameAnswersApi non défini dans general-config.json");
            return "";
        }
        
        if (gameId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ gameId invalide: {gameId}");
            return "";
        }
        
        // Remplacer {gameId} par la valeur réelle
        string apiPath = config.apiUrls.gameAnswersApi.Replace("{gameId}", gameId.ToString());
        string apiUrl = $"{baseUrl}{apiPath}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API answers construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour reset les réponses avant un jeu
    /// Format: {baseUrl}/api/ujsa/games/{gameId}/answers/reset
    /// </summary>
    public string GetGameAnswersResetApiUrl(int gameId)
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        var config = GetConfig();
        if (config.apiUrls == null || string.IsNullOrEmpty(config.apiUrls.gameAnswersResetApi))
        {
            Debug.LogError("[GeneralConfigManager] ❌ apiUrls.gameAnswersResetApi non défini dans general-config.json");
            return "";
        }
        
        if (gameId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ gameId invalide: {gameId}");
            return "";
        }
        
        // Remplacer {gameId} par la valeur réelle
        string apiPath = config.apiUrls.gameAnswersResetApi.Replace("{gameId}", gameId.ToString());
        string apiUrl = $"{baseUrl}{apiPath}";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API answers reset construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour reset la progression courante d'une quête
    /// Format: {baseUrl}/api/ujsa/quests/{questId}/answers/reset-current
    /// </summary>
    public string GetQuestResetCurrentApiUrl(int questId)
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        if (questId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ questId invalide: {questId}");
            return "";
        }
        
        string apiUrl = $"{baseUrl}/api/ujsa/quests/{questId}/answers/reset-current";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API quest reset-current construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL complète de l'API pour reset les réponses d'une quête par difficulté
    /// Format: {baseUrl}/api/ujsa/quests/{questId}/answers/reset-by-difficulty
    /// </summary>
    public string GetQuestAnswersResetByDifficultyUrl(int questId)
    {
        string baseUrl = GetApiBaseUrl();
        if (string.IsNullOrEmpty(baseUrl))
        {
            Debug.LogError("[GeneralConfigManager] ❌ baseUrl non défini dans general-config.json");
            return "";
        }
        
        if (questId <= 0)
        {
            Debug.LogError($"[GeneralConfigManager] ❌ questId invalide: {questId}");
            return "";
        }
        
        string apiUrl = $"{baseUrl}/api/ujsa/quests/{questId}/answers/reset-by-difficulty";
        LogVerbose($"[GeneralConfigManager] 🔍 URL API quest reset-by-difficulty construite: {apiUrl}");
        return apiUrl;
    }
    
    /// <summary>
    /// Obtient l'URL de configuration par défaut pour un type de jeu
    /// </summary>
    public string GetDefaultGameConfigUrl(string gameType)
    {
        var config = GetConfig();
        if (config.defaultConfigUrls == null)
        {
            Debug.LogWarning("[GeneralConfigManager] defaultConfigUrls non défini");
            return "";
        }
        
        switch (gameType.ToLower())
        {
            case "shooting":
                return ResolveStreamingAssetsPath(config.defaultConfigUrls.shootingGameConfig);
            case "calculator":
                return ResolveStreamingAssetsPath(config.defaultConfigUrls.calculatorGameConfig);
            case "levels":
                return ResolveStreamingAssetsPath(config.defaultConfigUrls.levelsConfigUrl);
            default:
                Debug.LogWarning($"[GeneralConfigManager] Type de jeu inconnu: {gameType}");
                return "";
        }
    }
    
    /// <summary>
    /// Obtient l'URL racine pour les JSON de niveaux
    /// </summary>
    public string GetLevelsConfigUrlRootJson()
    {
        var config = GetConfig();
        if (config.levelsConfig == null || string.IsNullOrEmpty(config.levelsConfig.urlRootJson))
        {
            Debug.LogError("[GeneralConfigManager] ❌ levelsConfig.urlRootJson non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        return config.levelsConfig.urlRootJson;
    }
    
    /// <summary>
    /// Obtient l'URL racine pour les thumbnails de niveaux
    /// </summary>
    public string GetLevelsConfigUrlRootThumbnails()
    {
        var config = GetConfig();
        if (config.levelsConfig == null || string.IsNullOrEmpty(config.levelsConfig.urlRootThumbnails))
        {
            Debug.LogError("[GeneralConfigManager] ❌ levelsConfig.urlRootThumbnails non défini dans general-config.json");
            return ""; // Retourner vide plutôt qu'une valeur en dur
        }
        return config.levelsConfig.urlRootThumbnails;
    }
    
    // ==========================================
    // GETTERS POUR LES VALEURS PAR DÉFAUT
    // ==========================================
    
    /// <summary>
    /// Obtient les assets par défaut
    /// </summary>
    public DefaultAssets GetDefaultAssets()
    {
        var config = GetConfig();
        return config.defaultAssets;
    }
    
    /// <summary>
    /// Obtient la palette de couleurs
    /// </summary>
    public ColorPalette GetColorPalette()
    {
        var config = GetConfig();
        return config.colorPalette;
    }
    
    /// <summary>
    /// Obtient la configuration popup par défaut
    /// </summary>
    public DefaultPopupConfig GetDefaultPopupConfig()
    {
        var config = GetConfig();
        return config.defaultPopupConfig;
    }
    
    /// <summary>
    /// Obtient la configuration header par défaut
    /// </summary>
    public DefaultHeaderConfig GetDefaultHeaderConfig()
    {
        var config = GetConfig();
        return config.defaultHeaderConfig;
    }
    
    /// <summary>
    /// Obtient la configuration dialogue par défaut
    /// </summary>
    public DefaultDialogueConfig GetDefaultDialogueConfig()
    {
        var config = GetConfig();
        return config.defaultDialogueConfig;
    }
    
    /// <summary>
    /// Obtient les messages de feedback par défaut
    /// </summary>
    public DefaultFeedbackMessages GetDefaultFeedbackMessages()
    {
        var config = GetConfig();
        return config.defaultFeedbackMessages;
    }
    
    /// <summary>
    /// Obtient un style de bouton par son nom (ex: "validationPurple")
    /// </summary>
    public ButtonStyleConfig GetButtonStyle(string styleName)
    {
        if (string.IsNullOrEmpty(styleName)) return null;
        
        var config = GetConfig();
        if (config?.buttonStyles == null) return null;
        
        return config.buttonStyles.GetStyle(styleName);
    }
    
    /// <summary>
    /// Obtient la configuration UI par défaut
    /// </summary>
    public DefaultUIConfig GetDefaultUIConfig()
    {
        var config = GetConfig();
        if (config == null)
        {
            Debug.LogWarning("[GeneralConfigManager] GetDefaultUIConfig - config est null");
            return null;
        }
        if (config.defaultUIConfig == null)
        {
            Debug.LogError("[GeneralConfigManager] GetDefaultUIConfig - config.defaultUIConfig est null!");
        }
        return config.defaultUIConfig;
    }
    
    /// <summary>
    /// Obtient la résolution par défaut
    /// </summary>
    public DefaultResolution GetDefaultResolution()
    {
        var config = GetConfig();
        return config.defaultResolution;
    }
    
    /// <summary>
    /// Obtient la configuration crosshair par défaut
    /// </summary>
    public DefaultCrosshairConfig GetDefaultCrosshairConfig()
    {
        var config = GetConfig();
        return config.defaultCrosshairConfig;
    }
    
    /// <summary>
    /// Obtient la configuration gun par défaut
    /// </summary>
    public DefaultGunConfig GetDefaultGunConfig()
    {
        var config = GetConfig();
        return config.defaultGunConfig;
    }
    
    /// <summary>
    /// Obtient la configuration zone decorator par défaut
    /// </summary>
    public DefaultZoneDecorator GetDefaultZoneDecorator()
    {
        var config = GetConfig();
        return config.defaultZoneDecorator;
    }
    
    /// <summary>
    /// Obtient les timings d'animations
    /// </summary>
    public AnimationTimings GetAnimationTimings()
    {
        var config = GetConfig();
        return config.animations;
    }
}
