using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// EntityRegistry — Zero-allocation replacement for FindObjectsOfType.
/// Each MonoBehaviour type auto-registers in OnEnable/OnDisable via EntityRegistryMember<T>.
/// Access all instances via EntityRegistry<T>.All (read-only list, no allocation).
/// 
/// Usage:
///   1) Add EntityRegistryMember<CashBag> component, or
///   2) Call EntityRegistry<CashBag>.Register(this) in OnEnable / Unregister in OnDisable
///   3) Query: foreach (var bag in EntityRegistry<CashBag>.All) { ... }
/// </summary>
public static class EntityRegistry<T> where T : class
{
    private static readonly List<T> _instances = new List<T>(32);
    private static readonly List<T> _readOnly = _instances; // Same ref, cast prevents mutation at call sites

    /// <summary>All currently active instances. Do NOT modify this list.</summary>
    public static List<T> All => _readOnly;

    /// <summary>Alias for All — convenience method matching GetAll() convention.</summary>
    public static List<T> GetAll() => _readOnly;

    /// <summary>Number of active instances.</summary>
    public static int Count => _instances.Count;

    public static void Register(T instance)
    {
        if (instance != null && !_instances.Contains(instance))
            _instances.Add(instance);
    }

    public static void Unregister(T instance)
    {
        _instances.Remove(instance);
    }

    /// <summary>
    /// Find nearest instance to a world position. 
    /// Returns null if no instances. O(n) but n is small (≤10 for bags/characters).
    /// </summary>
    public static T FindNearest(Vector3 position, System.Func<T, Vector3> getPosition, System.Func<T, bool> filter = null)
    {
        T nearest = null;
        float nearestSqrDist = float.MaxValue;

        for (int i = 0; i < _instances.Count; i++)
        {
            T instance = _instances[i];
            if (instance == null) continue;
            if (filter != null && !filter(instance)) continue;

            float sqrDist = (getPosition(instance) - position).sqrMagnitude;
            if (sqrDist < nearestSqrDist)
            {
                nearestSqrDist = sqrDist;
                nearest = instance;
            }
        }
        return nearest;
    }

    /// <summary>
    /// Find all instances within radius. Uses pre-allocated result list to avoid allocation.
    /// Caller should process results immediately (list is reused).
    /// </summary>
    private static readonly List<T> _withinRadiusResults = new List<T>(16);
    public static List<T> FindWithinRadius(Vector3 position, float radius, System.Func<T, Vector3> getPosition, System.Func<T, bool> filter = null)
    {
        _withinRadiusResults.Clear();
        float sqrRadius = radius * radius;

        for (int i = 0; i < _instances.Count; i++)
        {
            T instance = _instances[i];
            if (instance == null) continue;
            if (filter != null && !filter(instance)) continue;

            float sqrDist = (getPosition(instance) - position).sqrMagnitude;
            if (sqrDist <= sqrRadius)
                _withinRadiusResults.Add(instance);
        }
        return _withinRadiusResults;
    }

    /// <summary>Call on scene unload to prevent stale references.</summary>
    public static void Clear() => _instances.Clear();
}
