using NeonSprawl.Server.Game.Contracts; using NeonSprawl.Server.Game.Factions; namespace NeonSprawl.Server.Game.Rewards; /// Id normalization and composite keys for reward delivery stores (NEO-126). public static class RewardDeliveryIds { /// Trim + lowercase; empty when input is null/whitespace. public static string NormalizePlayerId(string? playerId) { var trimmed = playerId?.Trim(); if (string.IsNullOrEmpty(trimmed)) { return string.Empty; } return trimmed.ToLowerInvariant(); } /// Trim + lowercase; empty when input is null/whitespace. public static string NormalizeQuestId(string? questId) => NormalizePlayerId(questId); /// Trim + lowercase; empty when input is null/whitespace. public static string NormalizeContractInstanceId(string? contractInstanceId) => ContractInstanceIds.NormalizeContractInstanceId(contractInstanceId); /// Stable idempotency key for quest completion delivery. public static string MakeIdempotencyKey(string? playerId, string? questId) { var p = NormalizePlayerId(playerId); var q = NormalizeQuestId(questId); if (p.Length == 0 || q.Length == 0) { return string.Empty; } return $"{p}:quest_complete:{q}"; } /// Stable audit row id for one rep grant on a quest completion delivery (NEO-138). public static string MakeReputationDeltaId(string idempotencyKey, string? factionId) { var faction = FactionStandingIds.NormalizeFactionId(factionId); if (idempotencyKey.Length == 0 || faction.Length == 0) { return string.Empty; } return $"{idempotencyKey}:rep:{faction}"; } /// Rollback audit row id for compensating rep revert (NEO-138). public static string MakeReputationRollbackDeltaId(string reputationDeltaId) => reputationDeltaId.Length == 0 ? string.Empty : $"{reputationDeltaId}:rollback"; /// Composite store key for quest completion delivery. public static string MakeStoreKey(string? playerId, string? questId) => MakeStoreKey(playerId, RewardDeliverySourceKinds.QuestCompletion, questId); /// Composite store key for + + source id. public static string MakeStoreKey(string? playerId, string? sourceKind, string? sourceId) { var p = NormalizePlayerId(playerId); var kind = NormalizeSourceKind(sourceKind); var id = NormalizeSourceId(sourceKind, sourceId); if (p.Length == 0 || kind.Length == 0 || id.Length == 0) { return string.Empty; } return $"{p}\0{kind}\0{id}"; } /// Resolves the source id field for a persisted delivery event. public static string GetSourceId(in RewardDeliveryEvent deliveryEvent) => string.Equals(deliveryEvent.SourceKind, RewardDeliverySourceKinds.ContractCompletion, StringComparison.Ordinal) ? NormalizeContractInstanceId(deliveryEvent.ContractInstanceId) : NormalizeQuestId(deliveryEvent.QuestId); /// Composite store key for a persisted delivery event. public static string MakeStoreKey(in RewardDeliveryEvent deliveryEvent) => MakeStoreKey(deliveryEvent.PlayerId, deliveryEvent.SourceKind, GetSourceId(deliveryEvent)); private static string NormalizeSourceKind(string? sourceKind) { var trimmed = sourceKind?.Trim(); if (string.IsNullOrEmpty(trimmed)) { return string.Empty; } return trimmed.ToLowerInvariant(); } private static string NormalizeSourceId(string? sourceKind, string? sourceId) => string.Equals(sourceKind, RewardDeliverySourceKinds.ContractCompletion, StringComparison.OrdinalIgnoreCase) ? NormalizeContractInstanceId(sourceId) : NormalizeQuestId(sourceId); }