using Theriapolis.Core.Util;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 4 — MoistureGen
/// Independent noise layer, combined with elevation to derive biome detail.
/// Respects macro-cell moisture floors/ceilings.
/// </summary>
public sealed class MoistureGenStage : IWorldGenStage
{
    public string Name => "MoistureGen";

    public void Run(WorldGenContext ctx)
    {
        ulong seed = ctx.World.WorldSeed ^ C.RNG_MOISTURE;
        var noise = new FastNoiseLite
        {
            Seed       = (int)(seed & 0x7FFFFFFF),
            Frequency  = 2.3f / C.WORLD_WIDTH_TILES,
            Noise      = FastNoiseLite.NoiseType.OpenSimplex2,
            Fractal    = FastNoiseLite.FractalType.FBm,
            Octaves    = 5,
            Lacunarity = 2.0f,
            Gain       = 0.5f,
        };

        int W = C.WORLD_WIDTH_TILES;
        int H = C.WORLD_HEIGHT_TILES;

        // FastNoiseLite.EnsurePerm() is not thread-safe; calling GetNoise once here
        // populates _perm in the main thread before the parallel loop reads it.
        _ = noise.GetNoise01(0f, 0f);

        Parallel.For(0, H, ty =>
        {
            for (int tx = 0; tx < W; tx++)
            {
                float elev = ctx.World.Tiles[tx, ty].Elevation;

                float raw = noise.GetNoise01((float)tx, (float)ty);

                // Elevation modifier: high elevation slightly reduces moisture
                raw -= (elev - 0.5f) * 0.25f;
                raw = Math.Clamp(raw, 0f, 1f);

                // Apply macro cell constraints for all tiles including ocean.
                // Skipping ocean tiles here was a shortcut that breaks macro constraints
                // when later stages (CoastalFeatureGen, BorderDistortion) raise ocean
                // tiles to land: those tiles would inherit the wrong moisture value.
                // Use the tile's stored warped macro coords so moisture follows
                // the same organic cell boundaries as elevation.
                var cell = ctx.World.MacroCellForTile(ctx.World.Tiles[tx, ty]);
                float m = cell.MoistureFloor + raw * (cell.MoistureCeiling - cell.MoistureFloor);
                m = Math.Clamp(m, 0f, 1f);

                ctx.World.Tiles[tx, ty].Moisture = m;
            }
        });

        ctx.World.StageHashes["MoistureGen"] = ctx.World.HashMoisture();
        ctx.LogMessage($"[MoistureGen] Moisture hash: 0x{ctx.World.StageHashes["MoistureGen"]:X16}");
    }
}
