using UnityEngine;
using TD.Core;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace TD.Levels
{
    /// <summary>
    /// Authoring volume marking the boundary where enemies leak from one player's zone into
    /// another's. Leak exit tiles are <see cref="PlacementState.Restricted"/> in the baked grid
    /// but remain walkable.
    /// </summary>
    /// <remarks>
    /// Leak topology is recorded as metadata in the baked <see cref="LevelData"/> for lobby UI
    /// and future wave-balancing systems, but the runtime gameplay loop does not depend on it —
    /// enemies pathfind on the unified walkability grid and naturally cross zone boundaries.
    ///
    /// Final defenders (the player whose zone is goal-adjacent) do NOT have a LeakExitVolume.
    /// They are identified by zone-to-goal adjacency at bake time.
    ///
    /// Multiple leak exits with the same source zone may exist (e.g., Player 5 in the 9-player
    /// map splits leaks 50/50 between Player 4 and Player 6). The <see cref="weight"/> field
    /// controls the split. The bake normalizes weights to sum to 1.0 across a source zone's
    /// leak exits.
    /// </remarks>
    public class LeakExitVolume : VolumeBase
    {
        [Tooltip("Which player's zone this leak exits FROM.")]
        public PlayerSlot sourceZone = PlayerSlot.Player1;

        [Tooltip("Which player's zone this leak feeds INTO.")]
        public PlayerSlot target = PlayerSlot.Player2;

        [Tooltip("Relative weight for split exits. Bake normalizes weights to sum to 1.0 across all " +
                 "leak exits sharing the same source zone. Set both leaks' weights to 1.0 for a 50/50 split.")]
        public float weight = 1.0f;

        [Tooltip("Whether tiles in this volume are buildable. Defaults to Invalid.")]
        public PlacementValidity placementValidity = PlacementValidity.Invalid;

        // Leak exits draw above player zones but below spawners.
        private const float FillYLevel = 0.04f;
        private const float ArrowYLevel = 0.05f;
        private const float ArrowLength = 1.5f;

        protected override bool GetAlwaysShowToggle(LevelAuthoring authoring)
        {
            return authoring.alwaysShowLeakExits;
        }

        private void OnDrawGizmosSelected()
        {
            DrawGizmosCore();
        }

        private void OnDrawGizmos()
        {
            if (ShouldDrawAlwaysOn())
            {
                DrawGizmosCore();
            }
        }

        private void DrawGizmosCore()
        {
            // Leak exit fill uses the SOURCE zone's color (visually attaches the leak to the zone
            // it leaves). The target is conveyed by the arrow direction and label.
            Color baseColor = PlayerColors.Get(sourceZone);
            DrawTileCoverageFill(baseColor, alpha: 0.40f, yLevel: FillYLevel);
            DrawRectangularOutline(baseColor, yLevel: FillYLevel);

            // Arrow points from this leak exit's center toward the target zone's center.
            var col = Collider;
            if (col != null)
            {
                Vector3 origin = new Vector3(col.bounds.center.x, ArrowYLevel, col.bounds.center.z);
                Vector3 targetCenter = FindTargetZoneCenter();
                if (targetCenter.sqrMagnitude > 0f) // sentinel: zero means "not found"
                {
                    Vector3 directionXZ = new Vector3(targetCenter.x - origin.x, 0f, targetCenter.z - origin.z);
                    DrawArrow(origin, directionXZ, ArrowLength, baseColor);
                }
            }

#if UNITY_EDITOR
            if (col != null)
            {
                Vector3 labelPos = new Vector3(col.bounds.center.x, ArrowYLevel + 0.1f, col.bounds.center.z);
                Handles.Label(labelPos, $"→ {FormatPlayerName(target)}");
            }
#endif
        }

        /// <summary>
        /// Computes the world-space center of the target zone by averaging the bounds-centers
        /// of all PlayerZoneVolumes whose <see cref="PlayerZoneVolume.owner"/> matches
        /// <see cref="target"/>. Returns <c>Vector3.zero</c> if no matching zone is found —
        /// the caller treats zero as a "not found" sentinel.
        /// </summary>
        /// <remarks>
        /// Performs a scene-wide query each gizmo draw. This is fine for our worst case
        /// (9-player map: 8 leak exits × ~10 zone volumes = ~80 considerations per frame, only
        /// when gizmos are drawing). If this ever shows up as an editor-perf issue, switch to a
        /// cached lookup invalidated on hierarchy change.
        /// </remarks>
        private Vector3 FindTargetZoneCenter()
        {
            var zones = Object.FindObjectsByType<PlayerZoneVolume>(FindObjectsInactive.Exclude);
            if (zones == null || zones.Length == 0) return Vector3.zero;

            Vector3 accumulated = Vector3.zero;
            int count = 0;
            for (int i = 0; i < zones.Length; i++)
            {
                if (zones[i] == null) continue;
                if (zones[i].owner != target) continue;

                var c = zones[i].GetComponent<BoxCollider>();
                if (c == null) continue;

                accumulated += c.bounds.center;
                count++;
            }

            if (count == 0) return Vector3.zero;
            return accumulated / count;
        }
    }
}