package knowledge

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

// Parçalama varsayılanları (rune cinsinden). Embedding modeli num_ctx=512 ile çalışıyor
// (VRAM için bilinçli sınır); Türkçe ~1/2 token/rune varsayımıyla 700 rune ≈ 350 token
// + başlık, 512'nin altında KALIR. 800 sınıra dayanıyor ve parçanın kuyruğu embedding'e
// girmeden kesilebilirdi (sessiz kalite kaybı). Bir turda 3 parça ≈ 1000 token: 4096'lık
// pencerenin küçük bir kısmı.
const (
	DefaultChunkSize    = 700
	DefaultChunkOverlap = 100
	MinChunkSize        = 100
	MaxChunkSize        = 4000
)

// ChunkOptions parçalayıcı ayarlarıdır; ölçü RUNE'dur (bayt değil): Türkçe karakterler
// bir bayttan fazla yer tutsa da parça boyu insanın gördüğü karakter sayısıdır.
type ChunkOptions struct {
	Size    int // bir parçanın en çok rune sayısı
	Overlap int // ardışık parçalar arasındaki örtüşme (rune); bağlamın kopmaması için
}

func (o ChunkOptions) normalized() ChunkOptions {
	if o.Size <= 0 {
		o.Size = DefaultChunkSize
	}
	if o.Size < MinChunkSize {
		o.Size = MinChunkSize
	}
	if o.Size > MaxChunkSize {
		o.Size = MaxChunkSize
	}
	if o.Overlap < 0 {
		o.Overlap = 0
	}
	// Örtüşme parçanın yarısını aşarsa her parça öncekinin kopyası olur ve ilerleme yavaşlar.
	if o.Overlap > o.Size/2 {
		o.Overlap = o.Size / 2
	}
	return o
}

// Split metni anlam sınırlarına saygı göstererek parçalar:
//
//  1. Paragraflar (boş satır) bir arada tutulmaya çalışılır.
//  2. Paragraf sığmazsa cümlelere (ya da satırlara) bölünür.
//  3. Cümle bile sığmazsa kelime sınırında, o da olmazsa rune sınırında kesilir.
//
// Her parça en çok o.Size rune'dur. Parçalar arasında kelime sınırında başlayan o.Overlap
// rune'a kadar örtüşme vardır. Çıktı belirlidir (aynı girdi → aynı parçalar).
func Split(text string, o ChunkOptions) []string {
	o = o.normalized()
	text = NormalizeText(text)
	if text == "" {
		return nil
	}
	p := &packer{size: o.Size, overlap: o.Overlap}
	// Birim tavanı: örtüşme metni + ayraç (en çok 2 rune) + birim her zaman Size'a sığmalı.
	// Birimler Size'a yakın olsaydı örtüşme sığmaz ve SESSİZCE düşerdi (testle yakalandı).
	// Overlap ≤ Size/2 ve Size ≥ 100 olduğundan tavan her zaman ≥ 48'dir.
	for _, u := range splitUnits(text, o.Size-o.Overlap-2) {
		p.add(u)
	}
	p.finish()
	return p.out
}

// unit parçalanamayan en küçük birimdir (cümle/satır ya da zorla kesilmiş parça).
// sep, kendisinden ÖNCEKİ birimle birleştirilirken araya konan ayraçtır.
type unit struct {
	text string
	sep  string
}

func runeLen(s string) int { return utf8.RuneCountInString(s) }

// splitUnits metni birimlere böler; hiçbir birim size rune'u aşmaz.
func splitUnits(text string, size int) []unit {
	var out []unit
	for _, para := range strings.Split(text, "\n\n") {
		para = strings.TrimSpace(para)
		if para == "" {
			continue
		}
		for si, sent := range splitSentences(para) {
			for k, pc := range hardSplit(sent.text, size) {
				sep := pc.sep
				if k == 0 {
					sep = sent.sep
					if si == 0 {
						sep = "\n\n" // paragraf başı
					}
				}
				out = append(out, unit{text: pc.text, sep: sep})
			}
		}
	}
	if len(out) > 0 {
		out[0].sep = ""
	}
	return out
}

func isSentenceEnd(r rune) bool { return r == '.' || r == '!' || r == '?' || r == '…' }

// splitSentences bir paragrafı cümle/satır birimlerine böler.
// Sınır: yeni satır ya da (. ! ? …) + boşluk. "3.14" ve "a.b" bölünmez.
func splitSentences(p string) []unit {
	var out []unit
	start := 0
	nextSep := ""
	emit := func(end int, sepAfter string) {
		if s := strings.TrimSpace(p[start:end]); s != "" {
			out = append(out, unit{text: s, sep: nextSep})
		}
		nextSep = sepAfter
	}
	for i := 0; i < len(p); {
		r, w := utf8.DecodeRuneInString(p[i:])
		switch {
		case r == '\n':
			emit(i, "\n")
			i += w
			start = i
		case isSentenceEnd(r):
			j := i + w
			for j < len(p) { // "?!" ve "..." gibi ardışık noktalama tek sınırdır
				r2, w2 := utf8.DecodeRuneInString(p[j:])
				if !isSentenceEnd(r2) {
					break
				}
				j += w2
			}
			if j < len(p) {
				if r2, _ := utf8.DecodeRuneInString(p[j:]); unicode.IsSpace(r2) && r2 != '\n' {
					emit(j, " ")
					start = j
				}
			}
			i = j
		default:
			i += w
		}
	}
	emit(len(p), "")
	return out
}

// hardSplit size'ı aşan metni keser: önce son %25'lik dilimdeki boşlukta (kelime sınırı),
// yoksa tam size'da (rune sınırı). size'a sığan metin olduğu gibi döner.
func hardSplit(s string, size int) []unit {
	if runeLen(s) <= size {
		return []unit{{text: s}}
	}
	rs := []rune(s)
	var out []unit
	sep := ""
	for len(rs) > 0 {
		if len(rs) <= size {
			if t := strings.TrimSpace(string(rs)); t != "" {
				out = append(out, unit{text: t, sep: sep})
			}
			break
		}
		cut, atSpace := size, false
		// rs[size] pencerenin hemen ardındaki rune: boşluksa tam kelime sonunda kesilir.
		for k := size; k > size-size/4; k-- {
			if unicode.IsSpace(rs[k]) {
				cut, atSpace = k, true
				break
			}
		}
		if t := strings.TrimSpace(string(rs[:cut])); t != "" {
			out = append(out, unit{text: t, sep: sep})
		}
		rs = rs[cut:]
		for len(rs) > 0 && unicode.IsSpace(rs[0]) {
			rs = rs[1:]
		}
		sep = ""
		if atSpace {
			sep = " "
		}
	}
	return out
}

// packer birimleri size sınırına kadar parçalara doldurur.
type packer struct {
	size, overlap int
	out           []string
	buf           strings.Builder
	n             int // buf'taki rune sayısı
	fresh         int // buf'a örtüşme DIŞINDA eklenen birim sayısı
}

func (p *packer) add(u unit) {
	ul := runeLen(u.text)
	need := ul
	if p.n > 0 {
		need += runeLen(u.sep)
	}
	if p.n > 0 && p.n+need > p.size {
		p.flush()
		need = ul
		if p.n > 0 { // örtüşme tohumu var
			need += runeLen(u.sep)
			if p.n+need > p.size { // birimle birlikte sığmıyor: örtüşmeyi bırak
				p.reset()
				need = ul
			}
		}
	}
	if p.n > 0 {
		p.buf.WriteString(u.sep)
	}
	p.buf.WriteString(u.text)
	p.n += need
	p.fresh++
}

func (p *packer) reset() {
	p.buf.Reset()
	p.n, p.fresh = 0, 0
}

// flush dolu parçayı yayar ve bir sonrakini örtüşme metniyle başlatır.
func (p *packer) flush() {
	if p.fresh == 0 { // yalnızca örtüşme var: yeni içerik yok, kopya parça üretme
		p.reset()
		return
	}
	chunk := strings.TrimSpace(p.buf.String())
	if chunk != "" {
		p.out = append(p.out, chunk)
	}
	tail := overlapTail(chunk, p.overlap)
	p.reset()
	if tail != "" {
		p.buf.WriteString(tail)
		p.n = runeLen(tail)
	}
}

func (p *packer) finish() {
	if p.fresh > 0 {
		if chunk := strings.TrimSpace(p.buf.String()); chunk != "" {
			p.out = append(p.out, chunk)
		}
	}
	p.reset()
}

// overlapTail parçanın son ~overlap rune'unu, kelime ortasında başlamayacak biçimde döndürür.
// Parça zaten overlap'ten kısaysa ya da uygun kelime sınırı yoksa boş döner.
func overlapTail(chunk string, overlap int) string {
	if overlap <= 0 {
		return ""
	}
	rs := []rune(chunk)
	if len(rs) <= overlap {
		return ""
	}
	tail := rs[len(rs)-overlap:]
	if !unicode.IsSpace(rs[len(rs)-overlap-1]) { // kelimenin ortasından başlıyor
		idx := -1
		for i, r := range tail {
			if unicode.IsSpace(r) {
				idx = i
				break
			}
		}
		if idx < 0 {
			return ""
		}
		tail = tail[idx+1:]
	}
	return strings.TrimSpace(string(tail))
}
