// Assets/_Project/Scripts/Gameplay/PlayerBuilderSpawner.cs
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;
using TD.Core;
using TD.Levels;
using TD.Net;

namespace TD.Gameplay
{
    /// <summary>
    /// Lives on the Player Prefab. On the server, spawns and re-spawns a separate
    /// <see cref="Builder"/> NetworkObject owned by this player each time the Match
    /// scene loads. The builder is positioned at the centroid of the player's zone.
    /// </summary>
    /// <remarks>
    /// <para><b>Why a separate NetworkObject?</b> Multi-builder races (Path E) become "spawn
    /// N builder NetworkObjects" without restructuring the Player Prefab. See the design
    /// discussion in Path D scoping.</para>
    ///
    /// <para><b>Server-only.</b> Spawning is server-authoritative. Non-server peers are
    /// no-ops; they just receive the resulting Builder NetworkObject like any other
    /// replicated spawn.</para>
    ///
    /// <para><b>Lifetime &amp; scene flow.</b> The player NetworkObject persists across
    /// scenes (MainMenu → Lobby → Match → Lobby …) because it's the
    /// <c>NetworkManager.PlayerPrefab</c>. The Builder is spawned with
    /// <c>destroyWithScene: true</c> so it's torn down on every scene unload — we
    /// only want a builder in the Match scene. <see cref="HandleSceneLoadCompleted"/>
    /// re-spawns when entering Match; <see cref="OnNetworkDespawn"/> handles
    /// disconnect cleanup.</para>
    ///
    /// <para><b>Race-driven builder prefab.</b> If a <see cref="RaceRegistry"/> exists
    /// in the Match scene AND the player's selected <see cref="RaceDefinition"/> has
    /// a <c>BuilderPrefab</c> assigned, that prefab is used. Otherwise this falls back
    /// to the inspector-assigned <see cref="builderPrefab"/> (the universal default).
    /// Phase 1.8 races just need to fill in their <c>BuilderPrefab</c> field; no code
    /// change required.</para>
    /// </remarks>
    public class PlayerBuilderSpawner : NetworkBehaviour
    {
        [Tooltip("Default Builder prefab. Used when the player's race has no BuilderPrefab " +
                 "assigned (or when no RaceRegistry is present in the scene). Must be " +
                 "registered with the NetworkManager as a network prefab.")]
        [SerializeField] private GameObject builderPrefab;

        // Cached reference so we can despawn the builder if needed (e.g., player disconnects).
        private NetworkObject spawnedBuilder;

        // Track our scene-load subscription state so we can clean up correctly.
        private bool sceneSubscribed;

        public override void OnNetworkSpawn()
        {
            if (!IsServer) return;

            if (builderPrefab == null)
            {
                Debug.LogError("[PlayerBuilderSpawner] No default Builder prefab assigned. " +
                               "Cannot spawn builder for client " + OwnerClientId + ".");
                return;
            }

            // Subscribe to NGO's scene-load completion so we re-spawn the builder
            // every time the Match scene comes up (initial match start, Retry, etc.).
            if (NetworkManager != null && NetworkManager.SceneManager != null)
            {
                NetworkManager.SceneManager.OnLoadEventCompleted += HandleSceneLoadCompleted;
                sceneSubscribed = true;
            }

            // Edge case: the player connected while a match is already in progress.
            // The match scene is already loaded, so OnLoadEventCompleted won't fire
            // again until the next transition. Spawn now.
            if (IsMatchScene(SceneManager.GetActiveScene().name))
                TrySpawnBuilder();
        }

        // NGO fires this on the server once a scene load is acknowledged complete
        // by every connected client (or timed out). We only act when a registered
        // match scene loads; Lobby / MainMenu loads are no-ops here.
        private void HandleSceneLoadCompleted(string sceneName,
                                              LoadSceneMode loadSceneMode,
                                              List<ulong> clientsCompleted,
                                              List<ulong> clientsTimedOut)
        {
            if (!IsServer) return;
            if (!IsMatchScene(sceneName)) return;
            TrySpawnBuilder();
        }

        // Spawns the builder if it doesn't already exist. Defers to a SlotReady
        // event if PlayerMatchState hasn't finished assigning the slot yet.
        private void TrySpawnBuilder()
        {
            if (spawnedBuilder != null && spawnedBuilder.IsSpawned) return;

            var pms = GetComponent<PlayerMatchState>();
            if (pms == null)
            {
                Debug.LogError("[PlayerBuilderSpawner] PlayerMatchState not found on Player Prefab. " +
                               "Add it as a sibling component.");
                return;
            }

            if (pms.Slot != PlayerSlot.None)
                SpawnBuilderForOwner(pms.Slot);
            else
                pms.SlotReady += OnOwnerSlotReady;
        }

        private void OnOwnerSlotReady(PlayerSlot slot)
        {
            var pms = GetComponent<PlayerMatchState>();
            if (pms != null) pms.SlotReady -= OnOwnerSlotReady;

            // Only spawn if we're in a match scene. SlotReady can fire in MainMenu
            // (during initial connection) — we don't want a builder there.
            if (IsMatchScene(SceneManager.GetActiveScene().name))
                SpawnBuilderForOwner(slot);
        }

        public override void OnNetworkDespawn()
        {
            if (sceneSubscribed && NetworkManager != null && NetworkManager.SceneManager != null)
            {
                NetworkManager.SceneManager.OnLoadEventCompleted -= HandleSceneLoadCompleted;
                sceneSubscribed = false;
            }

            // When the player despawns (disconnect), also despawn their builder if it still exists.
            if (IsServer && spawnedBuilder != null && spawnedBuilder.IsSpawned)
            {
                spawnedBuilder.Despawn(destroy: true);
            }
            spawnedBuilder = null;
        }

        private void SpawnBuilderForOwner(PlayerSlot slot)
        {
            // Compute initial position: centroid of this player's zone.
            // Falls back to origin if loader/zone data isn't available.
            Vector3 spawnPos = ComputeZoneCentroid(slot);

            // Pick the prefab: race-specific takes priority, default falls back.
            // Falling back is what lets all races share the default builder
            // during Phase 1.7 — each RaceDefinition just needs the same default
            // assigned, and the spawner picks it up automatically.
            GameObject prefab = ResolveBuilderPrefab();
            if (prefab == null)
            {
                Debug.LogError("[PlayerBuilderSpawner] No builder prefab available. " +
                               "Set the default in the inspector or assign one to the player's race.");
                return;
            }

            var go = Instantiate(prefab, spawnPos, Quaternion.identity);
            var netObj = go.GetComponent<NetworkObject>();
            if (netObj == null)
            {
                Debug.LogError("[PlayerBuilderSpawner] Builder prefab is missing a " +
                               "NetworkObject component. Cannot spawn.");
                Destroy(go);
                return;
            }

            netObj.SpawnWithOwnership(OwnerClientId, destroyWithScene: true);
            spawnedBuilder = netObj;

            // Tell NetworkTransform to teleport to the spawn position, so clients don't
            // interpolate from the prefab's authoring position (typically the origin) to
            // the spawn position over the first few sync ticks. Without this, clients see
            // the builder smoothly drift from world origin to its spawn point — which is
            // exactly what we don't want for a brand-new spawn.
            //
            // Teleport sets a one-frame "no interpolation" flag that NetworkTransform
            // honors on its next sync, so clients snap to the position instead.
            var netTransform = go.GetComponent<Unity.Netcode.Components.NetworkTransform>();
            if (netTransform != null)
            {
                netTransform.Teleport(spawnPos, Quaternion.identity, go.transform.localScale);
            }
        }

        // ----- Helpers ----------------------------------------------------

        // True if the named scene is a registered match map. Source of truth is
        // MapRegistry — any scene whose LevelData is in the registry counts. Falls back
        // to comparing against the legacy hardcoded SceneNames.Match when the registry
        // is missing (editor standalone-scene testing, etc.), so the original 2-player
        // workflow keeps working.
        private static bool IsMatchScene(string sceneName)
        {
            if (string.IsNullOrEmpty(sceneName)) return false;
            var registry = MapRegistry.Instance;
            if (registry != null && registry.ContainsScene(sceneName)) return true;
            // Fallback for editor testing without MainMenu (no MapRegistry persistent
            // instance). Keeps the old behavior intact.
            return sceneName == SceneNames.Match;
        }

        // Picks the builder prefab to spawn for this player. Race-specific takes
        // priority when (a) RaceRegistry is in the scene, (b) the player picked
        // a race, and (c) that race's RaceDefinition has a BuilderPrefab assigned.
        // Otherwise falls back to the inspector-assigned default.
        private GameObject ResolveBuilderPrefab()
        {
            var pms = GetComponent<PlayerMatchState>();
            if (pms != null && pms.RaceSelection != RaceId.None && RaceRegistry.Instance != null)
            {
                var raceDef = RaceRegistry.Instance.Get(pms.RaceSelection);
                if (raceDef != null && raceDef.BuilderPrefab != null)
                    return raceDef.BuilderPrefab;
            }
            return builderPrefab;
        }

        private static Vector3 ComputeZoneCentroid(PlayerSlot slot)
        {
            var loader = LevelLoader.Instance;
            if (loader == null || !loader.IsLoaded) return Vector3.zero;
            if (slot == PlayerSlot.None) return Vector3.zero;

            var levelData = loader.LevelData;
            if (levelData == null || levelData.OwnerGrid == null) return Vector3.zero;

            Vector2 sum = Vector2.zero;
            int count = 0;

            for (int y = 0; y < levelData.GridSize.y; y++)
            {
                for (int x = 0; x < levelData.GridSize.x; x++)
                {
                    int idx = y * levelData.GridSize.x + x;
                    if (levelData.OwnerGrid[idx] != slot) continue;

                    Vector2Int tile = new Vector2Int(
                        levelData.GridOriginTile.x + x,
                        levelData.GridOriginTile.y + y);
                    Vector3 world = GridCoordinates.GridToWorld(tile);
                    sum.x += world.x;
                    sum.y += world.z;
                    count++;
                }
            }

            if (count == 0) return Vector3.zero;
            return new Vector3(sum.x / count, GridCoordinates.BUILDABLE_PLANE_Y, sum.y / count);
        }
    }
}