/*:#localized(key: "JobInThreeSteps")
 # A job in three dependent steps

 Back to the **print shop**. To produce a booklet, three things must happen *in
 order*, each needing the one before it:

 1. **Load a design** (a template to print from).
 2. **Render a proof** from that design (how many pages it becomes).
 3. **Bind the booklet** from that page count.

 Here are the three async building blocks. Each simulates real waiting (a disk
 read, a render, a bindery run) with `asyncAfter`, and reports back through a
 completion handler carrying a `Result`. Because each step needs the previous
 result, each call nests inside the last handler. We put this in a small class so a
 real-world detail comes along: the closures capture `self`, so we have to think
 about retain cycles.
 */
//#-editable-code
import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

enum PrintError: Error {
    case designMissing, renderFailed, binderyJammed
}

func loadDesign(named name: String,
                completion: @escaping @Sendable (Result<String, Error>) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
        completion(.success("Design(\(name))"))
    }
}

func renderProof(from design: String,
                 completion: @escaping @Sendable (Result<Int, Error>) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
        completion(.success(12))   // 12 rendered pages
    }
}

func bindBooklet(pageCount: Int,
                 completion: @escaping @Sendable (Result<String, Error>) -> Void) {
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
        completion(.success("Booklet(\(pageCount) pages)"))
    }
}

// @unchecked Sendable: we PROMISE the compiler this class is safe to hand
// across threads. Under Swift 6, that promise is one more bit of bookkeeping
// the classic, closure-based model leaves on you.
final class PrintShop: @unchecked Sendable {
    var lastBooklet = ""

    func assembleBooklet(completion: @escaping @Sendable (Result<String, Error>) -> Void) {
        loadDesign(named: "Spring Menu") { [weak self] designResult in
            // Weak capture, then unwrap to a strong local the nested
            // closures can safely use (yet more manual bookkeeping).
            guard let self else { return }
            switch designResult {
            case .failure(let error):
                completion(.failure(error))   // error path 1
            case .success(let design):
                renderProof(from: design) { proofResult in
                    switch proofResult {
                    case .failure(let error):
                        completion(.failure(error))   // error path 2
                    case .success(let pageCount):
                        bindBooklet(pageCount: pageCount) { bindResult in
                            switch bindResult {
                            case .failure(let error):
                                completion(.failure(error))   // error path 3
                            case .success(let booklet):
                                self.lastBooklet = booklet
                                // Results arrive on a background queue -
                                // hop back to main before reporting to UI.
                                DispatchQueue.main.async {
                                    completion(.success(booklet))
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

let shop = PrintShop()
shop.assembleBooklet { result in
    switch result {
    case .success(let booklet):
        print("✅ Ready: \(booklet)")
    case .failure(let error):
        print("❌ Failed: \(error)")
    }
    PlaygroundPage.current.finishExecution()
}
//#-end-editable-code
/*:#localized(key: "JobInThreeSteps-2")
 **Watch it run:** after about a second and a half you'll see `✅ Ready:
 Booklet(12 pages)`. It works. But look at *what it took* to make three ordered
 steps work, and at every place this could have gone wrong instead.

 * callout(Try it): Delete the `DispatchQueue.main.async { ... }` wrapper and call
 `completion(.success(booklet))` directly. The output looks identical here, which
 is exactly why a forgotten main-thread hop ships so easily.

 [Previous](@previous)  |  page 3 of 6  |  [Next: Where Bugs Hide](@next)
 */
