package agent

// toolrank.go: modele sunulacak tool'ları İLGİLİLİĞE göre seçer.
//
// CANLI HATA (04.10.2026): "dolar kurunu bulamıyor, araçları bulamıyor". Sebep: katalog
// 28 tool dönüyordu, `toolSpecs` listeyi ada göre sıralı `[:20]` kesiyordu. Alfabetik
// sırada `web.*`, `unit.*`, `text.*` ve `memory.remember` en sona düşer → HİÇ sunulmuyordu.
// Yani araç kodda vardı, sağlıklı çalışıyordu, ama model onu hiç görmüyordu.
//
// Çözüm: tool'ları kullanıcının mesajıyla ilgisine göre sırala (Türkçe anahtar sözcük
// tablosu), ilgisiz durumda sabit bir ÖNCELİK listesine düş. Alfabetik kesme YOKTUR.
// Küçük modeller için kısa liste daha iyidir: 14 tool, ilgili olanlar başta.

import (
	"sort"
	"strings"
	"unicode"

	"fiko-ai/internal/llm"
	"fiko-ai/pkg/text"
)

// intentRule, bir tool'u çağıran Türkçe anahtar sözcüklerdir (aksan/büyük harf KATLANMIŞ).
// "=" ile başlayan kök TAM eşleşme ister (kısa sözcüklerde gürültüyü önler: "=kur"),
// diğerleri ÖN EK eşleşmesidir ("sil" → "silin", "silelim").
type intentRule struct {
	tool   string
	weight int
	stems  []string
}

var intentRules = []intentRule{
	// ---- internet ----
	{"web.exchange_rate", 4, []string{"dolar", "euro", "avro", "sterlin", "pound", "usd", "eur", "gbp", "chf", "jpy",
		"doviz", "parite", "=kur", "=kuru", "=kurlar", "=kurlari", "=kurunu", "=kurun", "altin", "tcmb", "merkez bank"}},
	{"web.search", 2, []string{"arastir", "=ara", "=arar", "internet", "google", "haber", "guncel", "son dakika", "=web", "nedir", "kimdir", "kac yasinda"}},
	{"web.fetch", 3, []string{"http", "www.", ".com", ".org", ".net", "sayfa", "=site", "=link", "baglanti", "=url"}},

	// ---- hesaplama / dönüşüm / tarih ----
	{"calc.eval", 3, []string{"hesap", "kac eder", "topla", "=carp", "carpi", "=bol", "bolu", "yuzde", "karekok", "=kare", "ortalama"}},
	{"biz.vat", 4, []string{"kdv"}},
	{"unit.convert", 3, []string{"=cevir", "birim", "=km", "=mil", "=kg", "=lb", "metre", "santim", "litre", "derece", "celsius", "fahrenheit", "=inc", "=galon", "ons"}},
	{"unit.list", 1, []string{"birimler"}},
	{"date.diff", 3, []string{"kac gun", "kac hafta", "kac ay", "=fark", "arasinda", "=kalan", "geri sayim"}},
	{"date.add", 3, []string{"gun sonra", "hafta sonra", "ay sonra", "gun once", "hafta once", "ay once", "gun ekle", "=vade"}},
	{"date.workdays", 3, []string{"is gunu", "calisma gunu", "tatil", "mesai"}},
	{"fiko.now", 2, []string{"saat kac", "=tarih", "bugun ne", "hangi gun", "=simdi"}},

	// ---- dosya ----
	{"fs.list", 3, []string{"dosyalar", "klasor", "dizin", "listele dosya", "=ls"}},
	{"fs.search", 3, []string{"dosya ara", "dosya bul", "dosyalarda", "notlarimi bul", "icinde ara"}},
	{"fs.stat", 2, []string{"dosya boyut", "dosya bilgi", "ne zaman olusturul"}},
	{"fs.read_text", 3, []string{"dosya", "=oku", "=okumak", "okuyabilir", "icerigi", ".txt", ".md", ".json", ".csv"}},
	// "=oku" eylem cümlesinin KÖKÜDÜR: "oku/okumanı/okumak" hepsi yakalanmalıdır.
	// Önceden yalnız "=oku" tam eşleşmesiydi; kullanıcı "plan.txt'i okumak istiyorum"
	// derse okuma aracı puan almıyordu.
	{"fs.read_text", 2, []string{"oku", "okumak", "okuman", "okudum", "acikla", "ac", "goster"}},
	{"fs.write_text", 3, []string{"dosyaya yaz", "dosya olustur", "yaz", "yazdir", "yazdik", "dosyaya kaydet", "not dosya", "kaydet"}},
	{"text.stats", 3, []string{"kelime say", "karakter say", "kac kelime", "kac karakter", "kac satir"}},

	// ---- kalıcı bellek ----
	{"memory.remember", 4, []string{"hatirla", "aklinda tut", "unutma", "hafizaya", "bunu kaydet", "benim adim", "ismim"}},
	{"memory.recall", 3, []string{"hatirliyor", "ne biliyorsun", "hakkimda", "beni taniyor", "hafizanda"}},
	{"memory.update", 2, []string{"yanlis hatirla", "duzelt hafiza", "artik degil"}},

	// ---- şirket verisi (biz.*) ----
	{"biz.delete", 5, []string{"sil", "kaldir", "yok et", "cope", "iptal et", "kaydini sil"}},
	{"biz.add", 3, []string{"ekle", "kaydet", "olustur", "yeni", "kurdum", "kurdu", "kurul", "actim", "acti", "girdim", "kayit ac"}},
	{"biz.update", 3, []string{"guncelle", "degistir", "duzenle", "tamamla", "bitir", "kapat", "odendi", "ode", "arttir", "azalt", "durumunu"}},
	{"biz.find", 3, []string{"listele", "goster", "=bul", "hangi", "=liste", "var mi", "kac tane", "kimler", "neler", "=getir"}},
	{"biz.summary", 3, []string{"ozet", "rapor", "durum ne", "ne var", "bugun", "genel durum", "nasil gidiyor"}},
	{"biz.project", 3, []string{"proje", "sprint", "risk", "kilometre", "milestone", "ilerleme", "ne kaldi"}},
	// "=plan" TAM SÖZCÜK eşleşmesidir. Önceden "=plan" da ön ek eşleşmesiydi ve
	// "notlar/plan.txt oku" gibi mesajlarda dosya adındaki "plan" kelimesi bu aracı
	// öne alıyordu (canlı hata 05.10.2026): kullanıcı dosya okumak istiyordu,
	// modele ise proje planlama aracı önce gidiyordu. Proje planı denince "plan"
	// zaten tek başına bir sözcüktür, ön ek gerekmez.
	{"biz.plan", 3, []string{"planla", "=plan", "=planla", "termin", "deadline", "yetisir", "yetisecek", "kapasite"}},

	// ---- kullanıcıya interaktif form (bilgi toplama) ----
	// "=form..." tam sözcük ister: "format", "formül" gibi sözcükler yanlış eşleşmesin.
	{"ui.form", 4, []string{"=form", "=formu", "=forma", "=formda", "=formla", "=formlar", "anket",
		"bana sor", "soru sor", "bilgileri gir", "doldur", "secenek sun"}},

	// ---- kullanıcılar arası bildirim (notify.*) ----
	// AYIRMA KURALI: "soru sor" hem notify.ask hem ui.form'a uyar. Gerçek kullanım
	// "X'e şunu sor" olduğunda notify.ask öne geçmeliydi ama "bana sor" de kendine
	// sorulur (form). Bu yüzden KENDİNE soran kalıplar ui.form'da bırakıldı,
	// KİME soran kalıplar notify.ask'e kondu. Karışırsa model "bana bildirim
	// gönderdim" deyip hiçbir şey yapmaz.
	{"notify.ask", 4, []string{"=kim", "kime sor", "kime bildir", "kime yolla", "arkadasima sor",
		"calisma arkadasina", "yoneticiye sor", "patrona sor"}},
	{"notify.send", 4, []string{"bildir", "bildirim gonder", "duyur", "haber ver", "mesaj at",
		"bilgi ver", "=toplanti saat", "=toplantiyi"}},
	{"notify.users", 5, []string{"kimler var", "kullanicilar", "kullanici ara", "kime yazabilirim"}},
	{"notify.answers", 5, []string{"yanit geldi", "cevap geldi", "cevabi ne", "yanitlari oku", "yanit var mi"}},
}

// bizNouns, şirket verisi sözcükleridir: geçtiğinde biz.find/add/update/summary öne çıkar.
var bizNouns = []string{"musteri", "fatura", "gider", "masraf", "gorev", "stok", "urun", "personel", "calisan",
	"izin", "toplanti", "proje", "sprint", "firma", "sirket", "tahsilat", "cari", "siparis", "teklif"}

// toolPriority, ilgili sözcük bulunmayan mesajlarda (selam, sohbet) hangi tool'ların
// önce gösterileceğini belirler. Küçük sayı = daha önemli.
var toolPriority = map[string]int{
	"biz.find": 1, "biz.add": 2, "biz.update": 3, "biz.delete": 4, "biz.summary": 5,
	"web.search": 6, "web.exchange_rate": 7, "calc.eval": 8, "memory.remember": 9, "memory.recall": 10,
	"fs.list": 11, "fs.read_text": 12, "date.diff": 13, "biz.project": 14, "web.fetch": 15,
	"unit.convert": 16, "fs.write_text": 17, "biz.plan": 18, "biz.vat": 19, "memory.update": 20,
	"text.stats": 21, "fs.search": 22, "fs.stat": 23, "date.add": 24, "date.workdays": 25,
	"unit.list": 26, "fiko.now": 27, "fiko.echo": 28,
	"ui.form": 15,
	// Bildirim tool'ları: KİŞİLER ARASI yazma yapan araçlar. Öncelik listesinde
	// SONDA dururlar — hiçbir mesajda "ilgisiz" sayılmaları doğru değil ama
	// sohbet sırasında gereksiz yere öne çıkmamaları da yanlış. Sıra, "selam"
	// gibi bir mesajda modelin önce en alakalı aracı görmesini sağlar.
	"notify.users": 29, "notify.answers": 30, "notify.ask": 31, "notify.send": 32,
}

const defaultToolPriority = 50

// eksikOncelik, öncelik tablosunda ADI GEÇMEYEN yerleşik araçları sıralar.
// Neden var? (Canlı hata 05.10.2026) Tablo elle tutuluyordu; yeni eklenen araç
// (ör. fiko.echo) buraya girmezse priorityOf ona defaultToolPriority (50) döner
// ve 34 araçlık katalogda 14'lük sınıra girip HİÇ sunulmaz. "Olmayan araç" ile
// "var ama unutulmuş araç" ayırt edilemiyordu. Aşağıdaki liste canlı katalogla
// eşit tutulur; toolrank_erisilebilirlik_test.go'daki TestPickTools_ButunAraclarErisilebilir
// her aracın erişilebilirliğini kilitler (yeni araç eklenince o test kırmızıya döner).
//
// Yeni bir builtin tool eklerken buraya da bir satır eklemek gerekir.
var eksikOncelik = map[string]int{
	"fiko.echo": 28, // hat/şema akışı sınaması; sohbet sırasında gerekmez
	// memory.ask kalıcı bilgi kararını kullanıcıya soran araçtır; hafızaya
	// yazan üç araçla birlikte dar bir kümede, bu yüzden erken sırada olmalı.
	"memory.ask": 21,
}

func init() {
	for name, p := range eksikOncelik {
		if _, ok := toolPriority[name]; !ok {
			toolPriority[name] = p
		}
	}
}

// foldTR Türkçe aksan ve büyük/küçük harf farkını yok sayar ("Dolar KURU" → "dolar kuru").
// Uygulama paylaşılan pakettedir (pkg/text): kullanıcı araması da aynı katlamayı
// kullanır, iki ayrı uygulama tutarsız oluyordu.
func foldTR(s string) string { return text.FoldTR(s) }

// wordsOf katlanmış metni sözcüklere böler (rakamlar sözcüğün parçasıdır).
func wordsOf(folded string) []string {
	return strings.FieldsFunc(folded, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) })
}

// stemMatches kökün mesajda geçip geçmediğini söyler.
//   - "=kur"   : tam sözcük
//   - "iki sozcuk" (boşluklu): ifade olarak ARANIR (katlanmış metinde alt dize)
//   - "http"/".com" gibi noktalı/URL kökleri alt dize olarak aranır
//   - diğerleri: bir sözcüğün ÖN EKİ
func stemMatches(stem, folded string, words []string) bool {
	if strings.HasPrefix(stem, "=") {
		exact := stem[1:]
		for _, w := range words {
			if w == exact {
				return true
			}
		}
		return false
	}
	if strings.ContainsAny(stem, " .") {
		return strings.Contains(folded, strings.TrimSpace(stem))
	}
	for _, w := range words {
		if strings.HasPrefix(w, stem) {
			return true
		}
	}
	return false
}

// intentScores her tool için mesaja göre bir ilgi puanı hesaplar (0 = ilgisiz).
func intentScores(message string) map[string]int {
	folded := foldTR(message)
	words := wordsOf(folded)
	scores := make(map[string]int)
	if len(words) == 0 {
		return scores
	}
	for _, r := range intentRules {
		for _, st := range r.stems {
			if stemMatches(st, folded, words) {
				scores[r.tool] += r.weight
				break // aynı kuraldan tek puan: uzun listeler avantaj sağlamasın
			}
		}
	}
	// Şirket verisi sözcüğü geçtiyse okuma/yazma tool'ları aday olur.
	for _, n := range bizNouns {
		if stemMatches(n, folded, words) {
			for _, t := range []string{"biz.find", "biz.add", "biz.update", "biz.summary"} {
				scores[t]++
			}
			break
		}
	}
	// "15 + 4", "2*3", "100/4" gibi ifadeler → hesap makinesi.
	if hasArithmetic(folded) {
		scores["calc.eval"] += 4
	}
	return scores
}

// hasArithmetic mesajda iki sayı arasında işlem işareti olup olmadığına bakar.
func hasArithmetic(s string) bool {
	r := []rune(s)
	for i := 1; i < len(r)-1; i++ {
		switch r[i] {
		case '+', '*', '/', 'x', '×', '÷', '^':
			if r[i] == 'x' && (i < 1 || !unicode.IsDigit(r[i-1])) {
				continue
			}
			j, k := i-1, i+1
			for j >= 0 && r[j] == ' ' {
				j--
			}
			for k < len(r) && r[k] == ' ' {
				k++
			}
			if j >= 0 && k < len(r) && unicode.IsDigit(r[j]) && unicode.IsDigit(r[k]) {
				return true
			}
		}
	}
	return false
}

// overlapScore, araç adı/açıklaması ile mesaj arasındaki sözcük örtüşmesidir.
//
// Neden var? Kullanıcı bazen aracı adıyla ya da kısaltmayla anar ("yz", "yaz",
// "exchange"). intentRules yalnızca TÜRKÇE anahtar sözcükleri bilir; kullanıcı
// o sözcükleri kullanmazsa puan hiç oluşmaz ve araç 14'lük sınıra giremez.
//
// Eşik 5 harften 3'e düşürüldü (canlı hata 05.10.2026): "yz" gibi kısa bir
// kısaltma ya da "plan"/"stok" gibi gövde sözcükleri 5 harfin altında kalıyor ve
// hiç eşleşmiyordu. 3 harflik eşleşme yanlış pozitif üretse de sonuç yalnızca
// SINIRLAMA SİRALAMASI etkiler: modele daha çok araç göstermek, doğru aracı
// gizlemekten yeğildir (araç yanlış sırada olsa bile sunulmuş olur).
func overlapScore(spec llm.ToolSpec, words []string) int {
	hay := foldTR(spec.Name + " " + spec.Description)
	n := 0
	for _, w := range words {
		if len([]rune(w)) >= minOverlapWord && strings.Contains(hay, w) {
			n++
			if n == 2 {
				break
			}
		}
	}
	return n
}

// minOverlapWord, ad/açıklama örtüşmesi için gereken en kısa sözcük uzunluğudur.
const minOverlapWord = 3

func priorityOf(name string) int {
	if p, ok := toolPriority[name]; ok {
		return p
	}
	return defaultToolPriority
}

// strongIntent, tool'un skill daraltmasında bile korunması gereken kadar güçlü eşleşme eşiğidir.
const strongIntent = 3

// pickTools modele sunulacak tool listesini seçer.
//
//  1. Skill'ler varsa (tool retrieval): o skill'lerin tool'ları + mesajla GÜÇLÜ eşleşen tool'lar.
//     (Yanlış skill eşleşmesi doğru tool'u düşüremez; "dolar" diyen kullanıcıdan
//     web.exchange_rate, skill başka bir tool gösterse bile esirgenmez.)
//  2. Sıralama: ilgi puanı → öncelik → ad. HER ZAMAN uygulanır (kısa devre yok).
//  3. Boyut sınırı: en sonda MaxToolSpecs kesmesi.
//
// Sıralama boyuttan BAĞIMSIZDIR: en ilgili araç daima başta olmalıdır. Sınır
// katalogdan büyükse (MaxToolSpecs=50) liste eksiksiz döner ama YİNE de sıralıdır.
//
// Sonuç hiçbir zaman, katalogda olup da ilgili olan bir tool'u alfabetik sıra yüzünden düşürmez.
func pickTools(all []llm.ToolSpec, userMessage string, skillTools []string) []llm.ToolSpec {
	if len(all) == 0 {
		return nil
	}
	scores := intentScores(userMessage)
	words := wordsOf(foldTR(userMessage))
	// overlapScore TÜM araçlara uygulanır.
	//
	// CANLI HATA (05.10.2026): "araçlar filtreleniyor, yz bulamıyorum".
	// Önceki hâli `if _, known := toolPriority[s.Name]; !known` diyerek puanı
	// yalnızca öncelik tablosunda OLMAYAN araçlara veriyordu. Yerleşik araçların
	// TAMAMINI tabloda olduğu için bu dal hiç çalışmıyordu; yani kullanıcı mesajda
	// aracın adını/açıklamasını yazsa ("yz", "notlar/plan.txt oku") o araç yine
	// puan almıyor ve 14'lük sınıra giremezdi. Sonuç: araç kodda var, çalışıyor,
	// izinli — ama model onu hiç göremiyordu.
	for _, s := range all {
		scores[s.Name] += overlapScore(s, words)
	}

	candidates := all
	if len(skillTools) > 0 {
		keep := make(map[string]bool, len(skillTools))
		for _, n := range skillTools {
			keep[n] = true
		}
		narrowed := make([]llm.ToolSpec, 0, len(all))
		for _, s := range all {
			if keep[s.Name] || scores[s.Name] >= strongIntent {
				narrowed = append(narrowed, s)
			}
		}
		// Skill'in işaret ettiği tool bu kullanıcıya açık değilse daraltma anlamsızdır:
		// boş liste yerine ilgi sıralamasına düş (tool'suz tura düşmek yanlış olurdu).
		if len(narrowed) > 0 {
			candidates = narrowed
		}
	}
	// KISA DEVRE YOK: liste katalog sırasıyla ASLA döndürülmez.
	//
	// CANLI HATA (05.10.2026): `if len(candidates) <= MaxToolSpecs && len(skillTools) == 0
	// { return candidates }` kısa devresi vardı. MaxToolSpecs 14 iken katalog (28-34 araç)
	// daima sınırı aştığı için bu dal hiç çalışmıyordu; sınır 50'ye çıkınca ÇALIŞMAYA
	// BAŞLADI ve sıralamayı tamamen atladı. Sonuç: "dolar kuru ne" sorusunda
	// web.exchange_rate 3. değil 11. sıraya düştü, baştaki yerine alfabetik ilk araç
	// (biz.add) geldi — yani modele doğru araç gönderilmiyordu.
	//
	// Sıralama ucuzdur ve HER ZAMAN anlamlıdır: en ilgili araç başta olmalıdır.
	// Boyut sınırı zaten en sonda uygulanır.
	out := append([]llm.ToolSpec(nil), candidates...)
	skillSet := make(map[string]bool, len(skillTools))
	for _, n := range skillTools {
		skillSet[n] = true
	}
	rank := func(s llm.ToolSpec) int {
		v := scores[s.Name]
		if skillSet[s.Name] {
			v += 5
		}
		return v
	}
	sort.SliceStable(out, func(i, j int) bool {
		ri, rj := rank(out[i]), rank(out[j])
		if ri != rj {
			return ri > rj
		}
		pi, pj := priorityOf(out[i].Name), priorityOf(out[j].Name)
		if pi != pj {
			return pi < pj
		}
		return out[i].Name < out[j].Name
	})
	if len(out) > MaxToolSpecs {
		out = out[:MaxToolSpecs]
	}
	return out
}

// toolNames günlük için ad listesi üretir.
func toolNames(specs []llm.ToolSpec) []string {
	out := make([]string, 0, len(specs))
	for _, s := range specs {
		out = append(out, s.Name)
	}
	return out
}
