using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using TMPro;
using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// Onglet QUÊTES dans le panneau des paramètres
/// Affiche la liste des quêtes à gauche et les détails à droite (personnages, badges, formations)
/// </summary>
public class QuetesTab : SettingsTab
{
    private QuetesTabConfig config;
    private GeneralConfig generalConfig;
    
    // Références aux blocs
    private GameObject questListBlock;
    private GameObject detailsTabsMenu;
    private GameObject detailsBlock;
    
    // Contenu des détails
    private GameObject detailsHeader;
    private GameObject detailsContent;
    
    // Onglets des détails
    private List<Button> detailTabButtons = new List<Button>();
    private int activeDetailTab = 0;
    
    // Données
    private List<Quest> questsData = new List<Quest>();
    private Quest selectedQuest;
    private Dictionary<int, List<QuestCharacter>> questCharacters = new Dictionary<int, List<QuestCharacter>>();

    // Bouton "JOUER" (sous le cadre de gauche)
    private GameObject playQuestButtonObj;
    private Button playQuestButton;
    private TextMeshProUGUI playQuestButtonText;
    private bool isLaunchingFromQuetesTab = false;

    private const string PendingQuestPreviewMapIdKey = "PendingQuestPreviewMapId";
    private const string PendingQuestPreviewQuestIdKey = "PendingQuestPreviewQuestId";
    
    // Références aux items de quête pour la sélection
    private Dictionary<int, GameObject> questItemsMap = new Dictionary<int, GameObject>();
    
    // Mosaïque de personnages
    private List<QuestCharacter> currentCharacters = new List<QuestCharacter>();
    private int selectedCharacterIndex = 0;
    private int visibleStartIndex = 0; // Index du premier personnage visible
    private List<GameObject> avatarButtons = new List<GameObject>();
    private GameObject avatarsContainer;
    private GameObject characterDetailPanel;
    // private TextMeshProUGUI characterNameText; // Unused - gardé pour référence future
    private TextMeshProUGUI characterDescriptionText;
    private TextMeshProUGUI characterLongDescriptionText;
    
    // Flèches de navigation
    private GameObject leftArrow;
    private GameObject rightArrow;
    
    // Sprites et polices
    private TMP_FontAsset titleFont;
    private TMP_FontAsset textFont;
    // Textures/sprites persistants (UI de la liste) - nettoyés uniquement à la fermeture du panel
    private readonly List<Texture2D> dynamicTextures = new List<Texture2D>();
    private readonly List<Sprite> dynamicSprites = new List<Sprite>();

    // Textures/sprites liés aux détails de quête (personnages/badges/formations) - nettoyés à CHAQUE changement de quête / onglet
    private readonly List<Texture2D> detailTextures = new List<Texture2D>();
    private readonly List<Sprite> detailSprites = new List<Sprite>();

    // Permet d'invalider les coroutines de chargement quand on change de quête/onglet
    private int detailsGeneration = 0;

    private enum ResourceScope
    {
        Persistent,
        Details
    }

    private ResourceScope currentScope = ResourceScope.Persistent;

    // Cache pour fleche_formation.png (évite de re-télécharger et de recréer des textures en boucle)
    private Sprite cachedFormationArrowSprite;
    private Texture2D cachedFormationArrowTexture;
    private bool isLoadingFormationArrowSprite = false;
    private readonly List<Image> pendingFormationArrowTargets = new List<Image>();
    
    // Flag pour forcer la reconstruction de l'interface
    private bool needsRebuild = false;
    
    // Protection contre les clics multiples rapides
    private bool isLoadingData = false;
    private bool isRefreshingData = false;
    private bool isSwitchingTab = false;
    private bool isSelectingQuest = false;
    private float lastClickTime = 0f;
    private const float CLICK_DEBOUNCE_TIME = 0.3f; // 300ms entre les clics
    
    protected override void Awake()
    {
        base.Awake();
        
        // Charger la configuration
        if (GeneralConfigManager.Instance != null)
        {
            generalConfig = GeneralConfigManager.Instance.GetConfig();
            config = generalConfig?.quetesTabConfig;
            
            if (config != null)
            {
                // Charger les polices
                LoadFonts();
                
                // Démarrer la construction de l'interface
                StartCoroutine(BuildInterface());
            }
            else
            {
                Debug.LogError("[QuetesTab] ❌ Configuration quetesTabConfig non trouvée!");
            }
        }
        else
        {
            Debug.LogError("[QuetesTab] ❌ GeneralConfigManager.Instance est null!");
        }
    }
    
    private bool isInitialized = false;
    
    protected override void OnEnable()
    {
        base.OnEnable();
        Debug.Log($"[QuetesTab] OnEnable appelé - needsRebuild={needsRebuild}, questListBlock={questListBlock != null}");
        
        // Si l'interface a été détruite (needsRebuild=true), la reconstruire
        if (needsRebuild)
        {
            Debug.Log("[QuetesTab] 🔄 Reconstruction de l'interface après réouverture");
            needsRebuild = false;
            StartCoroutine(RebuildInterface());
            return;
        }
        
        // Si l'interface n'est pas encore construite, attendre
        if (questListBlock == null)
        {
            StartCoroutine(WaitForInterfaceAndPopulate());
            return;
        }
        
        // TOUJOURS recharger les données pour avoir les badges/progression à jour
        Debug.Log("[QuetesTab] 🔄 Rechargement des données depuis l'API...");
        LoadQuestsData();
        PopulateQuestList();
        isInitialized = true;
    }
    
    IEnumerator RebuildInterface()
    {
        // Recharger les polices
        LoadFonts();
        
        // Reconstruire l'interface
        yield return StartCoroutine(BuildInterface());
        
        // Recharger les données
        LoadQuestsData();
        PopulateQuestList();
        isInitialized = true;
    }
    
    IEnumerator WaitForInterfaceAndPopulate()
    {
        // Attendre que l'interface soit construite
        int maxWait = 50; // 5 secondes max
        int waited = 0;
        
        while (questListBlock == null && waited < maxWait)
        {
            yield return new WaitForSeconds(0.1f);
            waited++;
        }
        
        if (questListBlock != null && !isInitialized)
        {
            LoadQuestsData();
            PopulateQuestList();
            isInitialized = true;
        }
        else if (questListBlock == null)
        {
            Debug.LogError($"[QuetesTab] ❌ Interface toujours pas prête après {maxWait * 0.1f}s!");
        }
    }
    
    void OnDestroy()
    {
        Debug.Log("[QuetesTab] 🧹 OnDestroy - Nettoyage des ressources");
        CleanupResources();
    }
    
    /// <summary>
    /// Appelé par SettingsManager quand le panel se ferme (pas juste changement d'onglet)
    /// </summary>
    public void CleanupOnPanelClose()
    {
        Debug.Log("[QuetesTab] 🧹 CleanupOnPanelClose - Nettoyage pour éviter OOM WebGL");
        CleanupResources();
    }
    
    protected override void OnDisable()
    {
        base.OnDisable();
        Debug.Log("[QuetesTab] OnDisable - Ne rien faire, le cleanup se fera dans OnDestroy");
        // NE PAS détruire l'interface ici car OnDisable() est appelé même quand on change d'onglet !
        // Le cleanup se fera uniquement dans OnDestroy() quand le panel se ferme vraiment
    }
    
    void CleanupResources()
    {
        // Nettoyage complet (panel qui se ferme)
        CleanupDetailResources();

        for (int i = 0; i < dynamicSprites.Count; i++)
        {
            Sprite sp = dynamicSprites[i];
            if (sp != null) Destroy(sp);
        }
        dynamicSprites.Clear();

        for (int i = 0; i < dynamicTextures.Count; i++)
        {
            Texture2D tex = dynamicTextures[i];
            if (tex != null) Destroy(tex);
        }
        dynamicTextures.Clear();
        
        // NOTE(WebGL): On évite GC.Collect / Resources.UnloadUnusedAssets() ici.
        // On détruit déjà explicitement textures/sprites (persistants + détails).
    }

    private void CleanupDetailResources()
    {
        // Invalider toutes les coroutines de chargement en cours (avatars/badges/images)
        detailsGeneration++;

        for (int i = 0; i < detailSprites.Count; i++)
        {
            Sprite sp = detailSprites[i];
            if (sp != null) Destroy(sp);
        }
        detailSprites.Clear();

        for (int i = 0; i < detailTextures.Count; i++)
        {
            Texture2D tex = detailTextures[i];
            if (tex != null) Destroy(tex);
        }
        detailTextures.Clear();
    }

    private void TrackPersistent(Texture2D tex, Sprite sp)
    {
        if (tex != null) dynamicTextures.Add(tex);
        if (sp != null) dynamicSprites.Add(sp);
    }

    private void TrackDetails(Texture2D tex, Sprite sp)
    {
        if (tex != null) detailTextures.Add(tex);
        if (sp != null) detailSprites.Add(sp);
    }

    private void SetScope(ResourceScope scope)
    {
        currentScope = scope;
    }
    
    void LoadFonts()
    {
        string titleFontName = config?.fonts?.titleFontName ?? "Anton-Regular SDF";
        string textFontName = config?.fonts?.textFontName ?? "Lato-Regular SDF";
        
        titleFont = Resources.Load<TMP_FontAsset>($"Fonts/{titleFontName}");
        if (titleFont == null)
            titleFont = Resources.Load<TMP_FontAsset>(titleFontName);
            
        textFont = Resources.Load<TMP_FontAsset>($"Fonts/{textFontName}");
        if (textFont == null)
            textFont = Resources.Load<TMP_FontAsset>(textFontName);
            
    }
    
    void LoadQuestsData()
    {
        // Vider la liste existante pour recharger
        questsData.Clear();
        
        // Charger les données des quêtes depuis l'API
        // Essayer de trouver MainSceneManager dans la scène
        MainSceneManager mainSceneManager = FindFirstObjectByType<MainSceneManager>();
        
        if (mainSceneManager != null)
        {
            Debug.Log($"[QuetesTab] MainSceneManager: {mainSceneManager.name}");
            
            // Accéder aux données via réflexion car sceneConfig est privé
            var sceneConfigField = mainSceneManager.GetType().GetField("sceneConfig", 
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            
            if (sceneConfigField != null)
            {
                MainSceneConfig sceneConfig = sceneConfigField.GetValue(mainSceneManager) as MainSceneConfig;
                Debug.Log($"[QuetesTab] sceneConfig récupéré: {sceneConfig != null}");
                
                if (sceneConfig != null)
                {
                    Debug.Log($"[QuetesTab] sceneConfig.quests: {sceneConfig.quests != null}");
                    Debug.Log($"[QuetesTab] sceneConfig.quests.Count: {sceneConfig.quests?.Count ?? 0}");
                }
                
                if (sceneConfig != null && sceneConfig.quests != null && sceneConfig.quests.Count > 0)
                {
                    // Filtrer uniquement les quêtes où has_access == true
                    questsData = new List<Quest>();
                    foreach (Quest q in sceneConfig.quests)
                    {
                        if (q.has_access)
                        {
                            questsData.Add(q);
                            Debug.Log($"[QuetesTab] 📋 Quête {q.id}: {q.title} (has_access={q.has_access}, status={q.status ?? "null"})");
                            Debug.Log($"[QuetesTab]    Description: {q.description}");
                            Debug.Log($"[QuetesTab]    Durée: {q.duration}");
                            Debug.Log($"[QuetesTab]    Formations: {q.trainings?.Count ?? 0}");
                        }
                        else
                        {
                            Debug.Log($"[QuetesTab] ⚠️ Quête {q.id} ignorée (has_access=false)");
                        }
                    }
                    Debug.Log($"[QuetesTab] ✅ {questsData.Count} quêtes avec accès chargées depuis MainSceneManager");
                }
                else
                {
                    Debug.LogWarning($"[QuetesTab] ⚠️ sceneConfig trouvé mais pas de quêtes (quests = {sceneConfig?.quests?.Count ?? 0})");
                }
            }
            else
            {
                Debug.LogWarning("[QuetesTab] ⚠️ Impossible d'accéder au champ sceneConfig via réflexion");
            }
        }
        else
        {
            Debug.LogWarning("[QuetesTab] ⚠️ MainSceneManager non trouvé dans la scène (probablement dans MapManager)");
            Debug.Log("[QuetesTab] 🌐 Chargement direct depuis l'API...");
            // Charger directement depuis l'API (la coroutine s'occupera de tout y compris l'affichage)
            StartCoroutine(LoadQuestsFromApi());
            return; // On sort ici car LoadQuestsFromApi() s'occupera de tout
        }
        
        // Fallback: créer quelques quêtes d'exemple si aucune donnée trouvée (UNIQUEMENT en cas d'échec API)
        if (questsData.Count == 0)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Aucune donnée de l'API dans MainSceneManager, création du fallback...");
            CreateFallbackQuests();
        }
        
        // Charger les personnages des quêtes
        StartCoroutine(LoadQuestCharacters());
        
        // IMPORTANT : Aussi recharger depuis l'API pour mettre à jour les badges/progression
        Debug.Log("[QuetesTab] 🔄 Lancement du rechargement API en arrière-plan pour badges/progression...");
        StartCoroutine(RefreshQuestsFromApi());
    }
    
    IEnumerator LoadQuestsFromApi()
    {
        Debug.Log("[QuetesTab] 🌐 Chargement des quêtes depuis l'API...");
        
        // Obtenir l'URL de l'API des projets depuis la config
        GeneralConfigManager configManager = FindFirstObjectByType<GeneralConfigManager>();
        if (configManager == null)
        {
            Debug.LogError("[QuetesTab] ❌ GeneralConfigManager non trouvé !");
            // Utiliser fallback en dernier recours
            CreateFallbackQuests();
            PopulateQuestList();
            yield break;
        }
        
        string apiUrl = configManager.GetMainSceneConfigApiUrl();
        Debug.Log($"[QuetesTab] 📡 URL API: {apiUrl}");
        
        // Vérifier si un token est disponible
        string token = UserDataManager.Instance != null ? UserDataManager.Instance.token : "";
        bool hasToken = !string.IsNullOrEmpty(token);
        Debug.Log($"[QuetesTab] 🔑 Token disponible: {hasToken}");
        
        using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(apiUrl))
        {
            // Ajouter le token d'authentification si disponible
            if (hasToken)
            {
                request.SetRequestHeader("Authorization", $"Bearer {token}");
                Debug.Log("[QuetesTab] 🔑 Token ajouté à la requête");
            }
            
            yield return request.SendWebRequest();
            
            if (request.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
            {
                Debug.LogError($"[QuetesTab] ❌ Erreur API: {request.error}");
                // Utiliser fallback en cas d'erreur
                CreateFallbackQuests();
                PopulateQuestList();
                yield break;
            }
            
            string jsonResponse = request.downloadHandler.text;
            Debug.Log($"[QuetesTab] ✅ Réponse API reçue ({jsonResponse.Length} caractères)");
            
            try
            {
                // Parser la réponse JSON en utilisant le même format que MainSceneManager
                ApiMainSceneResponse response = JsonUtility.FromJson<ApiMainSceneResponse>(jsonResponse);
                
                if (response != null && response.status == "success" && response.data != null && response.data.quests != null)
                {
                    Debug.Log($"[QuetesTab] 📦 {response.data.quests.Count} quêtes reçues depuis l'API");
                    
                    // Convertir ApiMainSceneQuest vers Quest
                    foreach (var apiQuest in response.data.quests)
                    {
                        // Créer une Quest depuis ApiMainSceneQuest
                        Quest quest = new Quest
                        {
                            id = apiQuest.id,
                            title = apiQuest.title,
                            description = apiQuest.description,
                            trailer_url = apiQuest.trailer_url,
                            duration = apiQuest.duration,
                            visibility = apiQuest.visibility,
                            status = apiQuest.status,
                            trainings = new List<Training>(),
                            has_access = apiQuest.has_access,
                            sprite_url = apiQuest.sprite_url,
                            sprite_position = apiQuest.sprite_position,
                            sprite_size = apiQuest.sprite_size,
                            tooltip_text = apiQuest.tooltip_text,
                            user = apiQuest.user // Copier la progression utilisateur
                        };
                        
                        // Convertir les trainings
                        if (apiQuest.trainings != null)
                        {
                            foreach (var apiTraining in apiQuest.trainings)
                            {
                                quest.trainings.Add(new Training
                                {
                                    title = apiTraining.title,
                                    url = apiTraining.url
                                });
                            }
                        }
                        
                        // Ajouter seulement si has_access = true
                        if (quest.has_access)
                        {
                            questsData.Add(quest);
                            Debug.Log($"[QuetesTab] ✅ Quête ajoutée: {quest.title} (ID: {quest.id})");
                        }
                    }
                    
                    Debug.Log($"[QuetesTab] ✅ {questsData.Count} quêtes avec accès chargées depuis l'API");
                }
                else
                {
                    Debug.LogError("[QuetesTab] ❌ Réponse API invalide");
                    CreateFallbackQuests();
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError($"[QuetesTab] ❌ Erreur de parsing JSON: {e.Message}");
                CreateFallbackQuests();
            }
            
            // Charger les personnages
            yield return StartCoroutine(LoadQuestCharacters());
            
            // Rafraîchir l'affichage
            PopulateQuestList();
        }
    }
    
    IEnumerator RefreshQuestsFromApi()
    {
        Debug.Log("[QuetesTab] 🔄 Rafraîchissement des badges/progression depuis l'API...");
        
        // Obtenir l'URL de l'API des projets depuis la config
        GeneralConfigManager configManager = FindFirstObjectByType<GeneralConfigManager>();
        if (configManager == null)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ GeneralConfigManager non trouvé pour le refresh");
            yield break;
        }
        
        string apiUrl = configManager.GetMainSceneConfigApiUrl();
        Debug.Log($"[QuetesTab] 📡 URL API (refresh): {apiUrl}");
        
        // Vérifier si un token est disponible
        string token = UserDataManager.Instance != null ? UserDataManager.Instance.token : "";
        bool hasToken = !string.IsNullOrEmpty(token);
        Debug.Log($"[QuetesTab] 🔑 Token disponible (refresh): {hasToken}");
        
        using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(apiUrl))
        {
            // Ajouter le token d'authentification si disponible
            if (hasToken)
            {
                request.SetRequestHeader("Authorization", $"Bearer {token}");
                Debug.Log("[QuetesTab] 🔑 Token ajouté à la requête (refresh)");
            }
            
            yield return request.SendWebRequest();
            
            if (request.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Erreur refresh API: {request.error}");
                yield break;
            }
            
            string jsonResponse = request.downloadHandler.text;
            
            try
            {
                // Parser la réponse JSON en utilisant le même format que MainSceneManager
                ApiMainSceneResponse response = JsonUtility.FromJson<ApiMainSceneResponse>(jsonResponse);
                
                if (response != null && response.status == "success" && response.data != null && response.data.quests != null)
                {
                    Debug.Log($"[QuetesTab] 🔄 Mise à jour des badges/progression...");
                    
                    // Pour chaque quête de l'API
                    foreach (var apiQuest in response.data.quests)
                    {
                        // Trouver la quête correspondante dans questsData
                        Quest existingQuest = questsData.Find(q => q.id == apiQuest.id);
                        
                        if (existingQuest != null)
                        {
                            // Mettre à jour les badges/progression (user array)
                            if (apiQuest.user != null)
                            {
                                existingQuest.user = apiQuest.user;
                                Debug.Log($"[QuetesTab] ✅ Badges mis à jour pour quête {existingQuest.title}");
                            }
                        }
                    }
                    
                    Debug.Log("[QuetesTab] ✅ Refresh API terminé - badges mis à jour");
                }
            }
            catch (System.Exception e)
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Erreur refresh JSON: {e.Message}");
            }
        }
    }
    
    void CreateFallbackQuests()
    {
        Debug.Log("[QuetesTab] 🔨 Création de quêtes de fallback...");
        
        // Créer des quêtes d'exemple pour le fallback avec des titres réalistes
        string[] titles = new string[]
        {
            "QUÊTE 1 : LES DENTS DE L'AMER",
            "QUÊTE 2 : LA MARCHE DES ZOMBIES",
            "QUÊTE 3 : JURASSIC VILLE",
            "QUÊTE 4 : LE CLAN DES CIGARES",
            "QUÊTE 5 : BULLES SUR LA VILLE"
        };
        
        for (int i = 0; i < titles.Length; i++)
        {
            var quest = new Quest
            {
                id = i + 1,
                title = titles[i],
                description = $"Les rideaux de fer tombent sur la ville... Découvrez pourquoi les habitants du quartier sont sur les dents ! C'est une aventure passionnante qui vous attend dans cette quête {i + 1}.",
                duration = "2 H 00",
                trainings = new List<Training>
                {
                    new Training { title = "Comprendre le régime d'assurance maladie", url = "" },
                    new Training { title = "Comprendre l'assurance complémentaire santé", url = "" }
                }
            };
            
            questsData.Add(quest);
            Debug.Log($"[QuetesTab] ✅ Quête fallback créée: {quest.title}");
        }
        
        Debug.Log($"[QuetesTab] ✅ {questsData.Count} quêtes de fallback créées au total");
    }
    
    IEnumerator LoadQuestCharacters()
    {
        // Charger les personnages depuis persos.json
        string persosJsonPath = Application.streamingAssetsPath + "/persos.json";
        
        if (System.IO.File.Exists(persosJsonPath))
        {
            string json = System.IO.File.ReadAllText(persosJsonPath);
            CharactersData charactersData = JsonUtility.FromJson<CharactersData>(json);
            
            if (charactersData != null && charactersData.personnages != null)
            {
                Debug.Log($"[QuetesTab] ✅ {charactersData.personnages.Count} personnages chargés depuis persos.json");
                
                // Pour le moment, assigner tous les personnages à toutes les quêtes
                // TODO: Implémenter une logique pour assigner les personnages par quête
                foreach (Quest quest in questsData)
                {
                    questCharacters[quest.id] = charactersData.personnages;
                }
            }
        }
        else
        {
            Debug.LogWarning($"[QuetesTab] ⚠️ Fichier persos.json non trouvé à {persosJsonPath}");
        }
        
        yield return null;
    }
    
    IEnumerator BuildInterface()
    {
        Debug.Log("[QuetesTab] Construction de l'interface...");
        
        if (config == null)
        {
            Debug.LogError("[QuetesTab] ❌ Configuration null, impossible de construire l'interface");
            yield break;
        }
        
        // Créer le bloc de liste des quêtes (à gauche)
        CreateQuestListBlock();
        
        // Créer le menu d'onglets (au-dessus du bloc de détails)
        CreateDetailsTabsMenu();
        
        // Créer le bloc de détails (à droite, sous le menu)
        CreateDetailsBlock();
        
        Debug.Log("[QuetesTab] ✅ Interface construite");
        
        yield return null;
        
        // Charger les données des quêtes une seule fois
        Debug.Log("[QuetesTab] 🎯 Chargement initial des données...");
        LoadQuestsData();
        
        yield return new WaitForSeconds(0.2f);
        
        // Remplir la liste des quêtes (utilise fallback si pas de données API)
        Debug.Log("[QuetesTab] 📋 Remplissage de la liste...");
        PopulateQuestList();
        
        isInitialized = true;
        Debug.Log("[QuetesTab] ✅ Construction de l'interface terminée");
    }
    
    void CreateQuestListBlock()
    {
        var listConfig = config.questListBlock;
        
        questListBlock = new GameObject("QuestListBlock");
        questListBlock.transform.SetParent(transform, false);
        questListBlock.transform.SetAsLastSibling();
        
        RectTransform rect = questListBlock.AddComponent<RectTransform>();
        rect.anchorMin = new Vector2(0, 1);
        rect.anchorMax = new Vector2(0, 1);
        rect.pivot = new Vector2(0, 1);
        rect.anchoredPosition = new Vector2(listConfig.x, -listConfig.y);
        rect.sizeDelta = new Vector2(listConfig.width, listConfig.height);
        
        // Image de fond avec coins arrondis (sprite blanc + couleur via Image.color)
        Image bgImage = questListBlock.AddComponent<Image>();
        bgImage.sprite = CreateRoundedSprite((int)listConfig.width, (int)listConfig.height,
            listConfig.cornerRadius, Color.white);
        bgImage.color = HexToColor(listConfig.backgroundColor);
        bgImage.type = Image.Type.Simple;
        bgImage.raycastTarget = false;
        
        if (listConfig.shadowEnabled)
        {
            Shadow shadow = questListBlock.AddComponent<Shadow>();
            shadow.effectColor = HexToColor(listConfig.shadowColor);
            shadow.effectDistance = new Vector2(0, -listConfig.shadowOffsetY);
        }
        
        // ScrollRect pour la liste - APPROCHE SIMPLIFIÉE SANS LAYOUTGROUP
        GameObject viewport = new GameObject("Viewport");
        viewport.transform.SetParent(questListBlock.transform, false);
        
        RectTransform viewportRect = viewport.AddComponent<RectTransform>();
        viewportRect.anchorMin = Vector2.zero;
        viewportRect.anchorMax = Vector2.one;
        // Réserver de l'espace pour la scrollbar à droite
        float scrollbarWidth = 8f; // Largeur de la scrollbar
        float scrollbarSpacing = 8f; // Espace entre le contenu et la scrollbar
        viewportRect.offsetMin = new Vector2(listConfig.padding, listConfig.padding);
        viewportRect.offsetMax = new Vector2(-listConfig.padding - scrollbarWidth - scrollbarSpacing, -listConfig.padding);
        
        // IMPORTANT: RectMask2D au lieu de Mask pour un meilleur clipping
        RectMask2D rectMask = viewport.AddComponent<RectMask2D>();
        
        // Content container - SANS LayoutGroup, positionnement manuel
        GameObject content = new GameObject("Content");
        content.transform.SetParent(viewport.transform, false);
        
        RectTransform contentRect = content.AddComponent<RectTransform>();
        // Ancré en haut à gauche, taille calculée manuellement
        contentRect.anchorMin = new Vector2(0, 1);
        contentRect.anchorMax = new Vector2(0, 1);
        contentRect.pivot = new Vector2(0, 1);
        contentRect.anchoredPosition = Vector2.zero;
        // La taille sera mise à jour dans PopulateQuestList
        float contentWidth = listConfig.width - listConfig.padding * 2 - scrollbarWidth - scrollbarSpacing;
        contentRect.sizeDelta = new Vector2(contentWidth, 100); // Hauteur temporaire
        
        // Créer la Scrollbar verticale - APPROCHE SIMPLIFIÉE SANS SPRITES CUSTOM
        GameObject scrollbarObj = new GameObject("Scrollbar Vertical");
        scrollbarObj.transform.SetParent(questListBlock.transform, false);
        
        RectTransform scrollbarRect = scrollbarObj.AddComponent<RectTransform>();
        scrollbarRect.anchorMin = new Vector2(1, 0);
        scrollbarRect.anchorMax = new Vector2(1, 1);
        scrollbarRect.pivot = new Vector2(1, 0.5f);
        scrollbarRect.anchoredPosition = new Vector2(-listConfig.padding - 4f, 0);
        scrollbarRect.sizeDelta = new Vector2(scrollbarWidth, 0);
        
        Scrollbar scrollbar = scrollbarObj.AddComponent<Scrollbar>();
        scrollbar.direction = Scrollbar.Direction.BottomToTop;
        
        // Sliding Area (zone fonctionnelle)
        GameObject slidingArea = new GameObject("Sliding Area");
        slidingArea.transform.SetParent(scrollbarObj.transform, false);
        
        RectTransform slidingAreaRect = slidingArea.AddComponent<RectTransform>();
        slidingAreaRect.anchorMin = Vector2.zero;
        slidingAreaRect.anchorMax = Vector2.one;
        slidingAreaRect.sizeDelta = Vector2.zero;
        slidingAreaRect.offsetMin = Vector2.zero;
        slidingAreaRect.offsetMax = Vector2.zero;
        
        // Handle (simple, sans marges pour WebGL)
        GameObject handle = new GameObject("Handle");
        handle.transform.SetParent(slidingArea.transform, false);
        
        RectTransform handleRect = handle.AddComponent<RectTransform>();
        handleRect.anchorMin = Vector2.zero;
        handleRect.anchorMax = Vector2.one;
        handleRect.sizeDelta = Vector2.zero;
        handleRect.offsetMin = Vector2.zero;
        handleRect.offsetMax = Vector2.zero;
        
        Image handleImage = handle.AddComponent<Image>();
        handleImage.color = HexToColor("#ab907f");
        handleImage.raycastTarget = true;
        
        // Assigner le handle à la scrollbar
        scrollbar.targetGraphic = handleImage;
        scrollbar.handleRect = handleRect;
        
        // Configurer le ScrollRect avec la scrollbar
        ScrollRect scrollRect = questListBlock.AddComponent<ScrollRect>();
        scrollRect.content = contentRect;
        scrollRect.viewport = viewportRect;
        scrollRect.horizontal = false;
        scrollRect.vertical = true;
        scrollRect.verticalScrollbar = scrollbar;
        scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.Permanent; // Toujours visible
        scrollRect.verticalScrollbarSpacing = scrollbarSpacing;
        scrollRect.movementType = ScrollRect.MovementType.Clamped;
        scrollRect.scrollSensitivity = 20f;
        
        // Désactiver l'inertie en WebGL pour éviter les comportements bizarres
        #if UNITY_WEBGL && !UNITY_EDITOR
        scrollRect.inertia = false;
        #endif
        
        Debug.Log($"[QuetesTab] ✅ Bloc liste créé avec scrollbar verticale (style maquette) - Position: ({listConfig.x}, {listConfig.y}), Taille: {listConfig.width}x{listConfig.height}");
    }
    
    void CreateDetailsTabsMenu()
    {
        var menuConfig = config.detailsTabsMenu;
        
        detailsTabsMenu = new GameObject("DetailsTabsMenu");
        detailsTabsMenu.transform.SetParent(transform, false);
        
        RectTransform rect = detailsTabsMenu.AddComponent<RectTransform>();
        rect.anchorMin = new Vector2(0, 1);
        rect.anchorMax = new Vector2(0, 1);
        rect.pivot = new Vector2(0, 1);
        rect.anchoredPosition = new Vector2(menuConfig.x, -menuConfig.y);
        rect.sizeDelta = new Vector2(menuConfig.width, menuConfig.height);
        
        // Container pour les onglets
        GameObject tabsContainer = new GameObject("TabsContainer");
        tabsContainer.transform.SetParent(detailsTabsMenu.transform, false);
        
        RectTransform tabsRect = tabsContainer.AddComponent<RectTransform>();
        tabsRect.anchorMin = new Vector2(0, 0);
        tabsRect.anchorMax = new Vector2(1, 1);
        tabsRect.offsetMin = new Vector2(menuConfig.paddingHorizontal, menuConfig.paddingVertical);
        tabsRect.offsetMax = new Vector2(-menuConfig.paddingHorizontal, -menuConfig.paddingVertical);
        
        HorizontalLayoutGroup layout = tabsContainer.AddComponent<HorizontalLayoutGroup>();
        layout.spacing = menuConfig.tabSpacing;
        layout.childAlignment = TextAnchor.MiddleCenter;
        layout.childControlWidth = false;
        layout.childControlHeight = false;
        layout.childForceExpandWidth = false;
        layout.childForceExpandHeight = false;
        
        // Créer les 3 boutons d'onglets
        CreateDetailTabButton(tabsContainer.transform, "PERSONNAGES", 0);
        CreateDetailTabButton(tabsContainer.transform, "BADGES", 1);
        CreateDetailTabButton(tabsContainer.transform, "FORMATIONS", 2);
        
        Debug.Log($"[QuetesTab] ✅ Menu d'onglets créé au-dessus - Position: ({menuConfig.x}, {menuConfig.y}), Taille: {menuConfig.width}x{menuConfig.height}");
    }
    
    void CreateDetailsBlock()
    {
        var detailsConfig = config.detailsBlock;
        
        detailsBlock = new GameObject("DetailsBlock");
        detailsBlock.transform.SetParent(transform, false);
        
        RectTransform rect = detailsBlock.AddComponent<RectTransform>();
        rect.anchorMin = new Vector2(0, 1);
        rect.anchorMax = new Vector2(0, 1);
        rect.pivot = new Vector2(0, 1);
        rect.anchoredPosition = new Vector2(detailsConfig.x, -detailsConfig.y);
        rect.sizeDelta = new Vector2(detailsConfig.width, detailsConfig.height);
        
        // Image de fond avec coins arrondis (sprite blanc + couleur via Image.color)
        Image bgImage = detailsBlock.AddComponent<Image>();
        bgImage.sprite = CreateRoundedSprite((int)detailsConfig.width, (int)detailsConfig.height,
            detailsConfig.cornerRadius, Color.white);
        bgImage.color = HexToColor(detailsConfig.backgroundColor);
        bgImage.type = Image.Type.Simple;
        bgImage.raycastTarget = false;
        
        // Ombre
        if (detailsConfig.shadowEnabled)
        {
            Shadow shadow = detailsBlock.AddComponent<Shadow>();
            shadow.effectColor = HexToColor(detailsConfig.shadowColor);
            shadow.effectDistance = new Vector2(0, -detailsConfig.shadowOffsetY);
        }
        
        // Zone de contenu (sans header car maintenant c'est un bloc séparé au-dessus)
        CreateDetailsContent();
        
        Debug.Log($"[QuetesTab] ✅ Bloc détails créé - Position: ({detailsConfig.x}, {detailsConfig.y}), Taille: {detailsConfig.width}x{detailsConfig.height}");
    }
    
    void CreateDetailsHeader()
    {
        var headerConfig = config.detailsHeader;
        
        detailsHeader = new GameObject("DetailsHeader");
        detailsHeader.transform.SetParent(detailsBlock.transform, false);
        
        RectTransform rect = detailsHeader.AddComponent<RectTransform>();
        rect.anchorMin = new Vector2(0, 1);
        rect.anchorMax = new Vector2(1, 1);
        rect.pivot = new Vector2(0.5f, 1);
        rect.sizeDelta = new Vector2(0, headerConfig.height);
        rect.anchoredPosition = Vector2.zero;
        
        // Container pour les onglets
        GameObject tabsContainer = new GameObject("TabsContainer");
        tabsContainer.transform.SetParent(detailsHeader.transform, false);
        
        RectTransform tabsRect = tabsContainer.AddComponent<RectTransform>();
        tabsRect.anchorMin = new Vector2(0, 0);
        tabsRect.anchorMax = new Vector2(1, 1);
        tabsRect.offsetMin = new Vector2(headerConfig.paddingHorizontal, headerConfig.paddingVertical);
        tabsRect.offsetMax = new Vector2(-headerConfig.paddingHorizontal, -headerConfig.paddingVertical);
        
        HorizontalLayoutGroup layout = tabsContainer.AddComponent<HorizontalLayoutGroup>();
        layout.spacing = headerConfig.tabSpacing;
        layout.childAlignment = TextAnchor.MiddleCenter;
        layout.childControlWidth = false;
        layout.childControlHeight = false;
        layout.childForceExpandWidth = false;
        layout.childForceExpandHeight = false;
        
        // Créer les 3 boutons d'onglets
        CreateDetailTabButton(tabsContainer.transform, "PERSONNAGES", 0);
        CreateDetailTabButton(tabsContainer.transform, "BADGES", 1);
        CreateDetailTabButton(tabsContainer.transform, "FORMATIONS", 2);
        
        Debug.Log("[QuetesTab] ✅ Header des détails créé avec 3 onglets");
    }
    
    void CreateDetailTabButton(Transform parent, string label, int index)
    {
        var tabConfig = config.detailsTabsMenu.tabStyle;
        
        GameObject tabButton = new GameObject($"TabButton_{label}");
        tabButton.transform.SetParent(parent, false);
        
        RectTransform rect = tabButton.AddComponent<RectTransform>();
        rect.sizeDelta = new Vector2(tabConfig.width, tabConfig.height);
        
        LayoutElement layoutElement = tabButton.AddComponent<LayoutElement>();
        layoutElement.preferredWidth = tabConfig.width;
        layoutElement.preferredHeight = tabConfig.height;
        
        Image image = tabButton.AddComponent<Image>();
        image.sprite = CreateRoundedSprite((int)tabConfig.width, (int)tabConfig.height, 
            tabConfig.cornerRadius, Color.white);
        image.type = Image.Type.Simple;
        image.color = Color.clear; // Transparent par défaut
        image.raycastTarget = true;
        
        Button button = tabButton.AddComponent<Button>();
        button.targetGraphic = image;
        button.transition = Selectable.Transition.None;
        
        button.onClick.AddListener(() => {
            Debug.Log($"[QuetesTab] Clic sur l'onglet {label} (index {index})");
            ShowDetailTab(index);
        });
        
        // Texte
        GameObject textObj = new GameObject("Text");
        textObj.transform.SetParent(tabButton.transform, false);
        
        RectTransform textRect = textObj.AddComponent<RectTransform>();
        textRect.anchorMin = Vector2.zero;
        textRect.anchorMax = Vector2.one;
        textRect.offsetMin = Vector2.zero;
        textRect.offsetMax = Vector2.zero;
        
        TextMeshProUGUI text = textObj.AddComponent<TextMeshProUGUI>();
        text.text = label;
        text.fontSize = 32;
        // Couleur initiale : premier onglet sélectionné, les autres non
        text.color = (index == 0) ? HexToColor("#64477D") : HexToColor("#ac99b1");
        text.alignment = TextAlignmentOptions.Center;
        text.fontStyle = FontStyles.Normal;
        if (titleFont != null) text.font = titleFont;
        
        detailTabButtons.Add(button);
        
        Debug.Log($"[QuetesTab] Bouton d'onglet créé: {label}");
    }
    
    void CreateDetailsContent()
    {
        var contentConfig = config.detailsContent;
        
        // Le detailsContent occupe maintenant tout le bloc car le menu d'onglets est séparé au-dessus
        detailsContent = new GameObject("DetailsContent");
        detailsContent.transform.SetParent(detailsBlock.transform, false);
        
        RectTransform rect = detailsContent.AddComponent<RectTransform>();
        rect.anchorMin = new Vector2(0, 0);
        rect.anchorMax = new Vector2(1, 1);
        // Occupe tout le bloc avec du padding
        rect.offsetMin = new Vector2(contentConfig.padding, contentConfig.padding);
        rect.offsetMax = new Vector2(-contentConfig.padding, -contentConfig.padding);
        
        // Pas de cadre par défaut - il sera créé dynamiquement quand une quête est sélectionnée
        // Afficher le message par défaut
        ShowNoQuestSelectedMessage();
        
        Debug.Log("[QuetesTab] ✅ Zone de contenu des détails créée (occupe tout le bloc)");
    }
    
    void ShowNoQuestSelectedMessage()
    {
        // Nettoyer le contenu existant
        foreach (Transform child in detailsContent.transform)
        {
            Destroy(child.gameObject);
        }
        
        // Texte centré "Sélectionnez une quête"
        GameObject messageObj = new GameObject("NoQuestMessage");
        messageObj.transform.SetParent(detailsContent.transform, false);
        
        RectTransform rect = messageObj.AddComponent<RectTransform>();
        rect.anchorMin = Vector2.zero;
        rect.anchorMax = Vector2.one;
        rect.offsetMin = Vector2.zero;
        rect.offsetMax = Vector2.zero;
        
        TextMeshProUGUI text = messageObj.AddComponent<TextMeshProUGUI>();
        text.text = "Sélectionnez une quête";
        text.fontSize = 28;
        text.color = HexToColor("#8a7a9a");
        text.alignment = TextAlignmentOptions.Center;
        text.fontStyle = FontStyles.Italic;
        if (textFont != null) text.font = textFont;
    }
    
    void PopulateQuestList()
    {
        Debug.Log($"[QUESTLIST_DEBUG] 🔄 PopulateQuestList() appelé - {questsData.Count} quêtes à afficher");
        
        // Vider le dictionnaire des références
        questItemsMap.Clear();
        
        if (questListBlock == null)
        {
            Debug.LogError("[QuetesTab] ❌ questListBlock est null!");
            return;
        }
        
        Transform viewport = questListBlock.transform.Find("Viewport");
        if (viewport == null)
        {
            Debug.LogError("[QuetesTab] ❌ Viewport non trouvé!");
            return;
        }
        
        Transform content = viewport.Find("Content");
        if (content == null)
        {
            Debug.LogError("[QuetesTab] ❌ Content non trouvé!");
            return;
        }
        
        // Nettoyer le contenu existant
        while (content.childCount > 0)
        {
            DestroyImmediate(content.GetChild(0).gameObject);
        }
        
        if (questsData.Count == 0)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Aucune quête à afficher");
            return;
        }
        
        var listConfig = config.questListBlock;
        var itemConfig = listConfig.itemStyle;
        // Largeur ajustée pour tenir compte de la scrollbar (8px) + spacing (8px)
        float scrollbarWidth = 8f;
        float scrollbarSpacing = 8f;
        float itemWidth = listConfig.width - listConfig.padding * 2 - scrollbarWidth - scrollbarSpacing;
        float itemHeight = itemConfig.height;
        float spacing = listConfig.itemSpacing;
        
        // Calculer la hauteur totale du content
        float totalHeight = questsData.Count * itemHeight + (questsData.Count - 1) * spacing;
        
        RectTransform contentRect = content.GetComponent<RectTransform>();
        contentRect.sizeDelta = new Vector2(itemWidth, totalHeight);
        
        Debug.Log($"[QUESTLIST_DEBUG] 📐 Content size définie à: {itemWidth}x{totalHeight}");
        
        // Créer chaque item avec positionnement manuel
        float currentY = 0;
        for (int i = 0; i < questsData.Count; i++)
        {
            Quest quest = questsData[i];
            if (quest != null)
            {
                CreateQuestListItemManual(content, quest, itemWidth, itemHeight, currentY);
                currentY -= itemHeight + spacing;
            }
        }
        
        Debug.Log($"[QUESTLIST_DEBUG] ✅ {questsData.Count} quêtes créées manuellement");
        
        // Vérifier le premier item
        if (content.childCount > 0)
        {
            RectTransform firstItem = content.GetChild(0).GetComponent<RectTransform>();
            Debug.Log($"[QUESTLIST_DEBUG] 📐 Premier item - pos: {firstItem.anchoredPosition}, size: {firstItem.sizeDelta}");
        }
        
        // Sélectionner la première quête avec un petit délai pour s'assurer que tout est prêt
        if (questsData.Count > 0)
        {
            StartCoroutine(SelectFirstQuestWithDelay());
        }
    }
    
    IEnumerator SelectFirstQuestWithDelay()
    {
        // Attendre que l'interface soit complètement prête
        yield return new WaitForEndOfFrame();
        yield return new WaitForSeconds(0.1f);
        
        if (questsData == null || questsData.Count == 0)
        {
            yield break;
        }
        
        // Déterminer quelle quête sélectionner
        Quest questToSelect = null;
        
        // Vérifier si on vient d'une map (CurrentMapId est défini)
        string currentMapId = PlayerPrefs.GetString("CurrentMapId", "");
        if (!string.IsNullOrEmpty(currentMapId) && currentMapId.StartsWith("map-Q"))
        {
            // Extraire l'index de la map (map-Q0 -> 0, map-Q1 -> 1, etc.)
            string indexStr = currentMapId.Replace("map-Q", "");
            if (int.TryParse(indexStr, out int mapIndex))
            {
                // mapIndex correspond à la position dans la liste des quêtes
                if (mapIndex >= 0 && mapIndex < questsData.Count)
                {
                    questToSelect = questsData[mapIndex];
                    Debug.Log($"[QuetesTab] 🎯 Sélection automatique de la quête de la map actuelle (index {mapIndex}): {questToSelect.title}");
                }
                else
                {
                    Debug.LogWarning($"[QuetesTab] ⚠️ Index de map {mapIndex} hors limites ({questsData.Count} quêtes), sélection de la première");
                    questToSelect = questsData[0];
                }
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Impossible d'extraire l'index de '{currentMapId}', sélection de la première");
                questToSelect = questsData[0];
            }
        }
        else
        {
            // Pas de CurrentMapId ou format invalide -> on est dans la scène Main
            questToSelect = questsData[0];
            Debug.Log($"[QuetesTab] 🎯 Sélection automatique de la première quête (scène Main): {questToSelect.title}");
        }
        
        if (questToSelect != null)
        {
            SelectQuest(questToSelect);
        }
    }
    
    void CreateQuestListItemManual(Transform parent, Quest quest, float width, float height, float yPos)
    {
        var itemConfig = config.questListBlock.itemStyle;
        var lockedConfig = itemConfig.lockedStyle;
        
        // Déterminer si la quête est verrouillée
        bool isLocked = quest.status == "locked";
        
        GameObject item = new GameObject($"QuestItem_{quest.id}");
        item.transform.SetParent(parent, false);
        
        RectTransform itemRect = item.AddComponent<RectTransform>();
        // Positionnement manuel - ancré en haut à gauche du parent
        itemRect.anchorMin = new Vector2(0, 1);
        itemRect.anchorMax = new Vector2(0, 1);
        itemRect.pivot = new Vector2(0, 1);
        itemRect.anchoredPosition = new Vector2(0, yPos);
        itemRect.sizeDelta = new Vector2(width, height);
        
        Debug.Log($"[QUESTLIST_DEBUG] 🔨 Item '{quest.title}' créé - pos: (0, {yPos}), size: ({width}, {height}), locked: {isLocked}");
        
        // Fond avec coins arrondis - gris si locked
        Image bgImage = item.AddComponent<Image>();
        bgImage.sprite = CreateRoundedSprite((int)width, (int)height, itemConfig.cornerRadius, Color.white);
        
        string bgColor = isLocked && lockedConfig != null 
            ? lockedConfig.backgroundColor 
            : itemConfig.normalBackgroundColor;
        bgImage.color = HexToColor(bgColor);
        bgImage.type = Image.Type.Simple;
        bgImage.raycastTarget = true;
        
        // Bouton - désactivé si locked
        if (!isLocked)
        {
            Button button = item.AddComponent<Button>();
            button.targetGraphic = bgImage;
            button.transition = Selectable.Transition.None; // On gère manuellement les couleurs
            
            button.onClick.AddListener(() => {
                Debug.Log($"[QuetesTab] 🖱️ CLIC sur quête: {quest.title} (ID: {quest.id})");
                SelectQuest(quest);
            });
        }
        else
        {
            Debug.Log($"[QuetesTab] 🔒 Quête {quest.id} verrouillée - pas de bouton");
        }
        
        // Zone de texte qui remplit l'item avec padding
        float padding = itemConfig.paddingHorizontal;
        float paddingV = itemConfig.paddingVertical;
        float textWidth = width - padding * 2;
        
        // Couleurs du titre et description selon l'état
        string titleColorHex = isLocked && lockedConfig != null 
            ? lockedConfig.titleColor 
            : itemConfig.titleColor;
        string descColorHex = isLocked && lockedConfig != null 
            ? lockedConfig.descriptionColor 
            : itemConfig.descriptionColor;
        
        // Calculer la largeur du titre pour laisser de la place au cadenas si locked
        float lockIconSpace = 0;
        if (isLocked && lockedConfig != null)
        {
            lockIconSpace = lockedConfig.lockIconSize + lockedConfig.lockIconMarginLeft + 10;
        }
        
        // Titre en haut - centré horizontalement
        GameObject titleObj = new GameObject("Title");
        titleObj.transform.SetParent(item.transform, false);
        
        RectTransform titleRect = titleObj.AddComponent<RectTransform>();
        titleRect.anchorMin = new Vector2(0, 1);
        titleRect.anchorMax = new Vector2(0, 1);
        titleRect.pivot = new Vector2(0, 1);
        titleRect.anchoredPosition = new Vector2(padding, -paddingV);
        titleRect.sizeDelta = new Vector2(textWidth - lockIconSpace, 40); // Hauteur 40px pour le titre
        
        TextMeshProUGUI titleText = titleObj.AddComponent<TextMeshProUGUI>();
        titleText.text = (quest.title ?? "Titre manquant").ToUpper(); // En majuscules
        titleText.fontSize = itemConfig.titleFontSize;
        Color titleColorValue = HexToColor(titleColorHex);
        titleText.color = titleColorValue;
        titleText.fontStyle = FontStyles.Bold;
        titleText.alignment = TextAlignmentOptions.Center; // Texte centré dans la zone
        titleText.raycastTarget = false;
        titleText.overflowMode = TextOverflowModes.Truncate;
        // Police Anton pour le titre
        if (titleFont != null) titleText.font = titleFont;
        
        Debug.Log($"[QUESTLIST_DEBUG] 📝 Titre: '{titleText.text}' - couleur: {titleColorValue}, fontSize: {itemConfig.titleFontSize}, font: {(titleText.font != null ? titleText.font.name : "DEFAULT")}");
        
        // Ajouter l'icône cadenas si locked
        if (isLocked && lockedConfig != null)
        {
            GameObject lockObj = new GameObject("LockIcon");
            lockObj.transform.SetParent(item.transform, false);
            
            RectTransform lockRect = lockObj.AddComponent<RectTransform>();
            lockRect.anchorMin = new Vector2(1, 1);
            lockRect.anchorMax = new Vector2(1, 1);
            lockRect.pivot = new Vector2(1, 0.5f);
            // Positionné à droite du titre, aligné verticalement avec lui
            lockRect.anchoredPosition = new Vector2(-padding, -(paddingV + 20)); // 20 = moitié de 40px hauteur titre
            lockRect.sizeDelta = new Vector2(lockedConfig.lockIconSize, lockedConfig.lockIconSize);
            
            // D'abord essayer de charger l'image
            string lockIconUrl = GeneralConfigManager.Instance?.GetUIUrl(lockedConfig.lockIconUrl) ?? "";
            Debug.Log($"[QuetesTab] 🔒 URL cadenas construite: '{lockIconUrl}' (depuis '{lockedConfig.lockIconUrl}')");
            
            if (!string.IsNullOrEmpty(lockIconUrl))
            {
                Image lockImage = lockObj.AddComponent<Image>();
                lockImage.preserveAspect = true;
                lockImage.raycastTarget = false;
                lockImage.color = Color.clear; // Transparent jusqu'au chargement du sprite
                lockImage.type = Image.Type.Simple;
                
                // Lancer la coroutine sur GeneralConfigManager qui reste actif
                // (évite l'interruption quand l'onglet est désactivé)
                if (GeneralConfigManager.Instance != null)
                {
                    GeneralConfigManager.Instance.StartCoroutine(LoadLockIconSprite(lockIconUrl, lockImage, lockObj));
                }
                else
                {
                    StartCoroutine(LoadLockIconSprite(lockIconUrl, lockImage, lockObj));
                }
            }
            else
            {
                // Fallback: utiliser un texte cadenas
                Debug.LogWarning($"[QuetesTab] ⚠️ URL cadenas vide, utilisation du texte emoji");
                CreateLockTextFallback(lockObj, lockedConfig.lockIconSize);
            }
            
            Debug.Log($"[QuetesTab] 🔒 Icône cadenas ajoutée pour quête {quest.id}");
        }
        
        // Description sous le titre
        GameObject descObj = new GameObject("Description");
        descObj.transform.SetParent(item.transform, false);
        
        RectTransform descRect = descObj.AddComponent<RectTransform>();
        descRect.anchorMin = new Vector2(0, 1);
        descRect.anchorMax = new Vector2(0, 1);
        descRect.pivot = new Vector2(0, 1);
        descRect.anchoredPosition = new Vector2(padding, -(paddingV + 45)); // Sous le titre (40px + 5px spacing)
        descRect.sizeDelta = new Vector2(textWidth, height - paddingV * 2 - 50);
        
        TextMeshProUGUI descText = descObj.AddComponent<TextMeshProUGUI>();
        descText.text = quest.description ?? "Aucune description disponible";
        descText.fontSize = itemConfig.descriptionFontSize;
        descText.color = HexToColor(descColorHex);
        descText.alignment = TextAlignmentOptions.TopLeft;
        descText.overflowMode = TextOverflowModes.Truncate;
        descText.textWrappingMode = TMPro.TextWrappingModes.Normal;
        descText.maxVisibleLines = 4;
        descText.raycastTarget = false;
        if (textFont != null) descText.font = textFont;
        
        // Stocker la référence pour la sélection (même si locked, pour la mise à jour d'apparence)
        questItemsMap[quest.id] = item;
        
        // Marquer comme locked pour UpdateQuestItemsAppearance
        if (isLocked)
        {
            item.AddComponent<LockedQuestMarker>();
        }
        
        Debug.Log($"[QUESTLIST_DEBUG] ✅ Item complet créé pour: {quest.title}");
    }
    
    IEnumerator LoadLockIconSprite(string url, Image targetImage, GameObject lockObj)
    {
        Debug.Log($"[QuetesTab] 🔒 Tentative de chargement du cadenas: {url}");
        
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Debug.Log($"[QuetesTab] 🔒 Texture chargée: {texture.width}x{texture.height}, format: {texture.format}");
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);

                // Ressource persistante (liste des quêtes)
                TrackPersistent(texture, sprite);
                    
                if (targetImage != null)
                {
                    targetImage.sprite = sprite;
                    targetImage.color = Color.white; // Important : couleur blanche pour afficher le sprite correctement
                    targetImage.type = Image.Type.Simple;
                    Debug.Log($"[QuetesTab] ✅ Icône cadenas chargée et assignée avec succès");
                }
            }
            else
            {
                Debug.LogError($"[QuetesTab] ❌ Impossible de charger l'icône cadenas: {url}");
                Debug.LogError($"[QuetesTab] ❌ Erreur: {request.error}, Code: {request.responseCode}");
                
                // En cas d'échec, supprimer l'image et utiliser un texte emoji cadenas
                if (lockObj != null && targetImage != null)
                {
                    Destroy(targetImage);
                    float iconSize = config?.questListBlock?.itemStyle?.lockedStyle?.lockIconSize ?? 24;
                    CreateLockTextFallback(lockObj, iconSize);
                }
            }
        }
    }
    
    void CreateLockTextFallback(GameObject parent, float size)
    {
        // Créer un texte avec le symbole cadenas Unicode
        TextMeshProUGUI lockText = parent.AddComponent<TextMeshProUGUI>();
        lockText.text = "🔒"; // Emoji cadenas
        lockText.fontSize = size * 0.8f;
        lockText.color = HexToColor("#808080");
        lockText.alignment = TextAlignmentOptions.Center;
        lockText.raycastTarget = false;
        
        Debug.Log("[QuetesTab] ✅ Fallback texte cadenas créé");
    }
    
    void SelectQuest(Quest quest)
    {
        // **PROTECTION : Empêcher les clics multiples rapides**
        float currentTime = Time.realtimeSinceStartup;
        if (currentTime - lastClickTime < CLICK_DEBOUNCE_TIME)
        {
            Debug.Log($"[QuetesTab] ⏱️ Clic ignoré (debounce): {currentTime - lastClickTime:F3}s depuis le dernier clic");
            return;
        }
        lastClickTime = currentTime;
        
        if (isSelectingQuest)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Sélection déjà en cours, ignoré");
            return;
        }
        
        Debug.Log($"[QuetesTab] 🎯 SelectQuest() appelé pour: {quest?.title ?? "NULL"} (ID: {quest?.id ?? -1})");
        
        if (quest == null)
        {
            Debug.LogError("[QuetesTab] ❌ Quest est NULL!");
            return;
        }
        
        isSelectingQuest = true;
        StartCoroutine(SelectQuestCoroutine(quest));
    }
    
    IEnumerator SelectQuestCoroutine(Quest quest)
    {
        // Changement de quête => libérer immédiatement toutes les ressources liées aux détails précédents
        // (sinon OOM WebGL après quelques clics)
        CleanupDetailResources();

        selectedQuest = quest;
        Debug.Log($"[QuetesTab] ✅ selectedQuest défini: {selectedQuest.title}");
        
        // Mettre à jour l'apparence de tous les items de quête
        UpdateQuestItemsAppearance(quest.id);
        
        if (detailsContent == null)
        {
            Debug.LogError("[QuetesTab] ❌ detailsContent est NULL! Impossible d'afficher les détails.");
            isSelectingQuest = false; // Libérer le flag
            yield break;
        }
        
        Debug.Log($"[QuetesTab] ✅ detailsContent existe: {detailsContent.name}");
        
        // Appeler l'API pour récupérer les détails de la quête (personnages, badges, etc.)
        Debug.Log("[QuetesTab] 🚀 Démarrage de LoadQuestDetailsFromApi...");
        yield return StartCoroutine(LoadQuestDetailsFromApi(quest.id));
        
        // Afficher immédiatement l'onglet PERSONNAGES par défaut
        activeDetailTab = 0;
        UpdateDetailTabButtons(0);
        
        isSelectingQuest = false; // Libérer le flag
    }
    
    void UpdateQuestItemsAppearance(int selectedQuestId)
    {
        var itemConfig = config.questListBlock.itemStyle;
        var lockedConfig = itemConfig.lockedStyle;
        
        foreach (var kvp in questItemsMap)
        {
            int questId = kvp.Key;
            GameObject item = kvp.Value;
            
            if (item == null) continue;
            
            // Vérifier si c'est une quête verrouillée
            bool isLocked = item.GetComponent<LockedQuestMarker>() != null;
            
            // Ne pas changer l'apparence des quêtes verrouillées
            if (isLocked)
            {
                continue;
            }
            
            bool isSelected = (questId == selectedQuestId);
            
            // Mettre à jour la couleur de fond
            Image bgImage = item.GetComponent<Image>();
            if (bgImage != null)
            {
                bgImage.color = isSelected 
                    ? HexToColor(itemConfig.selectedBackgroundColor) 
                    : HexToColor(itemConfig.normalBackgroundColor);
            }
            
            // Mettre à jour la couleur du titre
            Transform titleTransform = item.transform.Find("Title");
            if (titleTransform != null)
            {
                TextMeshProUGUI titleText = titleTransform.GetComponent<TextMeshProUGUI>();
                if (titleText != null)
                {
                    titleText.color = isSelected ? Color.white : HexToColor(itemConfig.titleColor);
                }
            }
            
            // Mettre à jour la couleur de la description
            Transform descTransform = item.transform.Find("Description");
            if (descTransform != null)
            {
                TextMeshProUGUI descText = descTransform.GetComponent<TextMeshProUGUI>();
                if (descText != null)
                {
                    descText.color = isSelected ? Color.white : HexToColor(itemConfig.descriptionColor);
                }
            }
        }
    }
    
    IEnumerator LoadQuestDetailsFromApi(int questId)
    {
        Debug.Log($"[QuetesTab] 📡 Chargement des détails de la quête {questId}...");
        
        // Afficher un message de chargement
        ShowLoadingMessage("Chargement des détails...");
        
        // Construire l'URL de l'API
        string apiUrl = GeneralConfigManager.Instance?.GetQuestConfigApiUrl(questId);
        
        if (string.IsNullOrEmpty(apiUrl))
        {
            Debug.LogError("[QuetesTab] ❌ Impossible de construire l'URL de l'API");
            ShowDetailTab(activeDetailTab);
            yield break;
        }
        
        // Récupérer le token d'authentification
        string token = UserDataManager.Instance?.token;
        
        if (string.IsNullOrEmpty(token))
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Pas de token d'authentification");
            ShowDetailTab(activeDetailTab);
            yield break;
        }
        
        Debug.Log($"[QuetesTab] 🌐 Appel API: {apiUrl}");
        
        using (UnityWebRequest www = UnityWebRequest.Get(apiUrl))
        {
            www.SetRequestHeader("Authorization", $"Bearer {token}");
            yield return www.SendWebRequest();
            
            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.LogError($"[QuetesTab] ❌ Erreur API: {www.error}");
                Debug.LogError($"[QuetesTab] Code: {www.responseCode}");
                ShowDetailTab(activeDetailTab);
                yield break;
            }
            
            string jsonResponse = www.downloadHandler.text;
            Debug.Log($"[QuetesTab] ✅ Réponse reçue ({jsonResponse.Length} caractères)");
            Debug.Log($"[QuetesTab] 🔍 Début réponse: {jsonResponse.Substring(0, Mathf.Min(500, jsonResponse.Length))}");
            
            // Parser la réponse
            try
            {
                // Log la réponse brute pour debug
                Debug.Log($"[QuetesTab] 📄 Réponse JSON brute (premiers 1000 caractères):\n{jsonResponse.Substring(0, Mathf.Min(1000, jsonResponse.Length))}");
                
                QuestDetailApiResponse response = JsonUtility.FromJson<QuestDetailApiResponse>(jsonResponse);
                
                if (response != null && response.status == "success" && response.data != null)
                {
                    Debug.Log($"[QuetesTab] ✅ Détails de la quête chargés");
                    
                    // Stocker les personnages
                    if (response.data.characters != null && response.data.characters.Count > 0)
                    {
                        questCharacters[questId] = response.data.characters;
                        Debug.Log($"[QuetesTab] 👥 {response.data.characters.Count} personnages chargés");
                        
                        foreach (var character in response.data.characters)
                        {
                            // Log toutes les propriétés pour debug
                            Debug.Log($"[QuetesTab]   - Champs bruts: nom='{character.nom}', name='{character.name}', desc_courte='{character.description_courte}', short_desc='{character.short_description}'");
                            Debug.Log($"[QuetesTab]   - Valeurs finales: GetName()='{character.GetName()}', GetDescription()='{character.GetDescription()}', GetAvatarUrl()='{character.GetAvatarUrl()}'");
                        }
                    }
                    else
                    {
                        Debug.Log("[QuetesTab] ⚠️ Aucun personnage dans la réponse API");
                    }
                    
                    // Stocker les badges si présents
                    if (response.data.badges != null && response.data.badges.Count > 0)
                    {
                        Debug.Log($"[QuetesTab] 🏅 {response.data.badges.Count} badges chargés");
                    }
                    
                    // Stocker les formations dans selectedQuest
                    if (response.data.trainings != null && response.data.trainings.Count > 0)
                    {
                        selectedQuest.trainings = response.data.trainings;
                        Debug.Log($"[QuetesTab] 📚 {response.data.trainings.Count} formations chargées");
                        foreach (var training in response.data.trainings)
                        {
                            Debug.Log($"[QuetesTab]   - Formation: '{training.title}' → {training.url}");
                        }
                    }
                    else
                    {
                        Debug.Log("[QuetesTab] ⚠️ Aucune formation dans la réponse API");
                    }
                }
                else
                {
                    Debug.LogWarning($"[QuetesTab] ⚠️ Réponse API invalide ou sans données");
                }
            }
            catch (Exception e)
            {
                Debug.LogError($"[QuetesTab] ❌ Erreur parsing JSON: {e.Message}");
            }
        }
        
        // Afficher l'onglet actif avec les nouvelles données
        ShowDetailTab(activeDetailTab);
    }
    
    void ShowLoadingMessage(string message)
    {
        Debug.Log($"[QuetesTab] 📢 ShowLoadingMessage() appelé avec: '{message}'");
        
        if (detailsContent == null)
        {
            Debug.LogError("[QuetesTab] ❌ detailsContent est NULL dans ShowLoadingMessage!");
            return;
        }

        // On reconstruit le contenu des détails: nettoyer les ressources de détails avant de détruire l'UI
        CleanupDetailResources();
        SetScope(ResourceScope.Details);
        
        // Nettoyer le contenu existant
        foreach (Transform child in detailsContent.transform)
        {
            Destroy(child.gameObject);
        }
        
        GameObject loadingObj = new GameObject("LoadingMessage");
        loadingObj.transform.SetParent(detailsContent.transform, false);
        Debug.Log("[QuetesTab] ✅ LoadingMessage créé");
        
        RectTransform rect = loadingObj.AddComponent<RectTransform>();
        rect.anchorMin = Vector2.zero;
        rect.anchorMax = Vector2.one;
        rect.offsetMin = Vector2.zero;
        rect.offsetMax = Vector2.zero;
        
        TextMeshProUGUI text = loadingObj.AddComponent<TextMeshProUGUI>();
        text.text = message;
        text.fontSize = 24;
        text.color = HexToColor("#a95bfb");
        text.alignment = TextAlignmentOptions.Center;
        text.fontStyle = FontStyles.Italic;
        if (textFont != null) text.font = textFont;

        // Revenir au scope par défaut (liste persistante) après construction
        SetScope(ResourceScope.Persistent);
    }
    
    void ShowDetailTab(int index)
    {
        // **PROTECTION : Empêcher les clics multiples rapides sur les onglets**
        if (isSwitchingTab)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Changement d'onglet déjà en cours, ignoré");
            return;
        }

        isSwitchingTab = true;
        activeDetailTab = index;

        // Changement d'onglet => on reconstruit l'UI des détails => nettoyer ressources de détails
        CleanupDetailResources();
        SetScope(ResourceScope.Details);

        try
        {
            // Mettre à jour les couleurs des boutons d'onglets
            UpdateDetailTabButtons(index);

            // Afficher le contenu correspondant
            switch (index)
            {
                case 0: // PERSONNAGES
                    ShowPersonnagesContent();
                    break;
                case 1: // BADGES
                    ShowBadgesContent();
                    break;
                case 2: // FORMATIONS
                    ShowFormationsContent();
                    break;
            }
        }
        finally
        {
            SetScope(ResourceScope.Persistent);
            isSwitchingTab = false;
        }
    }
    
    void UpdateDetailTabButtons(int selectedIndex)
    {
        for (int i = 0; i < detailTabButtons.Count; i++)
        {
            Button button = detailTabButtons[i];
            Image image = button.GetComponent<Image>();
            TextMeshProUGUI text = button.GetComponentInChildren<TextMeshProUGUI>();
            
            // Pas de fond (gélule) - toujours transparent
            image.color = Color.clear;
            
            if (i == selectedIndex)
            {
                // Onglet sélectionné : texte en violet foncé
                if (text != null)
                    text.color = HexToColor("#64477D");
            }
            else
            {
                // Onglet non sélectionné : texte en gris violet clair
                if (text != null)
                    text.color = HexToColor("#ac99b1");
            }
        }
    }
    
    void ShowPersonnagesContent()
    {
        Debug.Log("[QuetesTab] ========== ShowPersonnagesContent() MOSAIQUE ==========");
        
        // Nettoyer le contenu existant
        foreach (Transform child in detailsContent.transform)
        {
            Destroy(child.gameObject);
        }
        avatarButtons.Clear();
        
        if (selectedQuest == null)
        {
            Debug.Log("[QuetesTab] ⚠️ selectedQuest est NULL");
            ShowNoQuestSelectedMessage();
            return;
        }
        
        
        // Récupérer les personnages de la quête
        currentCharacters = GetQuestCharacters(selectedQuest.id);
        
        if (currentCharacters == null || currentCharacters.Count == 0)
        {
            Debug.Log("[QuetesTab] ⚠️ Aucun personnage dans la liste");
            CreateEmptyMessage("Aucun personnage pour cette quête");
            return;
        }
        
        Debug.Log($"[QuetesTab] 👥 {currentCharacters.Count} personnages à afficher en mosaïque");
        
        var mosaicConfig = config.characterMosaic;
        var detailConfig = config.characterDetail;
        float padding = mosaicConfig.padding;
        float avatarSize = mosaicConfig.avatarSize;
        float spacing = mosaicConfig.avatarSpacing;
        float arrowSize = mosaicConfig.arrowSize;
        
        // Container principal
        GameObject container = new GameObject("PersonnagesContainer");
        container.transform.SetParent(detailsContent.transform, false);
        
        RectTransform containerRect = container.AddComponent<RectTransform>();
        containerRect.anchorMin = Vector2.zero;
        containerRect.anchorMax = Vector2.one;
        containerRect.offsetMin = Vector2.zero;
        containerRect.offsetMax = Vector2.zero;
        
        // Zone de mosaïque avec flèches
        GameObject mosaicZone = new GameObject("MosaicZone");
        mosaicZone.transform.SetParent(container.transform, false);
        
        RectTransform mosaicRect = mosaicZone.AddComponent<RectTransform>();
        mosaicRect.anchorMin = new Vector2(0, 1);
        mosaicRect.anchorMax = new Vector2(1, 1);
        mosaicRect.pivot = new Vector2(0.5f, 1);
        mosaicRect.anchoredPosition = new Vector2(0, -padding);
        float mosaicHeight = avatarSize + padding;
        mosaicRect.sizeDelta = new Vector2(0, mosaicHeight);
        
        // Flèche gauche
        leftArrow = CreateNavigationArrow(mosaicZone.transform, true, arrowSize, mosaicConfig.arrowColor);
        
        // Conteneur des avatars (centré) - seulement 3 visibles
        avatarsContainer = new GameObject("AvatarsContainer");
        avatarsContainer.transform.SetParent(mosaicZone.transform, false);
        
        RectTransform avatarsRect = avatarsContainer.AddComponent<RectTransform>();
        avatarsRect.anchorMin = new Vector2(0.5f, 0.5f);
        avatarsRect.anchorMax = new Vector2(0.5f, 0.5f);
        avatarsRect.pivot = new Vector2(0.5f, 0.5f);
        
        // Calculer la largeur totale pour 3 avatars max
        int maxVisible = mosaicConfig.columns; // 3 par défaut
        float totalAvatarsWidth = maxVisible * avatarSize + (maxVisible - 1) * spacing;
        avatarsRect.sizeDelta = new Vector2(totalAvatarsWidth, avatarSize);
        
        // Initialiser l'index de départ
        visibleStartIndex = 0;
        
        // Créer les 3 avatars visibles
        RefreshVisibleAvatars();
        
        // Flèche droite
        rightArrow = CreateNavigationArrow(mosaicZone.transform, false, arrowSize, mosaicConfig.arrowColor);
        
        // Mettre à jour la visibilité des flèches
        UpdateArrowsVisibility();
        
        // Zone de détail du personnage
        CreateCharacterDetailPanel(container.transform, mosaicHeight + padding, detailConfig);
        
        // Sélectionner le premier personnage par défaut
        selectedCharacterIndex = 0;
        UpdateSelectedCharacter();
        
        Debug.Log("[QuetesTab] ✅ Mosaïque créée");
    }
    
    GameObject CreateNavigationArrow(Transform parent, bool isLeft, float size, string colorHex)
    {
        GameObject arrow = new GameObject(isLeft ? "LeftArrow" : "RightArrow");
        arrow.transform.SetParent(parent, false);
        
        RectTransform arrowRect = arrow.AddComponent<RectTransform>();
        arrowRect.anchorMin = new Vector2(isLeft ? 0 : 1, 0.5f);
        arrowRect.anchorMax = new Vector2(isLeft ? 0 : 1, 0.5f);
        arrowRect.pivot = new Vector2(0.5f, 0.5f);
        arrowRect.anchoredPosition = new Vector2(isLeft ? size / 2 + 10 : -size / 2 - 10, 0);
        arrowRect.sizeDelta = new Vector2(size, size);
        
        // Image de la flèche
        Image arrowImage = arrow.AddComponent<Image>();
        arrowImage.color = Color.white;
        arrowImage.raycastTarget = true;
        
        // Charger l'image depuis l'URL
        string imageName = isLeft ? "nav_gauche.png" : "nav_droite.png";
        string imageUrl = GeneralConfigManager.Instance?.GetUIUrl(imageName) ?? "";
        if (!string.IsNullOrEmpty(imageUrl))
        {
            StartCoroutine(LoadArrowSprite(imageUrl, arrowImage));
        }
        
        // Bouton
        Button button = arrow.AddComponent<Button>();
        button.transition = Selectable.Transition.ColorTint;
        button.targetGraphic = arrowImage;
        
        int direction = isLeft ? -1 : 1;
        button.onClick.AddListener(() => NavigateCharacter(direction));
        
        return arrow;
    }
    
    IEnumerator LoadArrowSprite(string url, Image targetImage)
    {
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);
                TrackPersistent(texture, sprite);
                if (targetImage != null)
                {
                    targetImage.sprite = sprite;
                }
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Impossible de charger l'image flèche: {url}");
            }
        }
    }
    
    IEnumerator LoadFormationArrowSprite(Image targetImage)
    {
        // Cache (un seul téléchargement pour tout l'onglet QUÊTES)
        if (targetImage == null) yield break;

        if (cachedFormationArrowSprite != null)
        {
            targetImage.sprite = cachedFormationArrowSprite;
            yield break;
        }

        pendingFormationArrowTargets.Add(targetImage);
        if (isLoadingFormationArrowSprite) yield break;

        isLoadingFormationArrowSprite = true;

        string uiPath = generalConfig?.assetsPaths?.uiPath ?? "https://ujsa.studioplc.fr/datas/UI/";
        string url = uiPath + "fleche_formation.png";

        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
                    new Vector2(0.5f, 0.5f), 100f);

                cachedFormationArrowTexture = texture;
                cachedFormationArrowSprite = sprite;
                TrackPersistent(texture, sprite);

                for (int i = 0; i < pendingFormationArrowTargets.Count; i++)
                {
                    Image img = pendingFormationArrowTargets[i];
                    if (img != null)
                    {
                        img.sprite = cachedFormationArrowSprite;
                    }
                }
                pendingFormationArrowTargets.Clear();
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Impossible de charger fleche_formation.png: {url}");
            }
        }

        isLoadingFormationArrowSprite = false;
    }
    
    void CreateAvatarButton(Transform parent, QuestCharacter character, int index, float xPos, float size, QuetesTabCharacterMosaicConfig mosaicConfig)
    {
        // Cadre carré avec fond et coins arrondis
        GameObject frameObj = new GameObject($"AvatarFrame_{index}");
        frameObj.transform.SetParent(parent, false);
        
        RectTransform frameRect = frameObj.AddComponent<RectTransform>();
        frameRect.anchorMin = new Vector2(0.5f, 0.5f);
        frameRect.anchorMax = new Vector2(0.5f, 0.5f);
        frameRect.pivot = new Vector2(0.5f, 0.5f);
        frameRect.anchoredPosition = new Vector2(xPos, 0);
        frameRect.sizeDelta = new Vector2(size, size);
        
        // Fond du cadre avec coins arrondis (couleur fixe, pas de changement à la sélection)
        Image frameImage = frameObj.AddComponent<Image>();
        frameImage.sprite = CreateRoundedSprite((int)size, (int)size, (int)mosaicConfig.avatarCornerRadius, Color.white);
        frameImage.color = HexToColor(mosaicConfig.avatarBorderColor);
        frameImage.type = Image.Type.Simple;
        frameImage.raycastTarget = true;
        
        // Bouton
        Button button = frameObj.AddComponent<Button>();
        button.targetGraphic = frameImage;
        button.transition = Selectable.Transition.None;
        
        int capturedIndex = index;
        button.onClick.AddListener(() => SelectCharacter(capturedIndex));
        
        // Image de l'avatar qui remplit tout le cadre (sans bordure intérieure)
        GameObject avatarObj = new GameObject("AvatarImage");
        avatarObj.transform.SetParent(frameObj.transform, false);
        
        RectTransform avatarRect = avatarObj.AddComponent<RectTransform>();
        // L'avatar remplit tout le cadre
        avatarRect.anchorMin = Vector2.zero;
        avatarRect.anchorMax = Vector2.one;
        avatarRect.pivot = new Vector2(0.5f, 0.5f);
        avatarRect.offsetMin = Vector2.zero;
        avatarRect.offsetMax = Vector2.zero;
        
        Image avatarImage = avatarObj.AddComponent<Image>();
        avatarImage.sprite = CreateRoundedSprite((int)size, (int)size, (int)mosaicConfig.avatarCornerRadius, Color.white);
        avatarImage.color = HexToColor("#e8ddd5"); // Couleur par défaut avant chargement
        avatarImage.type = Image.Type.Simple;
        avatarImage.preserveAspect = false; // Remplir tout l'espace
        avatarImage.raycastTarget = false;
        
        // Charger l'avatar
        string avatarUrl = character.GetAvatarUrl();
        if (!string.IsNullOrEmpty(avatarUrl))
        {
            string fullUrl = GetAvatarUrl(avatarUrl);
            StartCoroutine(LoadAvatarSprite(fullUrl, avatarImage));
        }
        
        // Bordure pointillée (cachée par défaut)
        CreateDashedBorder(frameObj.transform, size, mosaicConfig.avatarCornerRadius);
        
        avatarButtons.Add(frameObj);
    }
    
    void CreateDashedBorder(Transform parent, float size, float cornerRadius)
    {
        // Créer un cadre de sélection propre (comme le cadre des avatars mais juste le contour)
        GameObject borderContainer = new GameObject("DashedBorder");
        borderContainer.transform.SetParent(parent, false);
        
        RectTransform containerRect = borderContainer.AddComponent<RectTransform>();
        containerRect.anchorMin = Vector2.zero;
        containerRect.anchorMax = Vector2.one;
        containerRect.offsetMin = new Vector2(-4, -4);
        containerRect.offsetMax = new Vector2(4, 4);
        
        // Utiliser la même méthode que pour les cadres existants
        Image borderImage = borderContainer.AddComponent<Image>();
        int borderSize = (int)(size + 8);
        borderImage.sprite = CreateRoundedBorderOnlySprite(borderSize, borderSize, (int)cornerRadius + 2, 3);
        borderImage.color = HexToColor("#a23cff");
        borderImage.type = Image.Type.Simple;
        borderImage.raycastTarget = false;
        
        borderContainer.SetActive(false);
    }
    
    Sprite CreateRoundedBorderOnlySprite(int width, int height, int radius, int borderWidth)
    {
        Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
        Color[] pixels = new Color[width * height];
        
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                // Vérifier si le pixel est dans le rectangle arrondi extérieur
                bool inOuter = IsInsideRoundedRect(x, y, width, height, radius);
                // Vérifier si le pixel est dans le rectangle arrondi intérieur
                bool inInner = IsInsideRoundedRect(x - borderWidth, y - borderWidth, 
                    width - borderWidth * 2, height - borderWidth * 2, 
                    Mathf.Max(0, radius - borderWidth));
                
                // Le pixel fait partie de la bordure s'il est dans l'extérieur mais pas dans l'intérieur
                if (inOuter && !inInner)
                {
                    pixels[y * width + x] = Color.white;
                }
                else
                {
                    pixels[y * width + x] = Color.clear;
                }
            }
        }
        
        texture.SetPixels(pixels);
        texture.Apply();
        
        return Sprite.Create(texture, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), 100f);
    }
    
    bool IsInsideRoundedRect(int x, int y, int width, int height, int radius)
    {
        if (x < 0 || y < 0 || x >= width || y >= height) return false;
        
        // Vérifier les coins
        // Coin bas-gauche
        if (x < radius && y < radius)
        {
            float dx = radius - x;
            float dy = radius - y;
            return (dx * dx + dy * dy) <= radius * radius;
        }
        // Coin bas-droite
        if (x >= width - radius && y < radius)
        {
            float dx = x - (width - radius - 1);
            float dy = radius - y;
            return (dx * dx + dy * dy) <= radius * radius;
        }
        // Coin haut-gauche
        if (x < radius && y >= height - radius)
        {
            float dx = radius - x;
            float dy = y - (height - radius - 1);
            return (dx * dx + dy * dy) <= radius * radius;
        }
        // Coin haut-droite
        if (x >= width - radius && y >= height - radius)
        {
            float dx = x - (width - radius - 1);
            float dy = y - (height - radius - 1);
            return (dx * dx + dy * dy) <= radius * radius;
        }
        
        // Dans la zone centrale ou les côtés
        return true;
    }
    
    void CreateCharacterDetailPanel(Transform parent, float topOffset, QuetesTabCharacterDetailConfig detailConfig)
    {
        characterDetailPanel = new GameObject("CharacterDetailPanel");
        characterDetailPanel.transform.SetParent(parent, false);
        
        RectTransform panelRect = characterDetailPanel.AddComponent<RectTransform>();
        panelRect.anchorMin = new Vector2(0, 1);
        panelRect.anchorMax = new Vector2(1, 1);
        panelRect.pivot = new Vector2(0.5f, 1);
        panelRect.anchoredPosition = new Vector2(0, -(topOffset + detailConfig.marginTop));
        panelRect.sizeDelta = new Vector2(-detailConfig.padding * 2, detailConfig.height);
        
        // Pas de fond pour le panel principal (transparent)
        
        // Charger les polices
        TMP_FontAsset latoBoldFont = Resources.Load<TMP_FontAsset>("Fonts/Lato-Bold SDF");
        TMP_FontAsset latoRegularFont = Resources.Load<TMP_FontAsset>("Fonts/Lato-Regular SDF");
        
        // Description courte (en haut, sans cadre)
        GameObject descObj = new GameObject("Description");
        descObj.transform.SetParent(characterDetailPanel.transform, false);
        
        RectTransform descRect = descObj.AddComponent<RectTransform>();
        descRect.anchorMin = new Vector2(0, 1);
        descRect.anchorMax = new Vector2(1, 1);
        descRect.pivot = new Vector2(0.5f, 1);
        descRect.anchoredPosition = new Vector2(0, 0);
        descRect.sizeDelta = new Vector2(-detailConfig.padding * 2, 35);
        
        characterDescriptionText = descObj.AddComponent<TextMeshProUGUI>();
        characterDescriptionText.text = "";
        characterDescriptionText.fontSize = 24;
        characterDescriptionText.color = HexToColor("#64477f");
        characterDescriptionText.alignment = TextAlignmentOptions.Center;
        characterDescriptionText.textWrappingMode = TextWrappingModes.Normal;
        characterDescriptionText.overflowMode = TextOverflowModes.Truncate;
        characterDescriptionText.fontStyle = FontStyles.Normal; // Pas d'italique
        characterDescriptionText.raycastTarget = false;
        if (latoBoldFont != null) characterDescriptionText.font = latoBoldFont;
        
        // Description longue (dans un cadre aux coins arrondis)
        float longDescTop = 45; // Après la description courte
        
        GameObject longDescContainer = new GameObject("LongDescriptionContainer");
        longDescContainer.transform.SetParent(characterDetailPanel.transform, false);
        
        RectTransform longDescContainerRect = longDescContainer.AddComponent<RectTransform>();
        longDescContainerRect.anchorMin = new Vector2(0, 0);
        longDescContainerRect.anchorMax = new Vector2(1, 1);
        longDescContainerRect.pivot = new Vector2(0.5f, 0.5f);
        longDescContainerRect.offsetMin = new Vector2(0, 0);
        longDescContainerRect.offsetMax = new Vector2(0, -longDescTop);
        
        // Fond avec coins arrondis pour la description longue
        Image longDescBg = longDescContainer.AddComponent<Image>();
        longDescBg.sprite = CreateRoundedSprite(600, 100, 12, Color.white);
        longDescBg.color = HexToColor(detailConfig.backgroundColor);
        longDescBg.type = Image.Type.Sliced;
        
        // Texte de la description longue
        GameObject longDescObj = new GameObject("LongDescriptionText");
        longDescObj.transform.SetParent(longDescContainer.transform, false);
        
        RectTransform longDescRect = longDescObj.AddComponent<RectTransform>();
        longDescRect.anchorMin = Vector2.zero;
        longDescRect.anchorMax = Vector2.one;
        longDescRect.offsetMin = new Vector2(15, 10);
        longDescRect.offsetMax = new Vector2(-15, -10);
        
        characterLongDescriptionText = longDescObj.AddComponent<TextMeshProUGUI>();
        characterLongDescriptionText.text = "";
        characterLongDescriptionText.fontSize = 18;
        characterLongDescriptionText.color = HexToColor("#64477f");
        characterLongDescriptionText.alignment = TextAlignmentOptions.Center;
        characterLongDescriptionText.textWrappingMode = TextWrappingModes.Normal;
        characterLongDescriptionText.overflowMode = TextOverflowModes.Ellipsis;
        characterLongDescriptionText.fontStyle = FontStyles.Normal;
        characterLongDescriptionText.raycastTarget = false;
        if (latoRegularFont != null) characterLongDescriptionText.font = latoRegularFont;
        
        // On n'utilise plus characterNameText (variable commentée)
        // characterNameText = null;
    }
    
    void RefreshVisibleAvatars()
    {
        if (avatarsContainer == null || currentCharacters == null) return;
        
        var mosaicConfig = config.characterMosaic;
        float avatarSize = mosaicConfig.avatarSize;
        float spacing = mosaicConfig.avatarSpacing;
        int maxVisible = mosaicConfig.columns;
        
        // Supprimer les anciens avatars
        foreach (var avatar in avatarButtons)
        {
            if (avatar != null) Destroy(avatar);
        }
        avatarButtons.Clear();
        
        // Calculer les positions
        float totalWidth = maxVisible * avatarSize + (maxVisible - 1) * spacing;
        float startX = -totalWidth / 2 + avatarSize / 2;
        
        // Créer les avatars visibles (max 3)
        for (int i = 0; i < maxVisible; i++)
        {
            int characterIndex = visibleStartIndex + i;
            if (characterIndex >= currentCharacters.Count) break;
            
            float xPos = startX + i * (avatarSize + spacing);
            CreateAvatarButton(avatarsContainer.transform, currentCharacters[characterIndex], characterIndex, xPos, avatarSize, mosaicConfig);
        }
        
        Debug.Log($"[QuetesTab] 🔄 Avatars rafraîchis: visibleStartIndex={visibleStartIndex}, total={currentCharacters.Count}");
    }
    
    void NavigateCharacter(int direction)
    {
        if (currentCharacters == null || currentCharacters.Count == 0) return;
        
        var mosaicConfig = config.characterMosaic;
        int maxVisible = mosaicConfig.columns;
        
        // Déplacer la fenêtre visible
        visibleStartIndex += direction;
        
        // Bornes
        if (visibleStartIndex < 0)
            visibleStartIndex = 0;
        else if (visibleStartIndex > currentCharacters.Count - maxVisible)
            visibleStartIndex = Mathf.Max(0, currentCharacters.Count - maxVisible);
        
        // Si l'avatar sélectionné n'est plus visible, sélectionner le premier visible
        int visibleEndIndex = visibleStartIndex + maxVisible - 1;
        if (selectedCharacterIndex < visibleStartIndex || selectedCharacterIndex > visibleEndIndex)
        {
            selectedCharacterIndex = visibleStartIndex;
        }
        
        // Rafraîchir l'affichage
        RefreshVisibleAvatars();
        
        // Mettre à jour la visibilité des flèches
        UpdateArrowsVisibility();
        
        // Mettre à jour la sélection
        UpdateSelectedCharacter();
    }
    
    void UpdateArrowsVisibility()
    {
        if (currentCharacters == null || currentCharacters.Count == 0) return;
        
        var mosaicConfig = config.characterMosaic;
        int maxVisible = mosaicConfig.columns;
        
        // Cacher la flèche gauche si on est au début
        if (leftArrow != null)
        {
            leftArrow.SetActive(visibleStartIndex > 0);
        }
        
        // Cacher la flèche droite si on est à la fin
        if (rightArrow != null)
        {
            int maxStartIndex = Mathf.Max(0, currentCharacters.Count - maxVisible);
            rightArrow.SetActive(visibleStartIndex < maxStartIndex);
        }
        
        Debug.Log($"[QuetesTab] 🔄 Flèches mises à jour: gauche={leftArrow?.activeSelf}, droite={rightArrow?.activeSelf}, visibleStartIndex={visibleStartIndex}, total={currentCharacters.Count}");
    }
    
    void SelectCharacter(int index)
    {
        if (currentCharacters == null || index < 0 || index >= currentCharacters.Count) return;
        
        selectedCharacterIndex = index;
        UpdateSelectedCharacter();
    }
    
    void UpdateSelectedCharacter()
    {
        if (currentCharacters == null || currentCharacters.Count == 0) return;
        
        var mosaicConfig = config.characterMosaic;
        QuestCharacter character = currentCharacters[selectedCharacterIndex];
        
        // Mettre à jour la bordure pointillée des avatars visibles
        for (int i = 0; i < avatarButtons.Count; i++)
        {
            int characterIndex = visibleStartIndex + i;
            bool isSelected = (characterIndex == selectedCharacterIndex);
            
            // Afficher/cacher la bordure pointillée
            Transform dashedBorder = avatarButtons[i].transform.Find("DashedBorder");
            if (dashedBorder != null)
            {
                dashedBorder.gameObject.SetActive(isSelected);
            }
        }
        
        // Mettre à jour le panneau de détail (sans le nom)
        if (characterDescriptionText != null)
        {
            characterDescriptionText.text = character.GetDescription() ?? "";
        }
        
        if (characterLongDescriptionText != null)
        {
            characterLongDescriptionText.text = character.GetLongDescription() ?? "";
        }
        
        Debug.Log($"[QuetesTab] 👤 Personnage sélectionné: {character.GetName()}");
    }
    
    IEnumerator LoadAvatarSprite(string url, Image targetImage)
    {
        int gen = detailsGeneration;
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);

                // Si on a changé de quête/onglet pendant le téléchargement, on détruit et on n'assigne pas
                if (gen != detailsGeneration || targetImage == null)
                {
                    Destroy(sprite);
                    Destroy(texture);
                    yield break;
                }

                TrackDetails(texture, sprite);
                targetImage.sprite = sprite;
                targetImage.color = Color.white;
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] Échec du chargement de l'avatar: {url}");
            }
        }
    }
    
    string GetAvatarUrl(string avatarFileName)
    {
        // Si c'est déjà une URL complète, la retourner directement
        if (!string.IsNullOrEmpty(avatarFileName) && (avatarFileName.StartsWith("http://") || avatarFileName.StartsWith("https://")))
        {
            return avatarFileName;
        }
        
        // Sinon, ajouter le préfixe
        string uiPath = generalConfig?.assetsPaths?.uiPath ?? "https://ujsa.studioplc.fr/datas/UI/";
        return uiPath + avatarFileName;
    }
    
    private int selectedBadgeIndex = -1; // Index du badge sélectionné (-1 = aucun)
    
    void ShowBadgesContent()
    {
        Debug.Log("[QuetesTab] 🏆 ShowBadgesContent() appelé");
        
        // Nettoyer le contenu existant
        foreach (Transform child in detailsContent.transform)
        {
            Destroy(child.gameObject);
        }
        
        if (selectedQuest == null)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ selectedQuest est NULL");
            ShowNoQuestSelectedMessage();
            return;
        }
        
        if (selectedQuest.user == null || selectedQuest.user.Count == 0)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ Aucun badge dans la liste");
            CreateEmptyMessage("Aucun badge disponible pour cette quête");
            return;
        }
        
        Debug.Log($"[QuetesTab] 🏆 Affichage de {selectedQuest.user.Count} badges");
        
        // Container principal
        GameObject container = new GameObject("BadgesContainer");
        container.transform.SetParent(detailsContent.transform, false);
        
        RectTransform containerRect = container.AddComponent<RectTransform>();
        containerRect.anchorMin = Vector2.zero;
        containerRect.anchorMax = Vector2.one;
        containerRect.offsetMin = Vector2.zero;
        containerRect.offsetMax = Vector2.zero;
        
        // Zone des badges (en haut, centré horizontalement)
        GameObject badgesRow = new GameObject("BadgesRow");
        badgesRow.transform.SetParent(container.transform, false);
        
        RectTransform badgesRowRect = badgesRow.AddComponent<RectTransform>();
        badgesRowRect.anchorMin = new Vector2(0.5f, 1);
        badgesRowRect.anchorMax = new Vector2(0.5f, 1);
        badgesRowRect.pivot = new Vector2(0.5f, 1);
        badgesRowRect.anchoredPosition = new Vector2(0, -10f); // topMargin (remonté de 20px)
        
        // Layout horizontal pour centrer les badges
        HorizontalLayoutGroup layout = badgesRow.AddComponent<HorizontalLayoutGroup>();
        layout.spacing = 30f;
        layout.childAlignment = TextAnchor.UpperCenter;
        layout.childControlWidth = false;
        layout.childControlHeight = false;
        layout.childForceExpandWidth = false;
        layout.childForceExpandHeight = false;
        
        ContentSizeFitter sizeFitter = badgesRow.AddComponent<ContentSizeFitter>();
        sizeFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
        sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
        
        float badgeSize = 150f;
        
        for (int i = 0; i < selectedQuest.user.Count; i++)
        {
            QuestUserProgress badgeData = selectedQuest.user[i];
            CreateBadgeItem(badgesRow.transform, badgeData, i, badgeSize);
        }
        
        // Zone de détail du badge sélectionné (en dessous) - masquée par défaut
        float badgeRatio = 461f / 400f;
        float badgeHeight = badgeSize * badgeRatio;
        float titleHeight = 35f;
        float spacing = 10f;
        // topMargin (10px) + badgeHeight + titleHeight + spacing
        float detailPanelTopOffset = 10f + badgeHeight + titleHeight + spacing;
        CreateBadgeDetailPanel(container.transform, detailPanelTopOffset);
        
        // Aucun badge sélectionné par défaut - le panneau de détails reste vide/masqué
        selectedBadgeIndex = -1;
        
        // Masquer le panneau de détails au départ
        Transform detailPanel = container.transform.Find("BadgeDetailPanel");
        if (detailPanel != null)
        {
            detailPanel.gameObject.SetActive(false);
        }
        
        Debug.Log("[QuetesTab] ✅ Badges affichés (aucun badge sélectionné par défaut)");
    }
    
    void CreateBadgeItem(Transform parent, QuestUserProgress badgeData, int index, float size)
    {
        // Container pour le badge et son titre
        GameObject badgeContainer = new GameObject($"BadgeContainer_{badgeData.difficulty}");
        badgeContainer.transform.SetParent(parent, false);
        
        // Calculer la hauteur en respectant le ratio 400x461 des badges
        float badgeRatio = 461f / 400f; // = 1.1525
        float badgeWidth = size;
        float badgeHeight = size * badgeRatio;
        float titleHeight = 35f;
        float spacing = 10f;
        float titleWidth = badgeWidth * 1.5f; // Largeur du titre 50% plus large que le badge
        
        RectTransform containerRect = badgeContainer.AddComponent<RectTransform>();
        // Container garde la largeur du badge pour le layout (les badges ne se chevauchent pas)
        containerRect.sizeDelta = new Vector2(badgeWidth, badgeHeight + titleHeight + spacing);
        
        LayoutElement layoutElement = badgeContainer.AddComponent<LayoutElement>();
        layoutElement.preferredWidth = badgeWidth; // Container à la largeur du badge
        layoutElement.preferredHeight = badgeHeight + titleHeight + spacing;
        
        // Image du badge
        GameObject badgeObj = new GameObject($"Badge_{badgeData.difficulty}");
        badgeObj.transform.SetParent(badgeContainer.transform, false);
        
        RectTransform badgeRect = badgeObj.AddComponent<RectTransform>();
        badgeRect.anchorMin = new Vector2(0.5f, 1);
        badgeRect.anchorMax = new Vector2(0.5f, 1);
        badgeRect.pivot = new Vector2(0.5f, 1);
        badgeRect.anchoredPosition = new Vector2(0, 0);
        badgeRect.sizeDelta = new Vector2(badgeWidth, badgeHeight);
        
        Image badgeImage = badgeObj.AddComponent<Image>();
        badgeImage.color = Color.white;
        badgeImage.preserveAspect = true; // Préserver le ratio de l'image
        
        // Charger l'image du badge depuis l'URL
        if (!string.IsNullOrEmpty(badgeData.badge))
        {
            StartCoroutine(LoadBadgeImage(badgeData.badge, badgeImage));
        }
        
        // Badge de pourcentage SEULEMENT si completion = 100
        if (badgeData.completion == 100)
        {
            CreateScoreBadge(badgeObj.transform, badgeData.score, badgeWidth);
        }
        
        // Titre sous le badge (centré, peut dépasser de la largeur du badge)
        GameObject titleObj = new GameObject("BadgeTitle");
        titleObj.transform.SetParent(badgeContainer.transform, false);
        
        RectTransform titleRect = titleObj.AddComponent<RectTransform>();
        // Ancré au centre du container (qui est à la largeur du badge)
        titleRect.anchorMin = new Vector2(0.5f, 0);
        titleRect.anchorMax = new Vector2(0.5f, 0);
        titleRect.pivot = new Vector2(0.5f, 0);
        titleRect.anchoredPosition = new Vector2(0, 0);
        // Le titre peut être plus large que le container (déborde)
        titleRect.sizeDelta = new Vector2(titleWidth, titleHeight);
        
        TextMeshProUGUI titleText = titleObj.AddComponent<TextMeshProUGUI>();
        titleText.text = ""; // Sera rempli par UpdateSelectedBadge
        titleText.fontSize = 20; // Réduit de 24 à 20
        titleText.color = HexToColor("#64477f");
        titleText.alignment = TextAlignmentOptions.Center;
        titleText.textWrappingMode = TextWrappingModes.Normal;
        titleText.overflowMode = TextOverflowModes.Overflow; // Permettre le débordement
        titleText.fontStyle = FontStyles.Normal;
        titleText.raycastTarget = false;
        
        // S'assurer que le titre peut dépasser du container (pas de masque)
        RectMask2D mask = badgeContainer.GetComponent<RectMask2D>();
        if (mask != null)
        {
            Destroy(mask);
        }
        
        TMP_FontAsset latoBoldFont = Resources.Load<TMP_FontAsset>("Fonts/Lato-Bold SDF");
        if (latoBoldFont != null) titleText.font = latoBoldFont;
        
        // Masquer le titre par défaut (sera affiché seulement pour le badge sélectionné)
        titleObj.SetActive(false);
        
        // Bouton pour sélectionner le badge (sur le container pour couvrir badge + titre)
        Button button = badgeContainer.AddComponent<Button>();
        button.targetGraphic = badgeImage;
        int capturedIndex = index;
        button.onClick.AddListener(() => OnBadgeClicked(capturedIndex));
        
        Debug.Log($"[QuetesTab] Badge créé: {badgeData.difficulty} (taille: {badgeWidth}x{badgeHeight}, score: {badgeData.score}%)");
    }
    
    void CreateScoreBadge(Transform parent, int score, float badgeWidth)
    {
        // Conteneur pour le badge de score
        GameObject scoreBadgeObj = new GameObject("ScoreBadge");
        scoreBadgeObj.transform.SetParent(parent, false);
        
        // Taille du badge de score (environ 25-30% de la largeur du badge)
        float badgeSize = badgeWidth * 0.35f;
        
        RectTransform scoreRect = scoreBadgeObj.AddComponent<RectTransform>();
        scoreRect.sizeDelta = new Vector2(badgeSize, badgeSize);
        
        // Position dans le coin supérieur droit
        scoreRect.anchorMin = new Vector2(1, 1);
        scoreRect.anchorMax = new Vector2(1, 1);
        scoreRect.pivot = new Vector2(1, 1);
        scoreRect.anchoredPosition = new Vector2(0, 0);
        
        // Fond violet circulaire
        Image circleBg = scoreBadgeObj.AddComponent<Image>();
        circleBg.sprite = CreateCircleSprite((int)badgeSize);
        circleBg.color = HexToColor("#8b5fb3"); // Violet comme sur la maquette
        
        // Texte du score
        GameObject textObj = new GameObject("ScoreText");
        textObj.transform.SetParent(scoreBadgeObj.transform, false);
        
        RectTransform textRect = textObj.AddComponent<RectTransform>();
        textRect.anchorMin = Vector2.zero;
        textRect.anchorMax = Vector2.one;
        textRect.sizeDelta = Vector2.zero;
        textRect.anchoredPosition = Vector2.zero;
        
        TextMeshProUGUI scoreText = textObj.AddComponent<TextMeshProUGUI>();
        scoreText.text = $"{score}%";
        scoreText.fontSize = badgeSize * 0.35f; // Taille proportionnelle
        scoreText.color = Color.white;
        scoreText.alignment = TextAlignmentOptions.Center;
        scoreText.fontStyle = FontStyles.Bold;
        if (titleFont != null) scoreText.font = titleFont; // Police Anton
        
        Debug.Log($"[QuetesTab] Badge de score créé: {score}% (taille: {badgeSize})");
    }
    
    Sprite CreateCircleSprite(int size)
    {
        Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
        texture.filterMode = FilterMode.Bilinear;
        
        Color[] pixels = new Color[size * size];
        float center = size / 2f;
        float radius = size / 2f;
        
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                float distance = Vector2.Distance(new Vector2(x, y), new Vector2(center, center));
                
                if (distance <= radius)
                {
                    pixels[y * size + x] = Color.white;
                }
                else
                {
                    pixels[y * size + x] = Color.clear;
                }
            }
        }
        
        texture.SetPixels(pixels);
        texture.Apply();

        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, size, size), new Vector2(0.5f, 0.5f), 100f);

        if (currentScope == ResourceScope.Details)
            TrackDetails(texture, sprite);
        else
            TrackPersistent(texture, sprite);

        return sprite;
    }
    
    System.Collections.IEnumerator LoadBadgeImage(string url, Image targetImage)
    {
        int gen = detailsGeneration;
        using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequestTexture.GetTexture(url))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
            {
                Texture2D texture = UnityEngine.Networking.DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);

                if (gen != detailsGeneration || targetImage == null)
                {
                    Destroy(sprite);
                    Destroy(texture);
                    yield break;
                }

                TrackDetails(texture, sprite);
                targetImage.sprite = sprite;
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] Échec du chargement du badge: {url}");
            }
        }
    }
    
    void CreateBadgeDetailPanel(Transform parent, float topOffset)
    {
        GameObject detailPanel = new GameObject("BadgeDetailPanel");
        detailPanel.transform.SetParent(parent, false);
        
        RectTransform panelRect = detailPanel.AddComponent<RectTransform>();
        panelRect.anchorMin = new Vector2(0, 1);
        panelRect.anchorMax = new Vector2(1, 1);
        panelRect.pivot = new Vector2(0.5f, 1);
        panelRect.anchoredPosition = new Vector2(0, -topOffset);
        panelRect.sizeDelta = new Vector2(-30, 250); // Hauteur augmentée pour le bouton (150 + 100 pour bouton 80px + marge)
        
        // Charger les polices (même style que les personnages)
        TMP_FontAsset latoRegularFont = Resources.Load<TMP_FontAsset>("Fonts/Lato-Regular SDF");
        
        // Description du badge dans un cadre (même style que description longue des personnages)
        // Plus besoin de marge pour le titre puisqu'il est sous le badge maintenant
        float descTop = 10; // Marge supérieure réduite
        
        GameObject descContainer = new GameObject("BadgeDescriptionContainer");
        descContainer.transform.SetParent(detailPanel.transform, false);
        
        RectTransform descContainerRect = descContainer.AddComponent<RectTransform>();
        descContainerRect.anchorMin = new Vector2(0, 0);
        descContainerRect.anchorMax = new Vector2(1, 1);
        descContainerRect.pivot = new Vector2(0.5f, 0.5f);
        descContainerRect.offsetMin = new Vector2(0, 100); // Laisser 100px en bas pour le bouton (80 + 20 marge)
        descContainerRect.offsetMax = new Vector2(0, -descTop);
        
        // Fond avec coins arrondis pour la description (même style que personnages)
        Image descBg = descContainer.AddComponent<Image>();
        descBg.sprite = CreateRoundedSprite(600, 100, 12, Color.white);
        descBg.color = HexToColor("#ecdfd6"); // Même couleur que characterDetail.backgroundColor
        descBg.type = Image.Type.Sliced;
        
        // Texte de la description
        GameObject descTextObj = new GameObject("BadgeDescriptionText");
        descTextObj.transform.SetParent(descContainer.transform, false);
        
        RectTransform descTextRect = descTextObj.AddComponent<RectTransform>();
        descTextRect.anchorMin = Vector2.zero;
        descTextRect.anchorMax = Vector2.one;
        descTextRect.offsetMin = new Vector2(15, 10);
        descTextRect.offsetMax = new Vector2(-15, -10);
        
        TextMeshProUGUI descText = descTextObj.AddComponent<TextMeshProUGUI>();
        descText.text = "";
        descText.fontSize = 18;
        descText.color = HexToColor("#64477f");
        descText.alignment = TextAlignmentOptions.Center;
        descText.textWrappingMode = TextWrappingModes.Normal;
        descText.overflowMode = TextOverflowModes.Ellipsis;
        descText.fontStyle = FontStyles.Normal;
        descText.raycastTarget = false;
        if (latoRegularFont != null) descText.font = latoRegularFont;
    }
    
    void OnBadgeClicked(int index)
    {
        Debug.Log($"[QuetesTab] Badge {index} cliqué");
        selectedBadgeIndex = index;
        
        // Afficher le panneau de détails s'il était masqué
        Transform detailPanel = detailsContent.transform.Find("BadgesContainer/BadgeDetailPanel");
        if (detailPanel != null && !detailPanel.gameObject.activeSelf)
        {
            detailPanel.gameObject.SetActive(true);
        }
        
        UpdateSelectedBadge();
    }
    
    void UpdateSelectedBadge()
    {
        if (selectedQuest == null || selectedQuest.user == null || selectedBadgeIndex < 0 || selectedBadgeIndex >= selectedQuest.user.Count)
            return;
        
        QuestUserProgress selectedBadge = selectedQuest.user[selectedBadgeIndex];
        string difficulty = selectedBadge.difficulty;
        bool isUnlocked = selectedBadge.completion >= 100;
        
        // Récupérer la config des badges
        var badgesConfig = GeneralConfigManager.Instance?.GetConfig()?.badges;
        if (badgesConfig == null)
        {
            Debug.LogWarning("[QuetesTab] Configuration des badges non trouvée");
            return;
        }
        
        // Masquer tous les titres d'abord
        Transform badgesRow = detailsContent.transform.Find("BadgesContainer/BadgesRow");
        if (badgesRow != null)
        {
            for (int i = 0; i < badgesRow.childCount; i++)
            {
                Transform badgeContainer = badgesRow.GetChild(i);
                Transform titleObj = badgeContainer.Find("BadgeTitle");
                if (titleObj != null)
                {
                    titleObj.gameObject.SetActive(false);
                }
            }
        }
        
        // Afficher et mettre à jour le titre sous le badge sélectionné
        if (badgesRow != null && selectedBadgeIndex < badgesRow.childCount)
        {
            Transform badgeContainer = badgesRow.GetChild(selectedBadgeIndex);
            Transform titleObj = badgeContainer.Find("BadgeTitle");
            if (titleObj != null)
            {
                titleObj.gameObject.SetActive(true);
                TextMeshProUGUI titleText = titleObj.GetComponent<TextMeshProUGUI>();
                
                if (titleText != null)
                {
                    if (isUnlocked)
                    {
                        string titre = GetBadgeTitre(badgesConfig, difficulty);
                        titleText.text = titre;
                    }
                    else
                    {
                        titleText.text = "???";
                    }
                }
            }
        }
        
        // Mettre à jour la description dans le panneau de détails
        Transform detailPanel = detailsContent.transform.Find("BadgesContainer/BadgeDetailPanel");
        if (detailPanel != null)
        {
            Debug.Log($"[QuetesTab] DetailPanel trouvé, actif: {detailPanel.gameObject.activeSelf}");
            
            Transform descContainer = detailPanel.Find("BadgeDescriptionContainer");
            if (descContainer == null)
            {
                Debug.LogWarning("[QuetesTab] ⚠️ BadgeDescriptionContainer non trouvé ou détruit");
                return;
            }
            
            TextMeshProUGUI descText = descContainer.Find("BadgeDescriptionText")?.GetComponent<TextMeshProUGUI>();
            
            if (descText != null)
            {
                if (isUnlocked)
                {
                    string texte = GetBadgeTexte(badgesConfig, difficulty);
                    descText.text = texte;
                }
                else
                {
                    descText.text = "Vous n'avez pas encore débloqué ce badge.";
                }
            }
            
            // Ajouter/afficher le bouton "EFFACER LE BADGE" si completion = 100
            UpdateResetBadgeButton(detailPanel, isUnlocked, difficulty);
        }
        else
        {
            Debug.LogWarning("[QuetesTab] ⚠️ DetailPanel non trouvé ou détruit");
        }
        
        Debug.Log($"[QuetesTab] Badge {difficulty} mis à jour - Débloqué: {isUnlocked}, Completion: {selectedBadge.completion}%");
    }
    
    void UpdateResetBadgeButton(Transform detailPanel, bool isCompleted, string difficulty)
    {
        // Vérifier que le panel existe toujours (Unity gère les objets détruits avec ==)
        if (detailPanel == null)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ DetailPanel est null ou détruit, impossible de créer le bouton reset");
            return;
        }
        
        Debug.Log($"[QuetesTab] 🔘 UpdateResetBadgeButton - isCompleted: {isCompleted}, difficulty: {difficulty}");
        
        // Chercher ou créer le bouton "EFFACER LE BADGE"
        Transform buttonObj = detailPanel.Find("ResetBadgeButton");
        Debug.Log($"[QuetesTab] 🔍 Bouton existant: {(buttonObj != null ? "Oui" : "Non")}");
        
        if (isCompleted)
        {
            // Créer le bouton s'il n'existe pas (Unity gère les objets détruits avec ==)
            if (buttonObj == null)
            {
                Debug.Log("[QuetesTab] 🔨 Début de création du bouton EFFACER LE BADGE...");
                
                // Récupérer le container de description pour positionner le bouton en dessous
                Transform descContainer = detailPanel.Find("BadgeDescriptionContainer");
                if (descContainer == null)
                {
                    Debug.LogError("[QuetesTab] ❌ BadgeDescriptionContainer non trouvé");
                    return;
                }
                
                // Récupérer le style validationDefault depuis la config
                ButtonStyleConfig buttonStyle = GeneralConfigManager.Instance?.GetButtonStyle("validationDefault");
                
                float buttonWidth = buttonStyle?.width ?? 300f;
                float buttonHeight = buttonStyle?.height ?? 80f;
                float borderRadius = buttonStyle?.borderRadius ?? 35f;
                float borderWidth = buttonStyle?.borderWidth ?? 4f;
                Color startColor = HexToColor(buttonStyle?.gradient?.startColor ?? "#CE9BFD");
                Color endColor = HexToColor(buttonStyle?.gradient?.endColor ?? "#9A2DFF");
                Color borderColor = HexToColor(buttonStyle?.borderColor ?? "#f5ece5");
                Color textColor = HexToColor(buttonStyle?.text?.color ?? "#FFFFFF");
                float textFontSize = buttonStyle?.text?.fontSize ?? 28f;
                
                // Créer le GameObject du bouton
                GameObject buttonGO = new GameObject("ResetBadgeButton");
                if (buttonGO == null)
                {
                    Debug.LogError("[QuetesTab] ❌ Impossible de créer le GameObject ResetBadgeButton");
                    return;
                }
                
                Debug.Log("[QuetesTab] ✓ GameObject créé");
                
                // Ajouter le RectTransform et le parenter
                RectTransform buttonRect = buttonGO.AddComponent<RectTransform>();
                buttonGO.transform.SetParent(detailPanel, false);
                
                Debug.Log("[QuetesTab] ✓ Parenté définie");
                
                // Positionner sous la description - utiliser les dimensions du style
                buttonRect.anchorMin = new Vector2(0.5f, 0);
                buttonRect.anchorMax = new Vector2(0.5f, 0);
                buttonRect.pivot = new Vector2(0.5f, 1); // Pivot en haut du bouton
                buttonRect.sizeDelta = new Vector2(buttonWidth, buttonHeight); // Dimensions du style validationDefault
                buttonRect.anchoredPosition = new Vector2(0, 10);
                
                Debug.Log($"[QuetesTab] ✓ Position définie - Taille: {buttonWidth}x{buttonHeight}");
                
                // Background du bouton avec le style validationDefault (dégradé violet)
                Image buttonBg = buttonGO.AddComponent<Image>();
                buttonBg.sprite = CreateGradientSpriteWithBorder((int)buttonWidth, (int)buttonHeight, borderRadius, 
                    startColor, endColor, borderColor, borderWidth);
                buttonBg.type = Image.Type.Simple;
                buttonBg.color = Color.white; // Important : blanc pour que le dégradé s'affiche correctement
                
                Debug.Log("[QuetesTab] ✓ Image avec dégradé validationDefault ajoutée");
                
                // Texte du bouton (style validationDefault)
                GameObject textObj = new GameObject("Text");
                textObj.transform.SetParent(buttonGO.transform, false);
                RectTransform textRect = textObj.AddComponent<RectTransform>();
                textRect.anchorMin = Vector2.zero;
                textRect.anchorMax = Vector2.one;
                textRect.sizeDelta = Vector2.zero;
                
                TextMeshProUGUI buttonText = textObj.AddComponent<TextMeshProUGUI>();
                buttonText.text = "EFFACER LE BADGE";
                buttonText.fontSize = textFontSize; // Taille du style
                buttonText.color = textColor; // Couleur du style
                buttonText.alignment = TextAlignmentOptions.Center;
                buttonText.verticalAlignment = VerticalAlignmentOptions.Middle; // Centrer verticalement
                buttonText.fontStyle = buttonStyle?.text?.GetFontStyle() ?? FontStyles.Normal; // FontWeight du style
                buttonText.raycastTarget = false;
                
                // Charger la police Anton (style validationDefault)
                TMP_FontAsset antonFont = Resources.Load<TMP_FontAsset>("Fonts/Anton-Regular SDF");
                if (antonFont != null) buttonText.font = antonFont;
                
                Debug.Log("[QuetesTab] ✓ Texte ajouté");
                
                // Ajouter le component Button
                Button button = buttonGO.AddComponent<Button>();
                button.targetGraphic = buttonBg;
                
                // Configurer les couleurs du bouton (effets hover/pressed)
                ColorBlock colors = button.colors;
                colors.normalColor = Color.white;
                colors.highlightedColor = new Color(1.1f, 1.1f, 1.1f, 1f); // Légèrement plus clair au survol
                colors.pressedColor = new Color(0.9f, 0.9f, 0.9f, 1f); // Légèrement plus sombre au clic
                button.colors = colors;
                
                // Récupérer la référence Transform pour la suite
                buttonObj = buttonGO.transform;
                
                Debug.Log($"[QuetesTab] ✅ Bouton EFFACER LE BADGE créé pour {difficulty}");
            }
            
            // Mettre à jour le listener du bouton avec la difficulté actuelle
            if (buttonObj != null)
            {
                Button btn = buttonObj.GetComponent<Button>();
                if (btn != null)
                {
                    btn.onClick.RemoveAllListeners();
                    btn.onClick.AddListener(() => OnResetBadgeClicked(difficulty));
                    Debug.Log($"[QuetesTab] 🔗 Listener ajouté au bouton pour {difficulty}");
                }
                
                // S'assurer que le bouton est visible
                buttonObj.gameObject.SetActive(true);
                Debug.Log($"[QuetesTab] 👁️ Bouton EFFACER LE BADGE activé et visible");
            }
        }
        else
        {
            // Masquer le bouton si le badge n'est pas complété
            if (buttonObj != null)
            {
                buttonObj.gameObject.SetActive(false);
            }
        }
    }
    
    void OnResetBadgeClicked(string difficulty)
    {
        if (selectedQuest == null) return;
        
        Debug.Log($"[QuetesTab] Demande d'effacement du badge {difficulty} pour la quête {selectedQuest.id}");
        
        // Appeler l'endpoint pour effacer le badge
        StartCoroutine(ResetBadgeCoroutine(selectedQuest.id, difficulty));
    }
    
    IEnumerator ResetBadgeCoroutine(int questId, string difficulty)
    {
        string url = GeneralConfigManager.Instance.GetQuestAnswersResetByDifficultyUrl(questId);
        
        Debug.Log($"[QuetesTab] 🌐 POST {url}");
        
        // Créer le body JSON
        string jsonBody = $"{{\"difficulty\":\"{difficulty}\"}}";
        Debug.Log($"[QuetesTab] 📦 Body: {jsonBody}");
        
        using (UnityWebRequest www = new UnityWebRequest(url, "POST"))
        {
            byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
            www.uploadHandler = new UploadHandlerRaw(bodyRaw);
            www.downloadHandler = new DownloadHandlerBuffer();
            www.SetRequestHeader("Content-Type", "application/json");
            
            // Ajouter le token d'authentification
            string token = PlayerPrefs.GetString("auth_token", "");
            if (!string.IsNullOrEmpty(token))
            {
                www.SetRequestHeader("Authorization", $"Bearer {token}");
                Debug.Log("[QuetesTab] 🔑 Token ajouté à la requête reset badge");
            }
            
            yield return www.SendWebRequest();
            
            if (www.result == UnityWebRequest.Result.Success)
            {
                Debug.Log($"[QuetesTab] ✅ Badge {difficulty} effacé avec succès pour la quête {questId}");
                
                // Recharger les données des quêtes pour rafraîchir l'affichage
                LoadQuestsData();
                yield return new WaitForSeconds(0.3f); // Délai pour laisser les données se charger
                
                // Rafraîchir l'affichage de la quête sélectionnée
                if (selectedQuest != null)
                {
                    // Retrouver la quête mise à jour dans la liste
                    Quest updatedQuest = questsData?.Find(q => q.id == selectedQuest.id);
                    if (updatedQuest != null)
                    {
                        SelectQuest(updatedQuest);
                        Debug.Log($"[QuetesTab] 🔄 Quête {questId} rafraîchie après reset du badge");
                    }
                }
            }
            else
            {
                Debug.LogError($"[QuetesTab] ❌ Erreur lors de l'effacement du badge: {www.error}");
            }
        }
    }
    
    string GetBadgeTitre(BadgesConfig config, string difficulty)
    {
        if (config == null || config.titres == null) return difficulty;
        return config.titres.Get(difficulty);
    }
    
    string GetBadgeTexte(BadgesConfig config, string difficulty)
    {
        if (config == null || config.textes == null) return "";
        return config.textes.Get(difficulty);
    }
    
    void ShowFormationsContent()
    {
        Debug.Log("[QuetesTab] 🎓 ShowFormationsContent() appelé");
        
        // Nettoyer le contenu existant
        foreach (Transform child in detailsContent.transform)
        {
            Destroy(child.gameObject);
        }
        
        if (selectedQuest == null)
        {
            Debug.LogWarning("[QuetesTab] ⚠️ selectedQuest est NULL");
            ShowNoQuestSelectedMessage();
            return;
        }
        
        
        if (selectedQuest.trainings == null || selectedQuest.trainings.Count == 0)
        {
            Debug.LogWarning($"[QuetesTab] ⚠️ Aucune formation pour la quête {selectedQuest.id}");
            CreateEmptyMessage("Aucune formation pour cette quête");
            return;
        }
        
        Debug.Log($"[QuetesTab] 📚 Affichage de {selectedQuest.trainings.Count} formations (mode images)");
        
        // Afficher les formations sous forme de grille 2 colonnes (TAILLE DOUBLÉE)
        float imageWidth = 270f; // Largeur de chaque image (portrait)
        float imageHeight = 360f; // Hauteur de chaque image (portrait, ratio 3:4)
        float spacing = 30f; // Espacement entre les images
        float startX = 15f; // Marge gauche (ajustée pour centrer horizontalement)
        float startY = -25f; // Marge haute (remonté de 5px)
        float cornerRadius = 15f; // Rayon des coins arrondis
        
        // Paramètres de l'ombre (depuis config)
        float shadowOffsetX = 0f; // Pas de décalage horizontal
        float shadowOffsetY = 8f; // Décalage vertical uniquement
        Color shadowColor = HexToColor("#dbc3b7"); // Couleur beige de la config
        
        int index = 0;
        foreach (Training training in selectedQuest.trainings)
        {
            int row = index / 2; // Ligne (0, 1, 2...)
            int col = index % 2; // Colonne (0 ou 1)
            
            float xPos = startX + col * (imageWidth + spacing);
            float yPos = startY - row * (imageHeight + spacing);
            
            // Créer le conteneur global
            GameObject container = new GameObject($"Formation_{index}_Container");
            container.transform.SetParent(detailsContent.transform, false);
            
            RectTransform containerRect = container.AddComponent<RectTransform>();
            containerRect.anchorMin = new Vector2(0, 1);
            containerRect.anchorMax = new Vector2(0, 1);
            containerRect.pivot = new Vector2(0, 1);
            containerRect.anchoredPosition = new Vector2(xPos, yPos);
            containerRect.sizeDelta = new Vector2(imageWidth, imageHeight);
            
            // Créer l'ombre DERRIÈRE (image avec même sprite arrondi)
            GameObject shadowObj = new GameObject("Shadow");
            shadowObj.transform.SetParent(container.transform, false);
            
            RectTransform shadowRect = shadowObj.AddComponent<RectTransform>();
            shadowRect.anchorMin = Vector2.zero;
            shadowRect.anchorMax = Vector2.one;
            shadowRect.sizeDelta = Vector2.zero;
            shadowRect.anchoredPosition = new Vector2(shadowOffsetX, -shadowOffsetY); // Ombre verticale uniquement
            
            Image shadowImage = shadowObj.AddComponent<Image>();
            shadowImage.sprite = CreateRoundedRectSprite((int)imageWidth, (int)imageHeight, (int)cornerRadius);
            shadowImage.color = shadowColor; // Couleur beige de la config
            shadowImage.raycastTarget = false;
            
            // Mettre l'ombre en premier (derrière)
            shadowObj.transform.SetAsFirstSibling();
            
            // Créer le conteneur de l'image (AU-DESSUS de l'ombre)
            GameObject item = new GameObject("FormationImage");
            item.transform.SetParent(container.transform, false);
            
            RectTransform itemRect = item.AddComponent<RectTransform>();
            itemRect.anchorMin = Vector2.zero;
            itemRect.anchorMax = Vector2.one;
            itemRect.sizeDelta = Vector2.zero;
            itemRect.anchoredPosition = Vector2.zero;
            
            // Image de masque (pour le Mask component)
            Image maskImage = item.AddComponent<Image>();
            maskImage.sprite = CreateRoundedRectSprite((int)imageWidth, (int)imageHeight, (int)cornerRadius);
            maskImage.color = Color.white;
            maskImage.raycastTarget = true;
            
            // Ajouter un Mask pour les coins arrondis
            Mask mask = item.AddComponent<Mask>();
            mask.showMaskGraphic = true; // Afficher le masque blanc comme fond
            
            // Image de la formation (enfant, sera masquée)
            GameObject imageObj = new GameObject("Portrait");
            imageObj.transform.SetParent(item.transform, false);
            
            RectTransform imageRect = imageObj.AddComponent<RectTransform>();
            imageRect.anchorMin = new Vector2(0.5f, 0.5f);
            imageRect.anchorMax = new Vector2(0.5f, 0.5f);
            imageRect.pivot = new Vector2(0.5f, 0.5f);
            imageRect.anchoredPosition = Vector2.zero;
            // La taille sera ajustée après le chargement de l'image
            
            Image formationImage = imageObj.AddComponent<Image>();
            formationImage.preserveAspect = true;
            formationImage.raycastTarget = false;
            
            // Charger l'image portrait depuis l'URL avec ajustement "cover"
            if (!string.IsNullOrEmpty(training.portrait))
            {
                StartCoroutine(LoadFormationImageCover(formationImage, imageRect, training.portrait, imageWidth, imageHeight));
            }
            else
            {
                // Image placeholder si pas d'URL
                formationImage.color = HexToColor("#ecdfd6");
                imageRect.sizeDelta = new Vector2(imageWidth, imageHeight);
            }
            
            // Rendre cliquable pour ouvrir l'URL de la formation
            if (!string.IsNullOrEmpty(training.url))
            {
                Button button = item.AddComponent<Button>();
                button.targetGraphic = maskImage;
                button.transition = Selectable.Transition.ColorTint;
                
                ColorBlock colors = button.colors;
                colors.normalColor = Color.white;
                colors.highlightedColor = new Color(0.95f, 0.95f, 0.95f, 1f);
                colors.pressedColor = new Color(0.85f, 0.85f, 0.85f, 1f);
                button.colors = colors;
                
                string url = training.url;
                button.onClick.AddListener(() => {
                    Debug.Log($"[QuetesTab] 🔗 Ouverture formation: {training.title} → {url}");
                    Application.OpenURL(url);
                });
            }
            
            Debug.Log($"[QuetesTab] ✓ Formation image créée avec ombre: {training.title} à ({xPos}, {yPos})");
            index++;
        }
        
        Debug.Log($"[QuetesTab] ✅ {selectedQuest.trainings.Count} formations affichées en grille");
    }
    
    /// <summary>
    /// Charge l'image d'une formation depuis une URL
    /// </summary>
    IEnumerator LoadFormationImage(Image targetImage, string imageUrl)
    {
        int gen = detailsGeneration;
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageUrl))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);
                
                if (gen != detailsGeneration || targetImage == null)
                {
                    Destroy(sprite);
                    Destroy(texture);
                    yield break;
                }

                TrackDetails(texture, sprite);
                targetImage.sprite = sprite;
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Erreur chargement image formation: {imageUrl}");
            }
        }
    }
    
    /// <summary>
    /// Charge l'image d'une formation et l'ajuste en mode "cover" (remplir le cadre sans blanc)
    /// </summary>
    IEnumerator LoadFormationImageCover(Image targetImage, RectTransform imageRect, string imageUrl, float containerWidth, float containerHeight)
    {
        int gen = detailsGeneration;
        using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageUrl))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityWebRequest.Result.Success)
            {
                Texture2D texture = DownloadHandlerTexture.GetContent(request);
                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), 
                    new Vector2(0.5f, 0.5f), 100f);
                
                if (gen != detailsGeneration || targetImage == null || imageRect == null)
                {
                    Destroy(sprite);
                    Destroy(texture);
                    yield break;
                }

                TrackDetails(texture, sprite);
                targetImage.sprite = sprite;

                // Calculer la taille pour remplir le cadre (mode "cover")
                float imageAspect = (float)texture.width / texture.height;
                float containerAspect = containerWidth / containerHeight;

                float width, height;

                if (imageAspect > containerAspect)
                {
                    // Image plus large que le conteneur : ajuster sur la hauteur
                    height = containerHeight;
                    width = height * imageAspect;
                }
                else
                {
                    // Image plus haute que le conteneur : ajuster sur la largeur
                    width = containerWidth;
                    height = width / imageAspect;
                }

                imageRect.sizeDelta = new Vector2(width, height);

                Debug.Log($"[QuetesTab] Image ajustée en mode cover: {width}x{height} (original: {texture.width}x{texture.height})");
            }
            else
            {
                Debug.LogWarning($"[QuetesTab] ⚠️ Erreur chargement image formation: {imageUrl}");
            }
        }
    }
    
    void CreateEmptyMessage(string message)
    {
        GameObject messageObj = new GameObject("EmptyMessage");
        messageObj.transform.SetParent(detailsContent.transform, false);
        
        RectTransform rect = messageObj.AddComponent<RectTransform>();
        rect.anchorMin = Vector2.zero;
        rect.anchorMax = Vector2.one;
        rect.offsetMin = Vector2.zero;
        rect.offsetMax = Vector2.zero;
        
        TextMeshProUGUI text = messageObj.AddComponent<TextMeshProUGUI>();
        text.text = message;
        text.fontSize = 24;
        text.color = HexToColor("#8a8a8a");
        text.alignment = TextAlignmentOptions.Center;
        if (textFont != null) text.font = textFont;
    }
    
    List<QuestCharacter> GetQuestCharacters(int questId)
    {
        if (questCharacters.ContainsKey(questId))
        {
            return questCharacters[questId];
        }
        return new List<QuestCharacter>();
    }
    
    // ========== Utilitaires ==========
    
    Sprite CreateRoundedSprite(int width, int height, float radius, Color fillColor)
    {
        Texture2D texture = new Texture2D(width, height);
        Color[] pixels = new Color[width * height];

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                float alpha = 1f;

                // Coins arrondis
                if (x < radius && y > height - radius)
                {
                    float dx = radius - x;
                    float dy = (height - radius) - y;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius) alpha = 0f;
                }
                else if (x > width - radius && y > height - radius)
                {
                    float dx = x - (width - radius);
                    float dy = (height - radius) - y;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius) alpha = 0f;
                }
                else if (x < radius && y < radius)
                {
                    float dx = radius - x;
                    float dy = radius - y;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius) alpha = 0f;
                }
                else if (x > width - radius && y < radius)
                {
                    float dx = x - (width - radius);
                    float dy = radius - y;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius) alpha = 0f;
                }

                pixels[y * width + x] = new Color(fillColor.r, fillColor.g, fillColor.b, fillColor.a * alpha);
            }
        }

        texture.SetPixels(pixels);
        texture.Apply();

        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), 100f);

        if (currentScope == ResourceScope.Details)
            TrackDetails(texture, sprite);
        else
            TrackPersistent(texture, sprite);

        return sprite;
    }
    
    Sprite CreateGradientSpriteWithBorder(int width, int height, float radius, Color startColor, Color endColor, Color borderColor, float borderWidth)
    {
        Texture2D texture = new Texture2D(width, height);
        Color[] pixels = new Color[width * height];

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                float alpha = 1f;
                bool isBorder = false;
                
                // Vérifier si on est dans la bordure
                if (x < borderWidth || x >= width - borderWidth || y < borderWidth || y >= height - borderWidth)
                {
                    isBorder = true;
                }

                // Coins arrondis
                float distanceFromCorner = float.MaxValue;
                
                // Coin supérieur gauche
                if (x < radius && y > height - radius)
                {
                    float dx = radius - x;
                    float dy = (height - radius) - y;
                    distanceFromCorner = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distanceFromCorner > radius) alpha = 0f;
                    else if (distanceFromCorner > radius - borderWidth) isBorder = true;
                }
                // Coin supérieur droit
                else if (x > width - radius && y > height - radius)
                {
                    float dx = x - (width - radius);
                    float dy = (height - radius) - y;
                    distanceFromCorner = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distanceFromCorner > radius) alpha = 0f;
                    else if (distanceFromCorner > radius - borderWidth) isBorder = true;
                }
                // Coin inférieur gauche
                else if (x < radius && y < radius)
                {
                    float dx = radius - x;
                    float dy = radius - y;
                    distanceFromCorner = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distanceFromCorner > radius) alpha = 0f;
                    else if (distanceFromCorner > radius - borderWidth) isBorder = true;
                }
                // Coin inférieur droit
                else if (x > width - radius && y < radius)
                {
                    float dx = x - (width - radius);
                    float dy = radius - y;
                    distanceFromCorner = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distanceFromCorner > radius) alpha = 0f;
                    else if (distanceFromCorner > radius - borderWidth) isBorder = true;
                }

                Color pixelColor;
                if (isBorder && alpha > 0)
                {
                    pixelColor = borderColor;
                }
                else
                {
                    // Dégradé vertical (du bas vers le haut)
                    float t = (float)y / height;
                    pixelColor = Color.Lerp(endColor, startColor, t);
                }
                
                pixelColor.a *= alpha;
                pixels[y * width + x] = pixelColor;
            }
        }

        texture.SetPixels(pixels);
        texture.Apply();

        Sprite sprite = Sprite.Create(texture, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f), 100f);

        if (currentScope == ResourceScope.Details)
            TrackDetails(texture, sprite);
        else
            TrackPersistent(texture, sprite);

        return sprite;
    }
    
    Color HexToColor(string hex)
    {
        if (string.IsNullOrEmpty(hex)) return Color.white;
        
        hex = hex.TrimStart('#');
        
        if (hex.Length == 6)
            hex = hex + "FF";
        
        if (hex.Length == 8)
        {
            byte r = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
            byte g = byte.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
            byte b = byte.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
            byte a = byte.Parse(hex.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
            
            return new Color(r / 255f, g / 255f, b / 255f, a / 255f);
        }
        
        return Color.white;
    }
    
    /// <summary>
    /// Crée un sprite avec coins arrondis (masque blanc)
    /// </summary>
    Sprite CreateRoundedRectSprite(int width, int height, int radius)
    {
        Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
        texture.filterMode = FilterMode.Bilinear;
        Color[] pixels = new Color[width * height];
        
        // Remplir tout en blanc transparent d'abord
        for (int i = 0; i < pixels.Length; i++)
        {
            pixels[i] = new Color(1f, 1f, 1f, 0f);
        }
        
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                float alpha = 1f;
                
                // Coin supérieur gauche
                if (x < radius && y > height - radius)
                {
                    float dx = radius - x - 0.5f;
                    float dy = y - (height - radius) - 0.5f;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius)
                    {
                        alpha = 0f;
                    }
                    else if (distance > radius - 1f)
                    {
                        // Anti-aliasing
                        alpha = 1f - (distance - (radius - 1f));
                    }
                }
                // Coin supérieur droit
                else if (x > width - radius - 1 && y > height - radius)
                {
                    float dx = x - (width - radius - 1) - 0.5f;
                    float dy = y - (height - radius) - 0.5f;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius)
                    {
                        alpha = 0f;
                    }
                    else if (distance > radius - 1f)
                    {
                        alpha = 1f - (distance - (radius - 1f));
                    }
                }
                // Coin inférieur gauche
                else if (x < radius && y < radius)
                {
                    float dx = radius - x - 0.5f;
                    float dy = radius - y - 0.5f;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius)
                    {
                        alpha = 0f;
                    }
                    else if (distance > radius - 1f)
                    {
                        alpha = 1f - (distance - (radius - 1f));
                    }
                }
                // Coin inférieur droit
                else if (x > width - radius - 1 && y < radius)
                {
                    float dx = x - (width - radius - 1) - 0.5f;
                    float dy = radius - y - 0.5f;
                    float distance = Mathf.Sqrt(dx * dx + dy * dy);
                    if (distance > radius)
                    {
                        alpha = 0f;
                    }
                    else if (distance > radius - 1f)
                    {
                        alpha = 1f - (distance - (radius - 1f));
                    }
                }
                
                pixels[y * width + x] = new Color(1f, 1f, 1f, alpha);
            }
        }
        
        texture.SetPixels(pixels);
        texture.Apply();
        
        return Sprite.Create(texture, new Rect(0, 0, width, height), new Vector2(0.5f, 0.5f));
    }
}

// ========== Classes de données ==========

[Serializable]
public class CharactersData
{
    public List<QuestCharacter> personnages;
}

[Serializable]
public class QuestCharacter
{
    // Champs français (depuis persos.json local)
    public string nom;
    public string description_courte;
    public string description;
    public string avatar;
    public string avatar_vide;
    public string png;
    
    // Champs anglais (depuis API)
    public string name;
    public string short_description;
    public string long_description;
    public string avatar_url;
    public string image_url;
    
    // Propriétés pour obtenir les valeurs (priorité à l'anglais car c'est ce que renvoie l'API)
    public string GetName() => !string.IsNullOrEmpty(name) ? name : nom;
    public string GetDescription() => !string.IsNullOrEmpty(short_description) ? short_description : description_courte;
    public string GetLongDescription() => !string.IsNullOrEmpty(long_description) ? long_description : description;
    public string GetAvatarUrl() => !string.IsNullOrEmpty(avatar_url) ? avatar_url : (!string.IsNullOrEmpty(image_url) ? image_url : avatar);
}

// Réponse de l'API pour une quête individuelle
[Serializable]
public class QuestDetailApiResponse
{
    public string status;
    public string message;
    public QuestDetailData data;
}

[Serializable]
public class QuestDetailData
{
    public int id;
    public string title;
    public string description;
    public string duration;
    public string trailer_url;
    public List<Training> trainings;
    public List<QuestCharacter> characters; // Personnages de la quête
    public List<QuestBadge> badges;
}

[Serializable]
public class QuestBadge
{
    public int id;
    public string name;
    public string description;
    public string image_url;
    public bool unlocked;
}

/// <summary>
/// Marqueur pour identifier les quêtes verrouillées dans l'onglet Quêtes
/// </summary>
public class LockedQuestMarker : MonoBehaviour { }
