using System.Collections.Frozen;

namespace NeonSprawl.Server.Game.Items;

/// <summary>
/// Prototype Slice 1 roster gate (NEO-50), mirrored from <c>scripts/validate_content.py</c>
/// <c>PROTOTYPE_SLICE1_ITEM_IDS</c> / <c>_prototype_slice1_item_gate</c>.
/// </summary>
public static class PrototypeSlice1ItemCatalogRules
{
    /// <summary>Keep in sync with <c>scripts/validate_content.py</c> <c>PROTOTYPE_SLICE1_ITEM_IDS</c>.</summary>
    public static readonly FrozenSet<string> ExpectedItemIds = FrozenSet.ToFrozenSet(
        [
            "scrap_metal_bulk",
            "refined_plate_stock",
            "field_stim_mk0",
            "survey_drone_kit",
            "contract_handoff_token",
            "prototype_armor_shell",
        ],
        StringComparer.Ordinal);

    /// <summary>Keep in sync with <c>scripts/validate_content.py</c> <c>PROTOTYPE_SLICE1_ITEM_ROLES</c>.</summary>
    public static readonly FrozenSet<string> ExpectedPrototypeRoles = FrozenSet.ToFrozenSet(
        ["material", "intermediate", "consumable", "utility", "quest_token", "equip_stub"],
        StringComparer.Ordinal);

    /// <summary>Returns a human-readable error if the Slice 1 contract fails, otherwise <see langword="null"/>.</summary>
    public static string? TryGetSlice1GateError(
        IReadOnlyDictionary<string, string> itemIdToSourceFile,
        IReadOnlyDictionary<string, string> idToRole,
        IReadOnlyDictionary<string, string> idToSlotKind,
        IReadOnlyDictionary<string, int> idToStackMax)
    {
        var ids = itemIdToSourceFile.Keys.ToFrozenSet(StringComparer.Ordinal);
        if (!ids.SetEquals(ExpectedItemIds))
        {
            return
                "error: prototype Slice 1 expects exactly item ids " +
                $"[{string.Join(", ", ExpectedItemIds.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}], " +
                $"got [{string.Join(", ", ids.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}]";
        }

        var roles = idToRole.Values.ToHashSet(StringComparer.Ordinal);
        if (!roles.SetEquals(ExpectedPrototypeRoles))
        {
            return
                "error: prototype Slice 1 requires exactly one row per prototypeRole " +
                $"[{string.Join(", ", ExpectedPrototypeRoles.Order(StringComparer.Ordinal).Select(r => "'" + r + "'"))}], " +
                $"roles seen: [{string.Join(", ", roles.Order(StringComparer.Ordinal).Select(r => "'" + r + "'"))}]";
        }

        string? equipStubId = null;
        foreach (var kv in idToRole)
        {
            if (kv.Value == "equip_stub")
            {
                equipStubId = kv.Key;
                break;
            }
        }

        if (equipStubId is not null)
        {
            if (!idToSlotKind.TryGetValue(equipStubId, out var slotKind) || slotKind != "equipment")
            {
                idToSlotKind.TryGetValue(equipStubId, out slotKind);
                return
                    $"error: '{equipStubId}' (equip_stub) must use inventorySlotKind 'equipment', got '{slotKind}'";
            }

            if (!idToStackMax.TryGetValue(equipStubId, out var stackMax) || stackMax != 1)
            {
                idToStackMax.TryGetValue(equipStubId, out stackMax);
                return $"error: '{equipStubId}' (equip_stub) must use stackMax 1, got {stackMax}";
            }
        }

        return null;
    }
}
