import Foundation
import Network

public struct NetworkPathSnapshot: Sendable, Equatable {
    public enum LinkType: Sendable, Hashable {
        case wifi, cellular, wired, loopback, other, unavailable
    }

    public let isAvailable: Bool
    public let isExpensive: Bool
    public let isConstrained: Bool
    public let link: LinkType

    public init(isAvailable: Bool,
                isExpensive: Bool,
                isConstrained: Bool,
                link: LinkType) {
        self.isAvailable = isAvailable
        self.isExpensive = isExpensive
        self.isConstrained = isConstrained
        self.link = link
    }

    public static let unknown = NetworkPathSnapshot(
        isAvailable: true,
        isExpensive: false,
        isConstrained: false,
        link: .other
    )
}

public protocol NetworkPathProviding: Sendable {
    var pathChanges: AsyncStream<NetworkPathSnapshot> { get }
}

public final class NWPathObserver: NetworkPathProviding, @unchecked Sendable {
    private let monitor: NWPathMonitor
    private let queue = DispatchQueue(label: "com.screens.vnc.path")

    public init() {
        self.monitor = NWPathMonitor()
    }

    public var pathChanges: AsyncStream<NetworkPathSnapshot> {
        AsyncStream { continuation in
            monitor.pathUpdateHandler = { path in
                continuation.yield(Self.snapshot(for: path))
            }
            monitor.start(queue: queue)
            continuation.onTermination = { [monitor] _ in
                monitor.cancel()
            }
        }
    }

    private static func snapshot(for path: NWPath) -> NetworkPathSnapshot {
        let link: NetworkPathSnapshot.LinkType
        switch path.status {
        case .satisfied:
            if path.usesInterfaceType(.wifi) {
                link = .wifi
            } else if path.usesInterfaceType(.cellular) {
                link = .cellular
            } else if path.usesInterfaceType(.wiredEthernet) {
                link = .wired
            } else if path.usesInterfaceType(.loopback) {
                link = .loopback
            } else {
                link = .other
            }
        default:
            link = .unavailable
        }
        return NetworkPathSnapshot(
            isAvailable: path.status == .satisfied,
            isExpensive: path.isExpensive,
            isConstrained: path.isConstrained,
            link: link
        )
    }
}

public struct StaticPathProvider: NetworkPathProviding {
    private let snapshot: NetworkPathSnapshot

    public init(_ snapshot: NetworkPathSnapshot = .unknown) {
        self.snapshot = snapshot
    }

    public var pathChanges: AsyncStream<NetworkPathSnapshot> {
        let snapshot = self.snapshot
        return AsyncStream { continuation in
            continuation.yield(snapshot)
            continuation.finish()
        }
    }
}
