using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// Cache LRU pour sprites téléchargés (url -> Texture2D + Sprite).
/// Conçu pour limiter les allocations et éviter les crash WebGL (abort/OOM).
/// </summary>
public sealed class RemoteSpriteCache : MonoBehaviour
{
    [Serializable]
    private sealed class Entry
    {
        public Texture2D texture;
        public Sprite sprite;
        public int width;
        public int height;
    }

    private static RemoteSpriteCache _instance;
    public static RemoteSpriteCache Instance
    {
        get
        {
            if (_instance != null) return _instance;
            var go = new GameObject("RemoteSpriteCache");
            _instance = go.AddComponent<RemoteSpriteCache>();
            DontDestroyOnLoad(go);
            return _instance;
        }
    }

    /// <summary>
    /// Retourne l'instance existante si elle existe, sans en créer.
    /// </summary>
    public static RemoteSpriteCache GetExisting() => _instance;

    [Header("Budget")]
    [Tooltip("Nombre maximum d'entrées en cache (LRU)")]
    public int maxEntries = 64;

    private LruCache<string, Entry> _cache;

    private void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else if (_instance != this)
        {
            Destroy(gameObject);
            return;
        }

        _cache = new LruCache<string, Entry>(Mathf.Max(1, maxEntries), StringComparer.Ordinal);
    }

    /// <summary>
    /// Tente de récupérer un sprite déjà en cache.
    /// </summary>
    public bool TryGet(string url, out Sprite sprite)
    {
        sprite = null;
        if (string.IsNullOrEmpty(url) || _cache == null) return false;
        if (_cache.TryGet(url, out var entry) && entry != null && entry.sprite != null)
        {
            sprite = entry.sprite;
            return true;
        }
        return false;
    }

    /// <summary>
    /// Charge (si nécessaire) puis retourne un sprite. Le sprite retourné est possédé par le cache.
    /// </summary>
    public IEnumerator GetOrLoad(string url, Action<Sprite> onSuccess, Action<string> onError = null, float pixelsPerUnit = 100f)
    {
        if (string.IsNullOrEmpty(url))
        {
            onError?.Invoke("URL vide");
            yield break;
        }

        if (_cache == null) _cache = new LruCache<string, Entry>(Mathf.Max(1, maxEntries), StringComparer.Ordinal);

        if (TryGet(url, out var cached))
        {
            onSuccess?.Invoke(cached);
            yield break;
        }

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

            if (request.result != UnityWebRequest.Result.Success)
            {
                onError?.Invoke(request.error);
                yield break;
            }

            Texture2D tex = DownloadHandlerTexture.GetContent(request);
            if (tex == null)
            {
                onError?.Invoke("Texture null");
                yield break;
            }

            Sprite sp = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
            var entry = new Entry { texture = tex, sprite = sp, width = tex.width, height = tex.height };

            _cache.Set(url, entry, OnEvict);
            onSuccess?.Invoke(sp);
        }
    }

    private void OnEvict(string url, Entry entry)
    {
        if (entry == null) return;
        if (entry.sprite != null) Destroy(entry.sprite);
        if (entry.texture != null) Destroy(entry.texture);
    }

    /// <summary>
    /// Nettoyage explicite (point de nettoyage).
    /// </summary>
    public void ClearAll()
    {
        if (_cache == null) return;
        _cache.Clear(OnEvict);
    }
}


