// Package prompt sistem istemini (system prompt) modüler parçalardan üretir.
// Her parça (identity, personality, rules, ...) ayrı ve bağımsız test edilebilir;
// ileride skill/tool/context parçaları aynı arayüzle eklenir.
package prompt

import (
	"strings"
	"time"
)

// Context bir istemin üretildiği andaki dinamik bilgileri taşır.
type Context struct {
	Now      time.Time
	Timezone string // IANA adı, ör. "Europe/Istanbul"; geçersizse UTC kullanılır
	UserName string // isteğe bağlı
}

// Part istemin adlandırılmış tek bir bölümüdür. Render boş dönerse bölüm atlanır.
type Part struct {
	Name   string
	Render func(Context) string
}

// Builder parçaları sırayla birleştirir.
type Builder struct {
	parts []Part
}

// New verilen parçalarla bir Builder üretir.
func New(parts ...Part) *Builder {
	return &Builder{parts: append([]Part(nil), parts...)}
}

// Default Phase 1 için varsayılan parça dizisini döndürür.
func Default() *Builder {
	return New(Identity(), Personality(), Rules(), DateTime())
}

// EditablePart, yönetici tarafından arayüzden düzenlenebilen bir istem parçasıdır.
//
// Neden? Sistem istemi kodda SABİT yazılıydı; ürün sahibi tonu/kuralları değiştirmek
// için kod derlemek gerekiyordu. Bu tip, parçayı düzenlenebilir metin + açık/kapalı
// anahtarına indirger: yönetici metni değiştirir, kapatır veya varsayılana döndürür.
//
// GÜVENLİK: "safe" işaretli parçalar prompt-injection savunmasıdır (Rules,
// NoFabrication, ToolUsage, MemoryCapability). Bunlar kapatılabilir ama bu bir
// bilinçli yönetici kararıdır; arayüz bu gerçeği açıkça uyarır. Varsayılan
// değerler KODDA yaşamaya devam eder: yönetici hiçbir şey değiştirmezse sistem
// eskisi gibi davranır.
type EditablePart struct {
	// Name parçanın teknik adıdır (API sözleşmesi; değiştirilemez).
	Name string
	// Title arayüzde gösterilen Türkçe başlıktır.
	Title string
	// Description parçanın ne yaptığını anlatır (arayüz ipucu).
	Description string
	// Safe true ise bu parça güvenlik/dürüstlük kuralıdır; kapatılırsa uyarılır.
	Safe bool
	// Default varsayılan metindir (koddan okunur, değiştirilemez).
	Default string
	// Varsayılanın AÇIK olup olmadığı; kapalı başlayan parça için false.
	DefaultEnabled bool
	// Render, Context alıp metin üreten varsayılan üreticidir (dinamik parçalar için).
	Render func(Context) string
	// Editable true ise yönetici metni değiştirebilir. Dinamik bölümler (tarih,
	// bellek, bilgi tabanı) false'tur: içerik her turda değişir.
	Editable bool
}

// EditableParts, arayüzde yönetilebilir istem parçalarının KATALOĞUDUR (sıra sabittir).
//
// Yeni bir parça eklemek için: önce buraya bir EditablePart ekle, sonra
// Overridable() içinde metni bu katalogdan üret. Böylece "varsayılan metin" tek
// yerde durur; kod ile arayüz aynı kaynağı görür.
var EditableParts = []EditablePart{
	{
		Name: "identity", Title: "Kimlik",
		Description: "Asistanın adı ve ne olduğu.",
		Editable:    true, DefaultEnabled: true,
	},
	{
		Name: "personality", Title: "Üslup",
		Description: "Nasıl konuştuğu (ton, dil, uzunluk).",
		Editable:    true, DefaultEnabled: true,
	},
	{
		Name: "rules", Title: "Temel kurallar",
		Description: "Dürüstlük ve davranış kuralları. Kapatılırsa model veri uydurabilir.",
		Safe:        true, Editable: true, DefaultEnabled: true,
	},
	{
		Name: "tool-usage", Title: "Araç (tool) kullanımı",
		Description: "Araç çağırma kuralları. Araç sunulduğunda eklenir.",
		Safe:        true, Editable: true, DefaultEnabled: true,
	},
	{
		Name: "no-fabrication", Title: "Veri uydurmama",
		Description: "Şirket verisinin yalnızca araç sonucundan gelmesi kuralı.",
		Safe:        true, Editable: true, DefaultEnabled: true,
	},
	{
		Name: "memory", Title: "Kalıcı bellek",
		Description: "Kalıcı belleğe kaydetme/hatırlama kuralları.",
		Safe:        true, Editable: true, DefaultEnabled: true,
	},
	{
		// Tarih/saat her turda değişir; yönetici metnini değiştiremez.
		Name: "datetime", Title: "Tarih ve saat",
		Description: "Güncel tarih bilgisi (otomatik).",
		Editable:    false, DefaultEnabled: true,
	},
}

// EditableByName, katalogda adı geçen parçayı döndürür (yoksa nil).
func EditableByName(name string) *EditablePart {
	for i := range EditableParts {
		if EditableParts[i].Name == name {
			return &EditableParts[i]
		}
	}
	return nil
}

// Part, katalog girdisinden bir Part üretir: yönetici metni varsa o kullanılır,
// kapalıysa boş döner (bölüm atlanır), yoksa kod varsayılanına düşülür.
func (e EditablePart) Part(override string, enabled bool) Part {
	render := e.Render
	return Part{Name: e.Name, Render: func(c Context) string {
		if !enabled {
			return ""
		}
		if t := strings.TrimSpace(override); t != "" {
			return t
		}
		if render == nil {
			return ""
		}
		return render(c)
	}}
}

// Overrides, parça adı → yönetici metni. Boş/eksik girdi kod varsayılanına düşer.
type Overrides map[string]string

// Disabled, parça adı → kapalı olsun mu. Yazılmamışsa parça AÇIK kalır.
type Disabled map[string]bool

// Identity asistanın kim olduğunu anlatır.
func Identity() Part {
	return Part{Name: "identity", Render: func(Context) string {
		return "Sen Fiko'sun: kullanıcının yerel bilgisayarında çalışan, sesli ve yazılı sohbet edebilen kişisel bir yapay zekâ asistanısın."
	}}
}

// With sona yeni parça ekleyen bir kopya döndürür (orijinal değişmez).
//
// Nil ALICI güvenlidir: `With` yeni bir Builder döndürdüğü için alıcıyı
// değiştiremez, ama yine de parçayı kaybetmemek için varsayılan Builder'dan
// başlar. (Bir ajanın prompt'u nil ise sohbet paniklemek yerine temel
// istemle devam eder; ajan.New her zaman bir Builder verir.)
func (b *Builder) With(p Part) *Builder {
	base := b
	if base == nil {
		base = Default()
	}
	return New(append(append([]Part(nil), base.parts...), p)...)
}

// Names parça adlarını sırayla döndürür (test ve hata ayıklama için).
func (b *Builder) Names() []string {
	out := make([]string, 0, len(b.parts))
	for _, p := range b.parts {
		out = append(out, p.Name)
	}
	return out
}

// Build tüm parçaları boş satırla ayırarak tek bir metne dönüştürür.
// Build, parçaları sırayla birleştirip sistem istemini döner.
// Nil ALICI ve parçasız Builder boş istem üretir (çökmmez).
func (b *Builder) Build(c Context) string {
	if b == nil {
		return ""
	}
	var sections []string
	for _, p := range b.parts {
		if p.Render == nil {
			continue
		}
		if s := strings.TrimSpace(p.Render(c)); s != "" {
			sections = append(sections, s)
		}
	}
	return strings.Join(sections, "\n\n")
}

// Personality üslubu belirler.
func Personality() Part {
	return Part{Name: "personality", Render: func(c Context) string {
		s := "Doğal, sıcak ve net konuş. Gereksiz süslemeden ve tekrar etmeden, doğrudan konuya gir. " +
			"Kullanıcı hangi dilde yazıyorsa o dilde yanıt ver; belirsizse Türkçe kullan."
		if c.UserName != "" {
			s += " Kullanıcının adı: " + c.UserName + "."
		}
		return s
	}}
}

// Rules dürüstlük ve güvenlik kurallarını içerir.
func Rules() Part {
	return Part{Name: "rules", Render: func(Context) string {
		return strings.Join([]string{
			"Kurallar:",
			"- Bilmediğin bir şeyi uydurma; emin değilsen bunu açıkça söyle.",
			"- Henüz erişimin olmayan araçları, dosyaları veya internet bağlantısını kullanabiliyormuş gibi davranma.",
			"- Yanıtlar kısa ve öz olsun; kullanıcı ayrıntı isterse genişlet.",
			"- Kod verirken çalışır ve eksiksiz ver; biçimlendirmede Markdown kullanabilirsin.",
		}, "\n")
	}}
}

var trWeekdays = [...]string{"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"}
var trMonths = [...]string{"", "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"}

// Skill, retrieval ile seçilen skill'leri sisteme anlatan bölümdür (Phase 5c).
//
// Neden ayrı bölüm? Kullanıcının mesajı hangi skill'e uyuyorsa YALNIZCA onlar
// anlatılır: (a) model yanlış konuda "yardımcı" olmaya çalışmaz,
// (b) sunulan tool listesi ilgili tool'lara iner (bkz. agent.filterTools).
type Skill struct {
	Name        string
	Description string
	Tool        string // varsa bu tool çağrılabilir
	Score       float64
}

func SkillSelector(skills []Skill) Part {
	return Part{Name: "skills", Render: func(Context) string {
		if len(skills) == 0 {
			return ""
		}
		lines := make([]string, 0, len(skills)+1)
		lines = append(lines, "Bu konuyla ilgili beceriler (skill):")
		for _, s := range skills {
			line := "- " + s.Name
			if s.Description != "" {
				line += ": " + s.Description
			}
			if s.Tool != "" {
				line += " — gerekirse `" + s.Tool + "` aracını çağırabilirsin"
			}
			lines = append(lines, line)
		}
		return strings.Join(lines, "\n")
	}}
}

// ToolUsage araç (tool) kullanım kurallarını anlatır (Phase 5).
//
// Bu parça YALNIZCA modele tool sunulduğunda eklenir (bkz. prompt.Builder.With):
// tool'suz oturumlarda modele var olmayan bir yetenek anlatmak yanıltıcı olurdu.
//
// Neden gerekli: tool geçmişi düz metin olarak geri beslenir (native "tool" rolü yok).
// Bunun bilinen sonucu, modelin tool sonucunu gördükten sonra AYNI çağrıyı tekrarlamasıdır.
// (Canlı testte `muse-glimmer:30b` bunu yaptı; düzeltme döngüde + istemde birlikte yapıldı.)
func ToolUsage() Part {
	return Part{Name: "tool-usage", Render: func(Context) string {
		return strings.Join([]string{
			"Araç (tool) kullanımı:",
			"- Yalnızca sana sunulan araçları çağır; listede olmayan bir aracı çağırma.",
			"- Araç çağrısı gönderdiğinde o araç GERÇEKTEN çalıştırılır ve sonucu sana `[araç sonucu]` ile döner.",
			"- Sonucu aldıktan sonra KULLANICIYA CEVAP VER. Aynı aracı aynı girdiyle tekrar çağırma.",
			"- Araç hata döndürürse aynı şekilde ısrar etme; hatayı kullanıcıya açıkla veya başka yol dene.",
			"- Kullanıcının sorusu araç gerektirmiyorsa doğrudan cevap ver.",
			"",
			"Araç seçimi (hangi soruda hangi araç):",
			"- Güncel DÖVİZ KURU sorusu (dolar/euro/sterlin/kur/altın) → `web.exchange_rate`.",
			"- Web'de güncel bilgi/haber arama → `web.search`; belirli bir adresin içeriği → `web.fetch`.",
			"- Şirket verisi (müşteri, fatura, görev, proje, stok, personel) → `biz.*`.",
			"- Kayıt YAZMA/SİLME/DÜZELTME isteyen cümle → `biz.add` / `biz.update` / `biz.delete`.",
			"- Silmeden önce kaydı bulmak için önce `biz.find`, sonra `biz.delete`.",
			"- Aritmetik/hesap (topla, çarp, yüzde, ortalama) → `calc.eval`; birim dönüşümü → `unit.convert`.",
			"- Tarih farkı/iş günü/vade → `date.*`; dosya okuma/yazma/listeleme → `fs.*`.",
			"- Kullanıcı bir şeyi hatırlamanı söylüyorsa → `memory.remember`; hatırladıklarını soruyorsa → `memory.recall`.",
			"",
			"Araç girdisi:",
			"- Şemada `number` yazan alana sayı, `boolean` yazan alana true/false, nesne istenen alana JSON nesnesi gönder.",
			"- `biz.*` araçlarında `confirmed` alanı YOKTUR ve gönderilmez; onayı sistem yönetir.",
			"- Yazma/silme araçlarında bir kez çağır: `[ONAY BEKLENİYOR]` mesajı gelirse aynı aracı TEKRAR ÇAĞIRMA.",
		}, "\n")
	}}
}

// NoFabrication, modelin VERİ UYDURMAMASINI engelleyen kurallardır (en sık canlı hata).
//
// Neden ayrı bir bölüm? "Dürüst ol" demek yetmiyor: model hangi verinin GERÇEK olduğunu
// ayırt edemiyor. Gözlenen canlı hata: kullanıcı "hangi projeler var, sınıf çıkarır mısın?"
// dediğinde model, HİÇ KAYDI OLMAYAN koleksiyonlardan (tasks, invoices, expenses, products,
// notes) varmış gibi liste uydurdu. Sebep: boş koleksiyonların sayısı 0 dönüyordu ve
// model "0 kayıt var" ile "o koleksiyon hiç yok" arasındaki farkı göremiyordu.
//
// Kural, tool çıktısındaki somut işaretlere bağlanır: `collections`/`nonEmptyCollections`
// (biz.summary), `empty: true` + `collectionTotal` (biz.find). Böylece model kendi
// verisine dayanarak karar verir; kısıt "sana öyle geliyor" düzeyinde kalmaz.
func NoFabrication() Part {
	return Part{Name: "no-fabrication", Render: func(Context) string {
		return strings.Join([]string{
			"Veri doğruluğu:",
			"- Şirket verisi SADECE araç sonuçlarındaki kayıtlardan gelir. Sana olmayan bir müşteri, proje, görev, tutar veya tarih YAZMA.",
			"- Bir araç `empty: true` veya `count: 0` döndürdüyse o kayıt YOKTUR. `collectionTotal: 0` ise o koleksiyonun tamamı boştur.",
			"- Şirket özetinde `nonEmptyCollections` yalnızca gerçekten kayıt olan koleksiyonları listeler; yalnızca oradakilerden bahset.",
			"- Bir şeyi bilmiyorsan veya kayıt yoksa bunu açıkça söyle: \"şu an kayıtlı veri yok\".",
			"- Kullanıcı sana yeni bilgi verdiyse (örn. \"WEB-2026 diye proje kurdum\") önce `biz.add` ile KAYDET, sonra özetle. Kaydetmeden var diye yazma.",
			"- Var olmayan bir koleksiyon/alan adı uydurma; geçerli adlar araç açıklamasındaki `collection` listesindedir.",
		}, "\n")
	}}
}

// DateTime güncel tarih/saat bilgisini ekler; Now sıfırsa bölüm atlanır.
func DateTime() Part {
	return Part{Name: "datetime", Render: func(c Context) string {
		if c.Now.IsZero() {
			return ""
		}
		loc, err := time.LoadLocation(c.Timezone)
		if err != nil || c.Timezone == "" {
			loc = time.UTC
		}
		t := c.Now.In(loc)
		return "Şu anki tarih ve saat: " +
			t.Format("2") + " " + trMonths[t.Month()] + " " + t.Format("2006") + " " + trWeekdays[t.Weekday()] +
			", " + t.Format("15:04") + " (" + loc.String() + ")."
	}}
}
