// Assets/_Project/Scripts/Gameplay/RaceRegistry.cs
using System.Collections.Generic;
using UnityEngine;
using TD.Core;

namespace TD.Gameplay
{
    /// <summary>
    /// Persistent (DontDestroyOnLoad) singleton that holds every
    /// <see cref="RaceDefinition"/> available in the current build and lets
    /// any code look one up by <see cref="RaceId"/>.
    /// </summary>
    /// <remarks>
    /// <para><b>Inspector setup.</b> Place ONE <c>RaceRegistry</c> GameObject in
    /// the <b>MainMenu scene</b> only. Drag every <c>RaceDefinition</c> asset
    /// into the <c>Definitions</c> array. The registry marks itself
    /// <c>DontDestroyOnLoad</c> on Awake, so it survives the scene transitions
    /// MainMenu → Lobby → Match → back to Lobby and is available to all of
    /// them through <see cref="Instance"/>.</para>
    ///
    /// <para><b>Why not also in Lobby/Match scenes?</b> Maintaining the
    /// <c>Definitions</c> array in multiple places is a designer trap — update
    /// one, forget the other, runtime mismatch. Single source of truth in
    /// MainMenu eliminates that class of bug. Duplicate instances in other
    /// scenes are detected in <see cref="Awake"/> and self-destruct, so an
    /// accidental copy doesn't break anything but does log a warning.</para>
    ///
    /// <para><b>Editor-only standalone testing.</b> If you open the Lobby or
    /// Match scene directly from the editor (without going through MainMenu
    /// first), no RaceRegistry will exist and race-dependent code falls back
    /// gracefully (<see cref="Get"/> returns null; UI shows "Coming Soon" or
    /// the default builder is used). For standalone-scene testing, you can
    /// temporarily add a registry to whatever scene you're testing — but don't
    /// commit it as part of normal play flow.</para>
    ///
    /// <para><b>Slot model.</b> The lobby grid shows 16 slots (one per
    /// <see cref="RaceId"/> value 1-16) regardless of how many are filled.
    /// <see cref="Get"/> returns null for unregistered slots, which the UI
    /// renders as a "Coming Soon" placeholder.</para>
    ///
    /// <para><b>Plain MonoBehaviour.</b> Not a NetworkBehaviour — the registry
    /// is identical on every peer (same ScriptableObject assets), so nothing
    /// to sync. Network state tracks only the chosen <see cref="RaceId"/> on
    /// <c>PlayerMatchState</c>; the rest is local lookup.</para>
    /// </remarks>
    public class RaceRegistry : MonoBehaviour
    {
        // ----- Singleton -------------------------------------------------

        public static RaceRegistry Instance { get; private set; }

        // ----- Inspector --------------------------------------------------

        [Tooltip("All RaceDefinition assets available in this build. Drag each asset " +
                 "into the array. Duplicate Ids are rejected with a warning; null entries " +
                 "are skipped.")]
        [SerializeField] private RaceDefinition[] definitions;

        // ----- Internal lookup -------------------------------------------

        private readonly Dictionary<RaceId, RaceDefinition> byId
            = new Dictionary<RaceId, RaceDefinition>();

        // ----- Lifecycle --------------------------------------------------

        private void Awake()
        {
            // Persistent-singleton pattern: the FIRST instance to wake up wins
            // and survives scene loads. Subsequent instances (e.g. a stale
            // copy left over in the Lobby or Match scene) are self-destroyed,
            // not just ignored — we want the scene to "self-heal" if someone
            // accidentally drops a second copy in.
            if (Instance != null && Instance != this)
            {
                Debug.LogWarning(
                    $"[RaceRegistry] Persistent instance already exists. " +
                    $"Destroying duplicate in scene '{gameObject.scene.name}'. " +
                    $"Keep RaceRegistry in the MainMenu scene only.");
                Destroy(gameObject);
                return;
            }
            Instance = this;
            DontDestroyOnLoad(gameObject);
            BuildLookup();
        }

        private void OnDestroy()
        {
            if (Instance == this) Instance = null;
        }

        // ----- Public API -------------------------------------------------

        /// <summary>
        /// Returns the <see cref="RaceDefinition"/> for the given id, or null
        /// if no asset is registered for that id (e.g. "Coming Soon" slots).
        /// </summary>
        public RaceDefinition Get(RaceId id)
        {
            byId.TryGetValue(id, out var def);
            return def;
        }

        /// <summary>
        /// Iterates the canonical 16 lobby slots (Race1..Race16). For each
        /// slot returns either the registered <see cref="RaceDefinition"/> or
        /// null. UI consumers use this to render a stable 16-cell grid where
        /// unfilled slots show a placeholder.
        /// </summary>
        public IEnumerable<(RaceId id, RaceDefinition def)> AllSlots()
        {
            for (int i = (int)RaceId.Race1; i <= (int)RaceId.Race16; i++)
            {
                var id = (RaceId)i;
                yield return (id, Get(id));
            }
        }

        // ----- Private ----------------------------------------------------

        private void BuildLookup()
        {
            byId.Clear();
            if (definitions == null || definitions.Length == 0)
            {
                Debug.LogWarning("[RaceRegistry] No RaceDefinition assets assigned. " +
                                 "Drag assets into the Definitions array.");
                return;
            }

            foreach (var def in definitions)
            {
                if (def == null) continue;
                if (def.Id == RaceId.None)
                {
                    Debug.LogWarning($"[RaceRegistry] '{def.name}' has Id=None — set it to " +
                                     "an unused Race1..Race16 value.");
                    continue;
                }
                if (byId.ContainsKey(def.Id))
                {
                    Debug.LogWarning($"[RaceRegistry] Duplicate Id '{def.Id}' detected on " +
                                     $"'{def.name}'. Earlier registration kept; rename one.");
                    continue;
                }
                byId[def.Id] = def;
            }

            Debug.Log($"[RaceRegistry] Registered {byId.Count} race(s).");
        }
    }
}
