using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Xml.Linq;
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.Pages
{
///
/// ViewModel for the Results navigation page (§5 in docs/ui-structure.md).
///
/// Owns a session-only history of completed test runs, drives the embedded
/// detail pane, hosts the operator
/// observations/notes field, and runs the PDF export flow against the selected
/// snapshot.
///
public sealed partial class ResultsPageViewModel : ObservableObject
{
// ── Services ──────────────────────────────────────────────────────────────
private readonly IPdfService _pdf;
private readonly IConfigurationService _config;
private readonly ILocalizationService _loc;
private readonly IAppLogger _log;
private const string LogId = "ResultsPage";
/// Remembers the last authenticated user to pre-fill the next auth dialog.
private string _lastAuthenticatedUser = string.Empty;
// ── Root reference ────────────────────────────────────────────────────────
/// Root ViewModel — lets page XAML bind app-global state when needed.
public MainViewModel Root { get; }
// ── Child VMs ─────────────────────────────────────────────────────────────
/// Detail-pane ViewModel driving the embedded ResultDisplayView.
public ResultDisplayViewModel Detail { get; }
// ── Observable state ──────────────────────────────────────────────────────
/// Newest-first list of captured completions for this session.
public ObservableCollection History { get; } = new();
/// Currently selected history entry, or null when the list is empty.
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ExportPdfCommand))]
[NotifyCanExecuteChangedFor(nameof(RemoveEntryCommand))]
private CompletedTestRun? _selectedRun;
/// True while the history list has no entries — drives the empty-state UI.
public bool IsHistoryEmpty => History.Count == 0;
/// True when at least one entry exists — drives the Clear Session button's IsEnabled.
public bool HasAnyEntries => History.Count > 0;
// ── Constructor ───────────────────────────────────────────────────────────
/// Initialises the Results page VM with the services it needs.
public ResultsPageViewModel(
MainViewModel root,
IPdfService pdfService,
IConfigurationService configService,
ILocalizationService localizationService,
IAppLogger logger)
{
Root = root;
_pdf = pdfService;
_config = configService;
_loc = localizationService;
_log = logger;
Detail = new ResultDisplayViewModel(localizationService);
History.CollectionChanged += (_, _) =>
{
OnPropertyChanged(nameof(IsHistoryEmpty));
OnPropertyChanged(nameof(HasAnyEntries));
ClearHistoryCommand.NotifyCanExecuteChanged();
};
}
// ── Capture ───────────────────────────────────────────────────────────────
///
/// Records a completed test session. Called from
/// 's OnTestFinished hook on the UI thread.
/// Captures a deep-cloned snapshot so that subsequent runs do not mutate
/// prior entries in place.
///
public void CaptureRun(PumpDefinition pump, bool interrupted, bool success)
{
try
{
var snapshot = CloneForHistory(pump);
var entry = new CompletedTestRun
{
CompletedAt = DateTime.Now,
PumpModel = pump.Model,
PumpSerial = pump.SerialNumber,
Interrupted = interrupted,
OverallPassed = !interrupted && success && EvaluatePassed(snapshot.Tests),
PumpSnapshot = snapshot,
};
History.Insert(0, entry);
SelectedRun = entry;
}
catch (Exception ex)
{
_log.Error(LogId, $"CaptureRun: {ex.Message}");
}
}
partial void OnSelectedRunChanged(CompletedTestRun? value)
{
if (value == null)
{
Detail.Clear();
return;
}
Detail.LoadAllResults(value.PumpSnapshot.Tests);
}
// ── Commands ──────────────────────────────────────────────────────────────
/// Authenticates the operator, collects report details, then writes the PDF.
[RelayCommand(CanExecute = nameof(CanExport))]
private void ExportPdf()
{
if (SelectedRun == null) return;
// Step 1: Authenticate operator (same flow as Test page report export).
var authVm = new UserCheckViewModel(_config, _loc, _lastAuthenticatedUser);
var authDlg = new UserCheckDialog(authVm) { Owner = Application.Current.MainWindow };
authDlg.ShowDialog();
if (!authVm.Accepted) return;
_lastAuthenticatedUser = authVm.AuthenticatedUser;
// Step 2: Collect report details, pre-filling observations from the captured run.
var reportVm = new ReportViewModel(_config)
{
OperatorName = authVm.AuthenticatedUser,
Observations = SelectedRun.Observations,
};
var reportDlg = new ReportDialog(reportVm) { Owner = Application.Current.MainWindow };
reportDlg.ShowDialog();
if (!reportVm.Accepted) return;
SelectedRun.Observations = reportVm.Observations;
try
{
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string path = _pdf.GenerateReport(
SelectedRun.PumpSnapshot,
reportVm.OperatorName,
reportVm.SelectedClientName,
desktop,
clientInfo: reportVm.ClientInfo,
observations: reportVm.Observations);
_log.Info(LogId, $"Report saved: {path}");
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path)
{ UseShellExecute = true });
}
catch (Exception ex)
{
_log.Error(LogId, $"ExportPdf: {ex.Message}");
MessageBox.Show(
string.Format(_loc.GetString("Error.ReportGeneration"), ex.Message),
_loc.GetString("Error.ReportTitle"),
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private bool CanExport() => SelectedRun != null;
/// Removes a single history entry by .
[RelayCommand(CanExecute = nameof(CanRemoveEntry))]
private void RemoveEntry(Guid id)
{
var target = History.FirstOrDefault(e => e.Id == id);
if (target == null) return;
bool wasSelected = ReferenceEquals(target, SelectedRun);
History.Remove(target);
if (wasSelected) SelectedRun = History.FirstOrDefault();
}
private bool CanRemoveEntry(Guid id) => History.Any(e => e.Id == id);
/// Clears the whole session history after operator confirmation.
[RelayCommand(CanExecute = nameof(HasAnyEntries))]
private void ClearHistory()
{
var result = MessageBox.Show(
_loc.GetString("Results.ClearSessionConfirm"),
_loc.GetString("Results.ClearSessionButton"),
MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result != MessageBoxResult.Yes) return;
History.Clear();
SelectedRun = null;
}
// ── Helpers ───────────────────────────────────────────────────────────────
///
/// Builds a minimal deep copy of that carries the
/// fields consumed by and the detail view, with
/// entries deep-cloned via XML round-trip so
/// results cannot be mutated by subsequent runs.
///
private static PumpDefinition CloneForHistory(PumpDefinition pump)
{
var clone = new PumpDefinition
{
Id = pump.Id,
Model = pump.Model,
SerialNumber = pump.SerialNumber,
Injector = pump.Injector,
Tube = pump.Tube,
Valve = pump.Valve,
Tension = pump.Tension,
Info = pump.Info,
EcuText = pump.EcuText,
Chaveta = pump.Chaveta,
LockAngle = pump.LockAngle,
LockAngleResult = pump.LockAngleResult,
HasPreInjection = pump.HasPreInjection,
Is4Cylinder = pump.Is4Cylinder,
UnlockType = pump.UnlockType,
Rotation = pump.Rotation,
KwpVersion = pump.KwpVersion,
};
foreach (var kv in pump.KlineInfo)
clone.KlineInfo[kv.Key] = kv.Value;
foreach (var test in pump.Tests)
{
XElement xml = test.ToXml();
clone.Tests.Add(TestDefinition.FromXml(xml));
}
return clone;
}
private static bool EvaluatePassed(IReadOnlyList tests)
{
bool anyEvaluated = false;
foreach (var t in tests)
{
foreach (var p in t.Phases)
{
if (!p.Enabled || p.Receives == null) continue;
foreach (var tp in p.Receives)
{
if (tp.Result == null) continue;
anyEvaluated = true;
if (!tp.Result.Passed) return false;
}
}
}
return anyEvaluated;
}
}
}