using Theriapolis.Core.Data;

namespace Theriapolis.Core.World.Generation.Stages;

/// <summary>
/// Stage 8 — BiomeAssign
/// Per-tile biome assignment from (elevation, moisture, temperature) values.
/// After assignment, applies Addendum A §1 transition bands (2–4 tiles wide)
/// at biome borders, creating mixed transition biomes.
/// </summary>
public sealed class BiomeAssignStage : IWorldGenStage
{
    public string Name => "BiomeAssign";

    public void Run(WorldGenContext ctx)
    {
        if (ctx.World.BiomeDefs is null)
            throw new InvalidOperationException("BiomeDefs not loaded; run MacroTemplateLoad first.");

        var nonTransition = ctx.World.BiomeDefs.Where(b => !b.IsTransition).ToArray();
        int W = C.WORLD_WIDTH_TILES;
        int H = C.WORLD_HEIGHT_TILES;

        // Pass 1: assign base biome per tile
        Parallel.For(0, H, ty =>
        {
            for (int tx = 0; tx < W; tx++)
            {
                ref var tile = ref ctx.World.Tiles[tx, ty];
                tile.Biome = AssignBiome(nonTransition, tile.Elevation, tile.Moisture, tile.Temperature);
            }
        });

        // Pass 2: apply transition bands at biome borders (2–4 tile radius)
        var transitionMap = BuildTransitionMap();
        var biomeCopy = new BiomeId[W, H];
        for (int ty = 0; ty < H; ty++)
            for (int tx = 0; tx < W; tx++)
                biomeCopy[tx, ty] = ctx.World.Tiles[tx, ty].Biome;

        const int bandRadius = 3;
        for (int ty = bandRadius; ty < H - bandRadius; ty++)
        for (int tx = bandRadius; tx < W - bandRadius; tx++)
        {
            BiomeId center = biomeCopy[tx, ty];
            if (center == BiomeId.Ocean) continue;

            for (int dy = -bandRadius; dy <= bandRadius; dy++)
            for (int dx = -bandRadius; dx <= bandRadius; dx++)
            {
                if (dx == 0 && dy == 0) continue;
                float dist = MathF.Sqrt(dx * dx + dy * dy);
                if (dist > bandRadius) continue;

                BiomeId nbr = biomeCopy[tx + dx, ty + dy];
                if (nbr == center) continue;

                // Apply transition biome at the border zone
                BiomeId transition = GetTransition(center, nbr, transitionMap);
                if (transition != BiomeId.None)
                {
                    float t = dist / bandRadius; // 0 at center, 1 at edge
                    if (t < 0.5f)
                        ctx.World.Tiles[tx, ty].Biome = transition;
                }
            }
        }

        ctx.World.StageHashes["BiomeAssign"] = ctx.World.HashBiomes();
        ctx.LogMessage($"[BiomeAssign] Biome hash: 0x{ctx.World.StageHashes["BiomeAssign"]:X16}");
    }

    internal static BiomeId AssignBiome(BiomeDef[] defs, float e, float m, float t)
    {
        if (e < WorldState.SeaLevel) return BiomeId.Ocean;

        BiomeDef? best = null;
        float bestScore = -1f;
        foreach (var def in defs)
        {
            if (def.Id == "ocean") continue;
            float s = def.Score(e, m, t);
            if (s > bestScore) { bestScore = s; best = def; }
        }

        if (best is null) return BiomeId.TemperateGrassland; // fallback
        return ParseBiomeId(best.Id);
    }

    public static BiomeId ParseBiomeId(string id) => id.ToLowerInvariant() switch
    {
        "ocean"                => BiomeId.Ocean,
        "tundra"               => BiomeId.Tundra,
        "boreal"               => BiomeId.Boreal,
        "temperate_deciduous"  => BiomeId.TemperateDeciduous,
        "temperate_grassland"  => BiomeId.TemperateGrassland,
        "mountain_alpine"      => BiomeId.MountainAlpine,
        "mountain_forested"    => BiomeId.MountainForested,
        "subtropical_forest"   => BiomeId.SubtropicalForest,
        "wetland"              => BiomeId.Wetland,
        "coastal"              => BiomeId.Coastal,
        "river_valley"         => BiomeId.RiverValley,
        "scrubland"            => BiomeId.Scrubland,
        "desert_cold"          => BiomeId.DesertCold,
        "forest_edge"          => BiomeId.ForestEdge,
        "foothills"            => BiomeId.Foothills,
        "marsh_edge"           => BiomeId.MarshEdge,
        "beach"                => BiomeId.Beach,
        "cliff"                => BiomeId.Cliff,
        "tidal_flat"           => BiomeId.TidalFlat,
        "mangrove"             => BiomeId.Mangrove,
        _                      => BiomeId.TemperateGrassland,
    };

    // Addendum A §1: transition biome pairs
    private static Dictionary<(BiomeId, BiomeId), BiomeId> BuildTransitionMap() => new()
    {
        {(BiomeId.TemperateDeciduous, BiomeId.TemperateGrassland), BiomeId.ForestEdge},
        {(BiomeId.TemperateGrassland, BiomeId.TemperateDeciduous), BiomeId.ForestEdge},
        {(BiomeId.Boreal,             BiomeId.TemperateDeciduous), BiomeId.ForestEdge},
        {(BiomeId.TemperateDeciduous, BiomeId.Boreal),             BiomeId.ForestEdge},
        {(BiomeId.TemperateGrassland, BiomeId.MountainAlpine),     BiomeId.Foothills},
        {(BiomeId.MountainAlpine,     BiomeId.TemperateGrassland), BiomeId.Foothills},
        {(BiomeId.TemperateDeciduous, BiomeId.MountainAlpine),     BiomeId.Foothills},
        {(BiomeId.MountainAlpine,     BiomeId.TemperateDeciduous), BiomeId.Foothills},
        {(BiomeId.Boreal,             BiomeId.MountainAlpine),     BiomeId.Foothills},
        {(BiomeId.TemperateDeciduous, BiomeId.Wetland),            BiomeId.MarshEdge},
        {(BiomeId.Wetland,            BiomeId.TemperateDeciduous), BiomeId.MarshEdge},
        {(BiomeId.TemperateGrassland, BiomeId.Wetland),            BiomeId.MarshEdge},
        {(BiomeId.Wetland,            BiomeId.TemperateGrassland), BiomeId.MarshEdge},
        // Coastal transitions from any land biome
        {(BiomeId.TemperateDeciduous, BiomeId.Ocean),              BiomeId.Beach},
        {(BiomeId.TemperateGrassland, BiomeId.Ocean),              BiomeId.Beach},
        {(BiomeId.Tundra,             BiomeId.Ocean),              BiomeId.Beach},
        {(BiomeId.SubtropicalForest,  BiomeId.Ocean),              BiomeId.Mangrove},
        {(BiomeId.Wetland,            BiomeId.Ocean),              BiomeId.TidalFlat},
        {(BiomeId.MountainAlpine,     BiomeId.Ocean),              BiomeId.Cliff},
    };

    private static BiomeId GetTransition(BiomeId a, BiomeId b, Dictionary<(BiomeId, BiomeId), BiomeId> map)
    {
        if (map.TryGetValue((a, b), out var t)) return t;
        return BiomeId.None;
    }
}
