using System; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using HC_APTBS.Models; using HC_APTBS.Services; namespace HC_APTBS.ViewModels.Pages { /// /// Developer Tools — saved KWP custom commands library. Wraps the persistence /// surface in , exposes commands to send the /// selected entry, save the parent's current hex input as a new entry, and /// delete entries. /// /// Compiled into Debug builds only — see HC_APTBS.csproj. /// public sealed partial class DeveloperToolsCommandsViewModel : ObservableObject { private readonly DeveloperPageViewModel _parent; private readonly IKwpService _kwp; private readonly IConfigurationService _config; private readonly IAppLogger _log; private const string LogId = nameof(DeveloperToolsCommandsViewModel); public DeveloperToolsCommandsViewModel( DeveloperPageViewModel parent, IKwpService kwp, IConfigurationService config, IAppLogger log) { _parent = parent; _kwp = kwp; _config = config; _log = log; } /// Persistent collection from . public ObservableCollection Items => _config.CustomCommands; /// Currently selected list entry. Drives Send / Delete enable state. [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SendSelectedCommand))] [NotifyCanExecuteChangedFor(nameof(DeleteSelectedCommand))] private CustomCommand? _selected; /// Name typed into the "Save current as…" input. [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SaveCurrentCommand))] private string _newName = string.Empty; // ── Commands ────────────────────────────────────────────────────────────── /// Sends the selected library entry over the persistent K-Line session. [RelayCommand(CanExecute = nameof(CanSendSelected))] private async Task SendSelectedAsync() { if (Selected is null) return; if (!DeveloperPageViewModel.TryParseHex(Selected.HexBytes, out var bytes, out var error)) { _parent.SetStatus($"'{Selected.Name}' has invalid hex: {error}"); return; } var hex = DeveloperPageViewModel.FormatHex(bytes); _parent.AppendLog(DeveloperLogDirection.Tx, $"[{Selected.Name}] {hex}"); _parent.SetStatus($"Sending '{Selected.Name}' ({bytes.Length} byte(s))…"); try { var responses = await _kwp.SendRawCustomAsync(bytes, CancellationToken.None); if (responses.Count == 0) { _parent.AppendLog(DeveloperLogDirection.Info, "(no response)"); _parent.SetStatus("No response packets."); return; } foreach (var pkt in responses) _parent.AppendLog(DeveloperLogDirection.Rx, DeveloperPageViewModel.FormatHex(pkt)); _parent.SetStatus($"Received {responses.Count} packet(s)."); } catch (Exception ex) { _parent.AppendLog(DeveloperLogDirection.Info, $"ERROR: {ex.Message}"); _parent.SetStatus($"Send failed: {ex.Message}"); _log.Warning(LogId, $"SendSelected failed: {ex.Message}"); } } private bool CanSendSelected() => Selected is not null; /// /// Saves the parent VM's current HexInput as a new entry under /// . Validates that hex parses before persisting. /// [RelayCommand(CanExecute = nameof(CanSaveCurrent))] private void SaveCurrent() { var name = (NewName ?? string.Empty).Trim(); if (name.Length == 0) { _parent.SetStatus("Enter a name before saving the current command."); return; } var hex = (_parent.HexInput ?? string.Empty).Trim(); if (!DeveloperPageViewModel.TryParseHex(hex, out var bytes, out var error)) { _parent.SetStatus($"Cannot save '{name}': {error}"); return; } var normalized = DeveloperPageViewModel.FormatHex(bytes); Items.Add(new CustomCommand { Name = name, HexBytes = normalized }); _config.SaveCustomCommands(); _parent.AppendLog(DeveloperLogDirection.Info, $"SAVED command '{name}' = {normalized}"); _parent.SetStatus($"Saved '{name}'."); NewName = string.Empty; } private bool CanSaveCurrent() => !string.IsNullOrWhiteSpace(NewName); /// Removes the selected entry from the library and persists. [RelayCommand(CanExecute = nameof(CanDeleteSelected))] private void DeleteSelected() { if (Selected is null) return; var name = Selected.Name; Items.Remove(Selected); _config.SaveCustomCommands(); _parent.AppendLog(DeveloperLogDirection.Info, $"DELETED command '{name}'"); _parent.SetStatus($"Deleted '{name}'."); Selected = null; } private bool CanDeleteSelected() => Selected is not null; } }