using NeonSprawl.Server.Game.PositionState;

namespace NeonSprawl.Server.Game.AbilityInput;

/// <summary>Maps hotbar loadout read/update APIs (NEO-29).</summary>
public static class HotbarLoadoutApi
{
    public const string ReasonSlotOutOfBounds = "slot_out_of_bounds";
    public const string ReasonUnknownAbility = "unknown_ability";
    public const string ReasonDuplicateSlot = "duplicate_slot";

    public static WebApplication MapHotbarLoadoutApi(this WebApplication app)
    {
        app.MapGet(
            "/game/players/{id}/hotbar-loadout",
            (string id, IPositionStateStore positions, IPlayerHotbarLoadoutStore store) =>
            {
                if (!positions.TryGetPosition(id, out _))
                {
                    return Results.NotFound();
                }

                if (!store.TryGetBindings(id, out var bindings))
                {
                    return Results.NotFound();
                }

                return Results.Json(BuildLoadoutResponse(id, bindings));
            });

        app.MapPost(
            "/game/players/{id}/hotbar-loadout",
            (string id, HotbarLoadoutUpdateRequest? body, IPositionStateStore positions, IPlayerHotbarLoadoutStore store) =>
            {
                if (body is null || body.SchemaVersion != HotbarLoadoutUpdateRequest.CurrentSchemaVersion)
                {
                    return Results.BadRequest();
                }

                if (!positions.TryGetPosition(id, out _))
                {
                    return Results.NotFound();
                }

                if (!store.TryGetBindings(id, out var before))
                {
                    return Results.NotFound();
                }

                var updates = body.Slots ?? [];
                var parsedUpdates = new List<HotbarSlotBinding>(updates.Count);
                var seenSlots = new HashSet<int>();
                foreach (var update in updates)
                {
                    if (update.SlotIndex < 0 || update.SlotIndex >= HotbarLoadoutResponse.SlotCountV1)
                    {
                        return Results.Json(
                            new HotbarLoadoutUpdateResponse
                            {
                                Updated = false,
                                ReasonCode = ReasonSlotOutOfBounds,
                                Loadout = BuildLoadoutResponse(id, before),
                            });
                    }

                    if (!seenSlots.Add(update.SlotIndex))
                    {
                        return Results.Json(
                            new HotbarLoadoutUpdateResponse
                            {
                                Updated = false,
                                ReasonCode = ReasonDuplicateSlot,
                                Loadout = BuildLoadoutResponse(id, before),
                            });
                    }

                    if (string.IsNullOrWhiteSpace(update.AbilityId) ||
                        !PrototypeAbilityRegistry.TryNormalizeKnown(update.AbilityId, out var normalizedAbilityId))
                    {
                        return Results.Json(
                            new HotbarLoadoutUpdateResponse
                            {
                                Updated = false,
                                ReasonCode = ReasonUnknownAbility,
                                Loadout = BuildLoadoutResponse(id, before),
                            });
                    }

                    parsedUpdates.Add(new HotbarSlotBinding(update.SlotIndex, normalizedAbilityId));
                }

                if (!store.TryUpsertBindings(id, parsedUpdates, out var after))
                {
                    return Results.NotFound();
                }

                return Results.Json(
                    new HotbarLoadoutUpdateResponse
                    {
                        Updated = true,
                        Loadout = BuildLoadoutResponse(id, after),
                    });
            });

        return app;
    }

    private static HotbarLoadoutResponse BuildLoadoutResponse(string playerId, IReadOnlyDictionary<int, string> bindings)
    {
        var slots = new List<HotbarSlotStateJson>(HotbarLoadoutResponse.SlotCountV1);
        for (var i = 0; i < HotbarLoadoutResponse.SlotCountV1; i++)
        {
            bindings.TryGetValue(i, out var abilityId);
            slots.Add(
                new HotbarSlotStateJson
                {
                    SlotIndex = i,
                    AbilityId = abilityId,
                });
        }

        return new HotbarLoadoutResponse
        {
            PlayerId = playerId.Trim(),
            Slots = slots,
        };
    }
}
