using Theriapolis.Core.Util;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 22 — ValidationPass
/// Checks all generated data for correctness:
///   1. Linear feature exclusion (Addendum A §2) — 0 violations required
///   2. River drainage to water
///   3. Settlement reachability via roads
///   4. Narrative anchor constraint re-verification
///   5. No overlapping settlements
/// Throws on critical failures; logs warnings for soft failures.
/// </summary>
public sealed class ValidationPassStage : IWorldGenStage
{
    public string Name => "ValidationPass";

    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;
        int violations = 0;
        int warnings   = 0;

        // ── 1. Linear feature exclusion ───────────────────────────────────────
        violations += CheckLinearExclusion(world, ctx);

        // ── 2. River drainage ────────────────────────────────────────────────
        warnings += CheckRiverDrainage(world, ctx);

        // ── 3. Settlement reachability ────────────────────────────────────────
        warnings += CheckSettlementReachability(world, ctx);

        // ── 4. Narrative anchor constraints ──────────────────────────────────
        warnings += CheckNarrativeAnchors(world, ctx);

        // ── 5. No overlapping settlements ─────────────────────────────────────
        violations += CheckOverlappingSettlements(world, ctx);

        ctx.World.StageHashes["ValidationPass"] = (ulong)(violations * 1000 + warnings);
        ctx.LogMessage($"[ValidationPass] Done. Violations: {violations}, Warnings: {warnings}");

        if (violations > 0)
            ctx.LogMessage($"[ValidationPass] WARNING: {violations} violations detected. See log above.");
    }

    private static int CheckLinearExclusion(WorldState world, WorldGenContext ctx)
    {
        int count = 0;
        for (int y = 0; y < H; y++)
        for (int x = 0; x < W; x++)
        {
            ref var tile = ref world.TileAt(x, y);

            // Skip settlement tiles (exempt from exclusion)
            if ((tile.Features & FeatureFlags.IsSettlement) != 0) continue;

            bool hasRiver = (tile.Features & FeatureFlags.HasRiver) != 0;
            bool hasRail  = (tile.Features & FeatureFlags.HasRail)  != 0;
            bool hasRoad  = (tile.Features & FeatureFlags.HasRoad)  != 0;

            // More than one linear feature on the same tile
            int featureCount = (hasRiver ? 1 : 0) + (hasRail ? 1 : 0) + (hasRoad ? 1 : 0);
            if (featureCount < 2) continue;

            // For crossings to be valid, they must be near-perpendicular
            if (hasRiver && hasRail && tile.RiverFlowDir != Dir.None && tile.RailDir != Dir.None)
            {
                if (Dir.IsParallel(tile.RiverFlowDir, tile.RailDir))
                {
                    ctx.LogMessage($"[ValidationPass] VIOLATION: River+Rail parallel at ({x},{y})");
                    count++;
                }
            }
            // Road over river = bridge crossing — NOT a violation (perpendicular crossing is allowed)
            if (hasRail && hasRoad && tile.RailDir != Dir.None)
            {
                ctx.LogMessage($"[ValidationPass] VIOLATION: Rail+Road on same tile at ({x},{y})");
                count++;
            }
        }
        if (count > 0)
            ctx.LogMessage($"[ValidationPass] {count} linear exclusion violations.");
        return count;
    }

    private static int CheckRiverDrainage(WorldState world, WorldGenContext ctx)
    {
        int warnings = 0;
        foreach (var river in world.Rivers)
        {
            if (river.Points.Count < 2) continue;
            var lastPt = river.Points[^1];
            int lx = (int)(lastPt.X / C.WORLD_TILE_PIXELS);
            int ly = (int)(lastPt.Y / C.WORLD_TILE_PIXELS);
            lx = Math.Clamp(lx, 0, W - 1);
            ly = Math.Clamp(ly, 0, H - 1);

            var lastBiome = world.Tiles[lx, ly].Biome;
            if (lastBiome != BiomeId.Ocean && lastBiome != BiomeId.Wetland &&
                (world.Tiles[lx, ly].Features & FeatureFlags.HasRiver) == 0)
            {
                ctx.LogMessage($"[ValidationPass] WARNING: River {river.Id} endpoint not at water ({lx},{ly} biome={lastBiome})");
                warnings++;
            }
        }
        return warnings;
    }

    private static int CheckSettlementReachability(WorldState world, WorldGenContext ctx)
    {
        var capital = world.Settlements.FirstOrDefault(s => s.Anchor == NarrativeAnchor.SanctumFidelis);
        if (capital == null) return 0;

        // BFS from capital via HasRoad tiles
        var reachable = new HashSet<int>(); // settlement IDs
        var visited   = new bool[W, H];
        var queue     = new Queue<(int x, int y)>();

        queue.Enqueue((capital.TileX, capital.TileY));
        visited[capital.TileX, capital.TileY] = true;
        reachable.Add(capital.Id);

        (int dx, int dy)[] dirs4 = { (0,-1),(1,0),(0,1),(-1,0) };
        while (queue.Count > 0)
        {
            var (cx, cy) = queue.Dequeue();
            ref var tile = ref world.TileAt(cx, cy);

            if (tile.SettlementId > 0)
            {
                int sid = tile.SettlementId; // copy before lambda capture
                var s = world.Settlements.FirstOrDefault(s => s.Id == sid);
                if (s != null) reachable.Add(s.Id);
            }

            if ((tile.Features & FeatureFlags.HasRoad) == 0 &&
                (tile.Features & FeatureFlags.IsSettlement) == 0) continue;

            foreach (var (ddx, ddy) in dirs4)
            {
                int nx = cx + ddx, ny = cy + ddy;
                if ((uint)nx >= W || (uint)ny >= H || visited[nx, ny]) continue;
                var nf = world.Tiles[nx, ny].Features;
                if ((nf & (FeatureFlags.HasRoad | FeatureFlags.IsSettlement)) == 0) continue;
                visited[nx, ny] = true;
                queue.Enqueue((nx, ny));
            }
        }

        int warnings = 0;
        foreach (var s in world.Settlements.Where(s => s.Tier <= 3 && !s.IsPoi))
        {
            if (!reachable.Contains(s.Id))
            {
                ctx.LogMessage($"[ValidationPass] WARNING: {s.Name} (Tier {s.Tier}) not reachable via roads.");
                warnings++;
            }
        }
        return warnings;
    }

    private static int CheckNarrativeAnchors(WorldState world, WorldGenContext ctx)
    {
        int warnings = 0;
        var millhaven = world.Settlements.FirstOrDefault(s => s.Anchor == NarrativeAnchor.Millhaven);
        if (millhaven != null && !millhaven.IsOnRiver)
        {
            ctx.LogMessage("[ValidationPass] WARNING: Millhaven is not near a river.");
            warnings++;
        }

        var heartstone = world.Settlements.FirstOrDefault(s => s.Anchor == NarrativeAnchor.Heartstone);
        if (heartstone != null)
        {
            var hs = world.TileAt(heartstone.TileX, heartstone.TileY);
            if ((hs.Features & (FeatureFlags.HasRail | FeatureFlags.RailroadAdjacent)) != 0)
            {
                ctx.LogMessage("[ValidationPass] WARNING: Heartstone has rail adjacent.");
                warnings++;
            }
        }

        return warnings;
    }

    private static int CheckOverlappingSettlements(WorldState world, WorldGenContext ctx)
    {
        int violations = 0;
        var positions = new HashSet<(int, int)>();
        foreach (var s in world.Settlements)
        {
            if (!positions.Add((s.TileX, s.TileY)))
            {
                ctx.LogMessage($"[ValidationPass] VIOLATION: Two settlements at ({s.TileX},{s.TileY})");
                violations++;
            }
        }
        return violations;
    }
}
