using UnityEngine;

namespace InfiniteWorld.VoxelWorld
{
    public sealed partial class VoxelWorldGenerator
    {
        private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session, int runtimeId)
        {
            int margin = Mathf.Max(2, smoothingPasses + 1);
            int sampleSize = chunkSize + margin * 2;
            bool[,] sampled = new bool[sampleSize, sampleSize];

            for (int z = 0; z < sampleSize; z++)
            {
                for (int x = 0; x < sampleSize; x++)
                {
                    int localX = x - margin;
                    int localZ = z - margin;
                    Vector2Int worldCell = ChunkToWorldCell(coord, localX, localZ);
                    sampled[x, z] = SampleRock(worldCell);
                }
            }

            for (int pass = 0; pass < smoothingPasses; pass++)
            {
                sampled = SmoothSampledMask(sampled);
            }

            int[] heights = new int[chunkSize * chunkSize];
            byte[] biomeIndices = new byte[chunkSize * chunkSize];
            for (int z = 0; z < chunkSize; z++)
            {
                for (int x = 0; x < chunkSize; x++)
                {
                    Vector2Int worldCell = ChunkToWorldCell(coord, x, z);
                    biomeIndices[z * chunkSize + x] = SampleBiomeIndex(worldCell);
                    bool hasMountain = sampled[x + margin, z + margin];
                    if (!hasMountain)
                    {
                        continue;
                    }

                    heights[z * chunkSize + x] = SampleHeight(worldCell);
                }
            }

            ApplyChunkPlacementHeights(coord, heights);

            return new ChunkBuildResult(coord, heights, biomeIndices, version, session, runtimeId);
        }

        private bool[,] SmoothSampledMask(bool[,] source)
        {
            int width = source.GetLength(0);
            int height = source.GetLength(1);
            bool[,] result = new bool[width, height];

            for (int z = 0; z < height; z++)
            {
                for (int x = 0; x < width; x++)
                {
                    int solidNeighbors = CountSampledNeighbors(source, x, z);
                    if (solidNeighbors >= 5)
                    {
                        result[x, z] = true;
                    }
                    else if (solidNeighbors <= 2)
                    {
                        result[x, z] = false;
                    }
                    else
                    {
                        result[x, z] = source[x, z];
                    }
                }
            }

            return result;
        }

        private static int CountSampledNeighbors(bool[,] sampled, int x, int z)
        {
            int width = sampled.GetLength(0);
            int height = sampled.GetLength(1);
            int count = 0;

            for (int oz = -1; oz <= 1; oz++)
            {
                for (int ox = -1; ox <= 1; ox++)
                {
                    if (ox == 0 && oz == 0)
                    {
                        continue;
                    }

                    int nx = x + ox;
                    int nz = z + oz;
                    if (nx < 0 || nz < 0 || nx >= width || nz >= height)
                    {
                        count++;
                        continue;
                    }

                    if (sampled[nx, nz])
                    {
                        count++;
                    }
                }
            }

            return count;
        }

        private bool SampleRock(Vector2Int worldCell)
        {
            float macro = Mathf.PerlinNoise((worldCell.x + seed * 0.13f) * macroNoiseScale, (worldCell.y - seed * 0.17f) * macroNoiseScale);
            float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.23f) * detailNoiseScale, (worldCell.y + seed * 0.19f) * detailNoiseScale);
            float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.31f) * ridgeNoiseScale, (worldCell.y + seed * 0.29f) * ridgeNoiseScale) * 2f - 1f);

            float rockValue = macro * 0.62f + detail * 0.18f + ridge * 0.20f + rockBias;
            if (IsInsideGlobalPass(worldCell))
            {
                rockValue -= 0.45f;
            }

            return rockValue >= wallThreshold;
        }

        private bool IsInsideGlobalPass(Vector2Int worldCell)
        {
            float primary = Mathf.PerlinNoise((worldCell.x + seed * 0.41f) * passNoiseScale, (worldCell.y - seed * 0.43f) * passNoiseScale);
            float detail = Mathf.PerlinNoise((worldCell.x - seed * 0.17f) * passDetailScale, (worldCell.y + seed * 0.23f) * passDetailScale);
            float ridged = Mathf.Abs(primary * 2f - 1f);
            float warped = Mathf.Lerp(ridged, Mathf.Abs(detail * 2f - 1f), 0.35f);
            return warped <= passThreshold + passFeather * detail;
        }

        private int SampleHeight(Vector2Int worldCell)
        {
            float macro = Mathf.PerlinNoise((worldCell.x - seed * 0.47f) * heightNoiseScale, (worldCell.y + seed * 0.37f) * heightNoiseScale);
            float terrace = Mathf.PerlinNoise((worldCell.x + seed * 0.67f) * terraceNoiseScale, (worldCell.y - seed * 0.59f) * terraceNoiseScale);
            float ridge = 1f - Mathf.Abs(Mathf.PerlinNoise((worldCell.x + seed * 0.71f) * ridgeNoiseScale, (worldCell.y - seed * 0.73f) * ridgeNoiseScale) * 2f - 1f);
            float heightValue = macro * 0.55f + terrace * 0.2f + ridge * 0.25f + heightBias;
            int height = 1 + Mathf.FloorToInt(Mathf.Clamp01(heightValue) * maxMountainHeight);
            return Mathf.Clamp(height, 1, maxMountainHeight);
        }

        private byte SampleBiomeIndex(Vector2Int worldCell)
        {
            int biomeCount = Mathf.Max(1, atlasBiomeCount > 0 ? atlasBiomeCount : CountConfiguredBiomes());
            if (biomeCount <= 1)
            {
                return 0;
            }

            float effectiveScale = biomeNoiseScale / Mathf.Max(1f, biomeSize);
            float noise = Mathf.PerlinNoise((worldCell.x + seed * 0.83f) * effectiveScale, (worldCell.y - seed * 0.79f) * effectiveScale);
            int biomeIndex = Mathf.FloorToInt(Mathf.Clamp01(noise) * biomeCount);
            return (byte)Mathf.Clamp(biomeIndex, 0, biomeCount - 1);
        }

        private Vector2Int ChunkToWorldCell(Vector2Int coord, int localX, int localZ)
        {
            return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ);
        }
    }
}
