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 { /// /// Root ViewModel for the test panel that displays all tests for the selected pump. /// /// /// Replaces the former TestDisplayViewModel. Holds one /// per and provides /// the public API that calls in response to /// events. /// /// public sealed partial class TestPanelViewModel : ObservableObject { private readonly ILocalizationService _loc; /// Initialises a new test panel with a localization service. public TestPanelViewModel(ILocalizationService loc) => _loc = loc; // ── Cached active phase for fast live-indicator lookup ───────────────────── private PhaseCardViewModel? _activePhaseCard; // ── Global toggles ──────────────────────────────────────────────────────── /// /// Controls visibility of operation values (RPM, ME, FBKW) on all phase cards. /// Cascades to every . /// [ObservableProperty] private bool _showOperationValues; // ── Status ──────────────────────────────────────────────────────────────── /// Current verbose status message from the bench service. [ObservableProperty] private string _statusText = string.Empty; /// True while a test sequence is in progress. [ObservableProperty] private bool _isRunning; /// Estimated remaining time for the entire test sequence (seconds). [ObservableProperty] private int _remainingSeconds; // ── Active phase countdown (driven by IBenchService.PhaseTimerTick) ─────── /// Name of the currently running phase (empty when idle). [ObservableProperty] private string _currentPhaseName = string.Empty; /// Sub-section of the current phase: "Conditioning", "Measuring", or empty. [ObservableProperty] private string _sectionLabel = string.Empty; /// Seconds remaining in the current sub-section countdown. [ObservableProperty] private int _phaseRemainingSeconds; /// Total seconds for the current sub-section (denominator for progress). [ObservableProperty] private int _phaseTotalSeconds; /// Progress through the current sub-section (0.0 → 1.0). [ObservableProperty] private double _phaseProgress; // ── Test sections ───────────────────────────────────────────────────────── /// All test sections for the currently loaded pump. public ObservableCollection Tests { get; } = new(); // ── Show values cascade ─────────────────────────────────────────────────── partial void OnShowOperationValuesChanged(bool value) { foreach (var section in Tests) foreach (var phase in section.Phases) phase.ShowOperationValues = value; } // ── Commands ────────────────────────────────────────────────────────────── /// /// Toggles enable/disable for every phase across all test sections. /// If any phase is currently disabled, enables all; otherwise disables all. /// [RelayCommand] private void ToggleCheckAll() { bool anyDisabled = Tests.Any(s => s.Phases.Any(p => !p.IsEnabled)); bool newState = anyDisabled; foreach (var section in Tests) { // Bypass per-section cascade guard by setting AllPhasesChecked directly, // which will cascade down to children. section.AllPhasesChecked = newState; } } // ── Public API: loading ─────────────────────────────────────────────────── /// /// Populates the test panel with all tests from the given pump definition. /// Call when the selected pump changes. /// /// The pump whose tests to display. public void LoadAllTests(PumpDefinition pump) { Tests.Clear(); _activePhaseCard = null; StatusText = string.Empty; RemainingSeconds = 0; foreach (var testDef in pump.Tests) { var section = TestSectionViewModel.FromDefinition(testDef, ShowOperationValues, _loc); Tests.Add(section); } // Compute initial remaining seconds estimate. RemainingSeconds = pump.Tests.Sum(t => t.EstimatedTotalSeconds()); } // ── Public API: real-time updates from BenchService events ───────────────── /// /// Marks the named phase as actively executing and clears any previous active state. /// Caches the active phase card for fast live-indicator updates. /// /// Name of the phase that is now running. public void SetActivePhase(string phaseName) { StatusText = phaseName; CurrentPhaseName = phaseName; ClearPhaseTimer(); _activePhaseCard = null; foreach (var section in Tests) { bool sectionActive = false; foreach (var phase in section.Phases) { if (phase.Name == phaseName && !phase.IsPassed && !phase.IsFailed) { phase.IsActive = true; _activePhaseCard = phase; sectionActive = true; } else { phase.IsActive = false; } } section.IsActiveTest = sectionActive; } } /// /// Marks a phase as completed with the given pass/fail result. /// /// Name of the completed phase. /// True if the phase passed all criteria. public void SetPhaseResult(string phaseName, bool passed) { foreach (var section in Tests) { foreach (var phase in section.Phases) { if (phase.Name != phaseName || (!phase.IsActive && !phase.IsPassed && !phase.IsFailed)) continue; // Only update if this is the active phase (avoid overwriting already-completed phases // with the same name in different tests). if (!phase.IsActive) continue; phase.IsActive = false; phase.IsPassed = passed; phase.IsFailed = !passed; phase.ResultText = passed ? _loc.GetString("Common.Pass") : _loc.GetString("Common.Fail"); break; } // Recalculate section active state. section.IsActiveTest = section.Phases.Any(p => p.IsActive); } if (_activePhaseCard?.Name == phaseName) _activePhaseCard = null; } /// /// Updates the live measurement value on the graphic indicator for the currently /// active phase that has a matching receive parameter. /// /// CAN parameter name (e.g. "QDelivery"). /// Current measured value. public void UpdateLiveIndicator(string paramName, double value) { if (_activePhaseCard == null) return; foreach (var indicator in _activePhaseCard.ResultIndicators) { if (indicator.ParameterName == paramName) { indicator.CurrentValue = value; return; } } } /// /// Applies a phase-timer tick from . /// Updates the sub-section label, remaining/total seconds and computed progress. /// Must be called on the UI thread. /// public void ApplyPhaseTimerTick(string section, int remaining, int total) { SectionLabel = section; PhaseRemainingSeconds = remaining; PhaseTotalSeconds = total; PhaseProgress = total > 0 ? 1.0 - (double)remaining / total : 0.0; } /// Clears the active-phase countdown (call on phase change and test end). public void ClearPhaseTimer() { SectionLabel = string.Empty; PhaseRemainingSeconds = 0; PhaseTotalSeconds = 0; PhaseProgress = 0; } /// /// Resets all phase execution states and graphic indicators for a fresh test run. /// public void ResetResults() { _activePhaseCard = null; StatusText = string.Empty; CurrentPhaseName = string.Empty; ClearPhaseTimer(); foreach (var section in Tests) { section.IsActiveTest = false; foreach (var phase in section.Phases) phase.Reset(); } // Recalculate remaining seconds. int total = 0; foreach (var section in Tests) { if (section.Source != null) total += section.Source.EstimatedTotalSeconds(); } RemainingSeconds = total; } } }