using Theriapolis.Core.Util;
using Theriapolis.Core.World.Polylines;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 11 — RiverMeanderGen
/// Adds oxbow lakes and width variation to rivers generated by HydrologyGenStage.
/// </summary>
public sealed class RiverMeanderGenStage : IWorldGenStage
{
    public string Name => "RiverMeanderGen";

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

    public void Run(WorldGenContext ctx)
    {
        var world = ctx.World;
        var rng   = SeededRng.ForSubsystem(world.WorldSeed, C.RNG_MEANDER);

        int oxbows = 0;
        foreach (var poly in world.Rivers)
        {
            // Width variation: wider in wetlands, narrower in mountains
            AdjustWidth(world, poly);

            // Oxbow generation for major rivers crossing flat terrain
            if (poly.RiverClassification >= RiverClass.River)
                oxbows += TryGenerateOxbows(world, poly, rng);
        }

        // Re-derive tile flags (widths may have changed)
        // No re-rasterize needed here — oxbows are just visual edits

        ctx.LogMessage($"[RiverMeanderGen] Generated {oxbows} oxbow lakes.");
        ctx.World.StageHashes["RiverMeanderGen"] = world.HashPolylines();
    }

    private static void AdjustWidth(WorldState world, Polyline poly)
    {
        if (poly.Points.Count < 2) return;

        // Sample biome at midpoint of polyline
        var mid = poly.Points[poly.Points.Count / 2];
        int tx  = (int)(mid.X / C.WORLD_TILE_PIXELS);
        int ty  = (int)(mid.Y / C.WORLD_TILE_PIXELS);
        tx = Math.Clamp(tx, 0, W - 1);
        ty = Math.Clamp(ty, 0, H - 1);

        var biome = world.Tiles[tx, ty].Biome;
        if (biome is BiomeId.Wetland or BiomeId.MarshEdge)
            poly.Width *= 1.5f;
        else if (biome is BiomeId.MountainAlpine or BiomeId.MountainForested)
            poly.Width *= 0.7f;

        poly.Width = Math.Clamp(poly.Width, 0.5f, 5f);
    }

    private static int TryGenerateOxbows(WorldState world, Polyline poly, SeededRng rng)
    {
        if (poly.Points.Count < 20) return 0;

        int created = 0;
        float oxbowProb = 0.04f;

        // Scan for meander loops: points that come close to each other.
        // Start inner j at i+20 (≈5 tiles) to skip zigzag artifacts from grid-aligned flow.
        for (int i = 5; i < poly.Points.Count - 10; i += 10)
        {
            for (int j = i + 20; j < Math.Min(i + 50, poly.Points.Count); j++)
            {
                float dsq = Vec2.DistSq(poly.Points[i], poly.Points[j]);
                if (dsq > 9f * C.WORLD_TILE_PIXELS * C.WORLD_TILE_PIXELS) continue;

                // Check average elevation in the loop area — must be flat
                int midTx = (int)(poly.Points[(i + j) / 2].X / C.WORLD_TILE_PIXELS);
                int midTy = (int)(poly.Points[(i + j) / 2].Y / C.WORLD_TILE_PIXELS);
                midTx = Math.Clamp(midTx, 0, W - 1);
                midTy = Math.Clamp(midTy, 0, H - 1);

                if (world.Tiles[midTx, midTy].Elevation > 0.45f) break;
                if (!rng.NextBool(oxbowProb)) break;

                // Mark a few tiles in the loop area as water (oxbow lake)
                for (int k = i; k <= j; k++)
                {
                    int ox = (int)(poly.Points[k].X / C.WORLD_TILE_PIXELS);
                    int oy = (int)(poly.Points[k].Y / C.WORLD_TILE_PIXELS);
                    if ((uint)ox < W && (uint)oy < H && world.Tiles[ox, oy].Biome != BiomeId.Ocean)
                    {
                        world.TileAt(ox, oy).Features |= FeatureFlags.HasRiver;
                    }
                }
                created++;
                break; // only one oxbow per pass
            }
        }

        return created;
    }
}
