//
//  DiagnosticsReport.swift
//  Reference code — copy into your app, adjust the type/file names to taste.
//
//  Adapted from the IndieDiag package (`IndieDiag.swift` + the
//  `DiagnosticReport` model, commit bc6f475), trimmed to the three
//  iCloud-documents-relevant pieces (container, document sync, network) —
//  `cloudKitStatus` and `storageQuota` are gone along with CloudKit. This
//  file shows how `ContainerDiagnostic`, `DocumentSyncInspector`, and
//  `NetworkReadiness` compose into the single snapshot a settings
//  "Diagnostics" screen wants — plus the optional `SchemaSkipScanner`
//  (a post-package addition, see that file's header) for counting
//  documents skipped by the schema-version forward gate.
//

import Foundation

/// Coarse overall health, derived from the three individual statuses.
public enum DiagnosticHealth: String, Sendable {
    case healthy = "Healthy"
    case syncing = "Syncing"
    case warning = "Warning"
    case critical = "Critical"
}

/// Combined snapshot of container, document sync, and network status.
public struct DiagnosticsReport: Sendable {
    public let containerStatus: ICloudContainerStatus
    public let documentSync: DocumentSyncSnapshot
    /// Present only when the coordinator was given a `SchemaSkipScanner`.
    public let schemaSkips: SchemaSkipSnapshot?
    public let networkStatus: NetworkStatus
    public let generatedAt: Date

    /// Note: schema-skipped documents deliberately do NOT degrade health.
    /// A document written by a newer app version is an expected state, not
    /// a fault — the data is intact and an app update recovers it.
    public var overallHealth: DiagnosticHealth {
        guard containerStatus.isAvailable, networkStatus.isConnected else { return .critical }
        if documentSync.errorCount > 0 || documentSync.conflictCount > 0 { return .warning }
        if documentSync.isSyncing { return .syncing }
        return .healthy
    }

    /// Human-readable summary, e.g. for a debug console or support email
    /// attachment — not for display as primary UI copy.
    public var summary: String {
        var lines = ["Diagnostics — \(overallHealth.rawValue)"]
        lines.append("iCloud: \(containerStatus.isAvailable ? "available" : "unavailable") (account signed in: \(containerStatus.hasSignedInAccount))")
        lines.append("Network: \(networkStatus.isConnected ? "connected via \(networkStatus.connectionType.rawValue)" : "disconnected")")
        lines.append("Documents: \(documentSync.currentCount)/\(documentSync.totalCount) current")
        // Skipped and evicted are paired deliberately: evicted documents
        // are never probed, so the skipped count is a floor — showing both
        // numbers side by side lets the reader draw the conclusion.
        if let schemaSkips, schemaSkips.skippedCount > 0 || documentSync.evictedCount > 0 {
            lines.append("Skipped (newer schema): \(schemaSkips.skippedCount); evicted (unprobed): \(documentSync.evictedCount)")
        } else if documentSync.evictedCount > 0 {
            lines.append("Evicted: \(documentSync.evictedCount)")
        }
        if documentSync.errorCount > 0 { lines.append("Errors: \(documentSync.errorCount)") }
        if documentSync.conflictCount > 0 { lines.append("Conflicts: \(documentSync.conflictCount)") }
        return lines.joined(separator: "\n")
    }
}

/// Coordinates the three diagnostics into a single on-demand snapshot.
/// Construct one (it's cheap — no I/O happens until `generate()` runs),
/// call `generate()` when a diagnostics screen appears and again on a
/// manual "Refresh" action. Don't keep polling it on a timer and don't
/// call it from a hot path — see SKILL.md's anti-patterns.
@MainActor
public final class DiagnosticsCoordinator {
    private let container: ContainerDiagnostic
    private let documentInspector = DocumentSyncInspector()
    private let network = NetworkReadiness()
    private let schemaScanner: SchemaSkipScanner?

    /// Pass a `SchemaSkipScanner` to also count documents this build skips
    /// because their schema version is newer than it understands:
    ///
    ///     DiagnosticsCoordinator(
    ///         schemaScanner: SchemaSkipScanner(
    ///             currentSchemaVersion: RecordDocument.currentSchema))
    public init(containerIdentifier: String? = nil, schemaScanner: SchemaSkipScanner? = nil) {
        self.container = ContainerDiagnostic(containerIdentifier: containerIdentifier)
        self.schemaScanner = schemaScanner
    }

    /// Runs all checks concurrently and combines them into one report. The
    /// schema scan (when configured) runs after the document snapshot it
    /// depends on; container and network checks overlap with both.
    public func generate() async -> DiagnosticsReport {
        async let containerStatus = container.checkStatus()
        async let networkStatus = network.currentStatus()

        let syncSnapshot = await documentInspector.snapshot()

        var schemaSkips: SchemaSkipSnapshot?
        if let schemaScanner {
            // Probe only documents that certainly have a full local copy —
            // reading an evicted (or still-downloading) document would
            // force a download just to inspect one integer. Evicted
            // documents therefore aren't in the skipped count; the report
            // already surfaces them separately as `evictedCount`.
            let locallyPresent: Set<DocumentSyncState> = [.current, .uploading, .notUploaded, .conflict]
            let urls = syncSnapshot.items
                .filter { locallyPresent.contains($0.syncState) }
                .map(\.url)
            schemaSkips = await schemaScanner.scan(urls: urls)
        }

        return await DiagnosticsReport(
            containerStatus: containerStatus,
            documentSync: syncSnapshot,
            schemaSkips: schemaSkips,
            networkStatus: networkStatus,
            generatedAt: Date()
        )
    }
}
