package backoff

import (
	
	
	
)

// DefaultMaxElapsedTime sets a default limit for the total retry duration.
const DefaultMaxElapsedTime = 15 * time.Minute

// Operation is a function that attempts an operation and may be retried.
type Operation[ any] func() (, error)

// Notify is a function called on operation error with the error and backoff duration.
type Notify func(error, time.Duration)

// retryOptions holds configuration settings for the retry mechanism.
type retryOptions struct {
	BackOff        BackOff       // Strategy for calculating backoff periods.
	Timer          timer         // Timer to manage retry delays.
	Notify         Notify        // Optional function to notify on each retry error.
	MaxTries       uint          // Maximum number of retry attempts.
	MaxElapsedTime time.Duration // Maximum total time for all retries.
}

type RetryOption func(*retryOptions)

// WithBackOff configures a custom backoff strategy.
func ( BackOff) RetryOption {
	return func( *retryOptions) {
		.BackOff = 
	}
}

// withTimer sets a custom timer for managing delays between retries.
func withTimer( timer) RetryOption {
	return func( *retryOptions) {
		.Timer = 
	}
}

// WithNotify sets a notification function to handle retry errors.
func ( Notify) RetryOption {
	return func( *retryOptions) {
		.Notify = 
	}
}

// WithMaxTries limits the number of all attempts.
func ( uint) RetryOption {
	return func( *retryOptions) {
		.MaxTries = 
	}
}

// WithMaxElapsedTime limits the total duration for retry attempts.
func ( time.Duration) RetryOption {
	return func( *retryOptions) {
		.MaxElapsedTime = 
	}
}

// Retry attempts the operation until success, a permanent error, or backoff completion.
// It ensures the operation is executed at least once.
//
// Returns the operation result or error if retries are exhausted or context is cancelled.
func [ any]( context.Context,  Operation[],  ...RetryOption) (, error) {
	// Initialize default retry options.
	 := &retryOptions{
		BackOff:        NewExponentialBackOff(),
		Timer:          &defaultTimer{},
		MaxElapsedTime: DefaultMaxElapsedTime,
	}

	// Apply user-provided options to the default settings.
	for ,  := range  {
		()
	}

	defer .Timer.Stop()

	 := time.Now()
	.BackOff.Reset()
	for  := uint(1); ; ++ {
		// Execute the operation.
		,  := ()
		if  == nil {
			return , nil
		}

		// Stop retrying if maximum tries exceeded.
		if .MaxTries > 0 &&  >= .MaxTries {
			return , 
		}

		// Handle permanent errors without retrying.
		var  *PermanentError
		if errors.As(, &) {
			return , .Unwrap()
		}

		// Stop retrying if context is cancelled.
		if  := context.Cause();  != nil {
			return , 
		}

		// Calculate next backoff duration.
		 := .BackOff.NextBackOff()
		if  == Stop {
			return , 
		}

		// Reset backoff if RetryAfterError is encountered.
		var  *RetryAfterError
		if errors.As(, &) {
			 = .Duration
			.BackOff.Reset()
		}

		// Stop retrying if maximum elapsed time exceeded.
		if .MaxElapsedTime > 0 && time.Since()+ > .MaxElapsedTime {
			return , 
		}

		// Notify on error if a notifier function is provided.
		if .Notify != nil {
			.Notify(, )
		}

		// Wait for the next backoff period or context cancellation.
		.Timer.Start()
		select {
		case <-.Timer.C():
		case <-.Done():
			return , context.Cause()
		}
	}
}