using System.Windows.Threading; using TeamsISO.App.ViewModels; using TeamsISO.Engine.Controller; namespace TeamsISO.App.Services; /// /// Shared preset-application logic. Originally lived inline in /// PresetsDialog.OnApply; lifted out so the REST control surface /// () and the auto-apply-on-launch path /// () can call the same /// implementation. Single source of truth for "what does Apply mean." /// /// Application proceeds participant-by-participant, matching by display name /// (the only stable join key across meetings since Ids regen each session). /// For each match, the custom output name is updated and IsEnabled is /// reconciled with the preset's value via /// / . Per-participant failures are /// caught and counted; one bad row never aborts applying the rest. /// public static class PresetApplier { /// Result counts from an apply pass. public sealed record ApplyResult(int Matched, int Changed, int Skipped); /// /// Apply to the live /// list. , when supplied, is used to marshal /// IsEnabled / CustomName property writes onto the UI thread; pass null in /// contexts that already run on the UI thread (e.g. the dialog's button click). /// public static async Task ApplyAsync( Services.OperatorPresetStore.Preset preset, IReadOnlyList participants, IIsoController controller, Dispatcher? dispatcher = null, CancellationToken cancellationToken = default) { // Build the lookup once, case-insensitive — Teams display names are // human-typed, so "Jane" and "jane" should match the same row. var byName = preset.Assignments.ToDictionary( a => a.DisplayName, StringComparer.OrdinalIgnoreCase); var matched = 0; var changed = 0; foreach (var p in participants) { cancellationToken.ThrowIfCancellationRequested(); if (!byName.TryGetValue(p.DisplayName, out var assignment)) continue; matched++; await SetOnUiAsync(dispatcher, () => p.CustomName = assignment.CustomOutputName ?? string.Empty); if (assignment.Enabled && !p.IsEnabled) { try { await controller.EnableIsoAsync( p.Id, string.IsNullOrWhiteSpace(p.CustomName) ? null : p.CustomName, cancellationToken); await SetOnUiAsync(dispatcher, () => p.IsEnabled = true); changed++; } catch { // Per-participant best-effort: the rest still get applied. } } else if (!assignment.Enabled && p.IsEnabled) { try { await controller.DisableIsoAsync(p.Id, cancellationToken); await SetOnUiAsync(dispatcher, () => p.IsEnabled = false); changed++; } catch { /* defensive */ } } } // Mark applied so auto-apply-on-launch picks the right preset next time. try { Services.OperatorPresetStore.MarkApplied(preset.Name); } catch { /* preference write is best-effort */ } var skipped = preset.Assignments.Count - matched; return new ApplyResult(matched, changed, skipped); } private static Task SetOnUiAsync(Dispatcher? dispatcher, Action action) { if (dispatcher is null || dispatcher.CheckAccess()) { action(); return Task.CompletedTask; } return dispatcher.InvokeAsync(action).Task; } }