using System.Collections.Frozen;

namespace NeonSprawl.Server.Game.Gathering;

/// <summary>
/// Prototype Slice 2 roster gate (NEO-57), mirrored from <c>scripts/validate_content.py</c>
/// <c>PROTOTYPE_SLICE2_*</c> / <c>_prototype_slice2_*_gate</c>.
/// </summary>
public static class PrototypeSlice2ResourceNodeCatalogRules
{
    /// <summary>Keep in sync with <c>scripts/validate_content.py</c> <c>PROTOTYPE_SLICE2_NODE_IDS</c>.</summary>
    public static readonly FrozenSet<string> ExpectedNodeDefIds = FrozenSet.ToFrozenSet(
        [
            "prototype_resource_node_alpha",
            "prototype_subsurface_vein_beta",
            "prototype_bio_mat_gamma",
            "prototype_urban_bulk_delta",
        ],
        StringComparer.Ordinal);

    /// <summary>Keep in sync with <c>scripts/validate_content.py</c> <c>PROTOTYPE_SLICE2_GATHER_LENSES</c>.</summary>
    public static readonly FrozenSet<string> ExpectedGatherLenses = FrozenSet.ToFrozenSet(
        ["consumer_salvage", "subsurface", "bio", "urban_bulk"],
        StringComparer.Ordinal);

    /// <summary>Keep in sync with <c>scripts/validate_content.py</c> <c>PROTOTYPE_SLICE2_YIELD_ITEM_IDS</c>.</summary>
    public static readonly FrozenSet<string> ExpectedYieldItemIds = FrozenSet.ToFrozenSet(
        ["scrap_metal_bulk"],
        StringComparer.Ordinal);

    /// <summary>Returns a human-readable error if the Slice 2 node contract fails, otherwise <see langword="null"/>.</summary>
    public static string? TryGetSlice2NodeGateError(
        IReadOnlyDictionary<string, string> nodeDefIdToSourceFile,
        IReadOnlyDictionary<string, string> idToGatherLens)
    {
        var ids = nodeDefIdToSourceFile.Keys.ToFrozenSet(StringComparer.Ordinal);
        if (!ids.SetEquals(ExpectedNodeDefIds))
        {
            return
                "error: prototype Slice 2 expects exactly nodeDefIds " +
                $"[{string.Join(", ", ExpectedNodeDefIds.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}], " +
                $"got [{string.Join(", ", ids.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}]";
        }

        var lenses = idToGatherLens.Values.ToHashSet(StringComparer.Ordinal);
        if (!lenses.SetEquals(ExpectedGatherLenses))
        {
            return
                "error: prototype Slice 2 requires exactly one row per gatherLens " +
                $"[{string.Join(", ", ExpectedGatherLenses.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}], " +
                $"lenses seen: [{string.Join(", ", lenses.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}]";
        }

        return null;
    }

    /// <summary>Returns a human-readable error if the Slice 2 yield contract fails, otherwise <see langword="null"/>.</summary>
    public static string? TryGetSlice2YieldGateError(
        IReadOnlySet<string> yieldNodeDefIds,
        IReadOnlySet<string> yieldItemIds)
    {
        var yieldNodes = yieldNodeDefIds.ToFrozenSet(StringComparer.Ordinal);
        if (!yieldNodes.SetEquals(ExpectedNodeDefIds))
        {
            return
                "error: prototype Slice 2 expects exactly one yield row per nodeDefId " +
                $"[{string.Join(", ", ExpectedNodeDefIds.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}], " +
                $"yield nodeDefIds: [{string.Join(", ", yieldNodes.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}]";
        }

        var items = yieldItemIds.ToFrozenSet(StringComparer.Ordinal);
        if (!items.SetEquals(ExpectedYieldItemIds))
        {
            return
                "error: prototype Slice 2 expects yield itemIds " +
                $"[{string.Join(", ", ExpectedYieldItemIds.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}] only, " +
                $"got [{string.Join(", ", items.Order(StringComparer.Ordinal).Select(s => "'" + s + "'"))}]";
        }

        return null;
    }
}
