import Foundation import Observation @MainActor @Observable final class LeadDetailViewModel { private(set) var detail: LeadDetail? private(set) var error: LeadError? var expandedOffers: Set = [] var expandedGroups: Set = [] var selectedDocument: LeadDocument? var signingRequest: LeadSigningRequest? private(set) var signedDocumentURLs: [String: URL] = [:] private let leadID: String private let salesCode: String private let getDetail: GetLeadDetailUseCase private var generation = 0 init(leadID: String, salesCode: String, repository: any LeadDetailRepository) { self.leadID = leadID self.salesCode = salesCode self.getDetail = GetLeadDetailUseCase(repository: repository) } var lead: Lead? { detail?.lead } var isAllExpanded: Bool { guard let detail, !detail.offers.isEmpty else { return false } return Set(detail.offers.map(\.id)).isSubset(of: expandedOffers) && Set(detail.offers.flatMap(\.groups).map(\.id)).isSubset(of: expandedGroups) } func load() async { generation += 1 let request = generation detail = nil error = nil do { let result = try await getDetail.execute(id: leadID, salesCode: salesCode) try Task.checkCancellation() guard request == generation else { return } detail = result let documents = result.offers.flatMap(\.documents) signedDocumentURLs = LeadSignedDocumentStore().savedURLs( documents: documents, leadID: leadID, salesCode: salesCode ) for document in documents { if let url = signedDocumentURLs[document.id] { didSign(document, url: url) } } if let first = result.offers.first { expandedOffers = [first.id] expandedGroups = Set(first.groups.filter { !$0.isComplete }.map(\.id)) } else { expandedOffers = [] expandedGroups = [] } } catch is CancellationError { } catch { guard request == generation, !Task.isCancelled else { return } self.error = error as? LeadError ?? .unexpected } } func startSigning(offer: LeadOffer, group: LeadDocumentGroup? = nil) { let documents = group?.documents ?? offer.documents guard documents.contains(where: { $0.status == .pending }) else { return } signingRequest = LeadSigningRequest( offerID: offer.id, signerName: detail?.contactName ?? detail?.lead.displayName ?? "Kunde", documents: documents ) } func didSign(_ document: LeadDocument, url: URL) { signedDocumentURLs[document.id] = url guard var detail else { return } for offerIndex in detail.offers.indices { for groupIndex in detail.offers[offerIndex].groups.indices { for documentIndex in detail.offers[offerIndex].groups[groupIndex].documents.indices { if detail.offers[offerIndex].groups[groupIndex].documents[documentIndex].id == document.id { detail.offers[offerIndex].groups[groupIndex].documents[documentIndex].status = .signed } } } } self.detail = detail } func toggleOffer(_ id: String) { if !expandedOffers.insert(id).inserted { expandedOffers.remove(id) } } func toggleGroup(_ id: String) { if !expandedGroups.insert(id).inserted { expandedGroups.remove(id) } } func toggleAll() { guard let detail else { return } if isAllExpanded { expandedOffers = [] expandedGroups = [] } else { expandedOffers = Set(detail.offers.map(\.id)) expandedGroups = Set(detail.offers.flatMap(\.groups).map(\.id)) } } }