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

namespace Theriapolis.Core.Entities;

/// <summary>
/// Plans a continuous-time path between two world tiles. The result is a
/// list of world-pixel waypoints (tile centers); the controller animates
/// the player along it at <see cref="C.PLAYER_TRAVEL_PX_PER_SEC"/>.
///
/// Cost function:
///   • Ocean tiles (Biome == Ocean) are impassable.
///   • Mountain tiles cost 4× a grassland tile (slow but not blocked).
///   • Tiles carrying a road get a strong discount (matches the ROAD_SPEED_MULT idea).
///   • Diagonal moves are √2 like everywhere else in the codebase.
///
/// The clock-advance amount is computed afterwards by walking the path
/// and summing per-segment travel time — see <see cref="EstimateSecondsForLeg"/>.
/// </summary>
public sealed class WorldTravelPlanner
{
    private readonly AStarPathfinder _astar = new();
    private readonly WorldState _world;

    public WorldTravelPlanner(WorldState world) { _world = world; }

    /// <summary>
    /// Returns null if no path exists. Returned waypoints are tile coordinates,
    /// not world-pixel coordinates — convert with <see cref="TileCenterToWorldPixel"/>.
    /// </summary>
    public List<(int X, int Y)>? PlanTilePath(int sx, int sy, int gx, int gy)
    {
        if (!IsWalkable(sx, sy)) return null;
        if (!IsWalkable(gx, gy)) return null;
        return _astar.FindPath(sx, sy, gx, gy, CostFn);
    }

    private float CostFn(int fx, int fy, int tx, int ty, byte _)
    {
        if (!IsWalkable(tx, ty)) return float.PositiveInfinity;

        ref var t = ref _world.TileAt(tx, ty);
        float baseCost = TerrainCost(t);
        // Strong incentive to follow roads — matches the EXISTING_ROAD_COST
        // worldgen philosophy. Pure additive (not multiplicative) so it
        // never goes negative and A* admissibility holds.
        if ((t.Features & FeatureFlags.HasRoad) != 0) baseCost *= 0.25f;
        return baseCost;
    }

    private bool IsWalkable(int x, int y)
    {
        if ((uint)x >= C.WORLD_WIDTH_TILES)  return false;
        if ((uint)y >= C.WORLD_HEIGHT_TILES) return false;
        ref var t = ref _world.TileAt(x, y);
        return t.Biome != BiomeId.Ocean;
    }

    private static float TerrainCost(in WorldTile t) => t.Biome switch
    {
        BiomeId.MountainAlpine    => 4.0f,
        BiomeId.MountainForested  => 3.0f,
        BiomeId.Wetland           => 2.5f,
        BiomeId.Foothills         => 1.8f,
        BiomeId.Boreal            => 1.6f,
        BiomeId.SubtropicalForest => 1.6f,
        BiomeId.TemperateDeciduous => 1.5f,
        BiomeId.Tundra            => 1.5f,
        BiomeId.DesertCold        => 1.4f,
        BiomeId.Mangrove          => 1.4f,
        BiomeId.MarshEdge         => 1.4f,
        BiomeId.Scrubland         => 1.2f,
        BiomeId.ForestEdge        => 1.3f,
        BiomeId.Cliff             => 3.0f,
        BiomeId.TemperateGrassland => 1.0f,
        BiomeId.RiverValley       => 1.0f,
        BiomeId.Coastal           => 1.0f,
        BiomeId.Beach             => 1.0f,
        BiomeId.TidalFlat         => 1.5f,
        _                          => 1.2f,
    };

    public static Vec2 TileCenterToWorldPixel(int x, int y)
        => new(x * C.WORLD_TILE_PIXELS + C.WORLD_TILE_PIXELS * 0.5f,
               y * C.WORLD_TILE_PIXELS + C.WORLD_TILE_PIXELS * 0.5f);

    /// <summary>
    /// In-game seconds to traverse a single world-pixel between adjacent tiles.
    /// Combines BASE_SEC_PER_WORLD_PIXEL with biome modifier and a road bonus.
    /// </summary>
    public float SecondsPerPixel(in WorldTile t)
    {
        float biomeMod = t.Biome switch
        {
            BiomeId.MountainAlpine    => 3.0f,
            BiomeId.MountainForested  => 2.5f,
            BiomeId.Wetland           => 2.0f,
            BiomeId.Foothills         => 1.6f,
            BiomeId.Boreal            => 1.5f,
            BiomeId.SubtropicalForest => 1.5f,
            BiomeId.TemperateDeciduous => 1.4f,
            BiomeId.Tundra            => 1.5f,
            _                          => 1.0f,
        };
        float roadMod = ((t.Features & FeatureFlags.HasRoad) != 0) ? C.ROAD_SPEED_MULT : 1f;
        return C.BASE_SEC_PER_WORLD_PIXEL * biomeMod * roadMod;
    }

    /// <summary>In-game seconds to walk between two adjacent tile centers.</summary>
    public float EstimateSecondsForLeg(int fx, int fy, int tx, int ty)
    {
        ref var to = ref _world.TileAt(tx, ty);
        float pixDist = Vec2.Dist(TileCenterToWorldPixel(fx, fy), TileCenterToWorldPixel(tx, ty));
        return pixDist * SecondsPerPixel(to);
    }
}
