using Theriapolis.Core.Util;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 6 — CoastalFeatureGen
/// Adds large-scale coastal features: peninsulas, bays, and offshore islands.
/// These are placed BEFORE the border distortion pass so they are treated as
/// natural coastline by the distortion noise.
/// </summary>
public sealed class CoastalFeatureGenStage : IWorldGenStage
{
    public string Name => "CoastalFeatureGen";

    public void Run(WorldGenContext ctx)
    {
        var rng = ctx.Rngs["coast"];
        int W = C.WORLD_WIDTH_TILES;
        int H = C.WORLD_HEIGHT_TILES;

        // Collect coastal land tiles (land tiles adjacent to ocean)
        var coastalLandTiles = new List<(int x, int y)>();
        for (int ty = 2; ty < H - 2; ty += 4)
            for (int tx = 2; tx < W - 2; tx += 4)
                if (IsCoastalLand(ctx, tx, ty))
                    coastalLandTiles.Add((tx, ty));

        if (coastalLandTiles.Count == 0) return;

        rng.Shuffle(coastalLandTiles.ToArray().AsSpan()); // shuffle in place copy

        // Generate 2–4 peninsulas
        int numPeninsula = rng.NextInt(2, 5);
        for (int i = 0; i < numPeninsula && i < coastalLandTiles.Count; i++)
        {
            var (bx, by) = coastalLandTiles[i * (coastalLandTiles.Count / numPeninsula)];
            GeneratePeninsula(ctx, rng, bx, by);
        }

        // Generate 2–4 bays
        int numBay = rng.NextInt(2, 5);
        for (int i = 0; i < numBay && i < coastalLandTiles.Count; i++)
        {
            int idx = coastalLandTiles.Count / 2 + i * (coastalLandTiles.Count / numBay);
            if (idx >= coastalLandTiles.Count) idx -= coastalLandTiles.Count;
            var (bx, by) = coastalLandTiles[idx];
            GenerateBay(ctx, rng, bx, by);
        }

        // Generate 3–8 islands
        int numIslands = rng.NextInt(3, 9);
        for (int i = 0; i < numIslands; i++)
        {
            int ox = rng.NextInt(20, W - 20);
            int oy = rng.NextInt(20, H - 20);
            if (ctx.World.Tiles[ox, oy].Elevation < WorldState.SeaLevel)
                GenerateIsland(ctx, rng, ox, oy);
        }

        ctx.World.StageHashes["CoastalFeatureGen"] = HashCoast(ctx);
        ctx.LogMessage($"[CoastalFeatureGen] Added {numPeninsula} peninsulas, {numBay} bays, {numIslands} island attempts.");
    }

    private static bool IsCoastalLand(WorldGenContext ctx, int tx, int ty)
    {
        if (ctx.World.Tiles[tx, ty].Elevation < WorldState.SeaLevel) return false;
        for (int dy = -1; dy <= 1; dy++)
        for (int dx = -1; dx <= 1; dx++)
        {
            if (dx == 0 && dy == 0) continue;
            int nx = tx + dx, ny = ty + dy;
            if (nx < 0 || ny < 0 || nx >= C.WORLD_WIDTH_TILES || ny >= C.WORLD_HEIGHT_TILES) continue;
            if (ctx.World.Tiles[nx, ny].Elevation < WorldState.SeaLevel) return true;
        }
        return false;
    }

    private static void GeneratePeninsula(WorldGenContext ctx, SeededRng rng, int bx, int by)
    {
        // Extend a finger of land outward in a random ocean-facing direction
        float angle = rng.NextFloat(0f, MathF.PI * 2f);
        int length  = rng.NextInt(10, 31);
        int baseWidth = rng.NextInt(5, 11);

        for (int step = 0; step < length; step++)
        {
            float t = (float)step / length;
            float width = baseWidth * (1f - t * 0.8f); // taper toward tip
            float cx = bx + MathF.Cos(angle) * step;
            float cy = by + MathF.Sin(angle) * step;
            int iw = Math.Max(1, (int)width);
            for (int w = -iw; w <= iw; w++)
            {
                int px = (int)(cx + MathF.Cos(angle + MathF.PI * 0.5f) * w);
                int py = (int)(cy + MathF.Sin(angle + MathF.PI * 0.5f) * w);
                if (px < 0 || py < 0 || px >= C.WORLD_WIDTH_TILES || py >= C.WORLD_HEIGHT_TILES) continue;
                float elev = ctx.World.Tiles[px, py].Elevation;
                if (elev < WorldState.SeaLevel)
                    ctx.World.Tiles[px, py].Elevation = WorldState.SeaLevel + 0.05f + rng.NextFloat(0f, 0.1f);
            }
        }
    }

    private static void GenerateBay(WorldGenContext ctx, SeededRng rng, int bx, int by)
    {
        // Carve a concavity into the land
        float angle = rng.NextFloat(0f, MathF.PI * 2f);
        int depth = rng.NextInt(10, 21);
        int maxWidth = rng.NextInt(5, 16);

        for (int step = 0; step < depth; step++)
        {
            float t = (float)step / depth;
            float width = maxWidth * MathF.Sin(t * MathF.PI); // widen then narrow
            float cx = bx + MathF.Cos(angle) * step;
            float cy = by + MathF.Sin(angle) * step;
            int iw = Math.Max(1, (int)width);
            for (int w = -iw; w <= iw; w++)
            {
                int px = (int)(cx + MathF.Cos(angle + MathF.PI * 0.5f) * w);
                int py = (int)(cy + MathF.Sin(angle + MathF.PI * 0.5f) * w);
                if (px < 0 || py < 0 || px >= C.WORLD_WIDTH_TILES || py >= C.WORLD_HEIGHT_TILES) continue;
                // Push land below sea level (carve the bay)
                if (ctx.World.Tiles[px, py].Elevation >= WorldState.SeaLevel)
                    ctx.World.Tiles[px, py].Elevation = WorldState.SeaLevel - 0.05f - rng.NextFloat(0f, 0.1f);
            }
        }
    }

    private static void GenerateIsland(WorldGenContext ctx, SeededRng rng, int ox, int oy)
    {
        int radius = rng.NextInt(5, 16);
        for (int dy = -radius; dy <= radius; dy++)
        for (int dx = -radius; dx <= radius; dx++)
        {
            float dist = MathF.Sqrt(dx * dx + dy * dy);
            if (dist > radius) continue;
            int px = ox + dx, py = oy + dy;
            if (px < 0 || py < 0 || px >= C.WORLD_WIDTH_TILES || py >= C.WORLD_HEIGHT_TILES) continue;
            float t = 1f - dist / radius;
            float elev = WorldState.SeaLevel + 0.1f + t * 0.3f + rng.NextFloat(-0.05f, 0.05f);
            if (elev > ctx.World.Tiles[px, py].Elevation)
                ctx.World.Tiles[px, py].Elevation = elev;
        }
    }

    private static ulong HashCoast(WorldGenContext ctx)
    {
        const ulong FNV_PRIME  = 1099511628211UL;
        const ulong FNV_OFFSET = 14695981039346656037UL;
        ulong h = FNV_OFFSET;
        for (int y = 0; y < C.WORLD_HEIGHT_TILES; y += 16)
            for (int x = 0; x < C.WORLD_WIDTH_TILES; x += 16)
                h = (h ^ BitConverter.SingleToUInt32Bits(ctx.World.Tiles[x, y].Elevation)) * FNV_PRIME;
        return h;
    }
}
