using System.Collections.Generic;
using UnityEngine;
using TD.Core;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace TD.Levels
{
    /// <summary>
    /// Scene-level coordinator for level authoring. One per scene, attached to an empty
    /// GameObject named <c>_LevelAuthoring</c> whose subtree contains all the volumes for the map.
    /// </summary>
    /// <remarks>
    /// LevelAuthoring is the entry point for the bake operation: the custom inspector exposes a
    /// "Bake LevelData" button that triggers the seven-phase algorithm, writing into the
    /// <see cref="targetAsset"/> ScriptableObject.
    ///
    /// This class also owns the per-volume-type "always show gizmos" toggles. Each volume walks
    /// its parent hierarchy to find this LevelAuthoring and reads the relevant toggle.
    ///
    /// In the current session the bake methods are stubs — only the field schema and gizmo
    /// rendering are implemented. The full bake script is the next session's work.
    /// </remarks>
    public class LevelAuthoring : MonoBehaviour
    {
        // -------------------------------------------------------------------
        // Bake target and lobby-facing metadata.
        // -------------------------------------------------------------------

        [Header("Bake Target")]
        [Tooltip("The ScriptableObject this scene bakes into. Must be set before baking.")]
        public LevelData targetAsset;

        [Header("Map Metadata")]
        [Tooltip("Display name for the lobby UI.")]
        public string mapName = "";

        [Tooltip("Number of players this map is designed for. Used by the lobby to filter maps " +
                 "and by bake validation. Allowed values: 1, 2, 3, 4, 5, 9.")]
        public int playerCount = 1;

        [Tooltip("Expected number of GoalVolume instances. Bake validates the count matches.")]
        public int expectedGoalCount = 1;

        [Tooltip("Optional description for the lobby UI. Empty triggers a soft warning at bake time.")]
        [TextArea(2, 5)]
        public string mapDescription = "";

        [Tooltip("Optional author/credit text. Empty triggers a soft warning at bake time.")]
        public string author = "";

        // -------------------------------------------------------------------
        // Gizmo visibility toggles. Each volume type reads its own toggle to decide whether to
        // draw always-on or only-when-selected.
        // -------------------------------------------------------------------

        [Header("Gizmo Visibility (Always-Show Toggles)")]
        [Tooltip("If true, PlayerZoneVolume gizmos draw whether or not the volume is selected.")]
        public bool alwaysShowPlayerZones = false;

        [Tooltip("If true, SpawnerVolume gizmos draw whether or not the volume is selected.")]
        public bool alwaysShowSpawners = false;

        [Tooltip("If true, LeakExitVolume gizmos draw whether or not the volume is selected.")]
        public bool alwaysShowLeakExits = false;

        [Tooltip("If true, GoalVolume gizmos draw whether or not the volume is selected.")]
        public bool alwaysShowGoals = false;

        [Tooltip("If true, MapAreaVolume gizmos draw whether or not the volume is selected. " +
                 "Defaults to true — the map boundary is generally useful to see while authoring " +
                 "the rest of the map, not just when the MapAreaVolume itself is selected.")]
        public bool alwaysShowMapArea = true;

        // -------------------------------------------------------------------
        // Bake API
        // -------------------------------------------------------------------
        // The bake operation is implemented in TD.Levels.Editor.LevelBakePipeline (in the Editor
        // assembly). It cannot be exposed as a method on this runtime class because the runtime
        // assembly cannot reference types in the Editor assembly. The custom inspector's "Bake
        // LevelData" button invokes the pipeline directly.

        // -------------------------------------------------------------------
        // Map-level gizmos: origin marker, map bounding rect, combined player zone outlines.
        // Always draw when LevelAuthoring is in the scene (no per-toggle gating for these).
        // -------------------------------------------------------------------

        private const float OriginMarkerY = 0.005f;
        private const float MapBoundsY = 0.01f;
        private const float CombinedZoneOutlineY = 0.03f;
        private const float OriginMarkerSize = 0.4f;

        private void OnDrawGizmos()
        {
            DrawOriginMarker();
            DrawMapBoundingRect();
            DrawCombinedPlayerZoneOutlines();
        }

        private void DrawOriginMarker()
        {
            Color prev = Gizmos.color;
            Gizmos.color = Color.white;

            // A small "+" cross at world (0,0) plus a sphere for visibility.
            Vector3 origin = new Vector3(0f, OriginMarkerY, 0f);
            Gizmos.DrawSphere(origin, 0.1f);
            Gizmos.DrawLine(origin + new Vector3(-OriginMarkerSize, 0f, 0f),
                            origin + new Vector3(OriginMarkerSize, 0f, 0f));
            Gizmos.DrawLine(origin + new Vector3(0f, 0f, -OriginMarkerSize),
                            origin + new Vector3(0f, 0f, OriginMarkerSize));

            Gizmos.color = prev;

#if UNITY_EDITOR
            Handles.Label(origin + new Vector3(0.15f, 0.05f, 0.15f), "World Origin (0,0)");
#endif
        }

        private void DrawMapBoundingRect()
        {
            // Find every VolumeBase in this LevelAuthoring's subtree (matches the bake's scoped scan)
            // and union their tile bounding rectangles.
            var volumes = GetComponentsInChildren<VolumeBase>(includeInactive: false);
            if (volumes == null || volumes.Length == 0) return;

            bool initialized = false;
            Vector2Int minTile = Vector2Int.zero;
            Vector2Int maxTile = Vector2Int.zero;

            for (int i = 0; i < volumes.Length; i++)
            {
                var col = volumes[i] != null ? volumes[i].GetComponent<BoxCollider>() : null;
                if (col == null) continue;

                if (!VolumeBase.TryGetTightTileRect(col.bounds, out Vector2Int vMin, out Vector2Int vMax))
                {
                    continue; // volume covers no tiles (e.g., bounds don't intersect Y=0)
                }

                if (!initialized)
                {
                    minTile = vMin;
                    maxTile = vMax;
                    initialized = true;
                }
                else
                {
                    if (vMin.x < minTile.x) minTile.x = vMin.x;
                    if (vMin.y < minTile.y) minTile.y = vMin.y;
                    if (vMax.x > maxTile.x) maxTile.x = vMax.x;
                    if (vMax.y > maxTile.y) maxTile.y = vMax.y;
                }
            }

            if (!initialized) return;

            // Convert tile-corner indices to world-space corners. Tiles are edge-aligned with
            // TILE_SIZE = 1, so a tile at (x,y) spans world XZ from (x, y) to (x+1, y+1).
            float tileSize = GridCoordinates.TILE_SIZE;
            Vector3 sw = new Vector3(minTile.x * tileSize, MapBoundsY, minTile.y * tileSize);
            Vector3 se = new Vector3((maxTile.x + 1) * tileSize, MapBoundsY, minTile.y * tileSize);
            Vector3 ne = new Vector3((maxTile.x + 1) * tileSize, MapBoundsY, (maxTile.y + 1) * tileSize);
            Vector3 nw = new Vector3(minTile.x * tileSize, MapBoundsY, (maxTile.y + 1) * tileSize);

            Color prev = Gizmos.color;
            Gizmos.color = new Color(1f, 1f, 1f, 0.6f); // muted white

            Gizmos.DrawLine(sw, se);
            Gizmos.DrawLine(se, ne);
            Gizmos.DrawLine(ne, nw);
            Gizmos.DrawLine(nw, sw);

            // Map SW corner marker — a small filled square so it reads distinctly from the
            // world-origin sphere+cross when both are visible. Sized slightly above the bounds
            // line so it doesn't z-fight.
            Vector3 swMarkerCenter = new Vector3(sw.x, MapBoundsY + 0.005f, sw.z);
            Gizmos.color = new Color(1f, 0.85f, 0.2f, 0.9f); // amber, distinct from origin's white
            Gizmos.DrawCube(swMarkerCenter, new Vector3(0.25f, 0.01f, 0.25f));

            Gizmos.color = prev;

#if UNITY_EDITOR
            // Place label slightly inside the map bounds (NE of the corner) so it doesn't get
            // covered by the world-origin label when the map is aligned.
            Handles.Label(swMarkerCenter + new Vector3(0.2f, 0.05f, 0.2f),
                          $"Map SW: tile ({minTile.x}, {minTile.y})");
#endif
        }

        private void DrawCombinedPlayerZoneOutlines()
        {
            // Group all PlayerZoneVolumes in this subtree by owner, then for each owner compute
            // the union of their tile coverage and draw the perimeter using the general
            // (non-rectangular) algorithm. This visualizes L-shaped multi-volume zones as a
            // single unified outline.
            var zones = GetComponentsInChildren<PlayerZoneVolume>(includeInactive: false);
            if (zones == null || zones.Length == 0) return;

            // Build per-owner tile sets.
            var perOwner = new Dictionary<PlayerSlot, HashSet<Vector2Int>>();
            for (int i = 0; i < zones.Length; i++)
            {
                var z = zones[i];
                if (z == null) continue;

                var col = z.GetComponent<BoxCollider>();
                if (col == null) continue;

                if (!perOwner.TryGetValue(z.owner, out var set))
                {
                    set = new HashSet<Vector2Int>();
                    perOwner[z.owner] = set;
                }

                // Use the shared rasterizer so this stays in lock-step with what the bake will see.
                VolumeBase.RasterizeBoundsToTiles(col.bounds, t => set.Add(t));
            }

            // For each owner, draw perimeter using the general algorithm. Tile (x,y) spans
            // world XZ from (x, y) to (x+1, y+1) (edge-aligned, TILE_SIZE = 1).
            float tileSize = GridCoordinates.TILE_SIZE;

            Color prev = Gizmos.color;
            foreach (var kv in perOwner)
            {
                Color outlineColor = PlayerColors.Get(kv.Key);
                Gizmos.color = outlineColor;

                foreach (var tile in kv.Value)
                {
                    // World-space corners of this tile.
                    Vector3 sw = new Vector3(tile.x * tileSize, CombinedZoneOutlineY, tile.y * tileSize);
                    Vector3 se = new Vector3((tile.x + 1) * tileSize, CombinedZoneOutlineY, tile.y * tileSize);
                    Vector3 ne = new Vector3((tile.x + 1) * tileSize, CombinedZoneOutlineY, (tile.y + 1) * tileSize);
                    Vector3 nw = new Vector3(tile.x * tileSize, CombinedZoneOutlineY, (tile.y + 1) * tileSize);

                    // For each of the four edges, draw it ONLY if the neighbor across that edge
                    // is not in the covered set (i.e., the edge is on the perimeter).
                    if (!kv.Value.Contains(new Vector2Int(tile.x, tile.y - 1))) Gizmos.DrawLine(sw, se); // south edge
                    if (!kv.Value.Contains(new Vector2Int(tile.x + 1, tile.y))) Gizmos.DrawLine(se, ne); // east edge
                    if (!kv.Value.Contains(new Vector2Int(tile.x, tile.y + 1))) Gizmos.DrawLine(ne, nw); // north edge
                    if (!kv.Value.Contains(new Vector2Int(tile.x - 1, tile.y))) Gizmos.DrawLine(nw, sw); // west edge
                }
            }
            Gizmos.color = prev;
        }
    }
}