package main

import (
	"fmt"
	"sync"
)

// section: wrapper
type WaitGroup struct {
	wg sync.WaitGroup
}

func (w *WaitGroup) Go(fn func()) {
	w.wg.Add(1)
	go func() {
		defer w.wg.Done()
		fn()
	}()
}

func (w *WaitGroup) Wait() {
	w.wg.Wait()
}

// section: wrapper

func processItem(item int) {
	fmt.Printf("Processing item %d\n", item)
}

// section: standard
// Standard WaitGroup usage - more verbose
func standardWaitGroup() {
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		i := i // capture range variable
		wg.Add(1)
		go func() {
			defer wg.Done()
			processItem(i)
		}()
	}

	wg.Wait()
}

// section: standard

// section: ergonomic
// WaitGroup with .Go() method - more concise and harder to misuse
func ergonomicWaitGroup() {
	var wg WaitGroup

	for i := 0; i < 5; i++ {
		i := i // capture range variable
		wg.Go(func() {
			processItem(i)
		})
	}

	wg.Wait()
}

// section: ergonomic

// section: main
func main() {
	fmt.Println("=== Standard WaitGroup ===")
	standardWaitGroup()

	fmt.Println("\n=== Ergonomic WaitGroup ===")
	ergonomicWaitGroup()
}

// section: main

/*
Benefits of the .Go() pattern:
1. Impossible to forget wg.Add(1) - it's automatic
2. Impossible to forget wg.Done() - defer is built-in
3. Less boilerplate - more readable code
4. Harder to misuse - fewer ways to make mistakes
5. Familiar API if you've used errgroup.Group
*/
