/*:#localized(key: "VersionAClassic")
 # Version A (classic): GCD + completion handlers + `DispatchGroup`

 Each fetch takes a **completion handler**, a closure it calls back with a
 `Result` when the (simulated) network finishes. Because the two fetches are
 independent, we push both onto background queues and use a **`DispatchGroup`**
 to learn when *both* have called back. Then we hop to main to combine and
 report.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

enum BarError: Error {
    case offline
}

func fetchMenu(completion: @escaping (Result<[String], Error>) -> Void) {
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 0.6)   // network wait
        completion(.success(["Latte", "Cold brew", "Espresso"]))
    }
}

func fetchStock(completion: @escaping (Result<[String: Int], Error>) -> Void) {
    DispatchQueue.global().async {
        Thread.sleep(forTimeInterval: 0.4)   // network wait
        completion(.success(["Latte": 3, "Cold brew": 0, "Espresso": 8]))
    }
}

let group = DispatchGroup()
var menuResult: Result<[String], Error>?
var stockResult: Result<[String: Int], Error>?

group.enter()
fetchMenu { result in
    menuResult = result
    group.leave()
}

group.enter()
fetchStock { result in
    stockResult = result
    group.leave()
}

group.notify(queue: .main) {   // back on main
    do {
        guard let menuResult, let stockResult else { return }
        let menu = try menuResult.get()
        let stock = try stockResult.get()
        let sellable = menu.filter { (stock[$0] ?? 0) > 0 }
        print("Sellable today: \(sellable)")
    } catch {
        print("Couldn't build the menu: \(error)")
    }
    PlaygroundPage.current.finishExecution()
}

print("Fetching menu and stock...")
//#-end-editable-code
/*:#localized(key: "VersionAClassic-2")
 **Watch it run:** `Fetching menu and stock...` prints first: both `fetch` calls
 returned immediately. The two fetches run in parallel (0.6s and 0.4s), so about
 0.6 seconds later the group empties and you get, on the main thread:

 * callout(Output): 
 `Fetching menu and stock...`  
 `Sellable today: ["Latte", "Espresso"]`

 Cold brew is filtered out because its stock is `0`.

 * callout(Try it): Change Cold brew's stock in `fetchStock` from `0` to `2`.
 Does it show up in the sellable list now?

 Now count what this cost you:

 * Two mutable `Result?` variables living outside the closures, to smuggle each
 answer back out of its completion handler.
 * Manual `enter()`/`leave()` bookkeeping: one slip and the group hangs or
 crashes.
 * The result is two **optional** `Result`s at the join point, so you need a
 `guard` *and* a `do`-`try`-`catch` to get from "two `Result?`s" to "two real
 values or an error."
 * The main-thread hop is a thing *you* remembered to do (`notify(queue: .main)`).
 Forget it and you'd combine on a background thread and update UI from there, a
 bug the compiler never mentions.

 None of it is wrong. But every piece is manual, and the actual logic,
 "filter the menu by stock," is buried in ceremony.

 * callout(Data-race aside): `menuResult` and `stockResult` are written from
 background threads and read in `notify`. This happens to be safe because the
 group's `leave()` → `notify` ordering guarantees the writes finish before the
 read, but *nothing checks that for you*. This is exactly the class of hazard
 that data races cause in general, and exactly what the modern model removes by
 construction.

 [Previous](@previous)  |  page 3 of 8  |  [Next: Version B: The Modern Way](@next)
 */
