using PiiRedaction.Core.Abstractions; using PiiRedaction.Core.Models; namespace PiiRedaction.Core.Redaction; public sealed class PlaceholderPiiRedactor : IPiiRedactor { public RedactionResult Redact(string text, IReadOnlyList entities) { ArgumentException.ThrowIfNullOrWhiteSpace(text); ArgumentNullException.ThrowIfNull(entities); var placeholderByValue = new Dictionary(StringComparer.Ordinal); var counters = new Dictionary(); var orderedEntities = entities.OrderByDescending(entity => entity.StartIndex).ToList(); var sanitized = text; foreach (var entity in orderedEntities) { var mapKey = CreateValueKey(entity); if (!placeholderByValue.TryGetValue(mapKey, out var placeholder)) { counters.TryGetValue(entity.Type, out var count); count++; counters[entity.Type] = count; placeholder = $"<{ToPlaceholderPrefix(entity.Type)}_{count}>"; placeholderByValue[mapKey] = placeholder; } sanitized = string.Concat( sanitized.AsSpan(0, entity.StartIndex), placeholder, sanitized.AsSpan(entity.EndIndex)); } var placeholderMap = placeholderByValue .ToDictionary(pair => pair.Value, pair => pair.Key.Split('|', 2)[1], StringComparer.Ordinal); return new RedactionResult(sanitized, placeholderMap); } private static string CreateValueKey(PiiEntity entity) => $"{entity.Type}|{entity.Value}"; private static string ToPlaceholderPrefix(PiiEntityType type) => type switch { PiiEntityType.Person => "PERSON", PiiEntityType.Email => "EMAIL", PiiEntityType.Phone => "PHONE", PiiEntityType.Pan => "PAN", PiiEntityType.Aadhaar => "AADHAAR", PiiEntityType.CreditCard => "CREDIT_CARD", PiiEntityType.LoanNumber => "LOAN_NUMBER", PiiEntityType.CustomerId => "CUSTOMER_ID", PiiEntityType.AccountNumber => "ACCOUNT_NUMBER", _ => type.ToString().ToUpperInvariant() }; }