using UnityEngine;
using System.Collections.Generic;

public class TerrainGenerator
{
    private NoiseGenerator noiseGenerator;
    private List<Vector2Int> originalRiverPositions;

    public TerrainGenerator()
    {
        noiseGenerator = new NoiseGenerator();
        originalRiverPositions = new List<Vector2Int>();
    }

    public void GenerateTerrain(MapData mapData)
    {
        // Clear previous river positions
        originalRiverPositions.Clear();

        // Generate base heightmap
        GenerateHeightmap(mapData);

        // Generate water bodies first (they take priority)
        GenerateOceans(mapData);
        GenerateLakes(mapData);
        GenerateRivers(mapData);

        // Store river positions after generation but before forests
        StoreRiverPositions(mapData);

        // Generate mountains (avoid water areas)
        GenerateMountains(mapData);

        // Generate forests (can overlap with rivers initially)
        GenerateForests(mapData);

        // Create river paths through forests (this cuts paths after forests are placed)
        CreateRiverPathsThroughForests(mapData);

        // Generate shores (around water)
        GenerateShores(mapData);
    }

    private void GenerateHeightmap(MapData mapData)
    {
        for (int x = 0; x < mapData.Width; x++)
        {
            for (int y = 0; y < mapData.Height; y++)
            {
                float height = noiseGenerator.GetNoise(x, y, 0.1f);
                mapData.GetTile(x, y).height = height;
            }
        }
    }

    private void GenerateOceans(MapData mapData)
    {
        // Generate one large ocean that extends to map edge
        int edgeChoice = Random.Range(0, 4); // 0=left, 1=right, 2=top, 3=bottom

        int oceanX, oceanY, oceanWidth, oceanHeight;

        switch (edgeChoice)
        {
            case 0: // Left edge ocean - cover entire left edge
                oceanX = 0;
                oceanY = 0;
                oceanWidth = Random.Range(40, 70);
                oceanHeight = mapData.Height;
                break;
            case 1: // Right edge ocean - cover entire right edge
                oceanWidth = Random.Range(40, 70);
                oceanX = mapData.Width - oceanWidth;
                oceanY = 0;
                oceanHeight = mapData.Height;
                break;
            case 2: // Top edge ocean - cover entire top edge
                oceanX = 0;
                oceanHeight = Random.Range(40, 70);
                oceanY = mapData.Height - oceanHeight;
                oceanWidth = mapData.Width;
                break;
            default: // Bottom edge ocean - cover entire bottom edge
                oceanX = 0;
                oceanY = 0;
                oceanWidth = mapData.Width;
                oceanHeight = Random.Range(40, 70);
                break;
        }

        // Create the main ocean shape
        List<Vector2Int> oceanTiles = new List<Vector2Int>();

        for (int x = oceanX; x < oceanX + oceanWidth && x < mapData.Width; x++)
        {
            for (int y = oceanY; y < oceanY + oceanHeight && y < mapData.Height; y++)
            {
                // Calculate distance from the map edge that should be ocean
                float distanceFromOceanEdge = 0f;

                if (edgeChoice == 0) distanceFromOceanEdge = x; // Left edge
                else if (edgeChoice == 1) distanceFromOceanEdge = mapData.Width - 1 - x; // Right edge
                else if (edgeChoice == 2) distanceFromOceanEdge = mapData.Height - 1 - y; // Top edge
                else distanceFromOceanEdge = y; // Bottom edge

                // Create probability that decreases from edge with some noise
                float maxDepth = Mathf.Min(oceanWidth, oceanHeight) * 0.8f;
                float edgeProbability = 1f - (distanceFromOceanEdge / maxDepth);

                // Add noise for coastline variation, but keep it more cohesive
                float noise = noiseGenerator.GetNoise(x, y, 0.1f) * 0.2f;
                float finalProbability = Mathf.Clamp01(edgeProbability + noise);

                // Ensure edge tiles are always ocean for proper ocean coverage
                if (distanceFromOceanEdge < 3 || finalProbability > 0.4f)
                {
                    oceanTiles.Add(new Vector2Int(x, y));
                }
            }
        }        // Apply ocean tiles
        foreach (var pos in oceanTiles)
        {
            mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Ocean;
            mapData.GetTile(pos.x, pos.y).isWalkable = false;
        }

        // Fill gaps more aggressively to reduce scattered cells
        FillTerrainGaps(mapData, TerrainType.Ocean, 3);
    }

    private void GenerateLakes(MapData mapData)
    {
        int lakeCount = Random.Range(3, 6);

        for (int i = 0; i < lakeCount; i++)
        {
            int attempts = 0;
            while (attempts < 20)
            {
                attempts++;

                int lakeX = Random.Range(10, mapData.Width - 10);
                int lakeY = Random.Range(10, mapData.Height - 10);
                int lakeSize = Random.Range(4, 10);

                // Check if area is suitable for a lake (no water nearby)
                if (IsAreaSuitable(mapData, lakeX, lakeY, lakeSize * 2, TerrainType.Plains))
                {
                    CreateLake(mapData, lakeX, lakeY, lakeSize);
                    break;
                }
            }
        }
    }

    private void CreateLake(MapData mapData, int centerX, int centerY, int size)
    {
        List<Vector2Int> lakeTiles = new List<Vector2Int>();

        for (int x = centerX - size; x <= centerX + size; x++)
        {
            for (int y = centerY - size; y <= centerY + size; y++)
            {
                if (mapData.IsValidPosition(x, y))
                {
                    float distance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
                    if (distance <= size * Random.Range(0.8f, 1.2f))
                    {
                        lakeTiles.Add(new Vector2Int(x, y));
                    }
                }
            }
        }

        // Apply lake tiles
        foreach (var pos in lakeTiles)
        {
            mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Lake;
            mapData.GetTile(pos.x, pos.y).isWalkable = false;
        }

        // Fill gaps
        FillTerrainGaps(mapData, TerrainType.Lake, 1);
    }

    private void GenerateRivers(MapData mapData)
    {
        var riverGenerator = new RiverGenerator();
        riverGenerator.GenerateRivers(mapData);
    }

    private void GenerateForests(MapData mapData)
    {
        // Generate one huge forest first (if possible)
        GenerateHugeForest(mapData);

        // Generate medium forests
        int mediumForestCount = Random.Range(3, 6);
        for (int i = 0; i < mediumForestCount; i++)
        {
            int attempts = 0;
            while (attempts < 20)
            {
                attempts++;

                int forestX = Random.Range(10, mapData.Width - 10);
                int forestY = Random.Range(10, mapData.Height - 10);
                int forestSize = Random.Range(8, 18);

                if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
                {
                    CreateForest(mapData, forestX, forestY, forestSize);
                    break;
                }
            }
        }

        // Generate small forests and forest patches
        int smallForestCount = Random.Range(5, 10);
        for (int i = 0; i < smallForestCount; i++)
        {
            int attempts = 0;
            while (attempts < 15)
            {
                attempts++;

                int forestX = Random.Range(5, mapData.Width - 5);
                int forestY = Random.Range(5, mapData.Height - 5);
                int forestSize = Random.Range(3, 8);

                if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
                {
                    CreateForest(mapData, forestX, forestY, forestSize);
                    break;
                }
            }
        }
    }

    private void GenerateHugeForest(MapData mapData)
    {
        int attempts = 0;
        while (attempts < 10)
        {
            attempts++;

            // Try to place a huge forest away from water and edges
            int forestX = Random.Range(25, mapData.Width - 45);
            int forestY = Random.Range(25, mapData.Height - 45);
            int forestSize = Random.Range(25, 40);

            if (IsAreaSuitableForForest(mapData, forestX, forestY, forestSize))
            {
                CreateHugeForest(mapData, forestX, forestY, forestSize);
                break;
            }
        }
    }

    private void CreateHugeForest(MapData mapData, int centerX, int centerY, int size)
    {
        List<Vector2Int> forestTiles = new List<Vector2Int>();

        // Create multiple overlapping forest areas for irregular huge forest
        int subForests = Random.Range(3, 6);

        for (int sf = 0; sf < subForests; sf++)
        {
            // Each sub-forest has its own center near the main center
            int subCenterX = centerX + Random.Range(-size / 3, size / 3);
            int subCenterY = centerY + Random.Range(-size / 3, size / 3);
            int subSize = Random.Range(size / 2, size);

            // Generate organic forest shape for this sub-forest
            for (int x = subCenterX - subSize; x <= subCenterX + subSize; x++)
            {
                for (int y = subCenterY - subSize; y <= subCenterY + subSize; y++)
                {
                    if (mapData.IsValidPosition(x, y))
                    {
                        float distance = Vector2.Distance(new Vector2(x, y), new Vector2(subCenterX, subCenterY));
                        float maxDistance = subSize * Random.Range(0.7f, 1.3f);

                        // Add some noise for irregular edges
                        float noise = noiseGenerator.GetNoise(x, y, 0.2f);
                        maxDistance += noise * (subSize * 0.2f);

                        if (distance <= maxDistance)
                        {
                            MapTile tile = mapData.GetTile(x, y);
                            // Allow forests on Plains and Rivers - we'll cut river paths later
                            if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.River)
                            {
                                forestTiles.Add(new Vector2Int(x, y));
                            }
                        }
                    }
                }
            }
        }

        // Apply forest tiles (only on Plains now, rivers are left as paths)
        foreach (var pos in forestTiles)
        {
            MapTile tile = mapData.GetTile(pos.x, pos.y);
            tile.terrainType = TerrainType.Forest;
        }

        // Fill gaps more aggressively for huge forests
        FillTerrainGaps(mapData, TerrainType.Forest, 2);
    }

    private bool IsAreaSuitableForForest(MapData mapData, int centerX, int centerY, int size)
    {
        int plainsCount = 0;
        int totalCount = 0;

        for (int x = centerX - size / 2; x <= centerX + size / 2; x++)
        {
            for (int y = centerY - size / 2; y <= centerY + size / 2; y++)
            {
                if (mapData.IsValidPosition(x, y))
                {
                    totalCount++;
                    var tile = mapData.GetTile(x, y);
                    if (tile.terrainType == TerrainType.Plains)
                    {
                        plainsCount++;
                    }
                }
            }
        }

        return totalCount > 0 && (float)plainsCount / totalCount > 0.8f;
    }

    private void CreateForest(MapData mapData, int centerX, int centerY, int size)
    {
        List<Vector2Int> forestTiles = new List<Vector2Int>();

        // Determine forest type - some are dense, some are sparse
        bool isDenseForest = Random.value < 0.4f; // 40% chance for dense forest
        bool hasClearing = Random.value < 0.3f && size > 6; // 30% chance for clearing in larger forests

        // Create organic forest shape with variation
        for (int x = centerX - size; x <= centerX + size; x++)
        {
            for (int y = centerY - size; y <= centerY + size; y++)
            {
                if (mapData.IsValidPosition(x, y))
                {
                    float distance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
                    float maxDistance = size * Random.Range(0.6f, 1.2f);

                    // Add noise for irregular edges
                    float noise = noiseGenerator.GetNoise(x, y, 0.15f);
                    float noiseInfluence = isDenseForest ? 0.1f : 0.3f; // Dense forests are more regular
                    maxDistance += noise * (size * noiseInfluence);

                    // Create clearings in some forests
                    bool inClearing = false;
                    if (hasClearing)
                    {
                        float clearingDistance = Vector2.Distance(new Vector2(x, y), new Vector2(centerX, centerY));
                        inClearing = clearingDistance < size * 0.3f && Random.value < 0.7f;
                    }

                    if (distance <= maxDistance && !inClearing)
                    {
                        MapTile tile = mapData.GetTile(x, y);
                        // Allow forests on Plains and Rivers - we'll cut river paths later
                        if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.River)
                        {
                            // Dense forests fill more completely, sparse forests are more scattered
                            float fillProbability = isDenseForest ? 0.85f : 0.65f;
                            if (Random.value < fillProbability)
                            {
                                forestTiles.Add(new Vector2Int(x, y));
                            }
                        }
                    }
                }
            }
        }

        // Apply forest tiles (only on Plains now, rivers are left as paths)
        foreach (var pos in forestTiles)
        {
            MapTile tile = mapData.GetTile(pos.x, pos.y);
            tile.terrainType = TerrainType.Forest;
        }

        // Dense forests get better gap filling
        int gapFillLevel = isDenseForest ? 2 : 1;
        FillTerrainGaps(mapData, TerrainType.Forest, gapFillLevel);
    }

    private void GenerateMountains(MapData mapData)
    {
        // Generate more varied mountain ranges with different sizes
        int mountainCount = Random.Range(2, 5); // Slightly more mountain ranges

        for (int i = 0; i < mountainCount; i++)
        {
            int attempts = 0;
            while (attempts < 20)
            {
                attempts++;

                int mountainX = Random.Range(15, mapData.Width - 15);
                int mountainY = Random.Range(15, mapData.Height - 15);

                // More varied mountain sizes - some small, some large
                int mountainSize;
                float sizeRoll = Random.value;
                if (sizeRoll < 0.3f)
                    mountainSize = Random.Range(5, 10); // Small mountains
                else if (sizeRoll < 0.7f)
                    mountainSize = Random.Range(10, 18); // Medium mountains
                else
                    mountainSize = Random.Range(18, 28); // Large mountain ranges

                if (IsAreaSuitableForMountains(mapData, mountainX, mountainY, mountainSize))
                {
                    CreateMountainRange(mapData, mountainX, mountainY, mountainSize);
                    break;
                }
            }
        }
    }

    private bool IsAreaSuitableForMountains(MapData mapData, int centerX, int centerY, int size)
    {
        // Check if area is mostly plains (not water or already mountains)
        int suitableCount = 0;
        int totalCount = 0;

        for (int x = centerX - size; x <= centerX + size; x++)
        {
            for (int y = centerY - size; y <= centerY + size; y++)
            {
                if (mapData.IsValidPosition(x, y))
                {
                    totalCount++;
                    var tile = mapData.GetTile(x, y);
                    if (tile.terrainType == TerrainType.Plains || tile.terrainType == TerrainType.Forest)
                    {
                        suitableCount++;
                    }
                }
            }
        }

        return totalCount > 0 && (float)suitableCount / totalCount > 0.7f;
    }

    private void CreateMountainRange(MapData mapData, int centerX, int centerY, int size)
    {
        List<Vector2Int> mountainTiles = new List<Vector2Int>();

        // Create mountain range with multiple peaks - more varied
        int peaks = Random.Range(2, 6); // More variation in peak count
        for (int p = 0; p < peaks; p++)
        {
            // More random peak placement instead of evenly spaced
            float angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
            float peakDistance = Random.Range(0.2f, 0.5f) * size; // Varied distance from center
            int peakX = centerX + Mathf.RoundToInt(Mathf.Cos(angle) * peakDistance);
            int peakY = centerY + Mathf.RoundToInt(Mathf.Sin(angle) * peakDistance);

            // Each peak has its own size variation
            int peakSize = Random.Range(size / 3, size);

            for (int x = peakX - peakSize / 2; x <= peakX + peakSize / 2; x++)
            {
                for (int y = peakY - peakSize / 2; y <= peakY + peakSize / 2; y++)
                {
                    if (mapData.IsValidPosition(x, y))
                    {
                        float distance = Vector2.Distance(new Vector2(x, y), new Vector2(peakX, peakY));
                        // More random distance threshold for irregular mountain shapes
                        float maxDistance = peakSize * Random.Range(0.3f, 0.9f);

                        // Add noise for more natural mountain edges
                        float noise = noiseGenerator.GetNoise(x, y, 0.15f);
                        maxDistance += noise * (peakSize * 0.2f);

                        if (distance <= maxDistance)
                        {
                            MapTile tile = mapData.GetTile(x, y);
                            if (!tile.IsWater()) // Don't override water
                            {
                                mountainTiles.Add(new Vector2Int(x, y));
                            }
                        }
                    }
                }
            }
        }

        // Apply mountain tiles
        foreach (var pos in mountainTiles)
        {
            mapData.GetTile(pos.x, pos.y).terrainType = TerrainType.Mountain;
            mapData.GetTile(pos.x, pos.y).isWalkable = false;
        }

        // Fill gaps
        FillTerrainGaps(mapData, TerrainType.Mountain, 1);
    }

    private void FillTerrainGaps(MapData mapData, TerrainType terrainType, int maxGapSize)
    {
        for (int x = 0; x < mapData.Width; x++)
        {
            for (int y = 0; y < mapData.Height; y++)
            {
                MapTile tile = mapData.GetTile(x, y);
                if (tile.terrainType != terrainType)
                {
                    // Check if this tile is surrounded by the target terrain type
                    int neighborCount = 0;
                    int targetNeighbors = 0;

                    for (int dx = -maxGapSize; dx <= maxGapSize; dx++)
                    {
                        for (int dy = -maxGapSize; dy <= maxGapSize; dy++)
                        {
                            if (dx == 0 && dy == 0) continue;

                            int checkX = x + dx;
                            int checkY = y + dy;

                            if (mapData.IsValidPosition(checkX, checkY))
                            {
                                neighborCount++;
                                if (mapData.GetTile(checkX, checkY).terrainType == terrainType)
                                {
                                    targetNeighbors++;
                                }
                            }
                        }
                    }

                    // If most neighbors are the target terrain, fill this gap
                    if (neighborCount > 0 && (float)targetNeighbors / neighborCount > 0.6f)
                    {
                        if (terrainType == TerrainType.Ocean || terrainType == TerrainType.Lake || terrainType == TerrainType.River)
                        {
                            tile.isWalkable = false;
                        }
                        if (terrainType == TerrainType.Mountain)
                        {
                            tile.isWalkable = false;
                        }
                        tile.terrainType = terrainType;
                    }
                }
            }
        }
    }

    private void GenerateShores(MapData mapData)
    {
        for (int x = 0; x < mapData.Width; x++)
        {
            for (int y = 0; y < mapData.Height; y++)
            {
                MapTile tile = mapData.GetTile(x, y);
                if (tile.terrainType == TerrainType.Plains)
                {
                    if (IsNearWater(mapData, x, y, 2)) // Reduced range for shores
                    {
                        tile.terrainType = TerrainType.Shore;
                    }
                }
            }
        }
    }

    private bool IsNearWater(MapData mapData, int x, int y, int range)
    {
        for (int dx = -range; dx <= range; dx++)
        {
            for (int dy = -range; dy <= range; dy++)
            {
                int checkX = x + dx;
                int checkY = y + dy;

                if (mapData.IsValidPosition(checkX, checkY))
                {
                    MapTile checkTile = mapData.GetTile(checkX, checkY);
                    if (checkTile.IsWater())
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private bool IsAreaSuitable(MapData mapData, int centerX, int centerY, int size, TerrainType requiredType)
    {
        int suitableCount = 0;
        int totalCount = 0;

        for (int x = centerX - size; x <= centerX + size; x++)
        {
            for (int y = centerY - size; y <= centerY + size; y++)
            {
                if (mapData.IsValidPosition(x, y))
                {
                    totalCount++;
                    if (mapData.GetTile(x, y).terrainType == requiredType)
                    {
                        suitableCount++;
                    }
                }
            }
        }

        return totalCount > 0 && (float)suitableCount / totalCount > 0.8f;
    }

    private void StoreRiverPositions(MapData mapData)
    {
        originalRiverPositions.Clear();

        for (int x = 0; x < mapData.Width; x++)
        {
            for (int y = 0; y < mapData.Height; y++)
            {
                var tile = mapData.GetTile(x, y);
                if (tile.terrainType == TerrainType.River)
                {
                    originalRiverPositions.Add(new Vector2Int(x, y));
                }
            }
        }
    }

    private void CreateRiverPathsThroughForests(MapData mapData)
    {
        // Restore rivers that got covered by forests with more natural-looking paths
        foreach (var riverPos in originalRiverPositions)
        {
            var tile = mapData.GetTile(riverPos.x, riverPos.y);

            // If this river position got turned into forest, restore it as river
            if (tile.terrainType == TerrainType.Forest)
            {
                tile.terrainType = TerrainType.River;

                // Create narrower, more natural river paths
                // Only expand along the main river flow directions (not diagonally)
                for (int dx = -1; dx <= 1; dx++)
                {
                    for (int dy = -1; dy <= 1; dy++)
                    {
                        // Skip center (already handled) and diagonal directions (keep forest)
                        if ((dx == 0 && dy == 0) || (Mathf.Abs(dx) == 1 && Mathf.Abs(dy) == 1))
                            continue;

                        int x = riverPos.x + dx;
                        int y = riverPos.y + dy;

                        if (mapData.IsValidPosition(x, y))
                        {
                            var neighborTile = mapData.GetTile(x, y);
                            // Only expand river into forests, and with lower probability for narrower channels
                            if (neighborTile.terrainType == TerrainType.Forest && Random.value < 0.3f)
                            {
                                neighborTile.terrainType = TerrainType.River;
                            }
                        }
                    }
                }
            }
        }
    }
}