import UIKit import PDFKit enum ExportFormat: String, CaseIterable { case insurance = "PDF"; case arena = "Arena"; case mtgo = "MTGO"; case csv = "CSV" } // MARK: - EXPORT ENGINE class ExportEngine { static func generateString(cards: [SavedCard], format: ExportFormat) -> String { if format == .csv { func cleanCSV(_ text: String) -> String { return text.replacingOccurrences(of: "\"", with: "\"\"") } var csv = "Count,Name,Set,Number,Condition,Foil,Price,Serialized\n" for card in cards { let ser = (card.isSerialized ?? false) ? "Yes" : "No" let line = "1,\"\(cleanCSV(card.name))\",\(card.setCode),\(card.collectorNumber),\(card.condition),\(card.foilType),\(card.currentValuation ?? 0.0),\(ser)\n" csv.append(line) } return csv } else if format == .mtgo { return cards.map { "1 \($0.name)" }.joined(separator: "\n") } else { // Arena return cards.map { "1 \($0.name) (\($0.setCode)) \($0.collectorNumber)" }.joined(separator: "\n") } } static func generate(cards: [SavedCard], format: ExportFormat) -> URL? { if format == .insurance { return generatePDF(cards: cards) } let text = generateString(cards: cards, format: format) let filename = format == .csv ? "Collection.csv" : (format == .mtgo ? "MTGO_List.txt" : "Arena_Decklist.txt") let path = FileManager.default.temporaryDirectory.appendingPathComponent(filename) try? text.write(to: path, atomically: true, encoding: .utf8) return path } private static func generatePDF(cards: [SavedCard]) -> URL? { let fmt = UIGraphicsPDFRendererFormat() fmt.documentInfo = [kCGPDFContextCreator: "IYmtg"] as [String: Any] let url = FileManager.default.temporaryDirectory.appendingPathComponent("Insurance.pdf") let totalVal = cards.reduce(0) { $0 + ($1.currentValuation ?? 0) } let count = cards.count UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: 612, height: 792), format: fmt).writePDF(to: url) { ctx in var pageY: CGFloat = 50 let headerAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 24)] let subHeaderAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 14)] let textAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 12)] func drawHeader() { "Insurance Schedule - \(Date().formatted(date: .abbreviated, time: .shortened))".draw(at: CGPoint(x: 50, y: 50), withAttributes: headerAttrs) } func drawColumnHeaders(y: CGFloat) { let hAttrs: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 10), .foregroundColor: UIColor.darkGray] "CARD DETAILS".draw(at: CGPoint(x: 100, y: y), withAttributes: hAttrs) "SET / #".draw(at: CGPoint(x: 310, y: y), withAttributes: hAttrs) "COND".draw(at: CGPoint(x: 420, y: y), withAttributes: hAttrs) "VALUE".draw(at: CGPoint(x: 520, y: y), withAttributes: hAttrs) } ctx.beginPage() drawHeader() // Summary Section "Policy Holder: __________________________".draw(at: CGPoint(x: 50, y: 90), withAttributes: subHeaderAttrs) "Total Items: \(count)".draw(at: CGPoint(x: 50, y: 115), withAttributes: subHeaderAttrs) "Total Value: \(cards.first?.currencyCode == "EUR" ? "€" : "$")\(String(format: "%.2f", totalVal))".draw(at: CGPoint(x: 200, y: 115), withAttributes: subHeaderAttrs) drawColumnHeaders(y: 135) pageY = 150 for card in cards { if pageY + 65 > 740 { ctx.beginPage() drawHeader() drawColumnHeaders(y: 75) pageY = 90 } // Column 0: Image if let img = ImageManager.load(name: card.imageFileName) { img.draw(in: CGRect(x: 50, y: pageY, width: 40, height: 56)) } let textY = pageY + 20 // Column 1: Name (Truncate if long) let name = "\(card.name)\((card.isSerialized ?? false) ? " [S]" : "")" name.draw(in: CGRect(x: 100, y: textY, width: 200, height: 18), withAttributes: textAttrs) // Column 2: Set let setInfo = "\(card.setCode.uppercased()) #\(card.collectorNumber)" setInfo.draw(at: CGPoint(x: 310, y: textY), withAttributes: textAttrs) // Column 3: Condition (Short code) let cond = card.condition.components(separatedBy: "(").last?.replacingOccurrences(of: ")", with: "") ?? card.condition cond.draw(at: CGPoint(x: 420, y: textY), withAttributes: textAttrs) // Column 4: Value let val = "\(card.currencyCode == "EUR" ? "€" : "$")\(String(format: "%.2f", card.currentValuation ?? 0.0))" val.draw(at: CGPoint(x: 520, y: textY), withAttributes: textAttrs) pageY += 65 } // Condensed Summary Section ctx.beginPage() drawHeader() "Condensed Manifest".draw(at: CGPoint(x: 50, y: 90), withAttributes: subHeaderAttrs) pageY = 120 for card in cards { if pageY > 740 { ctx.beginPage() drawHeader() "Condensed Manifest (Cont.)".draw(at: CGPoint(x: 50, y: 90), withAttributes: subHeaderAttrs) pageY = 120 } let line = "\(card.name) (\(card.setCode) #\(card.collectorNumber))" line.draw(in: CGRect(x: 50, y: pageY, width: 400, height: 18), withAttributes: textAttrs) let val = "\(card.currencyCode == "EUR" ? "€" : "$")\(String(format: "%.2f", card.currentValuation ?? 0.0))" val.draw(at: CGPoint(x: 500, y: pageY), withAttributes: textAttrs) pageY += 20 } // FIX: Ensure Grand Total doesn't fall off the page if pageY + 40 > 792 { ctx.beginPage() drawHeader() pageY = 90 } "GRAND TOTAL: \(cards.first?.currencyCode == "EUR" ? "€" : "$")\(String(format: "%.2f", totalVal))".draw(at: CGPoint(x: 400, y: pageY + 20), withAttributes: subHeaderAttrs) } return url } }