using System.Collections.Generic;
using UnityEngine;

namespace TD.Core
{
    /// <summary>
    /// Pure math utility for converting between tile-grid coordinates and world-space
    /// coordinates, plus common grid helpers (neighbors, distance, adjacency).
    ///
    /// This class knows nothing about maps, walkability, zones, or game state. It only
    /// answers: "where in the world is tile (x, y)?" and "what tile contains world point P?"
    ///
    /// Conventions:
    ///   - Tiles are 1.0 world unit on each side (TILE_SIZE).
    ///   - Tiles are EDGE-ALIGNED: tile (N, N) occupies world XZ from (N, N) to (N+1, N+1),
    ///     with its center at (N+0.5, N+0.5). This makes integer-aligned BoxCollider bounds
    ///     align naturally with tile boundaries — a volume sized to N whole tiles at an
    ///     integer position covers exactly N tiles, with the rasterized tile geometry
    ///     matching the bounds rectangle exactly (no half-tile overhang).
    ///   - The grid lives on the XZ plane at Y = BUILDABLE_PLANE_Y. Grid-y maps to world-z.
    ///   - 4-connected (no diagonals).
    /// </summary>
    public static class GridCoordinates
    {
        // ----- Constants -----------------------------------------------------------

        /// <summary>World units per tile edge. Single source of truth for tile sizing.</summary>
        public const float TILE_SIZE = 1.0f;

        /// <summary>Y coordinate of the buildable plane in world space.</summary>
        public const float BUILDABLE_PLANE_Y = 0.0f;

        // ----- Core conversions ----------------------------------------------------

        /// <summary>
        /// Returns the world-space center of the given tile, on the buildable plane.
        /// Use this for placing towers, drawing ghost previews, and for A* path waypoints.
        /// Tile (N, N) is centered at world (N+0.5, N+0.5) since tiles occupy [N, N+1].
        /// </summary>
        public static Vector3 GridToWorld(Vector2Int gridPos)
        {
            return new Vector3(
                (gridPos.x + 0.5f) * TILE_SIZE,
                BUILDABLE_PLANE_Y,
                (gridPos.y + 0.5f) * TILE_SIZE);
        }

        /// <summary>
        /// Returns the tile that contains the given world position.
        /// The Y component of worldPos is ignored. Uses floor because tiles are
        /// edge-aligned — tile N occupies the half-open interval [N, N+1) on each axis,
        /// so any world point in that range floors to tile N.
        /// </summary>
        public static Vector2Int WorldToGrid(Vector3 worldPos)
        {
            return new Vector2Int(
                Mathf.FloorToInt(worldPos.x / TILE_SIZE),
                Mathf.FloorToInt(worldPos.z / TILE_SIZE));
        }

        /// <summary>
        /// Overload for callers that already have a 2D XZ position (e.g., UI raycast hits
        /// projected onto the buildable plane).
        /// </summary>
        public static Vector2Int WorldToGrid(Vector2 worldPosXZ)
        {
            return new Vector2Int(
                Mathf.FloorToInt(worldPosXZ.x / TILE_SIZE),
                Mathf.FloorToInt(worldPosXZ.y / TILE_SIZE));
        }

        // ----- Grid helpers --------------------------------------------------------

        /// <summary>
        /// True if two tiles share an edge (4-connected). Diagonal neighbors return false.
        /// </summary>
        public static bool IsAdjacent(Vector2Int a, Vector2Int b)
        {
            int dx = Mathf.Abs(a.x - b.x);
            int dy = Mathf.Abs(a.y - b.y);
            return (dx + dy) == 1;
        }

        /// <summary>
        /// Manhattan distance between two tiles. This is the natural admissible
        /// heuristic for A* on a 4-connected grid with uniform step cost.
        /// </summary>
        public static int ManhattanDistance(Vector2Int a, Vector2Int b)
        {
            return Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y);
        }

        /// <summary>
        /// Yields the four cardinal neighbors of the given tile (N, E, S, W).
        /// Does NOT check walkability or map bounds — that is the caller's job.
        /// </summary>
        /// <remarks>
        /// This is the "edge-adjacency" primitive — two tiles are 4-connected if and
        /// only if they share an edge. Used by bake-time checks that test "is this
        /// volume adjacent to that volume" (goal-adjacency, leak-exit adjacency, etc.)
        /// where edge-sharing is the semantic — NOT for pathfinding. For pathing use
        /// <see cref="GetNeighbors8"/>.
        /// </remarks>
        public static IEnumerable<Vector2Int> GetNeighbors(Vector2Int tile)
        {
            yield return new Vector2Int(tile.x,     tile.y + 1); // North (+z)
            yield return new Vector2Int(tile.x + 1, tile.y);     // East  (+x)
            yield return new Vector2Int(tile.x,     tile.y - 1); // South (-z)
            yield return new Vector2Int(tile.x - 1, tile.y);     // West  (-x)
        }

        /// <summary>
        /// Yields all eight neighbors of the given tile — the four cardinals plus
        /// the four diagonals (NE, SE, SW, NW). Cardinals are yielded first so
        /// equal-cost ordering favors cardinal moves at tie-breaks. Does NOT check
        /// walkability or map bounds.
        /// </summary>
        /// <remarks>
        /// Used by pathfinding (A*, BFS) for 8-connected movement. Callers must
        /// check <see cref="IsDiagonal"/> for each yielded neighbor and reject the
        /// move when either of the two "shoulder" cardinal tiles is non-walkable
        /// (corner-cut prevention) — see <see cref="GetCornerShoulders"/>.
        /// </remarks>
        public static IEnumerable<Vector2Int> GetNeighbors8(Vector2Int tile)
        {
            // Cardinals first.
            yield return new Vector2Int(tile.x,     tile.y + 1); // N
            yield return new Vector2Int(tile.x + 1, tile.y);     // E
            yield return new Vector2Int(tile.x,     tile.y - 1); // S
            yield return new Vector2Int(tile.x - 1, tile.y);     // W
            // Diagonals.
            yield return new Vector2Int(tile.x + 1, tile.y + 1); // NE
            yield return new Vector2Int(tile.x + 1, tile.y - 1); // SE
            yield return new Vector2Int(tile.x - 1, tile.y - 1); // SW
            yield return new Vector2Int(tile.x - 1, tile.y + 1); // NW
        }

        /// <summary>
        /// True if <paramref name="from"/> → <paramref name="to"/> is a single diagonal
        /// step (both X and Y differ by exactly 1). Both tiles assumed to be 8-neighbors.
        /// </summary>
        public static bool IsDiagonal(Vector2Int from, Vector2Int to)
        {
            return Mathf.Abs(from.x - to.x) == 1 && Mathf.Abs(from.y - to.y) == 1;
        }

        /// <summary>
        /// For a diagonal step <paramref name="from"/> → <paramref name="to"/>, returns the
        /// two cardinal "shoulder" tiles. To prevent corner-cutting (squeezing through a
        /// 1-tile diagonal gap between walls), pathfinders should reject the diagonal step
        /// if either shoulder is non-walkable.
        /// </summary>
        public static void GetCornerShoulders(Vector2Int from, Vector2Int to,
                                              out Vector2Int shoulderA, out Vector2Int shoulderB)
        {
            shoulderA = new Vector2Int(to.x, from.y); // step in X first
            shoulderB = new Vector2Int(from.x, to.y); // step in Y first
        }

        /// <summary>
        /// Cost of stepping from <paramref name="from"/> to its 8-neighbor <paramref name="to"/>.
        /// Returns 1.0 for cardinal moves, √2 for diagonals. Used by 8-connected A*.
        /// </summary>
        public static float StepCost(Vector2Int from, Vector2Int to)
        {
            return IsDiagonal(from, to) ? 1.41421356f : 1f;
        }

        /// <summary>
        /// Octile distance between two tiles. Admissible heuristic for 8-connected A* on
        /// a uniform-cost grid (cardinal cost 1, diagonal cost √2). Equivalent to:
        /// <c>max(|dx|, |dy|) + (√2 - 1) * min(|dx|, |dy|)</c>.
        /// </summary>
        public static float OctileDistance(Vector2Int a, Vector2Int b)
        {
            int dx = Mathf.Abs(a.x - b.x);
            int dy = Mathf.Abs(a.y - b.y);
            int max = Mathf.Max(dx, dy);
            int min = Mathf.Min(dx, dy);
            return max + 0.41421356f * min;
        }

        // ----- Footprint helpers ---------------------------------------------------
        //
        // Towers occupy a footprint of N x M tiles anchored at a single tile coordinate.
        // The anchor is the SOUTHWEST (minimum-x, minimum-y) corner of the footprint.
        // For a 2x2 tower at anchor (5, 7), the footprint covers:
        //   (5, 7), (6, 7), (5, 8), (6, 8)

        /// <summary>
        /// Yields every tile covered by a footprint of the given size, anchored
        /// at the given tile (anchor is the southwest corner of the footprint).
        /// </summary>
        public static IEnumerable<Vector2Int> GetFootprintTiles(Vector2Int anchor, Vector2Int footprintSize)
        {
            for (int dx = 0; dx < footprintSize.x; dx++)
            {
                for (int dy = 0; dy < footprintSize.y; dy++)
                {
                    yield return new Vector2Int(anchor.x + dx, anchor.y + dy);
                }
            }
        }

        /// <summary>
        /// Returns the world-space center of a footprint anchored at the given tile.
        /// For a 2x2 footprint at anchor (5, 7) with TILE_SIZE = 1.0, returns (6, 0, 8) —
        /// the geometric center of the four tiles (5,7),(6,7),(5,8),(6,8), which span
        /// world XZ [5, 7]. Use this to position the tower's visual GameObject so it sits
        /// centered on its footprint rather than on the anchor tile's center.
        /// </summary>
        public static Vector3 GetFootprintCenterWorld(Vector2Int anchor, Vector2Int footprintSize)
        {
            float centerOffsetX = (footprintSize.x - 1) * 0.5f * TILE_SIZE;
            float centerOffsetZ = (footprintSize.y - 1) * 0.5f * TILE_SIZE;
            Vector3 anchorWorld = GridToWorld(anchor);
            return new Vector3(
                anchorWorld.x + centerOffsetX,
                anchorWorld.y,
                anchorWorld.z + centerOffsetZ);
        }
    }
}
