package prompt

import (
	"strings"
	"time"
)

// bindDefaults, EditableParts kataloğunun Default/Render alanlarını kod içindeki
// gerçek parça üreticilerine bağlar.
//
// Neden ayrı dosya? Catalog (EditableParts) bir "veri tablosu"dür ve API'ye
// dönüştürülürken Default metni gerekir. Bu metinleri üreten gerçek kod
// (Identity(), Rules()...) zaten var; burada tek yerden eşleştiririz. Böylece
// varsayılan metni iki kez yazmamış oluruz (ikinci kez yazmak = iki yerde
// yapılan değişikliklerin birbirinden kopması).
func bindDefaults() {
	set := func(name string, render func(Context) string) {
		if p := EditableByName(name); p != nil {
			p.Render = render
			p.Default = render(Context{}) // bağlama sırasında sabitlenir
		}
	}
	set("identity", Identity().Render)
	set("personality", Personality().Render)
	set("rules", Rules().Render)
	set("tool-usage", ToolUsage().Render)
	set("no-fabrication", NoFabrication().Render)
	set("memory", MemoryCapability().Render)
	// datetime DİNAMİK bir parçadır: gerçek metni her turda Now'a göre üretilir.
	// Bu yüzden bağlama sırasında Context{} ile üretmek boş döner; arayüzün
	// "varsayılan metni göster" işlevi için ÖRNEK bir metin veriyoruz (ör. sabit bir
	// tarihte üretilir). Bu metin isteme hiç girmez — yalnızca karşılaştırma/önizlemedir.
	if p := EditableByName("datetime"); p != nil {
		p.Render = DateTime().Render
		p.Default = DateTime().Render(Context{Now: time.Date(2026, 1, 2, 15, 4, 0, 0, time.UTC), Timezone: "Europe/Istanbul"})
	}
}

func init() { bindDefaults() }

// enabled, parça yönetici tarafından kapatılmadıysa AÇIKTIR.
//
// Kural: yalnızca Disabled sözlüğünde AÇIKÇA true yazan adlar kapanır. Böylece
// yönetici henüz bir şey değiştirmediyse ya da eski bir kayıt yüklendiyse sistem
// güvenli varsayılanlarla (hepsi açık) çalışır — kapatma bir opt-in'dir.
func enabled(name string, d Disabled) bool {
	if d == nil {
		return true
	}
	return !d[name]
}

// Base, yönetici değişikliklerini uygulanmış TEMEL istem parçalarını üretir
// (identity, personality, rules, datetime).
//
// Varsayılan (değişiklik yok) halinde bu, prompt.Default() ile BİREBİR aynı
// metni verir — mevcut davranış bozulmaz.
func Base(over Overrides, dis Disabled) *Builder {
	pick := func(name string) string { return over[name] }
	mk := func(name string) Part {
		e := EditableByName(name)
		if e == nil {
			return Part{}
		}
		return e.Part(pick(name), enabled(name, dis))
	}
	return New(mk("identity"), mk("personality"), mk("rules"), mk("datetime"))
}

// OptPart, koşullu (tool/memory gerektiren) bir parçayı yönetici değerleriyle üretir.
//
// Bu parçalar her turda değil, o yetenek sunulduğunda eklenir; bu yüzden
// varsayılan Builder'a değil, sistemPrompt içinde With(...) ile eklenirler.
// isEnabled false ise parça hiç eklenmez (yerine boş bölüm de konmaz).
func OptPart(name string, over Overrides, dis Disabled, isEnabled bool) (Part, bool) {
	if !isEnabled || !enabled(name, dis) {
		return Part{}, false
	}
	e := EditableByName(name)
	if e == nil {
		return Part{}, false
	}
	return e.Part(over[name], true), true
}

// CleanOverrides, yöneticinin gönderdiği metinleri temizler ve kataloğa SIKIŞTIRIR.
//
// Neden? Gelen sözlük istemci doğrudan koda girer. (a) Katalogda olmayan bir ad
// sessizce yutulur — aksi halde saldırgan modele istediği metni sokabilirdi.
// (b) Metinler kırpılır ve gereksiz boşluklar toplanır. (c) Düzenlenemez
// parçalar (datetime) yazılamaz: onların metni her turda üretilir.
func CleanOverrides(in Overrides) (Overrides, error) {
	out := Overrides{}
	if in == nil {
		return out, nil
	}
	for name, text := range in {
		e := EditableByName(name)
		if e == nil {
			continue // bilinmeyen ad: yut, hata verme (geriye uyumluluk)
		}
		if !e.Editable {
			continue // düzenlenemez parça: yazma isteği yok sayılır
		}
		t := strings.TrimSpace(text)
		if t == "" {
			continue // boş = varsayılana dön
		}
		out[name] = t
	}
	return out, nil
}

// CleanDisabled, kapalı parça adlarını katalogla sınırlar.
func CleanDisabled(in Disabled) Disabled {
	out := Disabled{}
	if in == nil {
		return out
	}
	for name, off := range in {
		if !off {
			continue
		}
		e := EditableByName(name)
		if e == nil || !e.Editable {
			continue
		}
		out[name] = true
	}
	return out
}
