package goroutine

import (
	"context"
	"fmt"
	"runtime/debug"

	"github.com/tidbcloud/pkgv2/log"
	"golang.org/x/sync/errgroup"
)

const (
	MaxConcurrent = 100
)

type Handle func() error

func SafeGo(ctx context.Context, fn func()) {
	go func() {
		defer Recover(ctx)

		fn()
	}()
}

func Recover(ctx context.Context) {
	if err := recover(); err != nil {
		logger := log.WithContextL(ctx)
		logger.Sugar().Errorf("panic happen: %s, stack: %s", err, debug.Stack())
	}
}

func Concurrent(ctx context.Context, fns ...Handle) error {
	return ConcurrentWithLimit(ctx, MaxConcurrent, fns...)
}

func callFn(fn Handle) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("panic with: %s", e)
		}
	}()

	err = fn()
	return err
}

//nolint
func ConcurrentWithLimit(ctx context.Context, limit int, fns ...Handle) error {
	if limit <= 0 {
		panic(fmt.Sprintf("invalid limit: %d", limit))
	}

	var g *errgroup.Group
	g, ctx = errgroup.WithContext(ctx)
	l := make(chan bool, limit)

	for _, each := range fns {
		fn := each
		each = nil // avoid use the `each` variable result in closure problem
		l <- true

		g.Go(func() error {
			defer func() {
				<-l
			}()

			return callFn(fn)
		})
	}
	return g.Wait()
}
