package backoff

import (
	
	
)

// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
// The operation will be retried using a backoff policy if it returns an error.
type OperationWithData[ any] func() (, error)

// An Operation is executing by Retry() or RetryNotify().
// The operation will be retried using a backoff policy if it returns an error.
type Operation func() error

func ( Operation) () OperationWithData[struct{}] {
	return func() (struct{}, error) {
		return struct{}{}, ()
	}
}

// Notify is a notify-on-error function. It receives an operation error and
// backoff delay if the operation failed (with an error).
//
// NOTE that if the backoff policy stated to stop retrying,
// the notify function isn't called.
type Notify func(error, time.Duration)

// Retry the operation o until it does not return error or BackOff stops.
// o is guaranteed to be run at least once.
//
// If o returns a *PermanentError, the operation is not retried, and the
// wrapped error is returned.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns.
func ( Operation,  BackOff) error {
	return RetryNotify(, , nil)
}

// RetryWithData is like Retry but returns data in the response too.
func [ any]( OperationWithData[],  BackOff) (, error) {
	return RetryNotifyWithData(, , nil)
}

// RetryNotify calls notify function with the error and wait duration
// for each failed attempt before sleep.
func ( Operation,  BackOff,  Notify) error {
	return RetryNotifyWithTimer(, , , nil)
}

// RetryNotifyWithData is like RetryNotify but returns data in the response too.
func [ any]( OperationWithData[],  BackOff,  Notify) (, error) {
	return doRetryNotify(, , , nil)
}

// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
// for each failed attempt before sleep.
// A default timer that uses system timer is used when nil is passed.
func ( Operation,  BackOff,  Notify,  Timer) error {
	,  := doRetryNotify(.withEmptyData(), , , )
	return 
}

// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
func [ any]( OperationWithData[],  BackOff,  Notify,  Timer) (, error) {
	return doRetryNotify(, , , )
}

func [ any]( OperationWithData[],  BackOff,  Notify,  Timer) (, error) {
	var (
		  error
		 time.Duration
		  
	)
	if  == nil {
		 = &defaultTimer{}
	}

	defer func() {
		.Stop()
	}()

	 := getContext()

	.Reset()
	for {
		,  = ()
		if  == nil {
			return , nil
		}

		var  *PermanentError
		if errors.As(, &) {
			return , .Err
		}

		if  = .NextBackOff();  == Stop {
			if  := .Err();  != nil {
				return , 
			}

			return , 
		}

		if  != nil {
			(, )
		}

		.Start()

		select {
		case <-.Done():
			return , .Err()
		case <-.C():
		}
	}
}

// PermanentError signals that the operation should not be retried.
type PermanentError struct {
	Err error
}

func ( *PermanentError) () string {
	return .Err.Error()
}

func ( *PermanentError) () error {
	return .Err
}

func ( *PermanentError) ( error) bool {
	,  := .(*PermanentError)
	return 
}

// Permanent wraps the given err in a *PermanentError.
func ( error) error {
	if  == nil {
		return nil
	}
	return &PermanentError{
		Err: ,
	}
}