package memory

import (
	"math"
	"strings"
	"unicode"
	"unicode/utf8"
)

// Bu dosyadaki metin yardımcıları knowledge/search.go ile AYNI mantıktır (Türkçe katlama,
// stopword, kök eşleşmesi, kosinüs). Paketler birbirine bağımlı olmasın diye kopyalandı;
// ortak bir pakete taşımak ayrı, düşük riskli bir temizliktir (bkz. docs/ILERLEME.md).

// turkishFold Türkçe harfleri ASCII karşılıklarına indirger ("tezgahi" ↔ "tezgâhı").
var turkishFold = strings.NewReplacer(
	"ı", "i", "İ", "i", "ş", "s", "Ş", "s", "ğ", "g", "Ğ", "g",
	"ü", "u", "Ü", "u", "ö", "o", "Ö", "o", "ç", "c", "Ç", "c",
	"â", "a", "Â", "a", "î", "i", "Î", "i", "û", "u", "Û", "u",
)

// fold küçük harfe çevirir ve Türkçe harfleri katlar.
func fold(s string) string { return strings.ToLower(turkishFold.Replace(s)) }

var stopwords = func() map[string]struct{} {
	raw := []string{
		"ve", "bir", "bu", "su", "ne", "mi", "mu", "da", "de", "ile", "icin", "ama", "gibi",
		"cok", "daha", "en", "her", "ben", "sen", "biz", "siz", "ki", "ya", "veya", "olan",
		"nedir", "nasil", "hakkinda", "var", "yok", "the", "and", "for", "what", "how",
		"kullanici", "benim", "bana", "beni",
	}
	m := make(map[string]struct{}, len(raw))
	for _, w := range raw {
		m[fold(w)] = struct{}{}
	}
	return m
}()

// tokenize metni katlanmış, en az 2 karakterlik, tekrarsız ve anlamlı sözcüklere böler.
// Hepsi stopword ise ayıklama yapılmaz.
func tokenize(q string) []string {
	fields := strings.FieldsFunc(fold(q), func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsDigit(r)
	})
	all := make([]string, 0, len(fields))
	seen := make(map[string]struct{}, len(fields))
	for _, f := range fields {
		if utf8.RuneCountInString(f) < 2 {
			continue
		}
		if _, dup := seen[f]; dup {
			continue
		}
		seen[f] = struct{}{}
		all = append(all, f)
	}
	kept := make([]string, 0, len(all))
	for _, t := range all {
		if _, stop := stopwords[t]; !stop {
			kept = append(kept, t)
		}
	}
	if len(kept) == 0 {
		return all
	}
	return kept
}

// stemMin köklenmiş sözcüğün en kısa uzunluğudur; daha kısa kökler ilgisiz sözcüklere eşleşir.
const stemMin = 4

// matchToken sözcük metinde geçiyor mu? Türkçe sondan eklemeli olduğu için 1-4 karakter
// kesilen kökler (en az stemMin uzunlukta) de denenir.
func matchToken(foldedText, tok string) bool {
	if strings.Contains(foldedText, tok) {
		return true
	}
	rs := []rune(tok)
	for cut := 1; cut <= 4; cut++ {
		if len(rs)-cut < stemMin {
			break
		}
		if strings.Contains(foldedText, string(rs[:len(rs)-cut])) {
			return true
		}
	}
	return false
}

// keywordScore sorgu sözcüklerinin ne kadarının (katlanmış) metinde geçtiğidir (0..1).
func keywordScore(qTokens []string, foldedText string) float64 {
	if len(qTokens) == 0 {
		return 0
	}
	hit := 0
	for _, t := range qTokens {
		if matchToken(foldedText, t) {
			hit++
		}
	}
	return float64(hit) / float64(len(qTokens))
}

// cosine iki vektörün kosinüs benzerliğidir; negatif değerler 0'a çekilir.
// Boyutlar farklıysa ya da bir vektör sıfırsa 0 döner.
func cosine(a, b []float32) float64 {
	if len(a) == 0 || len(a) != len(b) {
		return 0
	}
	var dot, na, nb float64
	for i := range a {
		x, y := float64(a[i]), float64(b[i])
		dot += x * y
		na += x * x
		nb += y * y
	}
	if na == 0 || nb == 0 {
		return 0
	}
	c := dot / (math.Sqrt(na) * math.Sqrt(nb))
	switch {
	case c < 0:
		return 0
	case c > 1:
		return 1
	}
	return c
}
