using System;
using System.Collections.Generic;

/// <summary>
/// Cache LRU (Least Recently Used) minimal et sans allocation excessive.
/// </summary>
public sealed class LruCache<TKey, TValue>
{
    private readonly int _capacity;
    private readonly Dictionary<TKey, LinkedListNode<(TKey key, TValue value)>> _map;
    private readonly LinkedList<(TKey key, TValue value)> _lru;

    public LruCache(int capacity, IEqualityComparer<TKey> comparer = null)
    {
        _capacity = Math.Max(1, capacity);
        _map = new Dictionary<TKey, LinkedListNode<(TKey key, TValue value)>>(_capacity, comparer);
        _lru = new LinkedList<(TKey key, TValue value)>();
    }

    public int Count => _map.Count;
    public int Capacity => _capacity;

    public bool TryGet(TKey key, out TValue value)
    {
        if (_map.TryGetValue(key, out var node))
        {
            value = node.Value.value;
            _lru.Remove(node);
            _lru.AddFirst(node);
            return true;
        }

        value = default;
        return false;
    }

    /// <summary>
    /// Ajoute/met à jour une entrée. Si éviction: appelle onEvict(key,value).
    /// </summary>
    public void Set(TKey key, TValue value, Action<TKey, TValue> onEvict = null)
    {
        if (_map.TryGetValue(key, out var existing))
        {
            existing.Value = (key, value);
            _lru.Remove(existing);
            _lru.AddFirst(existing);
            return;
        }

        var node = new LinkedListNode<(TKey key, TValue value)>((key, value));
        _lru.AddFirst(node);
        _map[key] = node;

        if (_map.Count > _capacity)
        {
            var last = _lru.Last;
            if (last != null)
            {
                _lru.RemoveLast();
                _map.Remove(last.Value.key);
                onEvict?.Invoke(last.Value.key, last.Value.value);
            }
        }
    }

    public bool Remove(TKey key, Action<TKey, TValue> onRemove = null)
    {
        if (!_map.TryGetValue(key, out var node)) return false;
        _map.Remove(key);
        _lru.Remove(node);
        onRemove?.Invoke(node.Value.key, node.Value.value);
        return true;
    }

    public void Clear(Action<TKey, TValue> onEvict = null)
    {
        if (onEvict != null)
        {
            foreach (var kv in _lru)
                onEvict(kv.key, kv.value);
        }
        _map.Clear();
        _lru.Clear();
    }
}


