using UnityEngine;
using UnityEngine.Tilemaps;

public class FogOfWar : MonoBehaviour
{
    public Tilemap fogTilemap;
    public TileBase fogTile;
    public Transform player;
    public int viewRadius = 5;
    public Camera mainCamera;

    private Vector3Int lastPlayerCell;
    private const float UpdateInterval = 0.1f; // Update every 100ms
    private float updateTimer;
    private TileBase[,] discoveredFog; // Store the lowest opacity fog level for each tile

    [System.Serializable]
    public class FogLevel
    {
        public TileBase tile;
        public float opacity;
    }
    public FogLevel[] fogLevels;

    private void Start()
    {
        if (mainCamera == null)
            mainCamera = Camera.main;
        // Initialize the discovered fog array
        int fogDepth = GenerateTileMap.groundDepth - 1; // Subtract 2 to account for array starting at y=-3
        discoveredFog = new TileBase[GenerateTileMap.maxWidth + 1, fogDepth];
        // Initialize fog using GenerateTileMap bounds
        for (int x = 0; x <= GenerateTileMap.maxWidth; x++)
        {
            // Clear surface tiles (0 to -1)
            for (int y = 0; y >= -1; y--)
            {
                Vector3Int tilePosition = new Vector3Int(x, y, 0);
                fogTilemap.SetTile(tilePosition, null);
            }

            // Set fog for underground tiles (-2 and below)
            for (int y = -2; y >= -GenerateTileMap.groundDepth; y--)
            {
                Vector3Int tilePosition = new Vector3Int(x, y, 0);
                fogTilemap.SetTile(tilePosition, fogTile);

                // Convert to array index (y=-2 maps to array index 0)
                int arrayY = Mathf.Abs(y + 2);
                discoveredFog[x, arrayY] = fogTile;
            }
        }

        // Position the fog tilemap at the same position as the main tilemap
        fogTilemap.transform.position = new Vector3(
            (GenerateTileMap.maxWidth / 2) * -1,
            -1,
            transform.position.z
        );
        lastPlayerCell = fogTilemap.WorldToCell(player.position);

        if (SaveSystem.isGameLoaded)
        {
            SaveDataMap mapState = SaveSystem.LoadMapState();
            if (mapState != null && mapState.fogOfWarData != null)
            {
                GetComponent<FogOfWar>().LoadFromSaveData(mapState.fogOfWarData);
            }
        }
    }

    private void Update()
    {
        updateTimer += Time.deltaTime;
        if (updateTimer < UpdateInterval)
            return;
        updateTimer = 0;
        Vector3Int playerCell = fogTilemap.WorldToCell(player.position);

        // Only update if player moved to a new cell
        if (playerCell == lastPlayerCell)
            return;

        lastPlayerCell = playerCell;

        // Get camera bounds in world space
        Vector2 cameraMin = mainCamera.ViewportToWorldPoint(new Vector3(0, 0));
        Vector2 cameraMax = mainCamera.ViewportToWorldPoint(new Vector3(1, 1));
        // Convert to tile coordinates
        Vector3Int tileMin = fogTilemap.WorldToCell(cameraMin);
        Vector3Int tileMax = fogTilemap.WorldToCell(cameraMax);
        // Clamp to map bounds
        tileMin.x = Mathf.Max(0, tileMin.x - viewRadius);
        tileMin.y = Mathf.Max(-GenerateTileMap.groundDepth, tileMin.y - viewRadius);
        tileMax.x = Mathf.Min(GenerateTileMap.maxWidth, tileMax.x + viewRadius);
        tileMax.y = Mathf.Min(-2, tileMax.y + viewRadius);

        // Clear fog around player
        // Only check tiles within camera view
        for (int x = tileMin.x; x <= tileMax.x; x++)
        {
            for (int y = tileMin.y; y <= tileMax.y; y++)
            {
                Vector3Int tilePosition = new Vector3Int(x, y, 0);
                float distance = Vector2.Distance(((Vector2Int)playerCell), (Vector2Int)tilePosition);

                if (distance <= viewRadius)
                {
                    int fogIndex = Mathf.FloorToInt((distance / viewRadius) * (fogLevels.Length - 1));
                    fogIndex = Mathf.Clamp(fogIndex, 0, fogLevels.Length - 1);

                    TileBase newFogTile = distance <= 1 ? null : fogLevels[fogIndex].tile;

                    // Update the discovered state with the lowest opacity (null or lower fog index)
                    int arrayY = Mathf.Abs(y + 2); // y=-2 maps to array index 0
                    if (x >= 0 && x < discoveredFog.GetLength(0) &&
                        arrayY >= 0 && arrayY < discoveredFog.GetLength(1))
                    {
                        if (newFogTile == null)
                        {
                            discoveredFog[x, arrayY] = null;
                        }
                        else if (discoveredFog[x, arrayY] != null)
                        {
                            // Find the index of the current and new fog tiles
                            int currentIndex = System.Array.FindIndex(fogLevels, f => f.tile == discoveredFog[x, arrayY]);
                            int newIndex = System.Array.FindIndex(fogLevels, f => f.tile == newFogTile);

                            // Keep the lower opacity version
                            if (newIndex < currentIndex)
                            {
                                discoveredFog[x, arrayY] = newFogTile;
                            }
                        }

                        // Always use the discovered state
                        fogTilemap.SetTile(tilePosition, discoveredFog[x, arrayY]);
                    }
                }
            }
        }
    }

    public FogOfWarData GetSaveData()
    {
        FogOfWarData saveData = new FogOfWarData();

        for (int x = 0; x < discoveredFog.GetLength(0); x++)
        {
            for (int y = 0; y < discoveredFog.GetLength(1); y++)
            {
                TileBase currentTile = discoveredFog[x, y];
                if (currentTile == null || currentTile != fogTile) // Only save revealed tiles
                {
                    FogTileData tileData = new FogTileData
                    {
                        x = x,
                        y = y,
                        fogLevelIndex = currentTile == null ? -1 : System.Array.FindIndex(fogLevels, f => f.tile == currentTile)
                    };
                    saveData.discoveredTiles.Add(tileData);
                }
            }
        }

        return saveData;
    }

    public void LoadFromSaveData(FogOfWarData saveData)
    {
        if (saveData == null || saveData.discoveredTiles == null)
            return;

        // Reset fog to full opacity
        for (int x = 0; x < discoveredFog.GetLength(0); x++)
        {
            // Reset underground tiles only (-2 and below)
            for (int y = -2; y >= -GenerateTileMap.groundDepth; y--)
            {
                Vector3Int tilePosition = new Vector3Int(x, y, 0);
                fogTilemap.SetTile(tilePosition, fogTile);

                int arrayY = Mathf.Abs(y + 2);
                discoveredFog[x, arrayY] = fogTile;
            }
        }

        // Apply saved fog states
        foreach (FogTileData tileData in saveData.discoveredTiles)
        {
            if (tileData.x >= 0 && tileData.x < discoveredFog.GetLength(0) &&
                tileData.y >= 0 && tileData.y < discoveredFog.GetLength(1))
            {
                TileBase tileToUse = tileData.fogLevelIndex == -1 ? null : fogLevels[tileData.fogLevelIndex].tile;
                discoveredFog[tileData.x, tileData.y] = tileToUse;

                // Convert array Y coordinate back to world Y coordinate
                int worldY = -(tileData.y + 2); // Convert array index back to world coordinates
                fogTilemap.SetTile(new Vector3Int(tileData.x, worldY, 0), tileToUse);
            }
        }
    }
}