using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;

/// <summary>
/// GAME 1303 Tier 6 — Stairs, elevators, and doorways that move the player between floors
/// or rooms WITHIN one scene. Detective Liz's five-story building.
///
/// WHY NOT JUST LOAD A SCENE: five scenes for five floors means five copies of the player,
/// the HUD, the flashlight state, and the ghosts. Keeping one scene and teleporting between
/// floor areas is far less to manage — and the battery, dread meter, and clue flags all
/// survive automatically.
///
/// SETUP
///  1. Lay out each floor in the same scene, stacked vertically or side by side with plenty
///     of space between them.
///  2. Stairs at floor 1: empty GameObject + Collider2D (Is Trigger) + this script.
///     Drag the matching stairs object on floor 2 into Destination. On that one, drag this
///     one back — two-way stairs are just two of these pointing at each other.
///  3. Mode:
///       OnEnter    – walk into it and you go (doorways).
///       OnInteract – needs the Interact key; add an Interactable to the same object and
///                    wire OnInteract -> FloorTransition.Use (stairs, elevator buttons).
///  4. Fade: uses SceneLoader's fade image if one exists, otherwise it's instant.
///  5. Camera: with a Cinemachine Confiner per floor, drag the destination floor's
///     bounding shape into New Confiner Shape and the camera re-frames on arrival.
///  6. Set Flag On Use records that the player reached this floor — useful for both the
///     ending logic and a "you've explored 4 of 5 floors" readout.
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class FloorTransition : MonoBehaviour
{
    public enum Mode { OnEnter, OnInteract }

    public Mode mode = Mode.OnInteract;
    [Tooltip("Where the player comes out. Usually another FloorTransition's transform.")]
    public Transform destination;
    public float fadeTime = 0.3f;
    public float lockTime = 0.4f;

    [Header("Camera (optional)")]
    [Tooltip("The destination floor's Cinemachine Confiner 2D bounding shape.")]
    public Collider2D newConfinerShape;
    [Tooltip("Drag the CinemachineConfiner2D component's GameObject here.")]
    public GameObject confinerObject;

    [Header("Story")]
    public string setFlagOnUse = "";
    [Tooltip("Flags required to use it (! to negate). e.g. 'has_elevator_key'")]
    public string requireFlags = "";

    [Header("Feedback")]
    public string useSfx = "stairs";
    public string blockedSfx = "door_locked";

    [Header("Events")]
    public UnityEvent OnUsed;
    public UnityEvent OnBlocked;

    private bool busy;
    private static float globalCooldown;

    void Reset() { GetComponent<Collider2D>().isTrigger = true; }

    void Update() { if (globalCooldown > 0f) globalCooldown -= Time.deltaTime; }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (mode != Mode.OnEnter || !other.CompareTag("Player")) return;
        Use();
    }

    /// <summary>Wire an Interactable's OnInteract here for stairs you press E on.</summary>
    public void Use()
    {
        if (busy || globalCooldown > 0f || destination == null) return;

        if (!string.IsNullOrWhiteSpace(requireFlags) &&
            ChoiceFlagManager.Instance != null && !ChoiceFlagManager.Instance.HasAll(requireFlags))
        {
            if (!string.IsNullOrEmpty(blockedSfx)) AudioManager.Instance?.PlaySFX(blockedSfx);
            OnBlocked?.Invoke();
            return;
        }

        var player = GameObject.FindGameObjectWithTag("Player");
        if (player == null) return;
        StartCoroutine(Travel(player));
    }

    IEnumerator Travel(GameObject player)
    {
        busy = true;
        globalCooldown = lockTime;

        var pc = player.GetComponent<PlayerController2D>();
        var td = player.GetComponent<TopDownMover>();
        if (pc != null) pc.InputEnabled = false;
        if (td != null) td.InputEnabled = false;

        if (!string.IsNullOrEmpty(useSfx)) AudioManager.Instance?.PlaySFX(useSfx);

        // Fade out using SceneLoader's image if there is one
        var fade = SceneLoader.Instance != null ? SceneLoader.Instance.fadeImage : null;
        if (fade != null && fadeTime > 0f)
        {
            fade.gameObject.SetActive(true);
            yield return FadeTo(fade, 1f);
        }

        player.transform.position = destination.position;
        if (pc != null) pc.ResetMotion();
        if (td != null) td.ResetMotion();

        SwapConfiner();

        if (!string.IsNullOrEmpty(setFlagOnUse)) ChoiceFlagManager.Instance?.SetFlag(setFlagOnUse);

        yield return null;   // let the camera settle before fading back in

        if (fade != null && fadeTime > 0f)
        {
            yield return FadeTo(fade, 0f);
            fade.gameObject.SetActive(false);
        }

        if (pc != null) pc.InputEnabled = true;
        if (td != null) td.InputEnabled = true;

        OnUsed?.Invoke();
        busy = false;
    }

    IEnumerator FadeTo(UnityEngine.UI.Image img, float target)
    {
        float from = img.color.a;
        float t = 0f;
        while (t < fadeTime)
        {
            t += Time.unscaledDeltaTime;
            var c = img.color;
            c.a = Mathf.Lerp(from, target, t / fadeTime);
            img.color = c;
            yield return null;
        }
        var final = img.color;
        final.a = target;
        img.color = final;
    }

    void SwapConfiner()
    {
        if (confinerObject == null || newConfinerShape == null) return;
        // Set the bounding shape by reflection so this script doesn't hard-require
        // Cinemachine at compile time (a student without it installed still builds).
        var confiner = confinerObject.GetComponent("CinemachineConfiner2D");
        if (confiner == null) return;
        var prop = confiner.GetType().GetProperty("BoundingShape2D")
                   ?? confiner.GetType().GetProperty("m_BoundingShape2D");
        if (prop != null) prop.SetValue(confiner, newConfinerShape);
        var field = confiner.GetType().GetField("BoundingShape2D")
                    ?? confiner.GetType().GetField("m_BoundingShape2D");
        if (field != null) field.SetValue(confiner, newConfinerShape);
        var invalidate = confiner.GetType().GetMethod("InvalidateBoundingShapeCache")
                         ?? confiner.GetType().GetMethod("InvalidateCache");
        invalidate?.Invoke(confiner, null);
    }

    void OnDrawGizmos()
    {
        if (destination == null) return;
        Gizmos.color = new Color(0.6f, 0.4f, 1f, 0.9f);
        Gizmos.DrawLine(transform.position, destination.position);
        Gizmos.DrawWireSphere(destination.position, 0.3f);
    }
}