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

namespace TD.Gameplay.Draft
{
    /// <summary>
    /// Server-authoritative draft orchestrator: generates weighted-random option sets and
    /// offers them to players' <see cref="PlayerDraft"/> components. Scene singleton.
    /// </summary>
    /// <remarks>
    /// Plain MonoBehaviour — the replicated state lives on <see cref="PlayerDraft"/>; this
    /// is pure server logic plus two designer tunables (<see cref="optionsPerDraft"/>,
    /// <see cref="rerollCost"/>). It exists on every peer (scene object) but only the server
    /// drives it; clients read <see cref="RerollCost"/> for the HUD's "buy roll" button.
    ///
    /// <para><b>Driven by <see cref="WaveManager"/>:</b> <see cref="ServerOfferToAll"/> at
    /// the start of each prep phase; <see cref="ServerAutoResolveAll"/> at prep end (and on
    /// dev force-advance) so unpicked free drafts aren't wasted.</para>
    /// </remarks>
    public class DraftService : MonoBehaviour
    {
        public static DraftService Instance { get; private set; }

        [Tooltip("How many options each draft offers (cards shown). Standard roguelike draw is 3.")]
        [SerializeField] private int optionsPerDraft = 3;

        [Tooltip("Gold cost for a player to buy one extra draft roll from the shop.")]
        [SerializeField] private int rerollCost = 50;

        /// <summary>Gold cost of one bought reroll. Read by the HUD and the buy RPC.</summary>
        public int RerollCost => rerollCost;

        // Scratch lists reused across generation to avoid per-draft GC. Server is
        // single-threaded, so sharing them across the per-player loop is safe.
        private readonly List<int> candidateScratch = new List<int>();
        private readonly List<int> resultScratch    = new List<int>();

        private static bool IsServer
            => NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer;

        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Debug.LogError("[DraftService] Multiple instances detected. Only one per scene.");
                return;
            }
            Instance = this;
        }

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

        // ----- Server orchestration ---------------------------------------

        /// <summary>Server-only: offer a fresh draft to every connected player.</summary>
        public void ServerOfferToAll()
        {
            if (!IsServer) return;
            if (DraftPool.Instance == null)
            {
                Debug.LogWarning("[DraftService] No DraftPool in scene — no drafts will be offered.");
                return;
            }

            foreach (var pms in PlayerMatchState.AllPlayers)
                ServerOfferTo(pms.OwnerClientId);
        }

        /// <summary>Server-only: offer a fresh draft to one player (also used by the paid reroll).</summary>
        public void ServerOfferTo(ulong clientId)
        {
            if (!IsServer) return;
            var draft = PlayerDraft.GetForClient(clientId);
            if (draft == null) return;

            GenerateOptionIds(clientId, optionsPerDraft, resultScratch);
            draft.ServerOffer(resultScratch);
        }

        /// <summary>Server-only: auto-resolve any unpicked drafts (called at prep end).</summary>
        public void ServerAutoResolveAll()
        {
            if (!IsServer) return;
            foreach (var pms in PlayerMatchState.AllPlayers)
                PlayerDraft.GetForClient(pms.OwnerClientId)?.ServerAutoResolve();
        }

        // ----- Generation -------------------------------------------------

        // Weighted random draw WITHOUT replacement: picks `count` distinct option ids from
        // the set currently valid for this player. If fewer valid options exist than
        // requested, offers what's available (possibly zero).
        private void GenerateOptionIds(ulong clientId, int count, List<int> into)
        {
            into.Clear();
            var pool = DraftPool.Instance;
            if (pool == null) return;

            pool.CollectValidIds(clientId, candidateScratch);

            int draws = Mathf.Min(count, candidateScratch.Count);
            for (int n = 0; n < draws; n++)
            {
                float total = 0f;
                for (int i = 0; i < candidateScratch.Count; i++)
                    total += OptionWeight(pool, candidateScratch[i]);

                float r = Random.value * total;
                int chosen = candidateScratch.Count - 1; // fallback to last on float drift
                for (int i = 0; i < candidateScratch.Count; i++)
                {
                    r -= OptionWeight(pool, candidateScratch[i]);
                    if (r <= 0f) { chosen = i; break; }
                }

                into.Add(candidateScratch[chosen]);
                candidateScratch.RemoveAt(chosen); // without replacement → distinct options
            }
        }

        private static float OptionWeight(DraftPool pool, int id)
        {
            var opt = pool.Get(id);
            return opt != null ? Mathf.Max(0.0001f, opt.Weight) : 0.0001f;
        }
    }
}
