package newagentshared

import (
	"context"
	"time"
)

// RetryOptions 描述公共重试策略。
//
// 职责边界：
// 1. 这里只定义“是否重试、最多几次、间隔多久”；
// 2. 不关心具体业务是工具调用失败、模型 JSON 失败还是 DB 暂时不可用；
// 3. 真正的业务兜底文案仍应由上层 node 决定。
type RetryOptions struct {
	MaxAttempts int
	Interval    time.Duration
	ShouldRetry func(err error) bool
	OnRetry     func(attempt int, err error)
}

// Do 执行一个只返回 error 的重试任务。
//
// 执行规则：
// 1. 第一次执行也算一次 attempt；
// 2. 任意一次成功即立即返回；
// 3. 上下文取消、达到最大次数、或 ShouldRetry=false 时立即停止。
func Do(ctx context.Context, options RetryOptions, fn func(attempt int) error) error {
	_, err := DoValue[struct{}](ctx, options, func(attempt int) (struct{}, error) {
		return struct{}{}, fn(attempt)
	})
	return err
}

// DoValue 执行一个带返回值的通用重试任务。
//
// 设计说明：
// 1. 旧 agent 里后续很多地方都会出现“失败重试 2~3 次”的模式；
// 2. 这里先把循环骨架统一，避免每个 skill 自己写 for + sleep + ctx.Done；
// 3. 上层只需关心“本轮失败要不要继续”，而不是重复造轮子。
func DoValue[T any](ctx context.Context, options RetryOptions, fn func(attempt int) (T, error)) (T, error) {
	var zero T

	maxAttempts := options.MaxAttempts
	if maxAttempts <= 0 {
		maxAttempts = 1
	}

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		if err := ctx.Err(); err != nil {
			return zero, err
		}

		value, err := fn(attempt)
		if err == nil {
			return value, nil
		}

		// 1. 到最后一次了，直接返回原错误，避免无意义等待。
		if attempt >= maxAttempts {
			return zero, err
		}
		// 2. 业务显式声明“不值得重试”时，立刻停止。
		if options.ShouldRetry != nil && !options.ShouldRetry(err) {
			return zero, err
		}
		// 3. 把重试钩子留给上层，用于打点或阶段提示。
		if options.OnRetry != nil {
			options.OnRetry(attempt, err)
		}
		// 4. 没有配置间隔则马上下一轮；配置了则等待，同时尊重 ctx 取消。
		if options.Interval <= 0 {
			continue
		}

		timer := time.NewTimer(options.Interval)
		select {
		case <-ctx.Done():
			timer.Stop()
			return zero, ctx.Err()
		case <-timer.C:
		}
	}

	return zero, nil
}
