using System; using System.Collections.Generic; using System.Linq; using MongoDB.Bson; namespace StellaOps.Concelier.Connector.Vndr.Apple.Internal; internal sealed record AppleCursor( DateTimeOffset? LastPosted, IReadOnlyCollection ProcessedIds, IReadOnlyCollection PendingDocuments, IReadOnlyCollection PendingMappings) { private static readonly IReadOnlyCollection EmptyGuidCollection = Array.Empty(); private static readonly IReadOnlyCollection EmptyStringCollection = Array.Empty(); public static AppleCursor Empty { get; } = new(null, EmptyStringCollection, EmptyGuidCollection, EmptyGuidCollection); public BsonDocument ToBson() { var document = new BsonDocument { ["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())), ["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())), }; if (LastPosted.HasValue) { document["lastPosted"] = LastPosted.Value.UtcDateTime; } if (ProcessedIds.Count > 0) { document["processedIds"] = new BsonArray(ProcessedIds); } return document; } public static AppleCursor FromBson(BsonDocument? document) { if (document is null || document.ElementCount == 0) { return Empty; } var lastPosted = document.TryGetValue("lastPosted", out var lastPostedValue) ? ParseDate(lastPostedValue) : null; var processedIds = document.TryGetValue("processedIds", out var processedValue) && processedValue is BsonArray processedArray ? processedArray.OfType() .Where(static value => value.BsonType == BsonType.String) .Select(static value => value.AsString.Trim()) .Where(static value => value.Length > 0) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray() : EmptyStringCollection; var pendingDocuments = ReadGuidArray(document, "pendingDocuments"); var pendingMappings = ReadGuidArray(document, "pendingMappings"); return new AppleCursor(lastPosted, processedIds, pendingDocuments, pendingMappings); } public AppleCursor WithLastPosted(DateTimeOffset timestamp, IEnumerable? processedIds = null) { var ids = processedIds is null ? ProcessedIds : processedIds.Where(static id => !string.IsNullOrWhiteSpace(id)) .Select(static id => id.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); return this with { LastPosted = timestamp.ToUniversalTime(), ProcessedIds = ids, }; } public AppleCursor WithPendingDocuments(IEnumerable? ids) => this with { PendingDocuments = ids?.Distinct().ToArray() ?? EmptyGuidCollection }; public AppleCursor WithPendingMappings(IEnumerable? ids) => this with { PendingMappings = ids?.Distinct().ToArray() ?? EmptyGuidCollection }; private static IReadOnlyCollection ReadGuidArray(BsonDocument document, string key) { if (!document.TryGetValue(key, out var value) || value is not BsonArray array) { return EmptyGuidCollection; } var results = new List(array.Count); foreach (var element in array) { if (Guid.TryParse(element.ToString(), out var guid)) { results.Add(guid); } } return results; } private static DateTimeOffset? ParseDate(BsonValue value) => value.BsonType switch { BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc), BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(), _ => null, }; }