using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows.Threading;
using UtopiaCanteenSystem.Services;
namespace UtopiaCanteenSystem.ViewModels;
///
/// ViewModel for MainDashboardView: shows last scanned card ID, date/time, scan count, and success/error message.
/// Manages a configurable session timer to navigate back to login.
///
public partial class MainDashboardViewModel : ObservableObject
{
private readonly INavigationService _navigation;
private readonly IRfidService _rfidService;
private readonly IConfigService _configService;
private readonly AppSession _session;
private CancellationTokenSource? _timerCancellation;
private readonly DispatcherTimer _clockTimer;
private readonly DispatcherTimer _todayCountTimer;
private DateTime _lastCountDate = DateTime.Today;
[ObservableProperty]
private string _lastCardId = "—";
[ObservableProperty]
private string _lastScanTime = "—";
[ObservableProperty]
private int _scanCount;
[ObservableProperty]
private string _statusMessage = "Scan recorded successfully.";
[ObservableProperty]
private string _currentTime = string.Empty;
[ObservableProperty]
private bool _isSuccess = true;
[ObservableProperty]
private bool _canScan = true;
[ObservableProperty]
private bool _isAdmin;
public MainDashboardViewModel(
INavigationService navigation,
IRfidService rfidService,
IConfigService configService,
AppSession session)
{
_navigation = navigation;
_rfidService = rfidService;
_configService = configService;
_session = session;
IsAdmin = _session.IsAdminAuthenticated;
UpdateCurrentTime();
_clockTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_clockTimer.Tick += (_, _) => UpdateCurrentTime();
_clockTimer.Start();
UpdateScanDetails();
// Refresh "today's scans" periodically so it auto-resets after midnight even without scans.
_todayCountTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(30)
};
_todayCountTimer.Tick += (_, _) =>
{
if (DateTime.Today == _lastCountDate)
return;
_lastCountDate = DateTime.Today;
_ = LoadTodayScanCountAsync();
};
_todayCountTimer.Start();
// Initial load of today's count (async).
_ = LoadTodayScanCountAsync();
StartDashboardTimer();
}
///
/// Updates scan details: last scan info, scan count for today, and last scan time.
/// Call this after a new scan occurs.
///
public void UpdateScanDetails()
{
var last = _rfidService.GetLastScan();
if (last != null)
{
LastCardId = last.CardId;
LastScanTime = last.ScanTime.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
// Default success message; may be replaced with "welcome back" if repeat detected.
IsSuccess = true;
StatusMessage = "Scan recorded successfully.";
_ = UpdateRepeatMessageAsync(last.CardId);
}
else
{
LastCardId = "—";
LastScanTime = "—";
IsSuccess = true;
StatusMessage = string.Empty;
}
}
private async Task UpdateRepeatMessageAsync(string cardId)
{
try
{
var todayCount = await _rfidService.GetTodayScanCountForCardAsync(cardId).ConfigureAwait(false);
// Show "scanned again" ONLY when the same card has 2+ scans today.
if (todayCount >= 2)
{
var msg = $"Welcome back! You scanned again (#{todayCount} today).";
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
IsSuccess = true;
StatusMessage = msg;
});
}
}
catch
{
// If DB query fails, keep the default status message.
}
}
public async Task LoadTodayScanCountAsync()
{
try
{
// Counts only scans for the current LOCAL day (converted to UTC boundaries internally).
var count = await _rfidService.GetTodayScanCountAsync().ConfigureAwait(false);
// Ensure property changes happen on the UI thread.
System.Windows.Application.Current.Dispatcher.Invoke(() => ScanCount = count);
}
catch
{
// If DB is unavailable for any reason, fail gracefully.
System.Windows.Application.Current.Dispatcher.Invoke(() => ScanCount = 0);
}
}
///
/// Starts the dashboard session timer. When the session expires, navigates back to login.
///
private void StartDashboardTimer()
{
// Cancel any existing timer
_timerCancellation?.Cancel();
_timerCancellation = new CancellationTokenSource();
// For the session, restrict scans to current employee
CanScan = true; // Current employee can scan
var timeout = GetDashboardTimeout();
if (_navigation.IsDashboardSessionExpired(timeout))
{
_navigation.NavigateToScanner();
return;
}
// Use remaining time since the dashboard session started.
var remaining = timeout - _navigation.GetDashboardSessionElapsed();
if (remaining <= TimeSpan.Zero)
{
_navigation.NavigateToScanner();
return;
}
// Start async timer
_ = Task.Run(async () =>
{
try
{
await Task.Delay(remaining, _timerCancellation.Token);
// Timer completed: navigate back to login
// Use Dispatcher to ensure UI thread
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
if (ReferenceEquals(_navigation.CurrentViewModel, this))
_navigation.NavigateToScanner();
});
}
catch (OperationCanceledException)
{
// Timer was cancelled (new scan occurred, timer reset)
}
});
}
///
/// Called when a new scan occurs.
///
public void OnScanOccurred()
{
UpdateScanDetails();
_lastCountDate = DateTime.Today;
_ = LoadTodayScanCountAsync();
}
[RelayCommand]
private void BackToLogin()
{
_timerCancellation?.Cancel();
_navigation.NavigateToScanner();
}
[RelayCommand]
private void GoToSettings()
{
// Require admin credentials at time of access (kiosk mode).
_navigation.NavigateToAdminSettingsAuth();
}
///
/// Cleanup: cancel timer when ViewModel is disposed or view is closed.
///
~MainDashboardViewModel()
{
_clockTimer.Stop();
_todayCountTimer.Stop();
_timerCancellation?.Cancel();
_timerCancellation?.Dispose();
}
private TimeSpan GetDashboardTimeout()
{
var seconds = _configService.GetScanTimeoutSeconds();
if (seconds <= 0)
seconds = 60;
return TimeSpan.FromSeconds(seconds);
}
private void UpdateCurrentTime()
{
CurrentTime = DateTime.Now.ToString("MM/dd/yyyy, hh:mm:ss tt");
}
}