// // ReportPDFGenerator.swift // Reflect // // Generates clinical PDF reports from MoodReport data using HTML + WKWebView. // import Foundation import WebKit @MainActor final class ReportPDFGenerator { enum PDFError: Error, LocalizedError { case htmlRenderFailed case pdfGenerationFailed(underlying: Error) case fileWriteFailed var errorDescription: String? { switch self { case .htmlRenderFailed: return "Failed to render report HTML" case .pdfGenerationFailed(let error): return "PDF generation failed: \(error.localizedDescription)" case .fileWriteFailed: return "Failed to save PDF file" } } } // MARK: - Public API func generatePDF(from report: MoodReport) async throws -> URL { let html = generateHTML(from: report) let pdfData = try await renderHTMLToPDF(html: html) let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let startStr = dateFormatter.string(from: report.weeks.first?.startDate ?? Date()) let endStr = dateFormatter.string(from: report.weeks.last?.endDate ?? Date()) let fileName = "Reflect-AI-Report-\(startStr)-to-\(endStr).pdf" let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) do { try pdfData.write(to: fileURL) return fileURL } catch { throw PDFError.fileWriteFailed } } // MARK: - HTML Generation func generateHTML(from report: MoodReport) -> String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long let generatedDate = dateFormatter.string(from: report.generatedAt) let useFahrenheit = Locale.current.measurementSystem == .us var html = """
""" // Header html += """\(report.overview.dateRange)
Generated \(generatedDate)
\(month.entryCount) entries · Average mood: \(String(format: "%.1f", month.averageMood))/5
\(month.aiSummary.map { "\(year.entryCount) entries · Average mood: \(String(format: "%.1f", year.averageMood))/5
\(year.aiSummary.map { "| Date | Mood | Notes | Weather |
|---|