package agent

import (
	"context"
	"strings"
	"time"
	"unicode/utf8"

	"fiko-ai/internal/prompt"
)

// Bilgi Tabanı (RAG) getirme sınırları. Ses gecikmesini ve bağlam penceresini korurlar.
const (
	// MaxPassagesPerTurn bir turda isteme girebilecek en çok alıntı.
	MaxPassagesPerTurn = 3
	// MaxPassageRunes tek alıntının üst sınırı (parçalar zaten ~800 karakter; bu bir emniyet).
	MaxPassageRunes = 900
	// MaxKnowledgeRunes tüm alıntıların toplam üst sınırı: küçük bağlam penceresini (4096) doldurmasın.
	MaxKnowledgeRunes = 2400
	// KnowledgeTimeout getirmenin üst süresi. Aşılırsa tur bilgi tabanı OLMADAN sürer.
	KnowledgeTimeout = 4 * time.Second
)

// Passage bilgi tabanından gelen ince alıntı görünümüdür (alan modelinin tamamı taşınmaz).
type Passage struct {
	Title  string
	Source string
	Text   string
	Score  float64
}

// KnowledgeRetriever kullanıcı mesajına en uygun belge parçalarını bulur (RAG).
//
// Sözleşme: HATA DÖNMEZ. Getirme bir yakınlık önerisidir; sağlayıcı çökse, bilgi tabanı boş olsa
// ya da süre dolsa boş dilim dönmelidir ve ajan normal cevap verir (SkillRetriever ile aynı ilke).
// Uygulama, ilgisiz parçaları kendi eşiğiyle elemelidir.
type KnowledgeRetriever interface {
	Passages(ctx context.Context, userID, query string, limit int) []Passage
}

// SetKnowledge bilgi tabanı kaynağını bağlar; nil vermek RAG'i kapatır.
// Kurulumda, sunucu istek almadan önce çağrılmalıdır.
func (a *Agent) SetKnowledge(k KnowledgeRetriever) { a.knowledge = k }

// retrieveKnowledge turun alıntılarını getirir. Araç (tool) durumundan BAĞIMSIZDIR:
// tool'suz bir kurulumda da belgelerden cevap verilebilir.
func (a *Agent) retrieveKnowledge(ctx context.Context, userID, userMessage string) []prompt.Passage {
	if a.knowledge == nil || strings.TrimSpace(userMessage) == "" {
		return nil
	}
	kctx, cancel := context.WithTimeout(ctx, KnowledgeTimeout)
	defer cancel()
	started := time.Now()
	got := a.knowledge.Passages(kctx, userID, userMessage, MaxPassagesPerTurn)
	if len(got) == 0 {
		return nil
	}

	out := make([]prompt.Passage, 0, len(got))
	titles := make([]string, 0, len(got))
	budget := MaxKnowledgeRunes
	for _, p := range got {
		if len(out) >= MaxPassagesPerTurn || budget <= 0 {
			break
		}
		text := clipRunes(strings.TrimSpace(p.Text), min(MaxPassageRunes, budget))
		if text == "" {
			continue
		}
		budget -= utf8.RuneCountInString(text)
		out = append(out, prompt.Passage{Title: p.Title, Source: p.Source, Text: text})
		titles = append(titles, p.Title)
	}
	if len(out) > 0 {
		a.log.Info("bilgi tabanından alıntı eklendi", "parca", len(out), "belgeler", titles,
			"sureMs", time.Since(started).Milliseconds())
	}
	return out
}

// clipRunes metni en çok n karaktere (bayta değil) keser; kesilirse sonuna "…" koyar.
func clipRunes(s string, n int) string {
	if n <= 0 {
		return ""
	}
	if utf8.RuneCountInString(s) <= n {
		return s
	}
	return string([]rune(s)[:n]) + "…"
}
