using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using UnityEngine.Video;


#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

// ─────────────────────────────────────────────────────────────────────────────
// Modèles JSON (UNE SEULE FOIS)
// ─────────────────────────────────────────────────────────────────────────────
[Serializable]
public class DialogueConfig
{
    public string skipDialogueKey = "Escape";
    public string nextKey = "Space";
    public string prevKey = "Backspace";

    public bool  requireInputToAdvance = true;
    public float autoAdvanceDelay = 0f;
    public bool  showSkipInstructions = true;
    public float fadeTransitionDuration = 0.35f;

    public float  dialogueTextSize = 28f;
    public string dialogueTextColor = "#ffffff";
    public float  speakerTextSize = 32f;
    public string speakerTextColor = "#faf1c8";
    public float  backgroundDimming = 0.25f;

    // Contrôle du bandeau bas
    public float  bottomBarHeightRatio = 0.32f;     // 0..1
    public string bottomBarColor = "#00000099";     // noir ~60% alpha
    public float  paddingLeft = 24f;                // px
    public float  paddingRight = 24f;               // px
}

[Serializable]
public class DialogueLine
{
    public string text;
    public string speaker;      // si vide -> on prendra DialogueSequence.speaker
    public string image;        // illustration par ligne (chemin relatif à imageRoot)
    public float  duration = 0; // auto-advance de la ligne (0 = désactivé)
    public float  delay = 0;    // délai AVANT d’afficher la ligne
}

[Serializable]
public class DialogueSequence
{
    public string id;
    public string title;
    public string speaker;     // orateur par défaut (hérite sur les lignes vides)
    public string video;       // nom du fichier vidéo de fond (utilisé par le GameManager)
    public List<DialogueLine> lines;
}

[Serializable]
public class DialogueWrapper
{
    public string videoRoot;              // racine vidéo (optionnel, sinon global)
    public string imageRoot;              // racine images (optionnel, sinon global)
    public DialogueConfig dialogueConfig; // config spécifique à ce dialogue (optionnel)
    public DialogueSequence dialogue;
}

/// <summary>
/// Composant Unity pour jouer les séquences de dialogue
/// </summary>
public class DialoguePlayer : MonoBehaviour
{
    [Header("Illustration (optionnel)")]
    public Image illustrationImage;             // assigner un Image dans le Canvas
    public float illustrationFadeDuration = 0.25f;

    private CanvasGroup _illustrationCg;
    private string _imageRoot = null;
    private string _currentImageUrl = null;

    [Header("Background (optionnel)")]
    // RawImage plein écran pour afficher la RenderTexture vidéo fournie par le GameManager
    public RawImage videoBackgroundUI;

    [Header("UI References")]
    public GameObject dialoguePanel;
    public TextMeshProUGUI dialogueText;
    public TextMeshProUGUI speakerText;
    public TextMeshProUGUI skipInstructionText;
    public Image backgroundImage; // voile noir (dimming)
    public Image overlayImage;    // fallback si tu n’utilises pas illustrationImage

    // Bandeau bas (auto-créé) — références pour appliquer la config
    private RectTransform bottomBarRect;
    private Image bottomBarBg;
    private RectTransform speakerRectRt, dialogueRectRt, instructionsRectRt;

    [Header("Auto-Creation Settings")]
    public bool autoCreateUI = true;

    // États et configuration
    private DialogueSequence currentDialogue;
    private DialogueConfig currentConfig;
    private int currentLineIndex = 0;
    private bool isPlaying = false;
    private bool isWaitingForInput = false;
    private Coroutine dialogueCoroutine;

    // Callbacks
    public Action OnDialogueComplete;
    public Action OnDialogueSkipped;
    public Action<DialogueLine> OnLineDisplayed;

    // Configuration par défaut
    private readonly DialogueConfig defaultConfig = new DialogueConfig();

    // --- Vidéo interne du dialogue ---
    private VideoPlayer _bgVideoPlayer;
    private RenderTexture _bgRT;
    private string _videoRoot = null;   // peut venir du JSON, sinon reste null

    // Appelé si tu veux forcer un root vidéo global depuis l’extérieur
    public void SetVideoRoot(string root) { _videoRoot = root; }

    private Coroutine _overlayLoadCo = null;

// Tente de retrouver les éléments du bas si l'UI n'a pas été auto-créée par le script
private void EnsureBottomBarRefs()
{
    if (dialoguePanel == null) return;

    if (bottomBarRect == null)
    {
        var bar = dialoguePanel.transform.Find("BottomBar");
        if (bar != null) bottomBarRect = bar.GetComponent<RectTransform>();
        if (bar != null) bottomBarBg   = bar.GetComponent<Image>();
    }

    if (speakerRectRt == null)
    {
        var t = dialoguePanel.transform.Find("BottomBar/SpeakerText");
        if (t != null) speakerRectRt = t.GetComponent<RectTransform>();
        if (speakerText == null && t != null) speakerText = t.GetComponent<TextMeshProUGUI>();
    }

    if (dialogueRectRt == null)
    {
        var t = dialoguePanel.transform.Find("BottomBar/DialogueText");
        if (t != null) dialogueRectRt = t.GetComponent<RectTransform>();
        if (dialogueText == null && t != null) dialogueText = t.GetComponent<TextMeshProUGUI>();
    }

    if (instructionsRectRt == null)
    {
        var t = dialoguePanel.transform.Find("BottomBar/Instructions");
        if (t != null) instructionsRectRt = t.GetComponent<RectTransform>();
        if (skipInstructionText == null && t != null) skipInstructionText = t.GetComponent<TextMeshProUGUI>();
    }
}


   

    private static bool IsAbsoluteUrl(string u)
{
    if (string.IsNullOrEmpty(u)) return false;
    return Uri.TryCreate(u, UriKind.Absolute, out _);
}

    // ─────────────────────────────────────────────────────────────────────────
    // Lifecycle
    // ─────────────────────────────────────────────────────────────────────────
    void Awake()
    {
        if (autoCreateUI && dialoguePanel == null)
            CreateDialogueUI();

        if (illustrationImage != null)
        {
            _illustrationCg = illustrationImage.GetComponent<CanvasGroup>();
            if (_illustrationCg == null) _illustrationCg = illustrationImage.gameObject.AddComponent<CanvasGroup>();
            _illustrationCg.alpha = 0f;
            illustrationImage.enabled = false;
        }

        if (overlayImage != null)
        {
            overlayImage.sprite = null;
            overlayImage.enabled = false;            // <- pas de quad blanc
            overlayImage.gameObject.SetActive(false);
        }


        if (videoBackgroundUI != null)
        {
            videoBackgroundUI.texture = null;
            videoBackgroundUI.gameObject.SetActive(false);
            videoBackgroundUI.enabled = false;
        }

        DisableEmptyUIRenderers();


    }

    void Start()
    {
        if (dialoguePanel != null)
            dialoguePanel.SetActive(false);

    }

    void Update() => HandleInput();

    // ─────────────────────────────────────────────────────────────────────────
    // Media roots / Vidéo UI
    // ─────────────────────────────────────────────────────────────────────────
    public void SetMediaRoots(string imageRoot) => _imageRoot = imageRoot;

    private void EnsureBackgroundUI()
    {
        if (dialoguePanel == null || videoBackgroundUI != null) return;

        var go = new GameObject("DialogueVideoBG");
        go.transform.SetParent(dialoguePanel.transform, false);

        var rt = go.AddComponent<RectTransform>();
        rt.anchorMin = Vector2.zero;
        rt.anchorMax = Vector2.one;
        rt.offsetMin = Vector2.zero;
        rt.offsetMax = Vector2.zero;

        videoBackgroundUI = go.AddComponent<RawImage>();
        videoBackgroundUI.color = Color.white;
        videoBackgroundUI.raycastTarget = false;

        // Derrière tout le reste
        go.transform.SetAsFirstSibling();
        videoBackgroundUI.gameObject.SetActive(false);
    }

    /// <summary>Appelé par le GameManager pour afficher la vidéo de fond (RenderTexture).</summary>
    public void SetDialogueBackgroundTexture(Texture tex)
    {
        EnsureBackgroundUI();
        if (videoBackgroundUI == null) return;

        videoBackgroundUI.texture = tex;
        var c = videoBackgroundUI.color; c.a = 1f; videoBackgroundUI.color = c;
        videoBackgroundUI.gameObject.SetActive(true);
        videoBackgroundUI.transform.SetAsFirstSibling();
    }

public void ClearDialogueBackgroundTexture()
{
    if (videoBackgroundUI == null) return;
    videoBackgroundUI.texture = null;
    // 🔒 Failsafe anti-cadre-blanc
    videoBackgroundUI.gameObject.SetActive(false);
    videoBackgroundUI.enabled = false;
}

    // ─────────────────────────────────────────────────────────────────────────
    // Utils illustration
    // ─────────────────────────────────────────────────────────────────────────
    private string CombineUrl(string root, string leaf)
    {
        if (string.IsNullOrEmpty(leaf)) return null;
        if (string.IsNullOrEmpty(root)) return leaf;
        return root.EndsWith("/") ? (root + leaf) : (root + "/" + leaf);
    }

    private IEnumerator ShowLineIllustration(DialogueLine line)
    {
        if (illustrationImage == null)
            yield break;

        if (line == null || string.IsNullOrEmpty(line.image))
        {
            // pas d'image : fade out
            if (_illustrationCg != null)
            {
                float t = 0f;
                float d = Mathf.Max(0.01f, illustrationFadeDuration);
                float from = _illustrationCg.alpha, to = 0f;
                while (t < d) { t += Time.deltaTime; _illustrationCg.alpha = Mathf.Lerp(from, to, t / d); yield return null; }
                _illustrationCg.alpha = 0f;
            }
            yield break;
        }

        var url = CombineUrl(_imageRoot, line.image);
        if (string.IsNullOrEmpty(url))
            yield break;

        // même image -> juste fade in si nécessaire
        if (url == _currentImageUrl && illustrationImage.sprite != null)
        {
            if (_illustrationCg != null && _illustrationCg.alpha < 1f)
            {
                float t = 0f; float d = Mathf.Max(0.01f, illustrationFadeDuration);
                float from = _illustrationCg.alpha, to = 1f;
                while (t < d) { t += Time.deltaTime; _illustrationCg.alpha = Mathf.Lerp(from, to, t / d); yield return null; }
                _illustrationCg.alpha = 1f;
            }
            yield break;
        }

        using (var req = UnityWebRequestTexture.GetTexture(url))
        {
            yield return req.SendWebRequest();
#if UNITY_2020_3_OR_NEWER
            if (req.result != UnityWebRequest.Result.Success)
#else
            if (req.isNetworkError || req.isHttpError)
#endif
            {
                Debug.LogWarning("[Dialogue] Load image failed: " + url + " -> " + req.error);
                yield break;
            }

            var tex = DownloadHandlerTexture.GetContent(req);
            var sp = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
            illustrationImage.sprite = sp;
            _currentImageUrl = url;

            if (_illustrationCg != null)
            {
                float t = 0f; float d = Mathf.Max(0.01f, illustrationFadeDuration);
                float from = _illustrationCg.alpha, to = 1f;
                while (t < d) { t += Time.deltaTime; _illustrationCg.alpha = Mathf.Lerp(from, to, t / d); yield return null; }
                _illustrationCg.alpha = 1f;
            }
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // API publique
    // ─────────────────────────────────────────────────────────────────────────
public void PlayDialogue(DialogueSequence dialogue, DialogueConfig config = null)
{
    if (dialogue == null) { Debug.LogError("DialoguePlayer: Dialogue sequence is null!"); return; }
    if (dialogue.lines == null || dialogue.lines.Count == 0) { Debug.LogError("DialoguePlayer: Dialogue has no lines!"); return; }

    if (isPlaying) StopDialogue();

    currentDialogue = dialogue;
    currentConfig   = config ?? defaultConfig;
    currentLineIndex = 0;
    isPlaying = true;

    // —> visible au-dessus TOUT DE SUITE (au cas où la fade serait freinée par timescale)
    ForceShowPanel();

    Debug.Log($"[DialoguePlayer] Starting dialogue '{dialogue.title}' with {dialogue.lines.Count} lines");
    Debug.Log($"[DialoguePlayer] Config: requireInputToAdvance={currentConfig.requireInputToAdvance}, nextKey={currentConfig.nextKey}, prevKey={currentConfig.prevKey}, skip={currentConfig.skipDialogueKey}");

    SetupDialogueUI();
    dialogueCoroutine = StartCoroutine(PlayDialogueSequence());
}


public void StopDialogue()
{
    if (dialogueCoroutine != null)
    {
        StopCoroutine(dialogueCoroutine);
        dialogueCoroutine = null;
    }

    isPlaying = false;
    isWaitingForInput = false;

    // 🔧 coupe le panel
    if (dialoguePanel != null)
        dialoguePanel.SetActive(false);

    // 🔧 coupe la vidéo de fond (évite le RawImage blanc)
    StopBackgroundVideo();

    // 🔧 coupe toute image résiduelle
    if (overlayImage != null)
    {
        overlayImage.sprite = null;
        overlayImage.enabled = false;
        overlayImage.gameObject.SetActive(false);
    }
    if (illustrationImage != null)
    {
        illustrationImage.enabled = false;
        if (_illustrationCg != null) _illustrationCg.alpha = 0f;
    }

    Debug.Log("DialoguePlayer: Dialogue stopped");
    HardCleanupVisuals();
}

    public bool IsPlayingDialogue() => isPlaying;

    public void NextLine()
    {
        if (!isPlaying || !isWaitingForInput) return;
        isWaitingForInput = false;
        currentLineIndex++;
    }

    public void PrevLine()
    {
        if (!isPlaying || !isWaitingForInput) return;
        isWaitingForInput = false;
        currentLineIndex = Mathf.Max(0, currentLineIndex - 1);
    }

    public void SkipDialogue()
    {
        if (!isPlaying) return;
        Debug.Log("DialoguePlayer: Dialogue skipped by user");
        StopDialogue();
        OnDialogueSkipped?.Invoke();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Coroutine principale (lecture)
    // ─────────────────────────────────────────────────────────────────────────
    IEnumerator PlayDialogueSequence()
    {
        // Apparition
        yield return StartCoroutine(FadeInDialogue());

        if (currentDialogue == null || currentDialogue.lines == null || currentDialogue.lines.Count == 0)
        {
            isPlaying = false;
            OnDialogueComplete?.Invoke();
            yield break;
        }

        // Boucle de lecture
        while (currentLineIndex >= 0 && currentLineIndex < currentDialogue.lines.Count)
        {
            DialogueLine line = currentDialogue.lines[currentLineIndex];

            // Délai avant affichage
            if (line != null && line.delay > 0f)
                yield return new WaitForSeconds(line.delay);

            // Speaker par défaut si vide
            if (line != null && string.IsNullOrEmpty(line.speaker) && !string.IsNullOrEmpty(currentDialogue.speaker))
                line.speaker = currentDialogue.speaker;

            // Illustration (si dispo)
            if (line != null)
                yield return StartCoroutine(ShowLineIllustration(line));

            // Afficher
            DisplayLine(line);
            OnLineDisplayed?.Invoke(line);

            // Attente d’avancement
            isWaitingForInput = true;

            // ► Mode navigation 100% manuelle : on ignore toute durée
            if (currentConfig != null && currentConfig.requireInputToAdvance)
            {
                yield return new WaitUntil(() => !isWaitingForInput || !isPlaying);
            }
            else
            {
                // Auto-advance si durée ligne > 0 sinon durée config > 0, sinon input
                float autoAdvance = 0f;
                if (line != null)
                {
                    var f = line.GetType().GetField("duration");
                    if (f != null)
                    {
                        try { autoAdvance = (float)f.GetValue(line); } catch { }
                    }
                }
                if (autoAdvance <= 0f && currentConfig != null && currentConfig.autoAdvanceDelay > 0f)
                    autoAdvance = currentConfig.autoAdvanceDelay;

                if (autoAdvance > 0f)
                {
                    float elapsed = 0f;
                    while (elapsed < autoAdvance && isWaitingForInput && isPlaying)
                    {
                        elapsed += Time.deltaTime;
                        yield return null;
                    }
                    if (isWaitingForInput && isPlaying)
                        NextLine();
                }
                else
                {
                    yield return new WaitUntil(() => !isWaitingForInput || !isPlaying);
                }
            }

            if (!isPlaying) yield break; // Skip/Stop
        }

        // Fin
        yield return StartCoroutine(FadeOutDialogue());
        isPlaying = false;
        Debug.Log($"DialoguePlayer: Dialogue '{currentDialogue.title}' completed");
        OnDialogueComplete?.Invoke();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Affichage UI
    // ─────────────────────────────────────────────────────────────────────────
    void DisplayLine(DialogueLine line)
    {
        
        DisableEmptyUIRenderers();
        
        
        if (dialogueText != null)
            dialogueText.text = line?.text ?? "";

        if (speakerText != null)
        {
            string who = (!string.IsNullOrEmpty(line?.speaker)) ? line.speaker : "";
            speakerText.text = who;
            speakerText.gameObject.SetActive(!string.IsNullOrEmpty(who));
        }

        // --- Gestion des images ---
        // Si tu utilises une Image dédiée "illustrationImage", laisse l'overlay OFF.
        if (illustrationImage != null)
        {
            // ici tu peux gérer ton illustration si besoin ; sinon on s’assure que l’overlay est coupé
            if (overlayImage != null)
            {
                overlayImage.sprite = null;
                overlayImage.enabled = false;
                overlayImage.gameObject.SetActive(false);
            }
        }
        else if (overlayImage != null)
        {
            // Stoppe un chargement précédent (cas: navigation rapide)
            if (_overlayLoadCo != null)
            {
                StopCoroutine(_overlayLoadCo);
                _overlayLoadCo = null;
            }

            if (!string.IsNullOrEmpty(line?.image))
            {
                // charge l’image et n’allume l’overlay qu’en cas de succès
                _overlayLoadCo = StartCoroutine(LoadOverlayImage(line.image));
            }
            else
            {
                // pas d’image pour cette ligne -> coupe tout
                overlayImage.sprite = null;
                overlayImage.enabled = false;
                overlayImage.gameObject.SetActive(false);
            }
        }

        string preview = line?.text ?? "";
        if (preview.Length > 50) preview = preview.Substring(0, 50) + "...";
        Debug.Log($"DialoguePlayer: Displaying line - Speaker: '{line?.speaker ?? ""}', Text: '{preview}'");
    }

    IEnumerator LoadOverlayImage(string imageUrl)
    {
        string finalUrl = CombineUrl(_imageRoot, imageUrl);

        using (var www = UnityWebRequestTexture.GetTexture(finalUrl))
        {
            yield return www.SendWebRequest();

    #if UNITY_2020_3_OR_NEWER
            if (www.result == UnityWebRequest.Result.Success)
    #else
            if (!www.isNetworkError && !www.isHttpError)
    #endif
            {
                Texture2D tex = ((DownloadHandlerTexture)www.downloadHandler).texture;
                Sprite sprite = Sprite.Create(tex, new Rect(0,0,tex.width,tex.height), new Vector2(0.5f,0.5f));
                overlayImage.sprite = sprite;
                overlayImage.enabled = true;
                overlayImage.gameObject.SetActive(true);
            }
            else
            {
                Debug.LogError($"DialoguePlayer: Failed to load overlay image: {www.error} ({finalUrl})");
                overlayImage.sprite = null;
                overlayImage.enabled = false;
                overlayImage.gameObject.SetActive(false);
            }
        }

        _overlayLoadCo = null;
    }

    void SetupDialogueUI()
    {
        if (dialoguePanel == null) return;

        // 🔧 S'assure d'avoir les refs même si l'UI vient d'un prefab
        EnsureBottomBarRefs();

        // — textes
        if (dialogueText != null)
        {
            dialogueText.fontSize = currentConfig.dialogueTextSize;
            if (ColorUtility.TryParseHtmlString(currentConfig.dialogueTextColor, out Color textColor))
                dialogueText.color = textColor;
        }
        if (speakerText != null)
        {
            speakerText.fontSize = currentConfig.speakerTextSize;
            if (ColorUtility.TryParseHtmlString(currentConfig.speakerTextColor, out Color speakerColor))
                speakerText.color = speakerColor;
        }

        // — instructions
        if (skipInstructionText != null)
        {
            skipInstructionText.gameObject.SetActive(currentConfig.showSkipInstructions);
            if (currentConfig.showSkipInstructions)
            {
                skipInstructionText.text =
                    $"←/{currentConfig.prevKey} : Reculer   •   {currentConfig.nextKey} : Suivant   •   {currentConfig.skipDialogueKey} : Skip";
            }
        }

        // — bandeau bas (taille / couleur / padding)
        if (bottomBarRect != null)
        {
            float ratio = Mathf.Clamp01(currentConfig.bottomBarHeightRatio);
            bottomBarRect.anchorMin = new Vector2(0f, 0f);
            bottomBarRect.anchorMax = new Vector2(1f, ratio);
            bottomBarRect.offsetMin = Vector2.zero;
            bottomBarRect.offsetMax = Vector2.zero;
        }
        if (bottomBarBg != null)
        {
            if (ColorUtility.TryParseHtmlString(currentConfig.bottomBarColor, out var barCol))
                bottomBarBg.color = barCol;
        }

        float padL = currentConfig.paddingLeft;
        float padR = currentConfig.paddingRight;

        if (speakerRectRt != null)
        {
            speakerRectRt.offsetMin = new Vector2(padL, speakerRectRt.offsetMin.y);
            speakerRectRt.offsetMax = new Vector2(-padR, speakerRectRt.offsetMax.y);
        }
        if (dialogueRectRt != null)
        {
            dialogueRectRt.offsetMin = new Vector2(padL, dialogueRectRt.offsetMin.y);
            dialogueRectRt.offsetMax = new Vector2(-padR, dialogueRectRt.offsetMax.y);
        }
        if (instructionsRectRt != null)
        {
            instructionsRectRt.offsetMin = new Vector2(padL, instructionsRectRt.offsetMin.y);
            instructionsRectRt.offsetMax = new Vector2(-padR, instructionsRectRt.offsetMax.y);
        }

        // — voile : 1.0 tant que pas de vidéo pour ne PAS voir le jeu ; dimming quand la vidéo est là
        if (backgroundImage != null)
        {
            bool hasVideo = (videoBackgroundUI != null && videoBackgroundUI.texture != null && videoBackgroundUI.gameObject.activeInHierarchy);
            float targetA = hasVideo ? Mathf.Clamp01(currentConfig.backgroundDimming) : 1f;
            var bg = backgroundImage.color; bg.a = targetA; backgroundImage.color = bg;
        }

        Debug.Log("DialoguePlayer: UI configured (bottom bar + colors + padding).");
        Debug.Log($"[DialoguePlayer] Apply bottom bar: height={currentConfig.bottomBarHeightRatio}, color={currentConfig.bottomBarColor}, padL={currentConfig.paddingLeft}, padR={currentConfig.paddingRight}");
    }


    // méthode utilitaire à ajouter dans la classe
    public void ForceHideAllUI()
    {
        if (overlayImage != null)
        {
            overlayImage.sprite = null;
            overlayImage.enabled = false;
            overlayImage.gameObject.SetActive(false);
        }
        if (illustrationImage != null)
        {
            illustrationImage.enabled = false;
            if (_illustrationCg != null) _illustrationCg.alpha = 0f;
        }
        if (dialoguePanel != null)
            dialoguePanel.SetActive(false);

        // si tu utilises un RawImage vidéo
        if (videoBackgroundUI != null)
        {
            videoBackgroundUI.texture = null;
            videoBackgroundUI.gameObject.SetActive(false);
        }
    }



IEnumerator FadeInDialogue()
{
    if (dialoguePanel == null) yield break;

    dialoguePanel.SetActive(true);
    var cg = dialoguePanel.GetComponent<CanvasGroup>();
    if (cg == null) cg = dialoguePanel.AddComponent<CanvasGroup>();
    cg.alpha = 0f;

    float dur = (currentConfig != null) ? currentConfig.fadeTransitionDuration : 0.35f;
    float t = 0f;
    while (t < dur)
    {
        t += Time.unscaledDeltaTime;
        cg.alpha = Mathf.Lerp(0f, 1f, Mathf.Clamp01(t / dur));
        yield return null;
    }
    cg.alpha = 1f;
}

IEnumerator FadeOutDialogue()
{
    if (dialoguePanel == null) yield break;

    var cg = dialoguePanel.GetComponent<CanvasGroup>();
    if (cg == null) cg = dialoguePanel.AddComponent<CanvasGroup>();
    cg.alpha = 1f;

    float dur = (currentConfig != null) ? currentConfig.fadeTransitionDuration : 0.35f;
    float t = 0f;
    while (t < dur)
    {
        t += Time.unscaledDeltaTime;
        cg.alpha = Mathf.Lerp(1f, 0f, Mathf.Clamp01(t / dur));
        yield return null;
    }
    cg.alpha = 0f;

    // éteint l'UI du dialogue
    dialoguePanel.SetActive(false);

    // 🔧 coupe la vidéo et son RawImage pour éviter le "cadre blanc"
    StopBackgroundVideo();

    // nettoie les images d'overlay/illustration
    if (overlayImage != null)
    {
        overlayImage.sprite = null;
        overlayImage.enabled = false;
        overlayImage.gameObject.SetActive(false);
    }
    if (illustrationImage != null)
    {
        illustrationImage.enabled = false;
        if (_illustrationCg != null) _illustrationCg.alpha = 0f;
    }

    HardCleanupVisuals();
}


    void CreateDialogueUI()
    {
        // robuste pour toutes versions Unity
        Canvas canvas =
        #if UNITY_2023_1_OR_NEWER
            FindFirstObjectByType<Canvas>();
        #else
            FindObjectOfType<Canvas>();
        #endif

        if (canvas == null)
        {
            Debug.LogError("DialoguePlayer: No Canvas found for auto-creation");
            return;
        }

        // Panel plein écran
        GameObject panel = new GameObject("DialoguePanel");
        panel.transform.SetParent(canvas.transform, false);

        RectTransform panelRect = panel.AddComponent<RectTransform>();
        panelRect.anchorMin = Vector2.zero;
        panelRect.anchorMax = Vector2.one;
        panelRect.offsetMin = Vector2.zero;
        panelRect.offsetMax = Vector2.zero;

        CanvasGroup canvasGroup = panel.AddComponent<CanvasGroup>();

        // Fond (alpha appliqué dans SetupDialogueUI)
        Image bgImage = panel.AddComponent<Image>();
        bgImage.color = new Color(0, 0, 0, 0f);
        bgImage.raycastTarget = false;
        
        // CORRECTION MAC : Sur Mac, forcer l'invisibilité complète
        #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
        bgImage.enabled = false;
        Debug.Log("[DialoguePlayer] MAC DETECTED - Fond du dialogue désactivé pour éviter le voile blanc");
        #endif
        
        backgroundImage = bgImage;

       
        // Canvas overlay
        Canvas dialogueCanvas = panel.AddComponent<Canvas>();
        dialogueCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
        dialogueCanvas.overrideSorting = true;
        dialogueCanvas.sortingOrder = 50000; // très haut

        // ===== Bande du bas =====
        GameObject textArea = new GameObject("BottomBar");
        textArea.transform.SetParent(panel.transform, false);

        bottomBarRect = textArea.AddComponent<RectTransform>();
        bottomBarRect.anchorMin = new Vector2(0f, 0f);
        bottomBarRect.anchorMax = new Vector2(1f, 0.32f); // valeur par défaut, surchargée dans SetupDialogueUI()
        bottomBarRect.offsetMin = Vector2.zero;
        bottomBarRect.offsetMax = Vector2.zero;

        bottomBarBg = textArea.AddComponent<Image>();
        bottomBarBg.color = new Color(0, 0, 0, 0.6f);

        // Speaker
        GameObject speakerObj = new GameObject("SpeakerText");
        speakerObj.transform.SetParent(textArea.transform, false);

        speakerRectRt = speakerObj.AddComponent<RectTransform>();
        speakerRectRt.anchorMin = new Vector2(0, 0.66f);
        speakerRectRt.anchorMax = new Vector2(1, 0.98f);
        speakerRectRt.offsetMin = new Vector2(24, 0);
        speakerRectRt.offsetMax = new Vector2(-24, 0);

        speakerText = speakerObj.AddComponent<TextMeshProUGUI>();
        speakerText.text = "";
        speakerText.fontSize = 32f;
        speakerText.color = Color.yellow;
        speakerText.fontStyle = FontStyles.Bold;
        speakerText.alignment = TextAlignmentOptions.Left;

        // Texte principal
        GameObject dialogueObj = new GameObject("DialogueText");
        dialogueObj.transform.SetParent(textArea.transform, false);

        dialogueRectRt = dialogueObj.AddComponent<RectTransform>();
        dialogueRectRt.anchorMin = new Vector2(0, 0.18f);
        dialogueRectRt.anchorMax = new Vector2(1, 0.66f);
        dialogueRectRt.offsetMin = new Vector2(24, 0);
        dialogueRectRt.offsetMax = new Vector2(-24, 0);

        dialogueText = dialogueObj.AddComponent<TextMeshProUGUI>();
        dialogueText.text = "";
        dialogueText.fontSize = 28f;
        dialogueText.color = Color.white;
        dialogueText.alignment = TextAlignmentOptions.TopLeft;

        // Instructions
        GameObject skipObj = new GameObject("Instructions");
        skipObj.transform.SetParent(textArea.transform, false);

        instructionsRectRt = skipObj.AddComponent<RectTransform>();
        instructionsRectRt.anchorMin = new Vector2(0, 0.0f);
        instructionsRectRt.anchorMax = new Vector2(1, 0.18f);
        instructionsRectRt.offsetMin = new Vector2(24, 0);
        instructionsRectRt.offsetMax = new Vector2(-24, 0);

        skipInstructionText = skipObj.AddComponent<TextMeshProUGUI>();
        skipInstructionText.text = "←/Backspace : Reculer   •   Espace : Suivant   •   Échap : Skip";
        skipInstructionText.fontSize = 20f;
        skipInstructionText.color = new Color(1, 1, 1, 0.85f);
        skipInstructionText.alignment = TextAlignmentOptions.BottomRight;

        dialoguePanel = panel;
        dialoguePanel.SetActive(false);


        // Crée le RawImage plein écran pour la vidéo du dialogue (derrière la bande)
        EnsureBackgroundUI();

        Debug.Log("DialoguePlayer: UI auto-created successfully");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Lecture & jeu d'un DialogueSequence depuis une URL (JSON au format DialogueWrapper)
    // ─────────────────────────────────────────────────────────────────────────
public void PlayDialogueFromUrl(string url, DialogueConfig overrideConfig = null, Action onCompleted = null)
{
    Debug.Log($"[DialoguePlayer] Fetching dialogue JSON: {url}");
    StartCoroutine(CoPlayDialogueFromUrl(url, overrideConfig, onCompleted));
}

private IEnumerator CoPlayDialogueFromUrl(string url, DialogueConfig overrideConfig, Action onCompleted)
{
    if (string.IsNullOrEmpty(url))
    {
        Debug.LogWarning("DialoguePlayer: URL vide");
        onCompleted?.Invoke();
        yield break;
    }

    DialogueSequence seq = null;
    string imgRoot = null;
    string vRoot   = null;
    DialogueConfig cfgFromJson = null;

    using (var www = UnityWebRequest.Get(url))
    {
        www.SetRequestHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        www.SetRequestHeader("Pragma", "no-cache");
        www.SetRequestHeader("Expires", "0");

        yield return www.SendWebRequest();

#if UNITY_2020_3_OR_NEWER
        if (www.result != UnityWebRequest.Result.Success)
#else
        if (www.isNetworkError || www.isHttpError)
#endif
        {
            Debug.LogError($"DialoguePlayer: GET {url} failed -> {www.error}");
            onCompleted?.Invoke();
            yield break;
        }

        string json = www.downloadHandler.text;
        try
        {
            var wrap = JsonUtility.FromJson<DialogueWrapper>(json);
            seq         = wrap != null ? wrap.dialogue       : null;
            imgRoot     = wrap != null ? wrap.imageRoot      : null;
            vRoot       = wrap != null ? wrap.videoRoot      : null;
            cfgFromJson = wrap != null ? wrap.dialogueConfig : null;

            Debug.Log($"[DialoguePlayer] JSON parsed → title='{seq?.title}', lines={(seq?.lines?.Count ?? 0)}, vRoot='{vRoot}', imgRoot='{imgRoot}', hasCfg={(cfgFromJson!=null)}");
        }
        catch (Exception e)
        {
            Debug.LogError($"DialoguePlayer: JSON parse error -> {e.Message}");
        }
    }

    // Appliquer les roots si fournis par le JSON
    if (!string.IsNullOrEmpty(imgRoot))
        SetMediaRoots(imgRoot);
    if (!string.IsNullOrEmpty(vRoot))
        _videoRoot = vRoot; // prioriser le root du JSON si présent

    if (seq == null || seq.lines == null || seq.lines.Count == 0)
    {
        Debug.LogWarning($"DialoguePlayer: dialogue vide depuis {url}");
        onCompleted?.Invoke();
        yield break;
    }

    // Construire une URL vidéo absolue si possible
    if (!string.IsNullOrEmpty(seq.video))
    {
        string fullVideoUrl = seq.video;

        if (!IsAbsoluteUrl(fullVideoUrl) && !string.IsNullOrEmpty(_videoRoot))
            fullVideoUrl = CombineUrl(_videoRoot, fullVideoUrl);

        if (!IsAbsoluteUrl(fullVideoUrl))
        {
            Debug.LogWarning($"[DialoguePlayer] Aucune URL vidéo absolue construite (video='{seq.video}', videoRoot='{_videoRoot}'). On ignore la vidéo.");
            StopBackgroundVideo();
        }
        else
        {
            Debug.Log($"[DialoguePlayer] Playing background video: {fullVideoUrl}");
            yield return StartCoroutine(PlayBackgroundVideo(fullVideoUrl));
        }
    }
    else
    {
        StopBackgroundVideo();
    }

    // (Optionnel) amener l'UI au premier plan si la méthode existe
    try { BringToFront(50000); } catch {}

    // Brancher les callbacks de fin/skip pour libérer l’appelant
    Action oldComp = OnDialogueComplete;
    Action oldSkip = OnDialogueSkipped;

    OnDialogueComplete += () =>
    {
        onCompleted?.Invoke();
        OnDialogueComplete = oldComp;
        OnDialogueSkipped  = oldSkip;
    };
    OnDialogueSkipped += () =>
    {
        onCompleted?.Invoke();
        OnDialogueComplete = oldComp;
        OnDialogueSkipped  = oldSkip;
    };

    // ⚠️ Priorité à la config GLOBALE (overrideConfig), sinon on prend celle du JSON
    PlayDialogue(seq, overrideConfig ?? cfgFromJson);
    yield break;
}







private IEnumerator PlayBackgroundVideo(string fullUrl)
{
    EnsureBackgroundUI();

    if (_bgVideoPlayer == null)
    {
        var go = new GameObject("DialogueVideoPlayer");
        go.transform.SetParent(this.transform, false);
        _bgVideoPlayer = go.AddComponent<VideoPlayer>();
        _bgVideoPlayer.playOnAwake = false;
        _bgVideoPlayer.isLooping = true;
        _bgVideoPlayer.renderMode = VideoRenderMode.RenderTexture;
        _bgVideoPlayer.audioOutputMode = VideoAudioOutputMode.None;
    }

    // RT (recréée si taille écran a changé)
    int w = Mathf.Max(8, Screen.width);
    int h = Mathf.Max(8, Screen.height);
    if (_bgRT == null || _bgRT.width != w || _bgRT.height != h)
    {
        if (_bgRT != null) _bgRT.Release();
        _bgRT = new RenderTexture(w, h, 0, RenderTextureFormat.ARGB32) { name = "Dialogue_RT" };
        _bgRT.Create();
    }

    _bgVideoPlayer.targetTexture = _bgRT;

    // Affichage RawImage
    if (videoBackgroundUI == null) EnsureBackgroundUI();
    if (videoBackgroundUI != null)
    {
        videoBackgroundUI.texture = _bgRT;
        videoBackgroundUI.transform.SetAsFirstSibling();
        videoBackgroundUI.gameObject.SetActive(true);

    }

    // Tant que la vidéo n’est pas prête, on masque le jeu par un noir opaque
    if (backgroundImage != null)
    {
        var c = backgroundImage.color; c.a = 1f; backgroundImage.color = c;
    }

    _bgVideoPlayer.url = fullUrl;
    _bgVideoPlayer.Prepare();
    while (!_bgVideoPlayer.isPrepared) yield return null;

    _bgVideoPlayer.Play();

    // Vidéo prête → appliquer le dimming voulu
    if (backgroundImage != null)
    {
        var c = backgroundImage.color;
        c.a = Mathf.Clamp01(currentConfig != null ? currentConfig.backgroundDimming : 0.25f);
        backgroundImage.color = c;
    }
}

private void StopBackgroundVideo()
{
    if (_bgVideoPlayer != null) _bgVideoPlayer.Stop();

    if (videoBackgroundUI != null)
    {
        videoBackgroundUI.texture = null;
        // 🔒 Failsafe anti-cadre-blanc : RawImage désactivé s'il n'a pas de texture
        videoBackgroundUI.gameObject.SetActive(false);
        videoBackgroundUI.enabled = false;
    }
}




    // ─────────────────────────────────────────────────────────────────────────
    // Input helpers
    // ─────────────────────────────────────────────────────────────────────────
    private string SkipKeyName()
    {
        if (currentConfig != null && !string.IsNullOrEmpty(currentConfig.skipDialogueKey))
            return currentConfig.skipDialogueKey;
        return "Escape";
    }

    // Helper d'input UNIQUE (New Input System + fallback legacy)
    private bool GetKeyPressedThisFrame(string keyName)
    {
        if (string.IsNullOrEmpty(keyName)) return false;

    #if ENABLE_INPUT_SYSTEM
        // New Input System
        if (Keyboard.current == null) return false;

        // Aliases usuels
        if (keyName.Equals("Enter", StringComparison.OrdinalIgnoreCase) ||
            keyName.Equals("Return", StringComparison.OrdinalIgnoreCase))
            return Keyboard.current.enterKey.wasPressedThisFrame
                || Keyboard.current.numpadEnterKey.wasPressedThisFrame;

        if (keyName.Equals("Space", StringComparison.OrdinalIgnoreCase))
            return Keyboard.current.spaceKey.wasPressedThisFrame;

        if (keyName.Equals("Backspace", StringComparison.OrdinalIgnoreCase))
            return Keyboard.current.backspaceKey.wasPressedThisFrame;

        // Tentative générique via l’enum Key
        if (Enum.TryParse<Key>(keyName, true, out var k))
        {
            var ctrl = Keyboard.current[k];
            return (ctrl != null) && ctrl.wasPressedThisFrame;
        }
        return false;
    #else
        // Legacy Input Manager
        if (keyName.Equals("Enter", StringComparison.OrdinalIgnoreCase))
            keyName = "Return";

        if (!Enum.TryParse<KeyCode>(keyName, true, out var kc))
            return false;

        return Input.GetKeyDown(kc);
    #endif
    }

    private bool GetSkipPressedThisFrame() => GetKeyPressedThisFrame(SkipKeyName());
    private bool GetNextPressedThisFrame() => GetKeyPressedThisFrame(currentConfig?.nextKey ?? "Space");
    private bool GetPrevPressedThisFrame() => GetKeyPressedThisFrame(currentConfig?.prevKey ?? "Backspace");

    void HandleInput()
    {
        if (!isPlaying) return;

        // Skip
        if (GetKeyPressedThisFrame(currentConfig?.skipDialogueKey ?? "Escape"))
        {
            if (isWaitingForInput) NextLine();
            else SkipDialogue();
            return;
        }

        // Navigation
        bool next = GetKeyPressedThisFrame(currentConfig?.nextKey ?? "Space");
        bool prev = GetKeyPressedThisFrame(currentConfig?.prevKey ?? "Backspace");

        // Click / Touch = Next
    #if ENABLE_INPUT_SYSTEM
        if (!next)
        {
            if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) next = true;
            if (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.wasPressedThisFrame) next = true;
        }
    #else
        if (!next && Input.GetMouseButtonDown(0)) next = true;
    #endif

        if (isWaitingForInput)
        {
            if (next)      NextLine();
            else if (prev) PrevLine();
        }
    }


public void BringToFront(int order = 50000)
{
    if (dialoguePanel == null) return;

    var cnv = dialoguePanel.GetComponent<Canvas>();
    if (cnv == null) cnv = dialoguePanel.AddComponent<Canvas>();
    cnv.renderMode = RenderMode.ScreenSpaceOverlay; // toujours au-dessus
    cnv.overrideSorting = true;
    cnv.sortingOrder = order;

    dialoguePanel.transform.SetAsLastSibling();
}

private void ForceShowPanel()
{
    if (dialoguePanel == null) return;

    var cg = dialoguePanel.GetComponent<CanvasGroup>();
    if (cg == null) cg = dialoguePanel.AddComponent<CanvasGroup>();
    dialoguePanel.SetActive(true);
    cg.alpha = 1f;

    BringToFront(50000);
    Debug.Log($"[DialoguePlayer] ForceShowPanel: active={dialoguePanel.activeInHierarchy}, sort={dialoguePanel.GetComponent<Canvas>()?.sortingOrder}");
}




// Coupe tout rendu "vide" susceptible d'apparaître blanc
private void DisableEmptyUIRenderers()
{
    // 1) RawImage (vidéo)
    if (videoBackgroundUI != null)
    {
        bool hasTex = videoBackgroundUI.texture != null;
        videoBackgroundUI.enabled = hasTex;
        videoBackgroundUI.gameObject.SetActive(hasTex);
        if (!hasTex)
        {
            // met la couleur totalement transparente pour éviter toute coloration blanche résiduelle
            var c = videoBackgroundUI.color; c.a = 0f; videoBackgroundUI.color = c;
        }
        videoBackgroundUI.raycastTarget = false;
    }

    // 2) Overlay (Image)
    if (overlayImage != null)
    {
        bool hasSprite = overlayImage.sprite != null;
        overlayImage.enabled = hasSprite;
        overlayImage.gameObject.SetActive(hasSprite);
        if (!hasSprite)
        {
            var c = overlayImage.color; c.a = 0f; overlayImage.color = c;
        }
        overlayImage.raycastTarget = false;
    }

    // 3) Illustration (Image)
    if (illustrationImage != null)
    {
        bool hasSprite = illustrationImage.sprite != null;
        illustrationImage.enabled = hasSprite;
        if (_illustrationCg != null) _illustrationCg.alpha = hasSprite ? 1f : 0f;
        illustrationImage.raycastTarget = false;
    }
}

// Appelle ceci juste avant d'afficher une ligne, et aussi quand tu quittes le dialogue
private void HardCleanupVisuals()
{
    // Nettoie l'état interne puis coupe tout
    if (overlayImage != null) { overlayImage.sprite = null; }
    if (illustrationImage != null) { illustrationImage.sprite = null; }
    if (videoBackgroundUI != null) { videoBackgroundUI.texture = null; }

    DisableEmptyUIRenderers();
}




public void ForceCleanupAndHide()
{
    // Arrêter toute vidéo
    if (_bgVideoPlayer != null)
    {
        _bgVideoPlayer.Stop();
        if (_bgVideoPlayer.gameObject != null)
            _bgVideoPlayer.gameObject.SetActive(false);
    }
    
    // Nettoyer la RenderTexture
    if (_bgRT != null)
    {
        _bgRT.Release();
        _bgRT = null;
    }
    
    // Forcer le RawImage vidéo à être complètement inactif
    if (videoBackgroundUI != null)
    {
        videoBackgroundUI.texture = null;
        videoBackgroundUI.enabled = false;
        videoBackgroundUI.gameObject.SetActive(false);
        var c = videoBackgroundUI.color; c.a = 0f; videoBackgroundUI.color = c;
    }
    
    // Nettoyer les images
    if (overlayImage != null)
    {
        overlayImage.sprite = null;
        overlayImage.enabled = false;
        overlayImage.gameObject.SetActive(false);
    }
    
    if (illustrationImage != null)
    {
        illustrationImage.sprite = null;
        illustrationImage.enabled = false;
        if (_illustrationCg != null) _illustrationCg.alpha = 0f;
    }
    
    // Désactiver complètement le panel
    if (dialoguePanel != null)
    {
        var canvas = dialoguePanel.GetComponent<Canvas>();
        if (canvas != null) canvas.sortingOrder = -1;
        dialoguePanel.SetActive(false);
    }
    
    // Réinitialiser l'état
    isPlaying = false;
    isWaitingForInput = false;
}




}
