using ZB.MOM.WW.OtOpcUa.Configuration.Validation; namespace ZB.MOM.WW.OtOpcUa.Configuration.Apply; public sealed class GenerationApplier(ApplyCallbacks callbacks) : IGenerationApplier { public async Task ApplyAsync(DraftSnapshot? from, DraftSnapshot to, CancellationToken ct) { var diff = GenerationDiffer.Compute(from, to); var errors = new List(); // Removed first, then Added/Modified — prevents FK dangling while cascades settle. await ApplyPass(diff.Tags, ChangeKind.Removed, callbacks.OnTag, errors, ct); await ApplyPass(diff.PollGroups, ChangeKind.Removed, callbacks.OnPollGroup, errors, ct); await ApplyPass(diff.Equipment, ChangeKind.Removed, callbacks.OnEquipment, errors, ct); await ApplyPass(diff.Devices, ChangeKind.Removed, callbacks.OnDevice, errors, ct); await ApplyPass(diff.Drivers, ChangeKind.Removed, callbacks.OnDriver, errors, ct); await ApplyPass(diff.Namespaces, ChangeKind.Removed, callbacks.OnNamespace, errors, ct); foreach (var kind in new[] { ChangeKind.Added, ChangeKind.Modified }) { await ApplyPass(diff.Namespaces, kind, callbacks.OnNamespace, errors, ct); await ApplyPass(diff.Drivers, kind, callbacks.OnDriver, errors, ct); await ApplyPass(diff.Devices, kind, callbacks.OnDevice, errors, ct); await ApplyPass(diff.Equipment, kind, callbacks.OnEquipment, errors, ct); await ApplyPass(diff.PollGroups, kind, callbacks.OnPollGroup, errors, ct); await ApplyPass(diff.Tags, kind, callbacks.OnTag, errors, ct); } return errors.Count == 0 ? ApplyResult.Ok(diff) : ApplyResult.Fail(diff, errors); } private static async Task ApplyPass( IReadOnlyList> changes, ChangeKind kind, Func, CancellationToken, Task>? callback, List errors, CancellationToken ct) { if (callback is null) return; foreach (var change in changes.Where(c => c.Kind == kind)) { try { await callback(change, ct); } catch (Exception ex) { errors.Add($"{typeof(T).Name} {change.Kind} '{change.LogicalId}': {ex.Message}"); } } } }