#nullable enable
using System;
using System.Collections.Generic;

namespace AdvChkSys.Util
{
    /// <summary>
    /// Thread-safe LRU (Least Recently Used) cache.
    /// </summary>
    public class LRUCache<TKey, TValue>
        where TKey : notnull
    {
        private readonly int _capacity;
        private readonly Dictionary<TKey, LinkedListNode<(TKey key, TValue value)>> _map;
        private readonly LinkedList<(TKey key, TValue value)> _lruList;
        private readonly object _lock = new();

        /// <summary>
        /// Initializes a new instance of the LRU cache with the specified capacity.
        /// </summary>
        /// <param name="capacity">Maximum number of items to store in the cache</param>
        public LRUCache(int capacity)
        {
            if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
            _capacity = capacity;
            _map = new Dictionary<TKey, LinkedListNode<(TKey, TValue)>>();
            _lruList = new LinkedList<(TKey, TValue)>();
        }

        /// <summary>
        /// Attempts to retrieve a value from the cache by key.
        /// </summary>
        /// <param name="key">The key to look up</param>
        /// <param name="value">The retrieved value if found, default otherwise</param>
        /// <returns>True if the key was found, false otherwise</returns>
        public bool TryGet(TKey key, out TValue value)
        {
            lock (_lock)
            {
                if (_map.TryGetValue(key, out var node))
                {
                    _lruList.Remove(node);
                    _lruList.AddFirst(node);
                    value = node.Value.value;
                    return true;
                }
                value = default!;
                return false;
            }
        }

        /// <summary>
        /// Adds or updates a key-value pair in the cache.
        /// </summary>
        /// <param name="key">The key to add or update</param>
        /// <param name="value">The value to store</param>
        /// <param name="onEvict">Optional callback when an item is evicted</param>
        public void Add(TKey key, TValue value, Action<TKey, TValue>? onEvict = null)
        {
            lock (_lock)
            {
                if (_map.TryGetValue(key, out var node))
                {
                    _lruList.Remove(node);
                    _lruList.AddFirst(node);
                    node.Value = (key, value);
                }
                else
                {
                    if (_map.Count >= _capacity)
                    {
                        var last = _lruList.Last;
                        if (last != null)
                        {
                            _map.Remove(last.Value.key);
                            onEvict?.Invoke(last.Value.key, last.Value.value);
                            _lruList.RemoveLast();
                        }
                    }
                    var newNode = new LinkedListNode<(TKey, TValue)>((key, value));
                    _lruList.AddFirst(newNode);
                    _map[key] = newNode;
                }
            }
        }

        /// <summary>
        /// Removes a key-value pair from the cache.
        /// </summary>
        /// <param name="key">The key to remove</param>
        /// <param name="value">The removed value if found</param>
        /// <returns>True if the key was found and removed, false otherwise</returns>
        public bool Remove(TKey key, out TValue? value)
        {
            lock (_lock)
            {
                if (_map.TryGetValue(key, out var node))
                {
                    value = node.Value.value;
                    _lruList.Remove(node);
                    _map.Remove(key);
                    return true;
                }
                value = default;
                return false;
            }
        }

        /// <summary>
        /// Gets all values currently in the cache.
        /// </summary>
        public IEnumerable<TValue> Values
        {
            get
            {
                lock (_lock)
                {
                    foreach (var (_, value) in _lruList)
                        yield return value;
                }
            }
        }

        /// <summary>
        /// Gets the current number of items in the cache.
        /// </summary>
        public int Count
        {
            get
            {
                lock (_lock) { return _map.Count; }
            }
        }
    }
}