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

/// <summary>
/// GAME 1303 Tier 6 — "The imagery of the world changes to fit the choices you made."
/// (Jax's note on Andraya's pitch, made literal.) Watches the flags and swaps sprites,
/// colours, objects, or whole scene decorations when the story state changes.
///
/// SETUP
///  1. Empty GameObject + this script (one per scene is plenty).
///  2. Add a State per variation. Each has:
///       Require Flags   – when this passes, the state is applied
///       Sprite Swaps    – SpriteRenderer + the sprite to show
///       Colour Tints    – SpriteRenderer + colour (fades over Transition Time)
///       Show Objects / Hide Objects – whole GameObjects
///       On Applied      – anything else (music change, particle burst)
///  3. Order matters like EndingSelector: the LAST matching state wins, so put the
///     most advanced/corrupted version at the bottom.
///  4. Check Every Frame is off by default (it re-checks whenever a flag changes, which
///     is enough). Turn it on only if something else changes state without flags.
///
/// For Anabasis this is how the mansion gets darker as the ghost remembers more: one
/// state per memory recovered, each tinting the walls and swapping a few key props.
/// </summary>
public class WorldStateSwapper : MonoBehaviour
{
    [System.Serializable]
    public class SpriteSwap
    {
        public SpriteRenderer target;
        public Sprite sprite;
    }

    [System.Serializable]
    public class ColourTint
    {
        public SpriteRenderer target;
        public Color colour = Color.white;
    }

    [System.Serializable]
    public class State
    {
        public string name = "State";
        [Tooltip("Flags required for this state (comma separated; ! to negate). Empty = base state.")]
        public string requireFlags = "";
        public List<SpriteSwap> spriteSwaps = new List<SpriteSwap>();
        public List<ColourTint> colourTints = new List<ColourTint>();
        public List<GameObject> showObjects = new List<GameObject>();
        public List<GameObject> hideObjects = new List<GameObject>();
        public UnityEvent onApplied;
    }

    [Tooltip("Checked top to bottom — the LAST match is applied. Base state first.")]
    public List<State> states = new List<State>();
    public float transitionTime = 0.5f;
    public bool applyOnStart = true;
    public bool checkEveryFrame = false;

    public UnityEvent<string> OnStateChanged;

    public State CurrentState { get; private set; }

    private readonly Dictionary<SpriteRenderer, Color> tintFrom = new Dictionary<SpriteRenderer, Color>();
    private readonly Dictionary<SpriteRenderer, Color> tintTo = new Dictionary<SpriteRenderer, Color>();
    private float tintProgress = 1f;

    void Start()
    {
        if (ChoiceFlagManager.Instance != null)
        {
            ChoiceFlagManager.Instance.OnFlagSet.AddListener(_ => Refresh());
            ChoiceFlagManager.Instance.OnFlagCleared.AddListener(_ => Refresh());
        }
        if (applyOnStart) Refresh();
    }

    void Update()
    {
        if (checkEveryFrame) Refresh();

        if (tintProgress < 1f && transitionTime > 0f)
        {
            tintProgress += Time.deltaTime / transitionTime;
            foreach (var kv in tintTo)
            {
                if (kv.Key == null) continue;
                Color from = tintFrom.TryGetValue(kv.Key, out var f) ? f : kv.Key.color;
                kv.Key.color = Color.Lerp(from, kv.Value, Mathf.Clamp01(tintProgress));
            }
        }
    }

    /// <summary>Re-evaluate which state should be active. Called automatically on flag changes.</summary>
    public void Refresh()
    {
        State match = null;
        foreach (var s in states)
            if (FlagsOk(s.requireFlags)) match = s;   // last match wins

        if (match == null || match == CurrentState) return;
        Apply(match);
    }

    void Apply(State s)
    {
        CurrentState = s;

        foreach (var swap in s.spriteSwaps)
            if (swap.target != null && swap.sprite != null) swap.target.sprite = swap.sprite;

        tintFrom.Clear();
        tintTo.Clear();
        foreach (var tint in s.colourTints)
        {
            if (tint.target == null) continue;
            tintFrom[tint.target] = tint.target.color;
            tintTo[tint.target] = tint.colour;
        }
        tintProgress = transitionTime > 0f ? 0f : 1f;
        if (transitionTime <= 0f)
            foreach (var kv in tintTo) if (kv.Key != null) kv.Key.color = kv.Value;

        foreach (var o in s.showObjects) if (o != null) o.SetActive(true);
        foreach (var o in s.hideObjects) if (o != null) o.SetActive(false);

        s.onApplied?.Invoke();
        OnStateChanged?.Invoke(s.name);
    }

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