using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Tilemaps;

/// <summary>
/// GAME 1303 Tier 5 — Builds a maze from a text layout. Weenie Maze; also handy for
/// Last Delivery's street grid, or any level you'd rather type than click.
///
/// WHY: drawing a maze tile by tile in the Scene view is slow to iterate on. With this,
/// a level is a block of text you can edit in seconds and even paste from a spreadsheet.
///
/// LAYOUT FORMAT (one character per cell, top row first)
///     ###########
///     #.........#
///     #.###.###.#
///     #....P....#
///     #.###.###.#
///     #.........#
///     ###########
///   #  wall          .  or space  empty
///   P  player start  T  treat spawn point   E  exit
///   1-9 extra markers you can map to any prefab (monsters, doors, pickups)
///
/// SETUP
///  1. Empty GameObject + this script.
///  2. Either paste the layout into Layout Text (multi-line box in the Inspector) or drag
///     in a TextAsset (.txt file in your project — easier for big mazes).
///  3. Assign either a Wall Tilemap + Wall Tile (fast, tidy) OR a Wall Prefab (simple).
///  4. Cell Size must match your GridMover's Cell Size.
///  5. Marker Prefabs: add entries mapping a character to a prefab. 'P' moves the existing
///     player instead of spawning one, unless you assign a player prefab to it.
///  6. Build On Start is on by default. Call Build() again from a button to rebuild.
///
/// The maze is centred on this object, so put it at the origin and the middle of your
/// layout lands there.
/// </summary>
public class MazeLoader : MonoBehaviour
{
    [System.Serializable]
    public class MarkerPrefab
    {
        [Tooltip("Single character in the layout, e.g. T")]
        public string character = "T";
        public GameObject prefab;
        [Tooltip("Leave the object where the maze places it, or just record the position?")]
        public bool spawnPrefab = true;
    }

    [Header("Layout (use one)")]
    [TextArea(6, 20)] public string layoutText = "";
    public TextAsset layoutFile;

    [Header("Grid")]
    public float cellSize = 1f;
    public bool centreOnThisObject = true;

    [Header("Walls (use one)")]
    public Tilemap wallTilemap;
    public TileBase wallTile;
    public GameObject wallPrefab;
    public char wallChar = '#';

    [Header("Markers")]
    public List<MarkerPrefab> markerPrefabs = new List<MarkerPrefab>();
    public char playerChar = 'P';
    [Tooltip("Move the existing tagged Player to the P cell instead of spawning one.")]
    public bool movePlayerToStart = true;

    [Header("Build")]
    public bool buildOnStart = true;
    public Transform wallParent;

    [Header("Events")]
    public UnityEvent OnBuilt;

    public Vector3 PlayerStart { get; private set; }
    public int Width { get; private set; }
    public int Height { get; private set; }
    /// <summary>Every position found for a given marker character, after Build().</summary>
    public Dictionary<char, List<Vector3>> MarkerPositions { get; private set; } = new Dictionary<char, List<Vector3>>();

    private readonly List<GameObject> spawned = new List<GameObject>();

    void Start()
    {
        if (buildOnStart) Build();
    }

    public void Build()
    {
        Clear();

        string raw = layoutFile != null ? layoutFile.text : layoutText;
        if (string.IsNullOrWhiteSpace(raw)) { Debug.LogWarning("MazeLoader: no layout given."); return; }

        string[] rows = raw.Replace("\r", "").Split('\n');
        // Drop trailing blank lines
        int last = rows.Length - 1;
        while (last > 0 && rows[last].Trim().Length == 0) last--;
        Height = last + 1;
        Width = 0;
        for (int i = 0; i <= last; i++) Width = Mathf.Max(Width, rows[i].Length);

        Vector3 origin = transform.position;
        if (centreOnThisObject)
            origin -= new Vector3((Width - 1) * cellSize * 0.5f, -(Height - 1) * cellSize * 0.5f, 0f);

        for (int y = 0; y <= last; y++)
        {
            string row = rows[y];
            for (int x = 0; x < row.Length; x++)
            {
                char c = row[x];
                if (c == ' ' || c == '.') continue;

                // Row 0 is the TOP, so y goes negative as we read down
                Vector3 pos = origin + new Vector3(x * cellSize, -y * cellSize, 0f);

                if (c == wallChar) { PlaceWall(pos); continue; }

                if (!MarkerPositions.ContainsKey(c)) MarkerPositions[c] = new List<Vector3>();
                MarkerPositions[c].Add(pos);

                if (c == playerChar)
                {
                    PlayerStart = pos;
                    if (movePlayerToStart)
                    {
                        var p = GameObject.FindGameObjectWithTag("Player");
                        if (p != null) p.transform.position = pos;
                    }
                }

                foreach (var m in markerPrefabs)
                {
                    if (string.IsNullOrEmpty(m.character) || m.character[0] != c) continue;
                    if (m.spawnPrefab && m.prefab != null)
                        spawned.Add(Instantiate(m.prefab, pos, Quaternion.identity, transform));
                }
            }
        }

        OnBuilt?.Invoke();
    }

    void PlaceWall(Vector3 pos)
    {
        if (wallTilemap != null && wallTile != null)
        {
            Vector3Int cell = wallTilemap.WorldToCell(pos);
            wallTilemap.SetTile(cell, wallTile);
        }
        else if (wallPrefab != null)
        {
            var parent = wallParent != null ? wallParent : transform;
            spawned.Add(Instantiate(wallPrefab, pos, Quaternion.identity, parent));
        }
    }

    public void Clear()
    {
        foreach (var o in spawned) if (o != null) DestroyImmediate(o);
        spawned.Clear();
        MarkerPositions.Clear();
        if (wallTilemap != null) wallTilemap.ClearAllTiles();
    }

    /// <summary>All positions for a marker, e.g. GetPositions('T') for treat spawn points.</summary>
    public List<Vector3> GetPositions(char marker) =>
        MarkerPositions.TryGetValue(marker, out var list) ? list : new List<Vector3>();
}