using LSFE.MobApp.Contracts; using LSFE.MobApp.Entities.Doctor; using LSFE.MobApp.Helpers; using LSFE.MobApp.Models; using LSFE.MobApp.Services; using LSFE.MobApp.ViewModels.Common; using Microsoft.Extensions.Configuration; using System.Windows.Input; namespace LSFE.MobApp.ViewModels.Coverage { public class DoctorSignatureVM : BaseDoctorVM { private readonly List _selectedMaterials; private readonly List _selectedSamplings; private Action _onSignatureCompleted; private readonly AddressService _addressService; #region Repository private readonly Repository _mrRawPlanRepository; private readonly Repository _doctorRepository; #endregion private readonly Entities.Doctor.Doctor _doctor; public DoctorSignatureVM(Entities.Doctor.Doctor doctor, AddressService addressService, ISyncService syncService, Repository mrRawPlanRepository, Repository doctorRepository, IConfiguration configuration, List selectedMaterials, List selectedSamplings, Action onSignatureCompleted = null) : base(doctorRepository, syncService, addressService, configuration) { _doctor=doctor; _mrRawPlanRepository = mrRawPlanRepository; _doctorRepository=doctorRepository; _selectedMaterials = selectedMaterials; _selectedSamplings = selectedSamplings; _onSignatureCompleted = onSignatureCompleted; _addressService = addressService; // Initialize commands SelectDrawTabCommand = new Command(SelectDrawTab); SelectPhotoTabCommand = new Command(SelectPhotoTab); ClearSignatureCommand = new Command(ClearSignature); PreviewSignatureCommand = new Command(async () => await PreviewSignature()); TakePhotoCommand = new Command(async () => await TakePhoto()); RemovePhotoCommand = new Command(RemovePhoto); ConfirmSignatureCommand = new Command(async () => await ConfirmSignature()); CancelCommand = new Command(async () => await Cancel()); // Default to draw tab IsDrawTabSelected = true; } #region Status Properties - Using Helper (DRY!) public string StatusText => DoctorStatusHelper.GetStatusText(_doctor.Status); public Color StatusBackgroundColor => DoctorStatusHelper.GetStatusBackgroundColor(_doctor.Status); public Color StatusTextColor => DoctorStatusHelper.GetStatusTextColor(_doctor.Status); #endregion /// /// This method is automatically called when status updates are broadcast protected override async void OnDoctorStatusUpdated(int doctorId) { // Only update if it's this doctor if (_doctor.DoctorId == doctorId) { var updated = await RefreshDoctorFromRepositoryAsync(doctorId); if (updated != null) { _doctor.Status = updated.Status; // Notify UI to refresh OnPropertyChanged(nameof(StatusText)); OnPropertyChanged(nameof(StatusBackgroundColor)); OnPropertyChanged(nameof(StatusTextColor)); } } } #region Properties private bool _isLoading; public bool IsLoading { get => _isLoading; set { _isLoading = value; OnPropertyChanged(); } } private bool _isDrawTabSelected; public bool IsDrawTabSelected { get => _isDrawTabSelected; set { _isDrawTabSelected = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsPhotoTabSelected)); OnPropertyChanged(nameof(DrawTabBackgroundColor)); OnPropertyChanged(nameof(DrawTabTextColor)); OnPropertyChanged(nameof(PhotoTabBackgroundColor)); OnPropertyChanged(nameof(PhotoTabTextColor)); OnPropertyChanged(nameof(ShowClearButton)); } } public bool IsPhotoTabSelected => !_isDrawTabSelected; public bool ShowClearButton => _isDrawTabSelected && !IsSignaturePadEmpty; public Color DrawTabBackgroundColor => _isDrawTabSelected ? Color.FromArgb("#0EA5E9") : Color.FromArgb("#F3F4F6"); public Color DrawTabTextColor => _isDrawTabSelected ? Colors.White : Color.FromArgb("#6B7280"); public Color PhotoTabBackgroundColor => !_isDrawTabSelected ? Color.FromArgb("#0EA5E9") : Color.FromArgb("#F3F4F6"); public Color PhotoTabTextColor => !_isDrawTabSelected ? Colors.White : Color.FromArgb("#6B7280"); public string DoctorName => $"{_doctor.FirstName} {_doctor.MiddleInitial} {_doctor.LastName}"; public string DoctorSpecialization => _doctor.SpecializationName; public string SelectedItemsSummary { get { var summary = ""; if (_selectedMaterials?.Any() == true) { summary += $"📄 {_selectedMaterials.Count} Marketing Material(s)\n"; } if (_selectedSamplings?.Any() == true) { summary += $"💊 {_selectedSamplings.Count} Sampling Item(s)"; } return summary.Trim(); } } private ImageSource _signaturePhotoSource; public ImageSource SignaturePhotoSource { get => _signaturePhotoSource; set { _signaturePhotoSource = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasSignaturePhoto)); OnPropertyChanged(nameof(HasNoSignaturePhoto)); OnPropertyChanged(nameof(CanConfirm)); } } public bool HasSignaturePhoto => _signaturePhotoSource != null; public bool HasNoSignaturePhoto => _signaturePhotoSource == null; private byte[] _signaturePhotoBytes; private ImageSource _finalSignatureSource; public ImageSource FinalSignatureSource { get => _finalSignatureSource; set { _finalSignatureSource = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasFinalSignature)); } } public bool HasFinalSignature => _finalSignatureSource != null; private byte[] _finalSignatureBytes; private string _signatureTimestamp; public string SignatureTimestamp { get => _signatureTimestamp; set { _signatureTimestamp = value; OnPropertyChanged(); } } public bool CanConfirm => HasFinalSignature || HasSignaturePhoto || (_isDrawTabSelected && !IsSignaturePadEmpty); private bool _isSignaturePadEmpty = true; public bool IsSignaturePadEmpty { get => _isSignaturePadEmpty; set { _isSignaturePadEmpty = value; OnPropertyChanged(); OnPropertyChanged(nameof(CanConfirm)); OnPropertyChanged(nameof(ShowClearButton)); } } #endregion #region Commands public ICommand SelectDrawTabCommand { get; } public ICommand SelectPhotoTabCommand { get; } public ICommand ClearSignatureCommand { get; set; } public ICommand PreviewSignatureCommand { get; set; } public ICommand TakePhotoCommand { get; } public ICommand ChoosePhotoCommand { get; } public ICommand RemovePhotoCommand { get; } public ICommand ConfirmSignatureCommand { get; } public ICommand CancelCommand { get; } #endregion #region Methods private void SelectDrawTab() { IsDrawTabSelected = true; } private void SelectPhotoTab() { IsDrawTabSelected = false; } public async Task PreviewSignature() { try { IsLoading = true; await Application.Current.MainPage.DisplayAlert( "Preview", "Signature preview will be shown here", "OK"); } catch (Exception ex) { await Application.Current.MainPage.DisplayAlert("Error", $"Failed to preview: {ex.Message}", "OK"); } finally { IsLoading = false; } } private bool _isPhotoSignature; public void SetSignatureFromPad(byte[] signatureBytes) { if (signatureBytes != null && signatureBytes.Length > 0) { _finalSignatureBytes = signatureBytes; FinalSignatureSource = ImageSource.FromStream(() => new MemoryStream(signatureBytes)); SignatureTimestamp = $"Signed on {DateTime.Now:MMMM dd, yyyy hh:mm tt}"; _isPhotoSignature = false; // This is a drawn signature } } private async Task ProcessPhoto(FileResult photo) { try { IsLoading = true; using var stream = await photo.OpenReadAsync(); using var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream); _signaturePhotoBytes = memoryStream.ToArray(); SignaturePhotoSource = ImageSource.FromStream(() => new MemoryStream(_signaturePhotoBytes)); // Set as final signature _finalSignatureBytes = _signaturePhotoBytes; FinalSignatureSource = SignaturePhotoSource; SignatureTimestamp = $"Captured on {DateTime.Now:MMMM dd, yyyy hh:mm tt}"; _isPhotoSignature = true; } catch (Exception ex) { await Application.Current.MainPage.DisplayAlert("Error", $"Failed to process photo: {ex.Message}", "OK"); } finally { IsLoading = false; } } private async Task TakePhoto() { try { if (!MediaPicker.Default.IsCaptureSupported) { await Application.Current.MainPage.DisplayAlert( "Not Supported", "Camera is not supported on this device", "OK"); return; } var photo = await MediaPicker.Default.CapturePhotoAsync(); if (photo != null) { await ProcessPhoto(photo); } } catch (PermissionException) { await Application.Current.MainPage.DisplayAlert( "Permission Denied", "Camera permission is required to take photos. Please enable it in settings.", "OK"); } catch (Exception ex) { await Application.Current.MainPage.DisplayAlert("Error", $"Failed to take photo: {ex.Message}", "OK"); } } private void RemovePhoto() { SignaturePhotoSource = null; _signaturePhotoBytes = null; FinalSignatureSource = null; _finalSignatureBytes = null; SignatureTimestamp = null; } #region Facade Actual Coverage Confirmation private async Task ConfirmSignature() { try { // STEP 1: Ensure signature exists if (!await EnsureSignatureIsValid()) return; // STEP 2: Ask user to confirm if (!await ConfirmSignatureSave()) return; IsLoading = true; // STEP 3: Save signature file var fileName = await SaveSignatureFile(); if (fileName == null) return; _onSignatureCompleted?.Invoke(_finalSignatureBytes, fileName); // STEP 4: Update plan var plan = await GetTodayPlan(); if (plan == null) return; var doctor = await _doctorRepository.FirstOrDefaultAsync(d => d.MRRawPlanId == _doctor.MRRawPlanId); if (doctor == null) { await UtilityVM.ShowAlert("Error: ", $"Doctor not found."); return; } await UpdatePlanWithSignature(plan, doctor, fileName); // STEP 5: Sync await PerformSyncAndNotify(); // STEP 6: Navigate back await Application.Current.MainPage.Navigation.PopAsync(); } catch (Exception ex) { await UtilityVM.ShowAlert("Error: ",$"Failed to save signature: {ex.Message}"); } finally { IsLoading = false; } } private async Task PerformSyncAndNotify() { bool isSynced = await _syncService.SyncPostCoverageTodayAsync(_doctor.MRRawPlanId); // Broadcast to ALL ViewModels - ONE LINE! DoctorStatusHelper.BroadcastStatusUpdate(_doctor.DoctorId); string message = isSynced ? "Actual coverage was saved and synced successfully." : "Saved locally. Please sync again later."; await Application.Current.MainPage.DisplayAlert("Success", message, "OK"); } private async Task ConfirmSignatureSave() { return await Application.Current.MainPage.DisplayAlert( "Confirm Signature", "Are you sure you want to save this signature?", "Yes, Save", "No" ); } private async Task EnsureSignatureIsValid() { if (_isDrawTabSelected && _finalSignatureBytes == null) { var bytes = await GetSignatureBytesFromPad(); if (bytes == null || bytes.Length == 0) { await UtilityVM.ShowAlert("Signature: ","Please draw a signature before confirming."); return false; } SetSignatureFromPad(bytes); } if (_finalSignatureBytes == null || _finalSignatureBytes.Length == 0) { await UtilityVM.ShowAlert("Signature: ", "Please provide a signature before confirming."); return false; } return true; } private async Task SaveSignatureFile() { var fileName = $"signature_{DateTime.Now:yyyyMMdd_HHmmss}.png"; var saved = await SaveSignatureToFile(_finalSignatureBytes, fileName); if (string.IsNullOrWhiteSpace(saved)) { await UtilityVM.ShowAlert("Saving signature: ", "Failed to save signature file."); return null; } return fileName; } private async Task GetTodayPlan() { var today = DateTime.Today; var tomorrow = today.AddDays(1); var plan = await _mrRawPlanRepository.FirstOrDefaultAsync( r => r.CallDate >= today && r.CallDate < tomorrow && r.DoctorId == _doctor.DoctorId); if (plan == null) await UtilityVM.ShowAlert("Actual coverage: ", "No plan found for today. Cannot save signature."); return plan; } private async Task UpdatePlanWithSignature( Entities.Planning.MRRawPlan plan,Doctor doctor, string fileName) { var (locationResult,isReadableAddress) = await _addressService.GetCurrentLocationAsync(); if (plan.EndTime != null) return; plan.SignatureFileName = fileName; plan.Latitude = locationResult.Latitude.ToString(); plan.Longitude = locationResult.Longitude.ToString(); plan.EndTime = DateTime.Now; plan.Location = locationResult.Address; plan.IsSignature = _isPhotoSignature; plan.IsMissed = false; plan.IsReschedule = false; plan.Status = 2; // completed plan.ForAddressTranslation = isReadableAddress ? false : true; doctor.Status = 2; // completed await _mrRawPlanRepository.UpdateAsync(plan); await _doctorRepository.UpdateAsync(doctor); } #endregion private Func> _getSignatureBytesFromPad; public void SetGetSignatureBytesAction(Func> getSignatureBytesAction) { _getSignatureBytesFromPad = getSignatureBytesAction; } private async Task GetSignatureBytesFromPad() { if (_getSignatureBytesFromPad != null) { return await _getSignatureBytesFromPad(); } return null; } public static string GetFullSignaturePath(string fileName) { return Path.Combine(FileSystem.AppDataDirectory, fileName); } private async Task SaveSignatureToFile(byte[] imageBytes, string fileName) { try { // Save to app data directory (just like your photo handling) var localFilePath = Path.Combine(FileSystem.AppDataDirectory, fileName); await File.WriteAllBytesAsync(localFilePath, imageBytes); // Return only the filename (not the full path) return fileName; } catch (Exception ex) { throw new Exception($"Failed to save signature file: {ex.Message}"); } } private async Task Cancel() { var confirm = await Application.Current.MainPage.DisplayAlert( "Cancel", "Are you sure you want to cancel? Any unsaved signature will be lost.", "Yes, Cancel", "No"); if (confirm) { await Application.Current.MainPage.Navigation.PopAsync(); } } private Action _clearSignaturePad; public void SetClearSignaturePadAction(Action clearAction) { _clearSignaturePad = clearAction; } public void ClearSignature() { // Clear the final signature FinalSignatureSource = null; _finalSignatureBytes = null; SignatureTimestamp = null; // Clear the actual signature pad if the action is set _clearSignaturePad?.Invoke(); OnPropertyChanged(nameof(CanConfirm)); } #endregion } }