using System;
using UnityEngine;
using TD.Core;

namespace TD.Levels
{
    /// <summary>
    /// Baked level data. Authored in a Unity scene via volume MonoBehaviours; produced by
    /// <see cref="LevelAuthoring.BakeLevelData"/>; consumed at runtime as the canonical map
    /// definition.
    /// </summary>
    /// <remarks>
    /// LevelData is the canonical map identity. The lobby browses LevelData assets; loading a
    /// map = read LevelData → load the referenced scene at <see cref="ScenePath"/>.
    ///
    /// All flat grid arrays use row-major indexing: <c>grid[y * GridSize.x + x]</c>.
    /// Origin-relative: <see cref="GridOriginTile"/> is the world-tile coordinate that grid index
    /// (0,0) corresponds to. Convert world-tile to grid-index via
    /// <c>worldTile - GridOriginTile</c>.
    /// </remarks>
    [CreateAssetMenu(fileName = "LevelData", menuName = "TD/Level Data", order = 1)]
    public class LevelData : ScriptableObject
    {
        // -------------------------------------------------------------------
        // Lobby-facing metadata.
        // -------------------------------------------------------------------

        [Header("Map Metadata")]
        public string MapName;
        public int PlayerCount;
        public string MapDescription;
        public string Author;

        [Tooltip("Auto-generated by bake. Use the Refresh Thumbnail button on LevelAuthoring to " +
                 "re-render without doing a full bake.")]
        public Sprite MapThumbnail;

        [Tooltip("Path to the scene this LevelData was baked from. Auto-populated by bake.")]
        public string ScenePath;

        /// <summary>
        /// Just the scene name (no path, no extension) — what NetworkManager.SceneManager.LoadScene
        /// expects. Derived from <see cref="ScenePath"/>; returns empty string if ScenePath is unset.
        /// </summary>
        public string SceneName =>
            string.IsNullOrEmpty(ScenePath)
                ? string.Empty
                : System.IO.Path.GetFileNameWithoutExtension(ScenePath);

        // -------------------------------------------------------------------
        // Bake metadata (used for dirty-detection and diagnostic display).
        // -------------------------------------------------------------------

        [Header("Bake Metadata")]
        [Tooltip("Hash of the authoring inputs at last bake. Compared at Play-mode entry to detect " +
                 "unbaked changes. Captures volumes + LevelAuthoring metadata; does NOT capture " +
                 "visual scene content.")]
        public string AuthoringHash;

        [Tooltip("ISO 8601 UTC timestamp of the last successful bake.")]
        public string LastBakeTimestamp;

        public BakeOutcome LastBakeOutcome;
        public int LastBakeWarningCount;

        // -------------------------------------------------------------------
        // Grid metadata.
        // -------------------------------------------------------------------

        [Header("Grid")]
        [Tooltip("World-tile coordinate that grid index (0,0) corresponds to. Origin-relative " +
                 "addressing: worldTile - GridOriginTile gives the index into the flat arrays.")]
        public Vector2Int GridOriginTile;

        [Tooltip("Width × height of the grid in tiles.")]
        public Vector2Int GridSize;

        // -------------------------------------------------------------------
        // Per-tile flat arrays. All indexed as grid[y * GridSize.x + x].
        // -------------------------------------------------------------------

        [Tooltip("Per-tile placement state: Outside / Buildable / Restricted. Length = GridSize.x * GridSize.y.")]
        public PlacementState[] PlacementGrid;

        [Tooltip("Per-tile initial walkability (does NOT account for towers — they stamp footprints " +
                 "at runtime). Length = GridSize.x * GridSize.y.")]
        public bool[] WalkabilityGrid;

        [Tooltip("Per-tile owning player slot. PlayerSlot.None for tiles not in any player zone. " +
                 "Length = GridSize.x * GridSize.y.")]
        public PlayerSlot[] OwnerGrid;

        [Tooltip("Per-tile map-area membership. True if the tile is part of the playable map " +
                 "(inside any MapAreaVolume); false for void tiles outside the map. This is the " +
                 "outermost gating layer — gameplay queries (placement, ownership, walkability) " +
                 "are only meaningful for tiles where MapAreaGrid is true. Length = GridSize.x * " +
                 "GridSize.y.")]
        public bool[] MapAreaGrid;

        // -------------------------------------------------------------------
        // Per-zone and per-goal structures (populated by bake from volume data).
        // -------------------------------------------------------------------

        [Tooltip("One entry per declared player zone, sorted by Owner.")]
        public PlayerZoneData[] PlayerZones;

        [Tooltip("One entry per GoalVolume, sorted by min tile coordinate.")]
        public GoalData[] Goals;
    }

    /// <summary>
    /// Per-zone baked data. The runtime can use this to enumerate a player's spawners and leak
    /// exits without scanning the full grid.
    /// </summary>
    [Serializable]
    public class PlayerZoneData
    {
        public PlayerSlot Owner;

        [Tooltip("Spawners in this zone, sorted by SpawnerIdInZone.")]
        public SpawnerData[] Spawners;

        [Tooltip("Leak exits OUT OF this zone, sorted by Target enum value. Empty for the final " +
                 "defender (whose zone is goal-adjacent and has no leak exit).")]
        public LeakExitData[] LeakExits;
    }

    [Serializable]
    public class SpawnerData
    {
        public int SpawnerIdInZone;

        [Tooltip("Tile coordinate of the spawner's center (its volume's bounds center, projected to grid).")]
        public Vector2Int TilePosition;

        [Tooltip("Full tile coverage of the spawner volume.")]
        public Vector2Int[] TileArea;

        public Direction Facing;
    }

    [Serializable]
    public class LeakExitData
    {
        public PlayerSlot Target;

        [Tooltip("Full tile coverage of the leak exit volume.")]
        public Vector2Int[] TileArea;

        [Tooltip("Weight normalized so all leak exits sharing the same source zone sum to 1.0.")]
        public float NormalizedWeight;
    }

    [Serializable]
    public class GoalData
    {
        [Tooltip("Full tile coverage of the goal volume.")]
        public Vector2Int[] TileArea;
    }
}