//
//  NetworkReadiness.swift
//  Reference code — copy into your app, adjust the type/file names to taste.
//
//  Adapted from the IndieDiag package (`NetworkDiagnostic.swift`,
//  commit bc6f475). Trimmed to the NWPathMonitor-based status/quality
//  estimate only — dropped the original's `testCloudConnectivity()` HTTP
//  probe to icloud.com as extra surface a documents-only app doesn't need;
//  `NWPathMonitor`'s path status is enough to decide "is this a reasonable
//  moment to sync." Combine publishers replaced with a plain `async`
//  one-shot read plus an `AsyncStream` for the rare screen that wants live
//  updates while visible.
//

import Foundation
import Network

/// Network connection type.
public enum ConnectionType: String, Sendable {
    case wifi = "Wi-Fi"
    case cellular = "Cellular"
    case ethernet = "Ethernet"
    case unknown = "Unknown"
    case none = "No Connection"
}

/// Network quality estimate for syncing.
public enum NetworkQuality: String, Sendable {
    case excellent = "Excellent"
    case good = "Good"
    case fair = "Fair"
    case poor = "Poor"
    case none = "No Connection"

    /// Suitable for large file uploads (e.g. media attachments).
    public var canSyncLargeFiles: Bool { self == .excellent || self == .good }

    /// Suitable for basic sync operations (small JSON documents).
    public var canSync: Bool { self != .none }
}

/// Network connectivity status relevant to iCloud sync.
public struct NetworkStatus: Sendable {
    public let isConnected: Bool
    public let connectionType: ConnectionType
    public let isExpensive: Bool
    public let isConstrained: Bool
    public let supportsCloudSync: Bool
    public let lastChecked: Date

    public init(
        isConnected: Bool,
        connectionType: ConnectionType,
        isExpensive: Bool = false,
        isConstrained: Bool = false,
        supportsCloudSync: Bool = true,
        lastChecked: Date = Date()
    ) {
        self.isConnected = isConnected
        self.connectionType = connectionType
        self.isExpensive = isExpensive
        self.isConstrained = isConstrained
        self.supportsCloudSync = supportsCloudSync
        self.lastChecked = lastChecked
    }

    public var quality: NetworkQuality {
        guard isConnected else { return .none }
        if isConstrained { return .poor }
        switch connectionType {
        case .wifi, .ethernet: return .excellent
        case .cellular: return isExpensive ? .fair : .good
        case .unknown, .none: return .poor
        }
    }
}

/// `NWPathMonitor`-based network readiness check. Two ways to use it:
///
/// - `currentStatus()` — a one-shot read for a diagnostics screen's initial
///   state, or before starting a sync operation.
/// - `statusUpdates()` — an `AsyncStream` for as long as the screen is
///   visible and iterating it. Stop iterating (or cancel the owning `Task`)
///   when the screen disappears; don't leave a monitor running for the
///   app's whole lifetime just for a diagnostics view.
public actor NetworkReadiness {
    private var monitor: NWPathMonitor?

    public init() {}

    /// Single point-in-time read: starts a path monitor, takes the first
    /// update, and cancels. Cheap enough to call every time a diagnostics
    /// screen appears — don't cache this across app launches or poll it on
    /// a timer (a real path change will call any *ongoing* monitor's
    /// handler immediately; polling adds nothing but battery drain).
    public func currentStatus() async -> NetworkStatus {
        let monitor = NWPathMonitor()
        return await withCheckedContinuation { continuation in
            monitor.pathUpdateHandler = { path in
                continuation.resume(returning: Self.buildStatus(from: path))
                monitor.cancel()
            }
            monitor.start(queue: DispatchQueue(label: "indie-diag.network.oneshot"))
        }
    }

    /// Ongoing updates for as long as the returned stream is iterated.
    /// Only one monitor runs at a time per `NetworkReadiness` instance —
    /// starting a new stream cancels any previous one.
    public func statusUpdates() -> AsyncStream<NetworkStatus> {
        stop()
        let monitor = NWPathMonitor()
        self.monitor = monitor

        return AsyncStream { continuation in
            monitor.pathUpdateHandler = { path in
                continuation.yield(Self.buildStatus(from: path))
            }
            continuation.onTermination = { _ in
                monitor.cancel()
            }
            monitor.start(queue: DispatchQueue(label: "indie-diag.network.monitor"))
        }
    }

    /// Cancels any monitor started by `statusUpdates()`. Call this when the
    /// diagnostics screen disappears if you're not relying on the stream's
    /// consumer dropping out to trigger `onTermination`.
    public func stop() {
        monitor?.cancel()
        monitor = nil
    }

    private nonisolated static func buildStatus(from path: NWPath) -> NetworkStatus {
        let isConnected = path.status == .satisfied

        let connectionType: ConnectionType
        if path.usesInterfaceType(.wifi) {
            connectionType = .wifi
        } else if path.usesInterfaceType(.cellular) {
            connectionType = .cellular
        } else if path.usesInterfaceType(.wiredEthernet) {
            connectionType = .ethernet
        } else if isConnected {
            connectionType = .unknown
        } else {
            connectionType = .none
        }

        let isExpensive = path.isExpensive
        let isConstrained = path.isConstrained

        // iCloud sync works best on Wi-Fi/Ethernet, or unconstrained cellular.
        let supportsCloudSync = isConnected && (!isConstrained || connectionType == .wifi || connectionType == .ethernet)

        return NetworkStatus(
            isConnected: isConnected,
            connectionType: connectionType,
            isExpensive: isExpensive,
            isConstrained: isConstrained,
            supportsCloudSync: supportsCloudSync
        )
    }
}
