using System.Security.Cryptography;
using System.Text;
namespace NeonSprawl.Server.Game.Contracts;
/// Id normalization for contract instance stores (NEO-146).
public static class ContractInstanceIds
{
/// 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 NormalizeContractInstanceId(string? contractInstanceId) => NormalizePlayerId(contractInstanceId);
/// Trim + lowercase; empty when input is null/whitespace.
public static string NormalizeTemplateId(string? templateId) => NormalizePlayerId(templateId);
/// Trim; empty when input is null/whitespace (seed bucket casing preserved).
public static string NormalizeSeedBucket(string? seedBucket)
{
var trimmed = seedBucket?.Trim();
return string.IsNullOrEmpty(trimmed) ? string.Empty : trimmed;
}
/// Composite store key for + .
public static string MakeInstanceKey(string? playerId, string? contractInstanceId)
{
var p = NormalizePlayerId(playerId);
var i = NormalizeContractInstanceId(contractInstanceId);
if (p.Length == 0 || i.Length == 0)
{
return string.Empty;
}
return $"{p}\0{i}";
}
///
/// Deterministic contract instance id from normalized seed inputs (NEO-147).
/// Format: ci_{32 lowercase hex} from SHA-256 of {playerId}|{templateId}|{seedBucket}.
///
public static string MakeDeterministicInstanceId(string? playerId, string? templateId, string? seedBucket)
{
var p = NormalizePlayerId(playerId);
var t = NormalizeTemplateId(templateId);
var b = NormalizeSeedBucket(seedBucket);
if (p.Length == 0 || t.Length == 0 || b.Length == 0)
{
return string.Empty;
}
var payload = $"{p}|{t}|{b}";
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(payload));
return $"ci_{Convert.ToHexStringLower(hash.AsSpan(0, 16))}";
}
}