using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using TMPro;

/// <summary>
/// GAME 1303 Tier 6 — Plays a DialogueData conversation: typewriter text, speaker name,
/// portrait, and choice buttons. One of these in the scene serves every conversation.
///
/// SETUP
///  1. Canvas -> a "DialoguePanel" (a box at the bottom of the screen) containing:
///       - TMP text for the line          -> Line Text
///       - TMP text for the speaker name  -> Speaker Text
///       - an Image for the portrait      -> Portrait Image
///       - a "ContinueIndicator" (a small blinking arrow) -> Continue Indicator
///       - an empty "ChoiceContainer" with a Vertical Layout Group -> Choice Container
///     Add this script to the panel's parent (or anywhere) and drag the panel in.
///  2. Choice Button Prefab: a UI Button with a TMP text child.
///  3. Start a conversation from any Interactable: OnInteract -> DialogueRunner.StartDialogue,
///     then drag the DialogueData asset into the event's object slot.
///  4. Advance: the player's InteractionPrompt forwards the Interact key here
///     automatically. Pressing it while text is still typing shows the whole line at once
///     (a small thing that players notice immediately).
///  5. Pause Player: the runner sets PlayerController2D / TopDownMover InputEnabled off
///     while a conversation with Pause Player runs.
///
/// Flags: lines and choices read and write Tier 4's ChoiceFlagManager, so dialogue and
/// world state share one memory.
/// </summary>
public class DialogueRunner : MonoBehaviour
{
    public static DialogueRunner Active { get; private set; }
    public static bool IsAnyOpen => Active != null && Active.IsOpen;

    [Header("UI")]
    public GameObject panel;
    public TMP_Text lineText;
    public TMP_Text speakerText;
    public Image portraitImage;
    public GameObject continueIndicator;
    public Transform choiceContainer;
    public Button choiceButtonPrefab;

    [Header("Typewriter")]
    public float charactersPerSecond = 40f;
    public bool typewriter = true;
    [Tooltip("Blip sound played every few characters. Leave blank for silence.")]
    public string typeSfx = "";
    public int blipEveryNChars = 3;

    [Header("Events")]
    public UnityEvent OnDialogueStarted;
    public UnityEvent OnDialogueEnded;
    public UnityEvent<string> OnLineShown;   // the line's text

    public bool IsOpen { get; private set; }

    private DialogueData data;
    private int index;
    private bool typing;
    private bool awaitingChoice;
    private int pendingJump = -1;
    private Coroutine typeRoutine;
    private readonly List<Button> spawnedChoices = new List<Button>();
    private MonoBehaviour playerMover;
    private static readonly HashSet<DialogueData> played = new HashSet<DialogueData>();

    void Awake()
    {
        if (panel != null) panel.SetActive(false);
    }

    /// <summary>Wire an Interactable's OnInteract here and drag in the DialogueData asset.</summary>
    public void StartDialogue(DialogueData dialogue)
    {
        if (dialogue == null || dialogue.lines.Count == 0) return;
        if (dialogue.oneShot && played.Contains(dialogue)) return;
        if (IsOpen) return;

        data = dialogue;
        played.Add(dialogue);
        Active = this;
        IsOpen = true;
        index = -1;
        pendingJump = -1;

        if (panel != null) panel.SetActive(true);
        if (data.pausePlayer) SetPlayerFrozen(true);

        OnDialogueStarted?.Invoke();
        Advance();
    }

    /// <summary>Next line, or finish the typewriter if it's still running.</summary>
    public void Advance()
    {
        if (!IsOpen || awaitingChoice) return;

        if (typing)   // skip the typewriter
        {
            if (typeRoutine != null) StopCoroutine(typeRoutine);
            typing = false;
            lineText.text = CurrentLine != null ? CurrentLine.text : lineText.text;
            ShowContinueIndicator(true);
            MaybeShowChoices();
            return;
        }

        // Where do we go? A pending jump (from a choice) wins, then the line's own
        // goToLine, otherwise just the next line down the list.
        int next;
        if (pendingJump >= 0) { next = pendingJump; pendingJump = -1; }
        else if (index >= 0 && index < data.lines.Count && data.lines[index].goToLine >= 0)
            next = data.lines[index].goToLine;
        else next = index + 1;

        // Skip any line whose flag test fails
        while (next < data.lines.Count && !FlagsOk(data.lines[next].requireFlags)) next++;

        if (next >= data.lines.Count) { EndDialogue(); return; }

        index = next;
        ShowLine(data.lines[index]);
    }

    DialogueData.Line CurrentLine =>
        (data != null && index >= 0 && index < data.lines.Count) ? data.lines[index] : null;

    void ShowLine(DialogueData.Line line)
    {
        ClearChoices();
        ShowContinueIndicator(false);

        if (speakerText != null)
        {
            speakerText.text = line.speaker;
            speakerText.gameObject.SetActive(!string.IsNullOrEmpty(line.speaker));
        }
        if (portraitImage != null)
        {
            portraitImage.sprite = line.portrait;
            portraitImage.gameObject.SetActive(line.portrait != null);
        }

        if (!string.IsNullOrEmpty(line.setFlag)) ChoiceFlagManager.Instance?.SetFlag(line.setFlag);
        if (line.sound != null) AudioManager.Instance?.PlaySFX(line.sound);

        OnLineShown?.Invoke(line.text);

        if (typewriter && charactersPerSecond > 0f)
            typeRoutine = StartCoroutine(Type(line.text));
        else
        {
            lineText.text = line.text;
            ShowContinueIndicator(true);
            MaybeShowChoices();
        }
    }

    IEnumerator Type(string text)
    {
        typing = true;
        lineText.text = "";
        float delay = 1f / charactersPerSecond;
        for (int i = 0; i < text.Length; i++)
        {
            lineText.text += text[i];
            if (!string.IsNullOrEmpty(typeSfx) && i % blipEveryNChars == 0 && text[i] != ' ')
                AudioManager.Instance?.PlaySFX(typeSfx, 0.3f);
            yield return new WaitForSecondsRealtime(delay);
        }
        typing = false;
        ShowContinueIndicator(true);
        MaybeShowChoices();
    }

    void MaybeShowChoices()
    {
        var line = CurrentLine;
        if (line == null || line.choices == null || line.choices.Count == 0) return;
        if (choiceContainer == null || choiceButtonPrefab == null)
        {
            Debug.LogWarning("DialogueRunner: this line has choices but no Choice Container / Button Prefab is set.");
            return;
        }

        awaitingChoice = true;
        ShowContinueIndicator(false);

        foreach (var choice in line.choices)
        {
            if (!FlagsOk(choice.requireFlags)) continue;

            var btn = Instantiate(choiceButtonPrefab, choiceContainer);
            var label = btn.GetComponentInChildren<TMP_Text>();
            if (label != null) label.text = choice.text;

            var captured = choice;
            btn.onClick.AddListener(() => PickChoice(captured));
            spawnedChoices.Add(btn);
        }

        // Every choice was filtered out — just carry on
        if (spawnedChoices.Count == 0) { awaitingChoice = false; ShowContinueIndicator(true); }
    }

    void PickChoice(DialogueData.Choice choice)
    {
        if (!string.IsNullOrEmpty(choice.setFlag)) ChoiceFlagManager.Instance?.SetFlag(choice.setFlag);
        ClearChoices();
        awaitingChoice = false;

        if (choice.goToLine < 0) { EndDialogue(); return; }

        pendingJump = choice.goToLine;
        Advance();
    }

    void ClearChoices()
    {
        foreach (var b in spawnedChoices) if (b != null) Destroy(b.gameObject);
        spawnedChoices.Clear();
    }

    void ShowContinueIndicator(bool on)
    {
        if (continueIndicator != null) continueIndicator.SetActive(on);
    }

    public void EndDialogue()
    {
        if (!IsOpen) return;
        IsOpen = false;
        awaitingChoice = false;
        typing = false;
        ClearChoices();
        if (panel != null) panel.SetActive(false);
        if (data != null && data.pausePlayer) SetPlayerFrozen(false);
        if (data != null && !string.IsNullOrEmpty(data.setFlagOnComplete))
            ChoiceFlagManager.Instance?.SetFlag(data.setFlagOnComplete);

        Active = null;
        data = null;
        OnDialogueEnded?.Invoke();
    }

    bool FlagsOk(string flags)
    {
        if (string.IsNullOrWhiteSpace(flags)) return true;
        return ChoiceFlagManager.Instance == null || ChoiceFlagManager.Instance.HasAll(flags);
    }

    void SetPlayerFrozen(bool frozen)
    {
        if (playerMover == null)
        {
            var p = GameObject.FindGameObjectWithTag("Player");
            if (p == null) return;
            var pc2d = p.GetComponent<PlayerController2D>();
            if (pc2d != null) playerMover = pc2d;
            else
            {
                var td2d = p.GetComponent<TopDownMover>();
                if (td2d != null) playerMover = td2d;
            }
            if (playerMover == null) return;
        }
        if (playerMover is PlayerController2D pc) pc.InputEnabled = !frozen;
        else if (playerMover is TopDownMover td) td.InputEnabled = !frozen;
    }

    /// <summary>Let one-shot conversations play again (new game / testing).</summary>
    public static void ResetPlayedHistory() => played.Clear();
}