package failsafe

import (
	
	
	
	

	
)

// ExecutionInfo contains execution info.
type ExecutionInfo interface {
	// Context returns the context configured for the execution, else context.Background if none was configured. For
	// executions involving a timeout or hedge, each attempt will get a separate child context.
	Context() context.Context

	// Attempts returns the number of execution attempts so far, including attempts that are currently in progress and
	// attempts that were blocked before being executed, such as by a CircuitBreaker or RateLimiter. These can include an initial
	// execution along with retries and hedges.
	Attempts() int

	// Executions returns the number of completed executions. Executions that are blocked, such as when a CircuitBreaker is
	// open, are not counted.
	Executions() int

	// Retries returns the number of retries so far, including retries that are currently in progress.
	Retries() int

	// Hedges returns the number of hedges that have been executed so far, including hedges that are currently in progress.
	Hedges() int

	// StartTime returns the time that the initial execution attempt started at.
	StartTime() time.Time

	// ElapsedTime returns the elapsed time since initial execution attempt began.
	ElapsedTime() time.Duration
}

// ExecutionAttempt contains information for an execution attempt.
type ExecutionAttempt[ any] interface {
	ExecutionInfo

	// LastResult returns the result, if any, from the last execution attempt.
	LastResult() 

	// LastError returns the error, if any, from the last execution attempt.
	LastError() error

	// IsFirstAttempt returns true when Attempts is 1, meaning this is the first execution attempt.
	IsFirstAttempt() bool

	// IsRetry returns true when Attempts is > 1, meaning the execution is being retried.
	IsRetry() bool

	// IsHedge returns true when the execution is part of a hedged attempt.
	IsHedge() bool

	// AttemptStartTime returns the time that the most recent execution attempt started at.
	AttemptStartTime() time.Time

	// ElapsedAttemptTime returns the elapsed time since the last execution attempt began.
	ElapsedAttemptTime() time.Duration
}

// Execution contains information about an execution.
type Execution[ any] interface {
	ExecutionAttempt[]

	// IsCanceled returns whether the execution has been canceled by an external Context or a timeout.Timeout.
	IsCanceled() bool

	// Canceled returns a channel that is closed when the execution is canceled, either by an external Context or a
	// timeout.Timeout.
	Canceled() <-chan struct{}
}

// A closed channel that can be used as a canceled channel where the canceled channel would have been closed before it
// was accessed.
var closedChan chan any

func init() {
	closedChan = make(chan any, 1)
	close(closedChan)
}

type execution[ any] struct {
	// Shared state across instances
	mtx        *sync.Mutex
	startTime  time.Time
	attempts   *atomic.Uint32
	retries    *atomic.Uint32
	hedges     *atomic.Uint32
	executions *atomic.Uint32

	// Partly shared cancellation state
	ctx            context.Context
	cancelFunc     context.CancelFunc
	canceledResult **common.PolicyResult[]

	// Per execution state
	attemptStartTime time.Time
	isHedge          bool
	lastResult            // The last error that occurred, else the zero value for R.
	lastError        error // The last error that occurred, else nil.
}

var _ Execution[any] = &execution[any]{}
var _ ExecutionInfo = &execution[any]{}

func ( *execution[]) () int {
	return int(.attempts.Load())
}

func ( *execution[]) () int {
	return int(.executions.Load())
}

func ( *execution[]) () int {
	return int(.retries.Load())
}

func ( *execution[]) () int {
	return int(.hedges.Load())
}

func ( *execution[]) () time.Time {
	return .startTime
}

func ( *execution[]) () bool {
	return .attempts.Load() == 1
}

func ( *execution[]) () bool {
	return .attempts.Load() > 1
}

func ( *execution[]) () bool {
	return .isHedge
}

func ( *execution[]) () time.Duration {
	return time.Since(.startTime)
}

func ( *execution[]) ()  {
	return .lastResult
}

func ( *execution[]) () error {
	if .lastError == nil && .ctx.Err() != nil {
		return .ctx.Err()
	}
	return .lastError
}

func ( *execution[]) () context.Context {
	return .ctx
}

func ( *execution[]) () time.Time {
	return .attemptStartTime
}

func ( *execution[]) () time.Duration {
	return time.Since(.attemptStartTime)
}

func ( *execution[]) () bool {
	return .ctx.Err() != nil
}

func ( *execution[]) () <-chan struct{} {
	return .ctx.Done()
}

func ( *execution[]) ( *common.PolicyResult[]) *common.PolicyResult[] {
	// Lock to guard against a race with a Timeout canceling the execution
	.mtx.Lock()
	defer .mtx.Unlock()
	if ,  := .isCanceledWithResult();  {
		return 
	}
	if  != nil {
		.lastResult = .Result
		.lastError = .Error
	}
	return nil
}

func ( *execution[]) () *common.PolicyResult[] {
	// Lock to guard against a race with a Timeout canceling the execution
	.mtx.Lock()
	defer .mtx.Unlock()
	if ,  := .isCanceledWithResult();  {
		return 
	}

	if .attempts.Add(1) > 1 {
		.retries.Add(1)
	}
	.attemptStartTime = time.Now()
	*.canceledResult = nil
	return nil
}

func ( *execution[]) ( *common.PolicyResult[]) {
	.mtx.Lock()
	defer .mtx.Unlock()
	if ,  := .isCanceledWithResult();  {
		return
	}

	*.canceledResult = 
	if  != nil {
		.lastResult = .Result
		.lastError = .Error
	}
	if .cancelFunc != nil {
		.cancelFunc()
	}
}

func ( *execution[]) () (bool, *common.PolicyResult[]) {
	.mtx.Lock()
	defer .mtx.Unlock()
	return .isCanceledWithResult()
}

// isCanceledWithResult must be locked externally
func ( *execution[]) () (bool, *common.PolicyResult[]) {
	if .ctx.Err() != nil {
		if *.canceledResult == nil {
			return true, &common.PolicyResult[]{
				Error: .ctx.Err(),
				Done:  true,
			}
		}
		return true, *.canceledResult
	}
	return false, nil
}

func ( *execution[]) ( *common.PolicyResult[]) Execution[] {
	 := .copy()
	if  != nil {
		.lastResult = .Result
		.lastError = .Error
	}
	return 
}

func ( *execution[]) () Execution[] {
	 := .copy()
	.ctx, .cancelFunc = context.WithCancel(.ctx)
	return 
}

func ( *execution[]) () Execution[] {
	 := .copy()
	.isHedge = true
	.attempts.Add(1)
	.hedges.Add(1)
	.ctx, .cancelFunc = context.WithCancel(.ctx)
	return 
}

func ( *execution[]) () *execution[] {
	.mtx.Lock()
	 := *
	.mtx.Unlock()
	return &
}

func ( *execution[]) () {
	.executions.Add(1)
}

func newExecution[ any]( context.Context) *execution[] {
	 := atomic.Uint32{}
	 := atomic.Uint32{}
	 := atomic.Uint32{}
	 := atomic.Uint32{}
	.Add(1)
	var  *common.PolicyResult[]
	 := time.Now()
	return &execution[]{
		ctx:              ,
		mtx:              &sync.Mutex{},
		attempts:         &,
		retries:          &,
		hedges:           &,
		executions:       &,
		canceledResult:   &,
		attemptStartTime: ,
		startTime:        ,
	}
}