// Package engine provides a mode-agnostic scan engine.
// This file implements global rate limit state for coordinated backoff.
package engine

import (
	"context"
	"sync"
	"time"
)

// RateLimitState tracks global rate limiting status.
// When any worker receives a 429, all workers slow down.
// Thread-safe: all methods can be called concurrently.
type RateLimitState struct {
	mu         sync.RWMutex
	limited    bool      // Currently rate limited?
	retryAfter time.Time // When to resume normal speed
}

// NewRateLimitState creates a new rate limit state tracker.
func NewRateLimitState() *RateLimitState {
	return &RateLimitState{}
}

// IsLimited returns true if currently in rate-limited state.
func (r *RateLimitState) IsLimited() bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.limited && time.Now().Before(r.retryAfter)
}

// SetLimited activates rate limiting for the given duration.
// If already limited with a longer duration, keeps the longer one.
func (r *RateLimitState) SetLimited(duration time.Duration) {
	r.mu.Lock()
	defer r.mu.Unlock()
	newRetryAfter := time.Now().Add(duration)
	// Only extend if new deadline is later than current
	if !r.limited || newRetryAfter.After(r.retryAfter) {
		r.limited = true
		r.retryAfter = newRetryAfter
	}
}

// WaitIfLimited blocks until rate limit expires or context is cancelled.
// Returns nil if wait completed, ctx.Err() if cancelled.
// CRITICAL: Always use select with ctx.Done() for clean cancellation.
func (r *RateLimitState) WaitIfLimited(ctx context.Context) error {
	if !r.IsLimited() {
		return nil
	}

	r.mu.RLock()
	waitUntil := r.retryAfter
	r.mu.RUnlock()

	waitDuration := time.Until(waitUntil)
	if waitDuration <= 0 {
		return nil
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-time.After(waitDuration):
		return nil
	}
}

// Clear resets the rate limit state (for testing or manual reset).
func (r *RateLimitState) Clear() {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.limited = false
}
