using System; using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using HC_APTBS.Services; namespace HC_APTBS.ViewModels { /// /// ViewModel for the Pump page §3.b DTC list (Diagnostic Trouble Codes). /// /// Exposes a list of fault-code lines parsed from /// , with read/clear commands. /// Each line is surfaced as a structured so the UI /// can render them as rows rather than a raw blob. /// public sealed partial class DtcListViewModel : ObservableObject { private readonly IKwpService _kwp; private readonly ILocalizationService _loc; private readonly IAppLogger _log; private const string LogId = "DtcListVM"; /// Initialises the ViewModel with the required services. public DtcListViewModel(IKwpService kwp, ILocalizationService loc, IAppLogger log) { _kwp = kwp; _loc = loc; _log = log; } // ── State ───────────────────────────────────────────────────────────────── /// Parsed fault-code entries, one per line returned from K-Line. public ObservableCollection Codes { get; } = new(); /// True while a read or clear operation is in progress. [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ReadCommand))] [NotifyCanExecuteChangedFor(nameof(ClearCommand))] private bool _isBusy; /// True when the last read returned no fault codes. [ObservableProperty] private bool _isClear; /// Status text shown above the list (empty, error, or "last read at …"). [ObservableProperty] private string _statusText = string.Empty; // ── Commands ────────────────────────────────────────────────────────────── /// Reads the current DTCs from the ECU over K-Line. [RelayCommand(CanExecute = nameof(CanOperate))] private async Task ReadAsync() { var port = _kwp.DetectKLinePort(); if (string.IsNullOrEmpty(port)) { StatusText = _loc.GetString("Error.KLineNotFound"); return; } IsBusy = true; try { string raw = await _kwp.ReadFaultCodesAsync(port); ApplyRawText(raw); StatusText = string.Format(_loc.GetString("Dtc.LastRead"), DateTime.Now); } catch (Exception ex) { _log.Error(LogId, $"ReadAsync: {ex.Message}"); StatusText = ex.Message; } finally { IsBusy = false; } } /// Clears all DTCs on the ECU and refreshes the list. [RelayCommand(CanExecute = nameof(CanOperate))] private async Task ClearAsync() { var port = _kwp.DetectKLinePort(); if (string.IsNullOrEmpty(port)) { StatusText = _loc.GetString("Error.KLineNotFound"); return; } IsBusy = true; try { string raw = await _kwp.ClearFaultCodesAsync(port); ApplyRawText(raw); StatusText = _loc.GetString("Dtc.Cleared"); } catch (Exception ex) { _log.Error(LogId, $"ClearAsync: {ex.Message}"); StatusText = ex.Message; } finally { IsBusy = false; } } private bool CanOperate() => !IsBusy; // ── Helpers ─────────────────────────────────────────────────────────────── /// /// Populates from the raw K-Line fault-code string. /// Handles the special "No fault codes" response by setting . /// private void ApplyRawText(string raw) { Application.Current.Dispatcher.Invoke(() => { Codes.Clear(); if (string.IsNullOrWhiteSpace(raw) || raw.Contains("No fault", StringComparison.OrdinalIgnoreCase)) { IsClear = true; return; } IsClear = false; foreach (var line in raw.Split('\n', StringSplitOptions.RemoveEmptyEntries)) { string trimmed = line.Trim(); if (trimmed.Length == 0) continue; Codes.Add(DtcEntry.Parse(trimmed)); } }); } /// Clears any cached DTCs (used when the pump selection changes). public void Reset() { Application.Current.Dispatcher.Invoke(() => { Codes.Clear(); IsClear = false; StatusText = string.Empty; }); } } /// /// A single diagnostic trouble code row. Split into and /// when the raw line follows the usual "CODE — text" /// layout, otherwise the raw text is surfaced in . /// public sealed class DtcEntry { /// DTC code identifier (e.g. "P1688"). public string Code { get; set; } = string.Empty; /// Human-readable description text. public string Description { get; set; } = string.Empty; /// Parses one line of K-Line fault-code output into a structured entry. public static DtcEntry Parse(string line) { // Common K-Line formats: "P1234 — Description", "P1234: Description", // "P1234 Description", or just raw text without a leading code. int split = -1; foreach (char sep in new[] { '—', '-', ':', '\t' }) { split = line.IndexOf(sep); if (split > 0) break; } if (split <= 0 || split > 10) return new DtcEntry { Description = line }; return new DtcEntry { Code = line[..split].Trim(), Description = line[(split + 1)..].Trim() }; } } }