namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 21 — EncounterDensityGen
/// Produces a per-tile encounter probability map used by the runtime encounter spawner (Phase 5).
/// Higher = more dangerous. Normalized to [0, 1].
/// </summary>
public sealed class EncounterDensityGenStage : IWorldGenStage
{
    public string Name => "EncounterDensityGen";

    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;

        // ── Precompute distance to nearest settlement ──────────────────────────
        var settleDist = BfsSettlementDistance(world);

        var density = new float[W, H];

        Parallel.For(0, H, y =>
        {
            for (int x = 0; x < W; x++)
            {
                ref var tile = ref world.TileAt(x, y);
                if (tile.Biome == BiomeId.Ocean) { density[x, y] = 0f; continue; }

                // Base biome danger
                float baseDanger = tile.Biome switch
                {
                    BiomeId.MountainAlpine  => 0.8f,
                    BiomeId.Wetland         => 0.7f,
                    BiomeId.Boreal          => 0.65f,
                    BiomeId.Tundra          => 0.75f,
                    BiomeId.SubtropicalForest=> 0.6f,
                    BiomeId.TemperateDeciduous=> 0.5f,
                    BiomeId.Scrubland       => 0.55f,
                    BiomeId.TemperateGrassland=> 0.35f,
                    BiomeId.Coastal         => 0.3f,
                    _                       => 0.45f,
                };

                // Distance from settlement (closer = safer)
                float dist = settleDist[x, y];
                float settleFactor = Math.Min(1f, dist / 60f);

                // Road proximity (on-road is safer)
                float roadFactor = (tile.Features & FeatureFlags.HasRoad) != 0 ? 0.5f : 1.0f;

                // Macro region hostility
                var macro = world.MacroCellForTile(in tile);
                float hostility = macro.Development?.ToLowerInvariant() switch
                {
                    "wilderness" => 1.2f,
                    "frontier"   => 1.0f,
                    "agricultural"=> 0.7f,
                    "industrial" => 0.6f,
                    "urban"      => 0.5f,
                    _            => 0.8f,
                };

                // Enforcer presence (if computed)
                float enforcerSafety = 1f;
                if (world.FactionInfluence != null)
                    enforcerSafety = 1f - world.FactionInfluence.Get((int)FactionId.CovenantEnforcers, x, y) * 0.6f;

                density[x, y] = baseDanger * settleFactor * roadFactor * hostility * enforcerSafety;
            }
        });

        // Normalize to [0, 1]
        float maxD = 0f;
        for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
            maxD = Math.Max(maxD, density[x, y]);

        if (maxD > 1e-6f)
            for (int y = 0; y < H; y++)
            for (int x = 0; x < W; x++)
                density[x, y] /= maxD;

        world.EncounterDensity = density;
        ctx.LogMessage("[EncounterDensityGen] Encounter density map computed.");
    }

    private static float[,] BfsSettlementDistance(WorldState world)
    {
        var dist = new float[W, H];
        for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
            dist[x, y] = float.MaxValue;

        var queue = new Queue<(int x, int y)>();

        // Seed from settlement tiles
        foreach (var s in world.Settlements.Where(s => !s.IsPoi))
        {
            for (int dy = -2; dy <= 2; dy++)
            for (int dx = -2; dx <= 2; dx++)
            {
                int nx = s.TileX + dx, ny = s.TileY + dy;
                if ((uint)nx < W && (uint)ny < H && dist[nx, ny] == float.MaxValue)
                {
                    dist[nx, ny] = 0f;
                    queue.Enqueue((nx, ny));
                }
            }
        }

        (int dx, int dy)[] dirs4 = { (0,-1),(1,0),(0,1),(-1,0) };
        while (queue.Count > 0)
        {
            var (cx, cy) = queue.Dequeue();
            foreach (var (ddx, ddy) in dirs4)
            {
                int nx = cx + ddx, ny = cy + ddy;
                if ((uint)nx >= W || (uint)ny >= H) continue;
                float nd = dist[cx, cy] + 1f;
                if (nd >= dist[nx, ny]) continue;
                dist[nx, ny] = nd;
                queue.Enqueue((nx, ny));
            }
        }

        return dist;
    }
}
