using Theriapolis.Core.Data;
using Theriapolis.Core.Util;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 13 — NarrativeAnchorPlace
/// Places the 6 narrative anchor settlements (Sanctum Fidelis first, then others).
/// Must run BEFORE general settlement placement.
/// </summary>
public sealed class NarrativeAnchorPlaceStage : IWorldGenStage
{
    public string Name => "NarrativeAnchorPlace";

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

    private int _nextId = 1;
    private int[,] _componentIds = null!;
    private int _mainLandmassId;

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

        // Confine anchors to the main (largest) landmass so the road network
        // doesn't need to cross ocean to reach them.
        var (componentIds, componentSizes) = LandmassMap.Compute(world);
        _componentIds    = componentIds;
        _mainLandmassId  = LandmassMap.LargestComponentId(componentSizes);

        var placed = new List<Settlement>();

        // Placement order: most-constrained first
        PlaceSanctumFidelis(world, placed, rng);
        PlaceHeartstone    (world, placed, rng);
        PlaceThornfield    (world, placed, rng);
        PlaceFortDustwall  (world, placed, rng);
        PlaceMillhaven     (world, placed, rng);
        PlaceTheTangles    (world, placed, rng);

        foreach (var s in placed)
        {
            world.Settlements.Add(s);
            MarkSettlementTiles(world, s);
        }

        ctx.LogMessage($"[NarrativeAnchorPlace] Placed {placed.Count} anchor settlements.");
    }

    // ── Anchor placements ─────────────────────────────────────────────────────

    private void PlaceSanctumFidelis(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        // Preferred: center region, near a river
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.MacroX < 12 || tile.MacroX > 22) return false;
            if (tile.MacroY < 10 || tile.MacroY > 22) return false;
            return (tile.Features & (FeatureFlags.HasRiver | FeatureFlags.RiverAdjacent)) != 0;
        });

        if (candidates.Count == 0)
        {
            // Relaxed: center region, any non-ocean land tile
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean &&
                tile.MacroX >= 10 && tile.MacroX <= 24 &&
                tile.MacroY >= 8  && tile.MacroY <= 24);
        }

        if (candidates.Count == 0)
        {
            // Last resort: any suitable non-ocean tile
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean && tile.Elevation < 0.75f);
        }

        var best = PickBest(candidates, world, placed, rng, (int)C.ANCHOR_MIN_DIST);
        if (best.HasValue)
        {
            var s = MakeSettlement(world, best.Value.x, best.Value.y, 1, NarrativeAnchor.SanctumFidelis);
            s.Name = "Sanctum Fidelis";
            placed.Add(s);
        }
    }

    private void PlaceHeartstone(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        // Preferred: high mountain in western half
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.Biome is not (BiomeId.MountainAlpine or BiomeId.MountainForested)) return false;
            if (tile.Elevation < 0.60f) return false;
            return tile.MacroX <= 14; // western half
        });

        if (candidates.Count == 0)
        {
            // Relaxed: any mountain tile anywhere, no elevation minimum
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean &&
                tile.Biome is BiomeId.MountainAlpine or BiomeId.MountainForested);
        }

        if (candidates.Count == 0)
        {
            // Last resort: any highland tile (elevation ≥ 0.50) that isn't ocean
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean && tile.Elevation >= 0.50f);
        }

        var best = PickBest(candidates, world, placed, rng, (int)(C.ANCHOR_MIN_DIST * 0.5f));
        if (best.HasValue)
        {
            var s = MakeSettlement(world, best.Value.x, best.Value.y, 2, NarrativeAnchor.Heartstone);
            s.Name = "Heartstone";
            placed.Add(s);
        }
    }

    private void PlaceThornfield(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.MacroX < 16) return false;
            var dev = macro.Development?.ToLowerInvariant() ?? "";
            if (!dev.Contains("industrial") && !dev.Contains("urban")) return false;
            return (tile.Features & (FeatureFlags.HasRiver | FeatureFlags.RiverAdjacent)) != 0;
        });

        if (candidates.Count == 0)
        {
            // Relaxed: any eastern developed region with river
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean && tile.MacroX >= 16 &&
                (tile.Features & (FeatureFlags.HasRiver | FeatureFlags.RiverAdjacent)) != 0);
        }

        if (candidates.Count == 0)
        {
            // Last resort: any non-ocean tile with a river (drop east-half
            // requirement). On small maps the eastern half may not happen to
            // contain any river tiles for a given seed; Thornfield still
            // needs to be placed somewhere reasonable.
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean &&
                (tile.Features & (FeatureFlags.HasRiver | FeatureFlags.RiverAdjacent)) != 0);
        }

        if (candidates.Count == 0)
        {
            // Absolute fallback: any non-ocean land tile. Matches the
            // last-resort tier that SanctumFidelis/Heartstone already use.
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean);
        }

        var best = PickBest(candidates, world, placed, rng, (int)C.ANCHOR_MIN_DIST);
        if (best.HasValue)
        {
            var s = MakeSettlement(world, best.Value.x, best.Value.y, 2, NarrativeAnchor.Thornfield);
            s.Name = "Thornfield";
            placed.Add(s);
        }
    }

    private void PlaceFortDustwall(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.Biome is not (BiomeId.TemperateGrassland or BiomeId.Scrubland
                or BiomeId.TemperateDeciduous)) return false;
            return tile.MacroX >= 10 && tile.MacroX <= 22 &&
                   tile.MacroY >= 12 && tile.MacroY <= 26;
        });

        var best = PickBest(candidates, world, placed, rng, (int)C.ANCHOR_MIN_DIST);
        if (best.HasValue)
        {
            int tier = rng.NextBool(0.6) ? 2 : 3;
            var s = MakeSettlement(world, best.Value.x, best.Value.y, tier, NarrativeAnchor.FortDustwall);
            s.Name = "Fort Dustwall";
            placed.Add(s);
        }
    }

    private void PlaceMillhaven(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        // Preferred: eastern forest/grassland near a river
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.Biome is not (BiomeId.TemperateDeciduous or BiomeId.ForestEdge
                or BiomeId.TemperateGrassland)) return false;
            if (tile.MacroX < 16) return false;
            if ((tile.Features & (FeatureFlags.HasRiver | FeatureFlags.RiverAdjacent)) == 0) return false;
            // At least 40 tiles from any Tier 2+ anchor already placed
            foreach (var p in placed)
            {
                if (p.Tier <= 2)
                {
                    float d = MathF.Sqrt((x - p.TileX) * (float)(x - p.TileX)
                                       + (y - p.TileY) * (float)(y - p.TileY));
                    if (d < 40) return false;
                }
            }
            return true;
        });

        if (candidates.Count == 0)
        {
            // Relaxed: drop river requirement, keep biome and location
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean &&
                tile.Biome is BiomeId.TemperateDeciduous or BiomeId.ForestEdge
                    or BiomeId.TemperateGrassland &&
                tile.MacroX >= 12);
        }

        if (candidates.Count == 0)
        {
            // Last resort: any non-ocean land tile in the eastern two-thirds
            candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
                tile.Biome != BiomeId.Ocean && tile.MacroX >= 10);
        }

        var best = PickBest(candidates, world, placed, rng, (int)C.ANCHOR_MIN_DIST / 2);
        if (best.HasValue)
        {
            var s = MakeSettlement(world, best.Value.x, best.Value.y, 3, NarrativeAnchor.Millhaven);
            s.Name = "Millhaven";
            placed.Add(s);
        }
    }

    private void PlaceTheTangles(WorldState world, List<Settlement> placed, SeededRng rng)
    {
        var candidates = CollectCandidates(world, placed, (x, y, tile, macro) =>
        {
            if (tile.Biome == BiomeId.Ocean) return false;
            if (tile.Biome is not (BiomeId.SubtropicalForest or BiomeId.Wetland
                or BiomeId.Mangrove or BiomeId.MarshEdge)) return false;
            var cov = macro.Covenant?.ToLowerInvariant() ?? "";
            return cov is "weak" or "nominal";
        });

        int count = rng.NextInt(2, 5);
        bool firstTangle = true;

        for (int i = 0; i < count && candidates.Count > 0; i++)
        {
            var best = PickBest(candidates, world, placed, rng, C.SETTLE_MIN_DIST_TIER4);
            if (!best.HasValue) break;

            var s = MakeSettlement(world, best.Value.x, best.Value.y, 4, NarrativeAnchor.TheTangles);
            s.Name = firstTangle ? "Thornback Hollow" : NameGenerator.Generate(rng, "subtropical");
            firstTangle = false;
            placed.Add(s);
            MarkSettlementTiles(world, s);

            candidates = candidates
                .Where(c => !IsCloseTo(c.x, c.y, best.Value.x, best.Value.y, C.SETTLE_MIN_DIST_TIER4))
                .ToList();
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private List<(int x, int y)> CollectCandidates(
        WorldState world,
        List<Settlement> placed,
        Func<int, int, WorldTile, MacroCell, bool> filter,
        int step = 2)
    {
        var result = new List<(int x, int y)>();
        for (int y = 0; y < H; y += step)
        for (int x = 0; x < W; x += step)
        {
            if (_componentIds[x, y] != _mainLandmassId) continue;
            ref var tile = ref world.TileAt(x, y);
            var macro = world.MacroCellForTile(in tile);
            if (!filter(x, y, tile, macro)) continue;
            if (IsTooCloseToAnySettlement(x, y, placed, C.SETTLE_MIN_DIST_TIER2)) continue;
            result.Add((x, y));
        }
        return result;
    }

    private static (int x, int y)? PickBest(
        List<(int x, int y)> candidates,
        WorldState world,
        List<Settlement> placed,
        SeededRng rng,
        int anchorMinDist)
    {
        if (candidates.Count == 0) return null;

        var ranked = candidates
            .Where(c => !IsTooCloseToAnySettlement(c.x, c.y, placed, anchorMinDist))
            .OrderByDescending(c => world.Habitability?[c.x, c.y] ?? 0f)
            .Take(10)
            .ToList();

        if (ranked.Count == 0)
            ranked = candidates.OrderByDescending(c => world.Habitability?[c.x, c.y] ?? 0f)
                               .Take(5).ToList();
        if (ranked.Count == 0) return null;

        return ranked[rng.NextInt(Math.Min(3, ranked.Count))];
    }

    private Settlement MakeSettlement(WorldState world, int x, int y, int tier, NarrativeAnchor anchor)
    {
        var (nx, ny) = SettlementPlaceStage.NudgeOffRiver(world, x, y);
        return new Settlement
        {
            Id     = _nextId++,
            Tier   = tier,
            TileX  = nx,
            TileY  = ny,
            Anchor = anchor,
        };
    }

    private static void MarkSettlementTiles(WorldState world, Settlement s)
    {
        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) continue;
            ref var tile = ref world.TileAt(nx, ny);
            tile.Features    |= FeatureFlags.IsSettlement;
            tile.SettlementId = (ushort)Math.Min(s.Id, ushort.MaxValue);
        }
    }

    private static bool IsCloseTo(int x1, int y1, int x2, int y2, int minDist)
        => (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) < minDist * minDist;

    private static bool IsTooCloseToAnySettlement(int x, int y, List<Settlement> placed, int minDist)
    {
        foreach (var s in placed)
            if (IsCloseTo(x, y, s.TileX, s.TileY, minDist)) return true;
        return false;
    }
}
