using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HC_APTBS.Services;
namespace HC_APTBS.ViewModels.Dialogs
{
///
/// ViewModel for the Ford VP44 immobilizer unlock progress dialog.
/// Tracks Phase 1 (CAN flood ~600 s), Phase 2 (handshake ~4 s), and verification.
/// Equivalent to the old WUnlocker window.
///
public sealed partial class UnlockProgressViewModel : ObservableObject, IDisposable
{
private readonly IUnlockService _unlockService;
private readonly ILocalizationService _loc;
private readonly CancellationTokenSource _cts;
/// Regex to extract percentage and elapsed time from Phase 1 status messages.
private static readonly Regex ProgressRegex =
new(@"Unlocking\.\.\. (\d+)% \((\d{2}:\d{2})\)", RegexOptions.Compiled);
/// Creates the ViewModel and subscribes to unlock service events.
/// The unlock service to monitor.
/// Pump unlock type (1 or 2).
/// Cancellation token source to cancel the unlock.
public UnlockProgressViewModel(IUnlockService unlockService, int unlockType, CancellationTokenSource cts, ILocalizationService loc)
{
_unlockService = unlockService;
_loc = loc;
_cts = cts;
_unlockTypeLabel = string.Format(_loc.GetString("Dialog.Unlock.TypeLabel"), unlockType);
_phaseText = _loc.GetString("Dialog.Unlock.Phase1");
_elapsedTime = "00:00";
_isCancellable = true;
_unlockService.StatusChanged += OnStatusChanged;
_unlockService.UnlockCompleted += OnUnlockCompleted;
_unlockService.PumpRelocked += OnPumpRelocked;
// PumpUnlocked fires as soon as the CAN TestUnlock parameter flips —
// regardless of which code path caused the unlock (fast unlock, Phase 1
// flood finishing, external manual unlock). This lets the dialog flip
// to its success state the instant the hardware confirms unlock, rather
// than waiting for UnlockService.UnlockAsync to reach its final
// verification step.
_unlockService.PumpUnlocked += OnPumpUnlocked;
}
// ── Observable properties ────────────────────────────────────────────────
/// Progress percentage (0–100).
[ObservableProperty] private int _progress;
/// Elapsed time formatted as MM:SS.
[ObservableProperty] private string _elapsedTime;
/// Current phase description.
[ObservableProperty] private string _phaseText;
/// Result text shown after completion.
[ObservableProperty] private string _resultText = string.Empty;
/// Label for unlock type (e.g. "Type 1").
[ObservableProperty] private string _unlockTypeLabel;
/// True when the unlock sequence has finished (success, failure, or cancelled).
[NotifyCanExecuteChangedFor(nameof(CloseCommand))]
[ObservableProperty] private bool _isComplete;
/// True while cancellation is allowed (Phase 1 only).
[NotifyCanExecuteChangedFor(nameof(CancelCommand))]
[ObservableProperty] private bool _isCancellable;
/// Tri-state result: null = in progress, true = success, false = failure.
[ObservableProperty] private bool? _isSuccess;
/// True when the pump is currently LOCKED and the operator can retry the unlock.
[NotifyCanExecuteChangedFor(nameof(RetryCommand))]
[ObservableProperty] private bool _canRetry;
// ── Commands ─────────────────────────────────────────────────────────────
/// Cancels the unlock sequence (only available during Phase 1).
[RelayCommand(CanExecute = nameof(IsCancellable))]
private void Cancel()
{
_cts.Cancel();
IsCancellable = false;
IsComplete = true;
IsSuccess = false;
ResultText = _loc.GetString("Dialog.Unlock.Cancelled");
}
/// Closes the dialog (only available after completion).
[RelayCommand(CanExecute = nameof(IsComplete))]
private void Close()
{
RequestClose?.Invoke();
}
/// Requests a new unlock attempt (only available when complete and pump is LOCKED).
[RelayCommand(CanExecute = nameof(CanRetry))]
private void Retry()
{
CanRetry = false;
RequestRetry?.Invoke();
}
// ── Events ───────────────────────────────────────────────────────────────
/// Raised when the dialog should close itself.
public event Action? RequestClose;
/// Raised when the operator presses Retry — parent should restart the unlock sequence.
public event Action? RequestRetry;
// ── Service event handlers ───────────────────────────────────────────────
private void OnStatusChanged(string msg)
{
Application.Current?.Dispatcher?.Invoke(() =>
{
var match = ProgressRegex.Match(msg);
if (match.Success)
{
Progress = int.Parse(match.Groups[1].Value);
ElapsedTime = match.Groups[2].Value;
return;
}
if (msg == "Fast unlock attempt...")
{
PhaseText = _loc.GetString("Dialog.Unlock.FastAttempt");
}
else if (msg == "Unlocking...")
{
PhaseText = _loc.GetString("Dialog.Unlock.Phase1");
}
else if (msg == "Testing unlock...")
{
PhaseText = _loc.GetString("Dialog.Unlock.Phase2Testing");
IsCancellable = false;
Progress = 100;
}
else if (msg == "Sending...")
{
PhaseText = _loc.GetString("Dialog.Unlock.Phase2Sending");
}
});
}
private void OnUnlockCompleted(bool success)
{
Application.Current?.Dispatcher?.Invoke(() =>
{
IsComplete = true;
IsCancellable = false;
IsSuccess = success;
ResultText = success ? _loc.GetString("Dialog.Unlock.Unlocked") : _loc.GetString("Dialog.Unlock.Failed");
// Enable Retry when the unlock finished but the pump is still LOCKED.
CanRetry = !_unlockService.IsPumpUnlocked;
});
}
///
/// Observer says the pump is now unlocked. Flip the dialog to the
/// success state immediately so the operator sees a responsive UI; the
/// later UnlockCompleted(true) event is idempotent and leaves this state
/// intact. If UnlockCompleted later arrives with failure=false, that
/// would overwrite — but that combination (observer unlocks then service
/// reports failure) is not a real scenario in the current state machine.
///
private void OnPumpUnlocked()
{
Application.Current?.Dispatcher?.Invoke(() =>
{
if (IsComplete) return;
IsComplete = true;
IsCancellable = false;
IsSuccess = true;
CanRetry = false;
ResultText = _loc.GetString("Dialog.Unlock.Unlocked");
});
}
///
/// Observer says the pump re-locked after a previously successful unlock.
/// If the snackbar is still visible (not dismissed), light up the Retry button
/// so the operator has a manual fallback without needing to reselect the pump.
///
private void OnPumpRelocked()
{
Application.Current?.Dispatcher?.Invoke(() =>
{
if (IsComplete)
CanRetry = true;
});
}
// ── IDisposable ──────────────────────────────────────────────────────────
/// Unsubscribes from service events to prevent leaks.
public void Dispose()
{
_unlockService.StatusChanged -= OnStatusChanged;
_unlockService.UnlockCompleted -= OnUnlockCompleted;
_unlockService.PumpUnlocked -= OnPumpUnlocked;
_unlockService.PumpRelocked -= OnPumpRelocked;
}
}
}