import Foundation import Observation import PDFKit import PencilKit struct LeadSigningRequest: Identifiable { let id = UUID() let offerID: String let signerName: String let documents: [LeadDocument] } @MainActor @Observable final class LeadSigningViewModel { let request: LeadSigningRequest let pendingDocuments: [LeadDocument] private(set) var currentIndex = 0 private(set) var sourceURL: URL? private(set) var signedURLs: [String: URL] = [:] private(set) var isLoading = false private(set) var isSigning = false private(set) var isComplete = false private(set) var isUploading = false private(set) var uploadResult: LeadPDFUploadResult? private(set) var uploadError: String? private(set) var message: String? var drawing = PKDrawing() private let repository: any LeadDetailRepository private let signingService: any PDFSigningService private let store: LeadSignedDocumentStore private let uploadService: any LeadSignedPDFUploadService private let leadID: String private let salesCode: String init(request: LeadSigningRequest, leadID: String, salesCode: String, repository: any LeadDetailRepository, signingService: (any PDFSigningService)? = nil, store: LeadSignedDocumentStore? = nil, uploadService: (any LeadSignedPDFUploadService)? = nil) { self.request = request self.pendingDocuments = request.documents.filter { $0.status == .pending } self.leadID = leadID self.salesCode = salesCode self.repository = repository self.signingService = signingService ?? DefaultPDFSigningService() self.store = store ?? LeadSignedDocumentStore() self.uploadService = uploadService ?? StubLeadSignedPDFUploadService() } var currentDocumentNumber: Int { guard let currentDocument, let index = request.documents.firstIndex(where: { $0.id == currentDocument.id }) else { return 0 } return index + 1 } var isLastPendingDocument: Bool { currentIndex == pendingDocuments.count - 1 } var isDemoUpload: Bool { uploadService.isSimulation } var isBusy: Bool { isSigning || isUploading } var canFinish: Bool { isComplete && uploadResult != nil && !isBusy } var showsCompletion: Bool { isComplete || (isSigning && isLastPendingDocument) } func uploadSignedDocuments() async { guard isComplete, !isUploading, uploadResult == nil else { return } isUploading = true uploadError = nil defer { isUploading = false } do { let files = pendingDocuments.compactMap { signedURLs[$0.id] } guard files.count == pendingDocuments.count else { throw PDFSigningError.invalidDocument } uploadResult = try await uploadService.upload( files, leadID: leadID, offerID: request.offerID, salesCode: salesCode ) } catch is CancellationError { uploadError = "Die Übertragung wurde unterbrochen. Die signierten PDFs bleiben gespeichert." } catch { uploadError = error.localizedDescription } } var currentDocument: LeadDocument? { pendingDocuments.indices.contains(currentIndex) ? pendingDocuments[currentIndex] : nil } var canSign: Bool { sourceURL != nil && currentDocument?.signaturePlacement != nil && !drawing.strokes.isEmpty && !isLoading && !isSigning && !isComplete } func loadCurrentDocument() async { guard let document = currentDocument else { return } isLoading = true sourceURL = nil message = nil defer { isLoading = false } do { let url = try await DownloadLeadDocumentUseCase(repository: repository) .execute(document, leadID: leadID, salesCode: salesCode) try Task.checkCancellation() guard let pdf = PDFDocument(url: url), !pdf.isLocked else { throw PDFSigningError.invalidDocument } guard let placement = document.signaturePlacement, placement.pageIndex >= 0, let page = pdf.page(at: placement.pageIndex), page.bounds(for: .mediaBox).contains(placement.bounds) else { throw PDFSigningError.invalidPlacement } sourceURL = url } catch is CancellationError { } catch { message = error.localizedDescription } } func signAndAdvance(onSigned: (LeadDocument, URL) -> Void) async { guard canSign, let document = currentDocument, let sourceURL, let placement = document.signaturePlacement else { return } isSigning = true message = nil defer { isSigning = false } do { let result = try await signingService.sign(source: sourceURL, drawing: drawing, placement: placement) try store.save(url: result, document: document, leadID: leadID, salesCode: salesCode) signedURLs[document.id] = result onSigned(document, result) drawing = PKDrawing() if currentIndex + 1 < pendingDocuments.count { currentIndex += 1 await loadCurrentDocument() } else { self.sourceURL = result isComplete = true await uploadSignedDocuments() } } catch is CancellationError { } catch { // Keep both the source and handwriting intact so saving can be retried. message = error.localizedDescription } } }