// Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

namespace TD.Gameplay
{
    /// <summary>
    /// Per-player set of unlocked tower types — the player's "deck". Lives on the
    /// Player prefab alongside <see cref="PlayerGoldManager"/>,
    /// <see cref="PlayerMatchState"/>, and <see cref="PlayerBuffManager"/>.
    /// </summary>
    /// <remarks>
    /// <para><b>Roguelike keystone.</b> Players no longer see every tower in the match
    /// catalog. Each starts with a small base set (the starter towers) and grows the
    /// deck through draft choices. This component is the data structure that growth
    /// targets.</para>
    ///
    /// <para><b>Identity.</b> The deck stores <c>TowerTypeId</c>s — indices into
    /// <see cref="TowerPlacementManager"/>'s catalog (<c>towerDefinitions[]</c>), the
    /// same identifier the placement RPC, <see cref="Builder"/>, and
    /// <see cref="BuildJob"/> already use. No new network identifier is introduced.</para>
    ///
    /// <para><b>Authority.</b> Server-only mutation (<see cref="ServerInitialize"/> /
    /// <see cref="ServerGrantTower"/>). The owning client reads its own deck to build
    /// the HUD command grid; the server reads it to gate placement
    /// (<see cref="TowerPlacementManager"/> rejects a requested tower that isn't in the
    /// requesting player's deck — the anti-cheat seam).</para>
    ///
    /// <para><b>Persistence caveat.</b> <c>TowerTypeId</c> is a catalog index — stable
    /// within a single match but NOT across sessions. Cross-match deck persistence (a
    /// later keystone) will need a stable per-tower id (asset GUID or a serialized
    /// StableId on <c>TowerDefinition</c>).</para>
    /// </remarks>
    public class PlayerTowerDeck : NetworkBehaviour
    {
        // ----- Static registry (mirrors PlayerGoldManager) ----------------

        private static readonly Dictionary<ulong, PlayerTowerDeck> s_byClientId
            = new Dictionary<ulong, PlayerTowerDeck>();

        /// <summary>Returns the deck owned by the given client, or null.</summary>
        public static PlayerTowerDeck GetForClient(ulong clientId)
        {
            s_byClientId.TryGetValue(clientId, out var deck);
            return deck;
        }

        /// <summary>Convenience: the local client's own deck. Null on a dedicated
        /// server or before the local player has spawned.</summary>
        public static PlayerTowerDeck Local
        {
            get
            {
                var nm = NetworkManager.Singleton;
                if (nm == null || !nm.IsClient) return null;
                return GetForClient(nm.LocalClientId);
            }
        }

        // ----- Networked state --------------------------------------------

        // Unlocked tower type ids (indices into TowerPlacementManager's catalog).
        // readPerm defaults to Everyone (consistent with the other per-player
        // managers); only the owner consumes it for the HUD. writePerm Server.
        private NetworkList<int> unlockedTypeIds;

        /// <summary>
        /// Fired on every peer when the deck contents change (server init, tower
        /// granted). The HUD subscribes to rebuild the command grid live when a draft
        /// grant lands while the builder is already selected.
        /// </summary>
        public event System.Action OnDeckChanged;

        private void Awake()
        {
            unlockedTypeIds = new NetworkList<int>();
        }

        // ----- NGO lifecycle ----------------------------------------------

        public override void OnNetworkSpawn()
        {
            s_byClientId[OwnerClientId] = this;
            unlockedTypeIds.OnListChanged += HandleListChanged;
        }

        public override void OnNetworkDespawn()
        {
            unlockedTypeIds.OnListChanged -= HandleListChanged;

            if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
                s_byClientId.Remove(OwnerClientId);
        }

        private void HandleListChanged(NetworkListEvent<int> _) => OnDeckChanged?.Invoke();

        // ----- Read API ---------------------------------------------------

        /// <summary>Number of towers unlocked in this deck.</summary>
        public int Count => unlockedTypeIds.Count;

        /// <summary>The TowerTypeId at the given deck slot (0..Count-1).</summary>
        public int GetTypeIdAt(int index) => unlockedTypeIds[index];

        /// <summary>True if the given TowerTypeId is unlocked in this deck.</summary>
        public bool Contains(int towerTypeId)
        {
            for (int i = 0; i < unlockedTypeIds.Count; i++)
                if (unlockedTypeIds[i] == towerTypeId) return true;
            return false;
        }

        // ----- Server mutation --------------------------------------------

        /// <summary>
        /// Server-only: replace the deck with the given starting set. Called at match
        /// start (<see cref="WaveManager"/>) so Retry / return-to-lobby cycles reset
        /// the deck cleanly. Ignores ids &lt;= 0 (the reserved catalog index) and
        /// silently de-dupes.
        /// </summary>
        public void ServerInitialize(IReadOnlyList<int> startingTypeIds)
        {
            if (!IsServer) return;

            unlockedTypeIds.Clear();
            if (startingTypeIds == null) return;

            for (int i = 0; i < startingTypeIds.Count; i++)
            {
                int id = startingTypeIds[i];
                if (id > 0 && !Contains(id)) unlockedTypeIds.Add(id);
            }
        }

        /// <summary>
        /// Server-only: add a tower type to this deck (a draft grant). No-op if the id
        /// is invalid or already present. Returns true if it was actually added.
        /// </summary>
        public bool ServerGrantTower(int towerTypeId)
        {
            if (!IsServer) return false;
            if (towerTypeId <= 0) return false;
            if (Contains(towerTypeId)) return false;

            unlockedTypeIds.Add(towerTypeId);
            return true;
        }
    }
}
