package harness

import (
	"context"
	"sync"
	"unicode"
)

// EstimateTokens 粗估一段文本的 token 数（无需依赖模型/分词器，作预算护栏足够）：
// CJK 字符约 1 token/字；其余（拉丁/数字/空白/标点）约 1 token/4 字符。偏保守（宁高勿低）。
func EstimateTokens(s string) int {
	cjk, other := 0, 0
	for _, r := range s {
		if unicode.Is(unicode.Han, r) || unicode.Is(unicode.Hiragana, r) ||
			unicode.Is(unicode.Katakana, r) || unicode.Is(unicode.Hangul, r) {
			cjk++
		} else {
			other++
		}
	}
	return cjk + (other+3)/4 // 向上取整
}

// Budget 是单任务的 token 预算计量与封顶（并发安全：流式回调与节点循环可能并发计量）。
// max ≤ 0 表示不限额（仅计量、不中止）。
type Budget struct {
	mu     sync.Mutex
	max    int
	prompt int
	comp   int
}

// NewBudget 建一个上限为 max 的任务预算（max≤0 → 不限额，纯计量）。
func NewBudget(max int) *Budget { return &Budget{max: max} }

// AddPrompt / AddComplete 累计输入/输出 token（按文本估算）。
func (b *Budget) AddPrompt(text string)   { b.add(EstimateTokens(text), 0) }
func (b *Budget) AddComplete(text string) { b.add(0, EstimateTokens(text)) }

func (b *Budget) add(p, c int) {
	if b == nil {
		return
	}
	b.mu.Lock()
	b.prompt += p
	b.comp += c
	b.mu.Unlock()
}

// Exceeded 报告是否已触顶（max≤0 恒为 false）。
func (b *Budget) Exceeded() bool {
	if b == nil {
		return false
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.max > 0 && b.prompt+b.comp > b.max
}

// Snapshot 返回当前用量快照（prompt / completion / total）。
func (b *Budget) Snapshot() (prompt, comp, total int) {
	if b == nil {
		return 0, 0, 0
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.prompt, b.comp, b.prompt + b.comp
}

// budgetKey 是 context 里承载任务预算的私有键（避免跨包碰撞）。
type budgetKey struct{}

// WithBudget 把任务预算挂到 context，沿图执行透传（免改各节点函数签名）。
func WithBudget(ctx context.Context, b *Budget) context.Context {
	return context.WithValue(ctx, budgetKey{}, b)
}

// BudgetFrom 取出 context 里的任务预算（无则返回 nil，调用方按"不限额"处理）。
func BudgetFrom(ctx context.Context) *Budget {
	b, _ := ctx.Value(budgetKey{}).(*Budget)
	return b
}
