package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"

	"golang.org/x/sync/errgroup"
)

func main() {
	// Basic usage - run multiple goroutines, wait for all, collect first error
	g := new(errgroup.Group)

	urls := []string{
		"https://golang.org",
		"https://pkg.go.dev",
		"https://github.com",
	}

	for _, url := range urls {
		url := url // capture loop variable
		g.Go(func() error {
			resp, err := http.Get(url)
			if err != nil {
				return err
			}
			defer resp.Body.Close()
			fmt.Printf("Fetched %s: %d\n", url, resp.StatusCode)
			return nil
		})
	}

	// Wait for all goroutines and get first error
	if err := g.Wait(); err != nil {
		fmt.Println("Error:", err)
	}

	// With context - cancels remaining on first error
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	g2, ctx2 := errgroup.WithContext(ctx)

	// Simulate tasks where one fails
	tasks := []string{"task1", "task2", "task3"}
	for _, task := range tasks {
		task := task
		g2.Go(func() error {
			select {
			case <-ctx2.Done():
				fmt.Printf("%s: cancelled\n", task)
				return ctx2.Err()
			case <-time.After(100 * time.Millisecond):
				if task == "task2" {
					return errors.New("task2 failed")
				}
				fmt.Printf("%s: completed\n", task)
				return nil
			}
		})
	}

	if err := g2.Wait(); err != nil {
		fmt.Println("Group error:", err)
	}

	// Limit concurrency with SetLimit
	g3 := new(errgroup.Group)
	g3.SetLimit(3) // Max 3 concurrent goroutines

	for i := 0; i < 10; i++ {
		i := i
		g3.Go(func() error {
			fmt.Printf("Worker %d starting\n", i)
			time.Sleep(50 * time.Millisecond)
			fmt.Printf("Worker %d done\n", i)
			return nil
		})
	}
	g3.Wait()

	// TryGo - non-blocking add (returns false if limit reached)
	g4 := new(errgroup.Group)
	g4.SetLimit(2)

	started := 0
	for i := 0; i < 5; i++ {
		if g4.TryGo(func() error {
			time.Sleep(100 * time.Millisecond)
			return nil
		}) {
			started++
		}
	}
	fmt.Printf("Started %d of 5 tasks immediately\n", started)
	g4.Wait()

	// Collecting results with channels
	results := make(chan string, len(urls))
	g5 := new(errgroup.Group)

	for _, url := range urls {
		url := url
		g5.Go(func() error {
			resp, err := http.Get(url)
			if err != nil {
				return err
			}
			defer resp.Body.Close()
			results <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
			return nil
		})
	}

	// Must close results after Wait
	go func() {
		g5.Wait()
		close(results)
	}()

	for result := range results {
		fmt.Println(result)
	}
}
