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()
};
}
}
}