using System.Collections.ObjectModel; using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using HC_APTBS.Models; using HC_APTBS.Services; namespace HC_APTBS.ViewModels { /// /// Represents one test type section (e.g. "F", "DFI", "SVME") containing /// a header with metadata and a horizontal list of cards. /// public sealed partial class TestSectionViewModel : ObservableObject { private readonly ILocalizationService _loc; /// Initialises a new test section with a localization service. public TestSectionViewModel(ILocalizationService loc) => _loc = loc; // ── Suppress cascade guard ──────────────────────────────────────────────── private bool _suppressCascade; // ── Identity / metadata ─────────────────────────────────────────────────── /// Test type identifier (e.g. "F", "DFI", "SVME"). [ObservableProperty] private string _testName = string.Empty; /// Human-readable description of the test type. [ObservableProperty] private string _description = string.Empty; /// WPF-UI SymbolIcon name to show in the card header (Fluent Tests page). [ObservableProperty] private string _iconSymbol = "Beaker24"; /// Conditioning time in seconds. [ObservableProperty] private int _conditioningTimeSec; /// Measurement time in seconds. [ObservableProperty] private int _measurementTimeSec; /// Measurements per second during the measurement window. [ObservableProperty] private double _measurementsPerSecond; // ── UI state ────────────────────────────────────────────────────────────── /// Whether this section's expander is open. [ObservableProperty] private bool _isExpanded = true; /// True when any phase in this test section is currently executing. [ObservableProperty] private bool _isActiveTest; /// /// Bidirectional check state for all phases. Setting this cascades down to /// all child properties; child changes /// cascade back up to recalculate this value. /// [ObservableProperty] private bool _allPhasesChecked = true; // ── Phases ──────────────────────────────────────────────────────────────── /// Phase cards shown in the horizontal scroll area. public ObservableCollection Phases { get; } = new(); // ── Back-reference ──────────────────────────────────────────────────────── /// Back-reference to the source model. internal TestDefinition? Source { get; set; } // ── Cascade: parent → children ──────────────────────────────────────────── partial void OnAllPhasesCheckedChanged(bool value) { if (_suppressCascade) return; _suppressCascade = true; try { foreach (var phase in Phases) phase.IsEnabled = value; } finally { _suppressCascade = false; } } // ── Commands ────────────────────────────────────────────────────────────── /// Inverts . Bound to the section card's click gesture. [RelayCommand] private void ToggleAllPhases() => AllPhasesChecked = !AllPhasesChecked; // ── Cascade: child → parent ────────────────────────────────────────────── /// /// Called by a child when its /// changes. /// Recalculates without re-cascading. /// internal void OnChildEnabledChanged(PhaseCardViewModel _) { if (_suppressCascade) return; _suppressCascade = true; try { AllPhasesChecked = Phases.All(p => p.IsEnabled); } finally { _suppressCascade = false; } } // ── Factory ─────────────────────────────────────────────────────────────── /// /// Creates a from a model, /// populating all child instances. /// /// Source test definition. /// Initial show-operation-values state. /// Localization service for user-facing strings. public static TestSectionViewModel FromDefinition(TestDefinition test, bool showValues, ILocalizationService loc) { var section = new TestSectionViewModel(loc) { TestName = test.Name, Description = loc.GetString(MapDescriptionKey(test.Name)), IconSymbol = MapIconSymbol(test.Name), ConditioningTimeSec = test.ConditioningTimeSec, MeasurementTimeSec = test.MeasurementTimeSec, MeasurementsPerSecond = test.MeasurementsPerSecond, Source = test }; foreach (var phaseDef in test.Phases) { var card = new PhaseCardViewModel(loc) { Name = phaseDef.Name, IsCritical = phaseDef.IsCritical, IsEnabled = phaseDef.Enabled, ResultText = phaseDef.Enabled ? "\u2013" : loc.GetString("Common.Disabled"), ShowOperationValues = showValues, Source = phaseDef, EnabledChanged = section.OnChildEnabledChanged }; // Populate operation values from Sends. foreach (var tp in phaseDef.Sends) card.OperationValues.Add(new OperationValueViewModel { Name = tp.Name, Value = tp.Value }); // Populate readiness conditions from Readies. foreach (var tp in phaseDef.Readies) card.ReadyValues.Add(new OperationValueViewModel { Name = tp.Name, Value = tp.Value }); // Populate graphic result indicators from Receives. foreach (var tp in phaseDef.Receives) { card.ResultIndicators.Add(new GraphicIndicatorViewModel { ParameterName = tp.Name, ExpectedValue = tp.Value, Tolerance = tp.Tolerance }); } section.Phases.Add(card); } section.AllPhasesChecked = section.Phases.All(p => p.IsEnabled); return section; } /// /// Maps a test type identifier to a localization resource key. /// Returns the test name itself for unknown types (fail-visible). /// private static string MapDescriptionKey(string testName) => testName switch { TestType.Wl => "TestType.Warmup", TestType.Dfi => "TestType.Adjustment", TestType.F => "TestType.Flow", TestType.Svme => "TestType.ServoValve", TestType.Up => "TestType.Upstroke", TestType.Pfp => "TestType.PreInjection", _ => testName }; /// /// Maps a test type identifier to a WPF-UI SymbolIcon name used in the /// Fluent Tests page card header. /// private static string MapIconSymbol(string testName) => testName switch { TestType.Wl => "Temperature24", TestType.Dfi => "WrenchScrewdriver24", TestType.F => "Drop24", TestType.Svme => "Timeline24", TestType.Up => "ArrowTrendingLines24", TestType.Pfp => "Gauge24", _ => "Beaker24" }; } }