summaryrefslogtreecommitdiff
path: root/DomainDig/DomainViewModel+Export.swift
blob: 5ca0514486e0d95bcc62eccd8c068b2ee7f4b103 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import Foundation

/// Export and data-portability surface of `DomainViewModel`: rendering the
/// current report, batch results, tracked domains, timelines, and workflow runs
/// into the shareable formats, plus the full-backup / portable-slice exporters
/// and the import + app-settings persistence entry points.
///
/// These are thin adapters over `DomainReportExporter` and
/// `DomainDataPortabilityService`. The report-projection helpers they call
/// (`currentBatchReports`, `reports(for:)`, `timelineReports`, `workflowReports`)
/// remain on the main type — they belong to the report layer shared with
/// inspection, not to export.
extension DomainViewModel {
    func exportJSONData() -> Data? {
        guard let currentReport else { return nil }
        return try? DomainReportExporter.data(for: currentReport, format: .json)
    }

    func exportJSONString() -> String? {
        guard let data = exportJSONData() else { return nil }
        return String(data: data, encoding: .utf8)
    }

    func exportSingleReportData(format: DomainExportFormat) -> Data? {
        guard let currentReport else { return nil }
        return try? DomainReportExporter.data(for: currentReport, format: format)
    }

    func exportBatchReportData(format: DomainExportFormat) -> Data? {
        try? DomainReportExporter.data(
            for: currentBatchReports(),
            format: format,
            title: batchLookupSource == .watchlistRefresh ? "Tracked Domains Export" : "Batch Results Export"
        )
    }

    func exportTrackedDomainsData(domains: [TrackedDomain], format: DomainExportFormat) -> Data? {
        try? DomainReportExporter.data(
            for: reports(for: domains),
            format: format,
            title: "Tracked Domains Export"
        )
    }

    func exportTimelineText(domain: String, includeDiffSummary: Bool) -> String {
        DomainReportExporter.timelineText(
            for: timelineReports(for: domain),
            domain: domain,
            includeDiffSummary: includeDiffSummary
        )
    }

    func exportTimelineJSONData(domain: String, includeDiffSummary: Bool) -> Data? {
        try? DomainReportExporter.timelineData(
            for: timelineReports(for: domain),
            domain: domain,
            includeDiffSummary: includeDiffSummary
        )
    }

    func exportFullBackupData() -> Data? {
        try? DomainDataPortabilityService.backupData()
    }

    func exportPortableTrackedDomainsJSONData() -> Data? {
        try? DomainDataPortabilityService.trackedDomainsExportData()
    }

    func exportPortableTrackedDomainsCSV() -> String {
        DomainDataPortabilityService.trackedDomainsCSV()
    }

    func exportPortableWorkflowsJSONData() -> Data? {
        try? DomainDataPortabilityService.workflowsExportData()
    }

    func exportPortableWorkflowsCSV() -> String {
        DomainDataPortabilityService.workflowsCSV()
    }

    func exportPortableHistoryJSONData() -> Data? {
        try? DomainDataPortabilityService.historyExportData()
    }

    func prepareDataImport(
        data: Data,
        fileName: String,
        mode: DataPortabilityImportMode
    ) throws -> DataImportPreview {
        try DomainDataPortabilityService.prepareImport(data: data, fileName: fileName, mode: mode)
    }

    func applyDataImport(_ preview: DataImportPreview, mode: DataPortabilityImportMode) throws -> DataImportResult {
        let result = try DomainDataPortabilityService.applyImport(preview, mode: mode)
        refreshPersistedData()
        portabilityStatusMessage = result.summary
        CloudSyncService.shared.markAppSettingsChanged()
        CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: monitoringSettings.isEnabled)
        CloudSyncService.shared.scheduleSyncIfNeeded(trigger: .imported)
        return result
    }

    func persistCurrentAppSettings(resolverURLString: String, appDensityRawValue: String) {
        DomainDataPortabilityService.saveAppSettings(
            AppSettingsSnapshot(
                recentSearches: recentSearches,
                savedDomains: savedDomains,
                resolverURLString: resolverURLString,
                appDensityRawValue: appDensityRawValue
            )
        )
        CloudSyncService.shared.markAppSettingsChanged()
        refreshDataLifecycleSummary()
    }

    func exportWorkflowText(summary: WorkflowRunSummary, changedOnly: Bool) -> String {
        let reports = workflowReports(from: summary, changedOnly: changedOnly)
        let base = DomainReportExporter.batchText(
            for: reports,
            title: "\(summary.workflowName) Workflow Export"
        )
        guard !summary.workflowInsights.isEmpty else { return base }
        let insightLines = summary.workflowInsights.map {
            "- \($0.description): \($0.domainsInvolved.joined(separator: ", "))"
        }
        return ([ "\(summary.workflowName) Workflow Insights", String(repeating: "-", count: 32) ] + insightLines + ["", base]).joined(separator: "\n")
    }

    func exportWorkflowCSV(summary: WorkflowRunSummary, changedOnly: Bool) -> String {
        DomainReportExporter.csv(
            for: workflowReports(from: summary, changedOnly: changedOnly),
            workflowInsights: summary.workflowInsights
        )
    }

    func exportWorkflowJSONData(summary: WorkflowRunSummary, changedOnly: Bool) -> Data? {
        let payload = WorkflowExportPayload(
            workflowName: summary.workflowName,
            generatedAt: summary.generatedAt,
            workflowInsights: summary.workflowInsights,
            reports: workflowReports(from: summary, changedOnly: changedOnly)
        )
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        encoder.dateEncodingStrategy = .iso8601
        return try? encoder.encode(payload)
    }

    func exportWorkflowMarkdown(summary: WorkflowRunSummary, changedOnly: Bool) -> String {
        let reports = workflowReports(from: summary, changedOnly: changedOnly)
        let base = DomainReportExporter.batchMarkdown(for: reports, title: "\(summary.workflowName) Workflow Export")
        guard !summary.workflowInsights.isEmpty else { return base }
        let insightLines = summary.workflowInsights.map {
            "- \($0.description): \($0.domainsInvolved.joined(separator: ", "))"
        }
        return (["## \(summary.workflowName) Workflow Insights"] + insightLines + ["", base]).joined(separator: "\n")
    }

    func exportWorkflowData(summary: WorkflowRunSummary, changedOnly: Bool, format: DomainExportFormat) -> Data? {
        switch format {
        case .text:
            return Data(exportWorkflowText(summary: summary, changedOnly: changedOnly).utf8)
        case .csv:
            return Data(exportWorkflowCSV(summary: summary, changedOnly: changedOnly).utf8)
        case .json:
            return exportWorkflowJSONData(summary: summary, changedOnly: changedOnly)
        case .markdown:
            return Data(exportWorkflowMarkdown(summary: summary, changedOnly: changedOnly).utf8)
        case .pdf:
            return DomainReportExporter.pdfData(fromMarkdown: exportWorkflowMarkdown(summary: summary, changedOnly: changedOnly))
        }
    }
}

private struct WorkflowExportPayload: Codable {
    let workflowName: String
    let generatedAt: Date
    let workflowInsights: [WorkflowInsight]
    let reports: [DomainReport]
}