using System; using System.Collections.Generic; using System.Linq; using MongoDB.Bson; namespace StellaOps.Concelier.Connector.Kisa.Internal; internal sealed record KisaCursor( IReadOnlyCollection PendingDocuments, IReadOnlyCollection PendingMappings, IReadOnlyCollection KnownIds, DateTimeOffset? LastPublished, DateTimeOffset? LastFetchAt) { private static readonly IReadOnlyCollection EmptyGuids = Array.Empty(); private static readonly IReadOnlyCollection EmptyStrings = Array.Empty(); public static KisaCursor Empty { get; } = new(EmptyGuids, EmptyGuids, EmptyStrings, null, null); public KisaCursor WithPendingDocuments(IEnumerable documents) => this with { PendingDocuments = Distinct(documents) }; public KisaCursor WithPendingMappings(IEnumerable mappings) => this with { PendingMappings = Distinct(mappings) }; public KisaCursor WithKnownIds(IEnumerable ids) => this with { KnownIds = ids?.Distinct(StringComparer.OrdinalIgnoreCase).ToArray() ?? EmptyStrings }; public KisaCursor WithLastPublished(DateTimeOffset? published) => this with { LastPublished = published }; public KisaCursor WithLastFetch(DateTimeOffset? timestamp) => this with { LastFetchAt = timestamp }; public BsonDocument ToBsonDocument() { var document = new BsonDocument { ["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())), ["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())), ["knownIds"] = new BsonArray(KnownIds), }; if (LastPublished.HasValue) { document["lastPublished"] = LastPublished.Value.UtcDateTime; } if (LastFetchAt.HasValue) { document["lastFetchAt"] = LastFetchAt.Value.UtcDateTime; } return document; } public static KisaCursor FromBson(BsonDocument? document) { if (document is null || document.ElementCount == 0) { return Empty; } var pendingDocuments = ReadGuidArray(document, "pendingDocuments"); var pendingMappings = ReadGuidArray(document, "pendingMappings"); var knownIds = ReadStringArray(document, "knownIds"); var lastPublished = document.TryGetValue("lastPublished", out var publishedValue) ? ParseDate(publishedValue) : null; var lastFetch = document.TryGetValue("lastFetchAt", out var fetchValue) ? ParseDate(fetchValue) : null; return new KisaCursor(pendingDocuments, pendingMappings, knownIds, lastPublished, lastFetch); } private static IReadOnlyCollection Distinct(IEnumerable? values) => values?.Distinct().ToArray() ?? EmptyGuids; private static IReadOnlyCollection ReadGuidArray(BsonDocument document, string field) { if (!document.TryGetValue(field, out var value) || value is not BsonArray array) { return EmptyGuids; } var items = new List(array.Count); foreach (var element in array) { if (Guid.TryParse(element?.ToString(), out var id)) { items.Add(id); } } return items; } private static IReadOnlyCollection ReadStringArray(BsonDocument document, string field) { if (!document.TryGetValue(field, out var value) || value is not BsonArray array) { return EmptyStrings; } return array .Select(element => element?.ToString() ?? string.Empty) .Where(static s => !string.IsNullOrWhiteSpace(s)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); } 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, }; }