// Package memory asistanın kullanıcı hakkında KALICI olarak hatırladıklarını yönetir (P10).
//
// PROJE.MD beş bellek türü sayar; üçü burada kalıcıdır, ikisi zaten başka yerde yaşar:
//
//	SHORT_TERM → konuşmanın mesaj geçmişi (internal/conversation + ctxmgr penceresi)
//	WORKING    → turun bağlamı (internal/agent turnPlan: skill/tool/alıntılar)
//	LONG_TERM  → kullanıcı hakkındaki kalıcı gerçekler ve tercihler        (burada)
//	SEMANTIC   → kullanıcının alanıyla ilgili bilgi/kural                   (burada)
//	EPISODIC   → belirli bir olay ("Geçen hafta X'i taşıdık")               (burada)
//
// "Her şeyi hafızaya kaydetme" kuralı: otomatik çıkarım (extract.go) yalnızca kalıcı olabilecek
// kişisel bilgileri alır; "bugün bunu 30 mm yap" gibi geçici istekler bellek DEĞİL, bağlamdır.
package memory

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"
	"time"
	"unicode/utf8"
)

// Kind bellek türüdür.
type Kind string

const (
	KindLongTerm Kind = "long_term"
	KindSemantic Kind = "semantic"
	KindEpisodic Kind = "episodic"
)

// Valid bilinen bir tür mü?
func (k Kind) Valid() bool {
	switch k {
	case KindLongTerm, KindSemantic, KindEpisodic:
		return true
	}
	return false
}

// Origin anının nereden geldiğidir. Kullanıcının elle yazdığı anı, otomatik çıkarımla EZİLMEZ.
type Origin string

const (
	OriginUser Origin = "user" // arayüzden elle eklendi/düzenlendi
	OriginAuto Origin = "auto" // sohbetten otomatik çıkarıldı
)

// Valid bilinen bir kaynak mı?
func (o Origin) Valid() bool { return o == OriginUser || o == OriginAuto }

// Sınırlar.
const (
	MinContentRunes   = 4
	MaxContentRunes   = 400
	MaxTags           = 8
	MaxTagRunes       = 32
	DefaultMaxPerUser = 500
)

// Memory tek bir anıdır. Embedding ve içerik özeti (Hash) istemciye gönderilmez.
type Memory struct {
	ID             string     `json:"id" bson:"_id"`
	UserID         string     `json:"userId" bson:"userId"`
	Kind           Kind       `json:"kind" bson:"kind"`
	Content        string     `json:"content" bson:"content"`
	Tags           []string   `json:"tags" bson:"tags"`
	Origin         Origin     `json:"origin" bson:"origin"`
	ConversationID string     `json:"conversationId,omitempty" bson:"conversationId,omitempty"`
	Importance     float64    `json:"importance" bson:"importance"` // 0..1
	UseCount       int        `json:"useCount" bson:"useCount"`     // kaç turda isteme girdi
	LastUsedAt     *time.Time `json:"lastUsedAt,omitempty" bson:"lastUsedAt,omitempty"`
	CreatedAt      time.Time  `json:"createdAt" bson:"createdAt"`
	UpdatedAt      time.Time  `json:"updatedAt" bson:"updatedAt"`

	Hash       string    `json:"-" bson:"hash"`
	Embedding  []float32 `json:"-" bson:"embedding,omitempty"`
	EmbedModel string    `json:"-" bson:"embedModel,omitempty"`
}

// HasEmbedding anı verilen modelin vektörünü taşıyor mu? (Model değişince eski vektör geçersizdir.)
func (m *Memory) HasEmbedding(model string) bool {
	return len(m.Embedding) > 0 && m.EmbedModel == model
}

// Clone derin kopya döndürür (depolar iç durumu sızdırmasın).
func (m Memory) Clone() Memory {
	c := m
	c.Tags = append([]string(nil), m.Tags...)
	c.Embedding = append([]float32(nil), m.Embedding...)
	if m.LastUsedAt != nil {
		t := *m.LastUsedAt
		c.LastUsedAt = &t
	}
	return c
}

// Hata değerleri.
var (
	ErrNotFound   = errors.New("memory: anı bulunamadı")
	ErrDuplicate  = errors.New("memory: aynı anı zaten var")
	ErrLimit      = errors.New("memory: kullanıcı başına anı sınırına ulaşıldı")
	ErrNoEmbedder = errors.New("memory: embedding sağlayıcısı yapılandırılmamış")
)

// FieldError tek bir alanın doğrulama hatasıdır.
type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

// ValidationError bir ya da daha çok alanın geçersiz olduğunu bildirir.
type ValidationError struct{ Errors []FieldError }

func (e *ValidationError) Error() string {
	parts := make([]string, 0, len(e.Errors))
	for _, f := range e.Errors {
		parts = append(parts, f.Field+": "+f.Message)
	}
	return "memory: geçersiz anı: " + strings.Join(parts, "; ")
}

// Normalize içeriği tek satıra indirir (art arda boşluklar, satır sonları) ve baştaki/sondaki boşlukları atar.
func Normalize(s string) string { return strings.Join(strings.Fields(s), " ") }

// HashOf içeriğin KATLANMIŞ özetidir: büyük/küçük harf, Türkçe aksan ve boşluk farkı aynı anıyı verir
// ("Angular kullanıyorum" == "angular KULLANIYORUM").
func HashOf(content string) string {
	sum := sha256.Sum256([]byte(fold(Normalize(content))))
	return hex.EncodeToString(sum[:16])
}

// validateContent içerik kuralını uygular ve temizlenmiş metni döndürür.
func validateContent(content string) (string, *FieldError) {
	c := Normalize(content)
	n := utf8.RuneCountInString(c)
	switch {
	case n < MinContentRunes:
		return c, &FieldError{Field: "content", Message: fmt.Sprintf("en az %d karakter olmalı", MinContentRunes)}
	case n > MaxContentRunes:
		return c, &FieldError{Field: "content", Message: fmt.Sprintf("en fazla %d karakter olabilir", MaxContentRunes)}
	}
	return c, nil
}

// cleanTags etiketleri küçük harfe çevirir, tekrarları ve boşları atar, sayıyı/uzunluğu sınırlar.
func cleanTags(in []string) ([]string, *FieldError) {
	out := make([]string, 0, len(in))
	seen := map[string]struct{}{}
	for _, t := range in {
		t = strings.ToLower(Normalize(t))
		if t == "" {
			continue
		}
		if utf8.RuneCountInString(t) > MaxTagRunes {
			return nil, &FieldError{Field: "tags", Message: fmt.Sprintf("etiket en fazla %d karakter olabilir", MaxTagRunes)}
		}
		if _, dup := seen[t]; dup {
			continue
		}
		seen[t] = struct{}{}
		out = append(out, t)
	}
	if len(out) > MaxTags {
		return nil, &FieldError{Field: "tags", Message: fmt.Sprintf("en fazla %d etiket olabilir", MaxTags)}
	}
	return out, nil
}

func clamp01(v float64) float64 {
	switch {
	case v < 0:
		return 0
	case v > 1:
		return 1
	}
	return v
}
