using System;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HC_APTBS.Models;
using HC_APTBS.Services;
using HC_APTBS.ViewModels.Dialogs;
using HC_APTBS.Views.Dialogs;
namespace HC_APTBS.ViewModels
{
///
/// ViewModel for manual bench controls: direction toggle, RPM start/stop with
/// PID ramp, oil pump toggle, and turn downcounter.
/// Created by as a child ViewModel.
///
public sealed partial class BenchControlViewModel : ObservableObject
{
private readonly IBenchService _bench;
private readonly IConfigurationService _config;
// ── Direction ─────────────────────────────────────────────────────────────
/// True when the bench rotates clockwise (right). False for counter-clockwise (left).
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsDirectionLeft))]
private bool _isDirectionRight = true;
/// True when the bench rotates counter-clockwise (left). Computed inverse of .
public bool IsDirectionLeft => !IsDirectionRight;
// ── Oil pump ──────────────────────────────────────────────────────────────
/// True when the oil pump relay is energised.
[ObservableProperty] private bool _isOilPumpOn;
// ── RPM control ───────────────────────────────────────────────────────────
/// True when the bench motor is running (last RPM command > 0).
[ObservableProperty] private bool _isBenchRunning;
/// Controls the RPM quick-select popup visibility.
[ObservableProperty] private bool _isRpmPopupOpen;
/// Text in the RPM input field.
[ObservableProperty] private string _rpmInputText = string.Empty;
/// Last RPM setpoint commanded via PID or direct set.
[ObservableProperty] private double _targetRpm;
/// Last voltage sent to the motor CAN parameter.
[ObservableProperty] private double _commandVoltage;
// ── Counter ───────────────────────────────────────────────────────────────
/// Controls the counter popup visibility.
[ObservableProperty] private bool _isCounterPopupOpen;
/// Text in the counter input field.
[ObservableProperty] private string _counterInputText = string.Empty;
/// Live counter count-down value read from CAN.
[ObservableProperty] private double _benchCounterValue;
// ── Constructor ───────────────────────────────────────────────────────────
///
/// Creates the bench control ViewModel and subscribes to service events.
///
/// Bench service for RPM, relay, and parameter operations.
/// Configuration service for bench parameters.
public BenchControlViewModel(IBenchService benchService, IConfigurationService configService)
{
_bench = benchService;
_config = configService;
_bench.RpmCommandSent += () =>
Application.Current.Dispatcher.Invoke(() =>
{
TargetRpm = _bench.LastTargetRpm;
CommandVoltage = _bench.LastCommandVoltage;
});
}
// ── Direction toggle ──────────────────────────────────────────────────────
partial void OnIsDirectionRightChanged(bool value)
{
_bench.SetRelay(RelayNames.DirectionRight, value);
_bench.SetRelay(RelayNames.DirectionLeft, !value);
}
/// Sets the bench rotation direction to clockwise (right).
[RelayCommand]
private void SetDirectionRight() => IsDirectionRight = true;
/// Sets the bench rotation direction to counter-clockwise (left).
[RelayCommand]
private void SetDirectionLeft() => IsDirectionRight = false;
// ── Oil pump toggle ───────────────────────────────────────────────────────
partial void OnIsOilPumpOnChanged(bool value)
{
// Show confirmation dialog when turning oil pump ON (WAcceptOilTurnOn equivalent).
if (value)
{
var vm = new OilPumpConfirmViewModel();
var dlg = new OilPumpConfirmDialog(vm) { Owner = Application.Current.MainWindow };
dlg.ShowDialog();
if (!vm.Accepted)
{
// Revert without re-triggering this handler.
_isOilPumpOn = false;
OnPropertyChanged(nameof(IsOilPumpOn));
return;
}
}
_bench.SetRelay(RelayNames.OilPump, value);
// The dialog's ShowDialog call runs a nested dispatcher message pump.
// While it was blocking, RefreshFromTick may have fired and written
// _isOilPumpOn back to the stale relay.State value (false, because
// SetRelay above hadn't run yet). Re-assert the backing field now
// that relay.State is committed so IsOilPumpOn reports the correct
// value to callers downstream (e.g. TestsPageViewModel.StartTestAsync
// which guards on it right after this setter returns).
// See docs/gotcha-oil-pump-dialog-race.md.
if (_isOilPumpOn != value)
{
_isOilPumpOn = value;
OnPropertyChanged(nameof(IsOilPumpOn));
}
}
///
/// Energises the oil-pump relay and flags without
/// presenting the leak-check confirmation dialog. Used by the Dashboard
/// "Connect & Auto Test" flow when the operator has opted in via
/// . Writes the backing
/// field directly to avoid re-entering .
///
public void TurnOilPumpOnSilent()
{
if (_isOilPumpOn) return;
_bench.SetRelay(RelayNames.OilPump, true);
_isOilPumpOn = true;
OnPropertyChanged(nameof(IsOilPumpOn));
}
// ── RPM commands ──────────────────────────────────────────────────────────
/// Opens the RPM quick-select popup.
[RelayCommand]
private void OpenRpmPopup()
{
IsRpmPopupOpen = true;
}
///
/// Starts the bench motor at the RPM specified in .
/// Shows a safety warning dialog if the oil pump is off.
///
[RelayCommand]
private void StartBench()
{
if (!int.TryParse(RpmInputText, out int rpm) || rpm <= 0) return;
// Safety warning if oil pump is not running (WCareOnRpmOn equivalent).
if (!IsOilPumpOn)
{
var vm = new RpmSafetyWarningViewModel();
var dlg = new RpmSafetyWarningDialog(vm) { Owner = Application.Current.MainWindow };
dlg.ShowDialog();
switch (vm.Result)
{
case RpmSafetyResult.Cancel:
return;
case RpmSafetyResult.ProceedWithOil:
IsOilPumpOn = true;
break;
case RpmSafetyResult.ProceedWithoutOil:
// Operator accepted the risk.
break;
}
}
// Ensure direction relays are set.
_bench.SetRelay(RelayNames.DirectionRight, IsDirectionRight);
_bench.SetRelay(RelayNames.DirectionLeft, !IsDirectionRight);
_bench.StartRpmPid(rpm);
IsBenchRunning = true;
IsRpmPopupOpen = false;
}
///
/// Stops the bench motor via PID stop and clears direction relays.
///
[RelayCommand]
private void StopBench()
{
_bench.StopRpmPid();
_bench.SetRelay(RelayNames.DirectionLeft, false);
_bench.SetRelay(RelayNames.DirectionRight, false);
IsBenchRunning = false;
}
///
/// Applies the RPM value from and starts the bench.
/// Bound to the inline Apply button in BenchRpmCommandCard.
///
[RelayCommand]
private void ApplyRpm() => StartBench();
///
/// Quick-select button handler: sets the RPM input and starts the bench.
///
/// RPM value as a string from the button content.
[RelayCommand]
private void SetQuickRpm(string rpmString)
{
RpmInputText = rpmString;
StartBench();
}
// ── Counter commands ──────────────────────────────────────────────────────
/// Toggles the counter popup visibility.
[RelayCommand]
private void ToggleCounterPopup()
{
IsCounterPopupOpen = !IsCounterPopupOpen;
}
///
/// Sends the counter value over CAN and activates the counter relay.
///
[RelayCommand]
private void SendCounter()
{
if (!int.TryParse(CounterInputText, out int count) || count <= 0) return;
// Set the counter parameter value and transmit.
_bench.SetParameter(BenchParameterNames.Counter, count);
if (_config.Bench.ParametersByName.TryGetValue(
BenchParameterNames.Counter, out var counterParam))
{
_bench.SendParameters(counterParam.MessageId);
}
// Activate the counter relay.
_bench.SetRelay(RelayNames.Counter, true);
}
// ── Refresh (called from MainViewModel timer tick) ────────────────────────
///
/// Updates live counter readback from CAN, and mirrors the oil-pump relay
/// state so this VM's stays in sync even when the
/// relay is toggled outside the manual Bench page (e.g. the Dashboard
/// auto-test orchestrator). Writes through the backing field to avoid
/// re-triggering the confirmation dialog in .
/// Called on the UI thread from .
///
public void RefreshFromTick()
{
BenchCounterValue = _bench.ReadBenchParameter(BenchParameterNames.BenchCounter);
bool relayOn = _config.Bench.Relays.TryGetValue(RelayNames.OilPump, out var oilRelay) && oilRelay.State;
if (_isOilPumpOn != relayOn)
{
_isOilPumpOn = relayOn;
OnPropertyChanged(nameof(IsOilPumpOn));
}
}
}
}