// Package claim ekip genelinde paylaşılan "iddia" (claim) hafızasıdır: kullanıcıların sisteme
// girdiği bilgiler (firma, kişi, ürün, görev...) tek tek kayıt olarak saklanır, embedding ile
// mühürlenir ve herkesin sorularında kullanılır.
//
// `memory` paketinden FARKI: memory kullanıcıya ÖZELDİR (her sorgu userId ile sınırlı); claim
// EKİP ORTAKTIR. Bu yüzden depo sahiplik süzmez; kimin neyi görebileceği/silebileceği servis
// katmanında (Faz 5-8) uygulanır. Yazar (AuthorID/AuthorName) yalnızca izlenebilirlik içindir.
//
// Bu dosya veri modelidir; depo sözleşmesi repository.go, bellek içi uygulama memstore.go,
// ortak sözleşme testi contract.go içindedir.
package claim

import (
	"errors"
	"fmt"
	"regexp"
	"strings"
	"time"
	"unicode"
	"unicode/utf8"

	"fiko-ai/internal/claim/kinds"
)

// Status kaydın yaşam döngüsü durumudur.
type Status string

const (
	StatusActive   Status = "aktif"       // geçerli bilgi
	StatusUpdated  Status = "guncellendi" // zamanla değişti; yenisi onun yerini aldı (eski kayıt silinmez)
	StatusConflict Status = "celiskili"   // iki kayıt çelişiyor; hangisinin geçerli olduğunu patron belirler
	StatusInvalid  Status = "gecersiz"    // patron geçersiz saydı
)

// Valid bilinen bir durum mu?
func (s Status) Valid() bool {
	switch s {
	case StatusActive, StatusUpdated, StatusConflict, StatusInvalid:
		return true
	}
	return false
}

// Bilinen kind değerleri. Kind serbest bir etikettir (yeni ihtiyaç = yeni kind, kod değişmez);
// bunlar yalnızca Faz 3-4'ün bildiği çekirdek türlerdir.
const (
	KindFirma   = "firma"
	KindKisi    = "kisi"
	KindUrun    = "urun"
	KindGorev   = "gorev"
	KindGeneral = "genel"
)

// IsListKind listelik tür mü? Listelik türlerde birleştirme YALNIZCA `Key` birebir eşleşmesine
// göre yapılır; benzer ama farklı adlar ikisi de tutulur (spec: yazma akışı, madde 3).
func IsListKind(kind string) bool {
	switch kind {
	case KindFirma, KindKisi, KindUrun:
		return true
	}
	return kinds.Default().IsList(kind) // şemada listelik tanımlı türler (proje, personel...)
}

// Sınırlar.
const (
	MaxTextRunes = 2000
	MaxKeyRunes  = 200
	MaxNameRunes = 120
)

// kindPattern: küçük harf ASCII, rakam ve alt çizgi; harfle başlar ("firma", "pdks", "izin_talebi").
var kindPattern = regexp.MustCompile(`^[a-z][a-z0-9_]{0,31}$`)

// Claim tek bir iddia/kayıttır. Embedding ve vektör modeli istemciye gönderilmez.
type Claim struct {
	ID         string    `json:"id" bson:"_id"`
	Text       string    `json:"text" bson:"text"`
	Kind       string    `json:"kind" bson:"kind"`
	Key        string    `json:"key,omitempty" bson:"key,omitempty"` // NormalizeKey çıktısı; yalnız birleştirme/arama için
	AuthorID   string    `json:"authorId" bson:"authorId"`
	AuthorName string    `json:"authorName" bson:"authorName"`
	CreatedAt  time.Time `json:"createdAt" bson:"createdAt"`
	UpdatedAt  time.Time `json:"updatedAt" bson:"updatedAt"`
	Status     Status    `json:"status" bson:"status"`
	// Supersedes bu kaydın yerini aldığı eski kaydın kimliğidir (güncelleme zinciri).
	Supersedes string `json:"supersedes,omitempty" bson:"supersedes,omitempty"`
	// ConflictsWith bu kaydın çeliştiği (çelişki anında var olan) kaydın kimliğidir. Yalnızca YENİ
	// kayıtta dolu olur; eski kayıt buna işaret eden kayıtlarla bulunur. Patronun "hangisi geçerli?"
	// kararı bu bağdan çelişki kümesini çıkarır (bkz. conflicts.go). Çelişki çözülünce de silinmez:
	// "bu kayıt şunla çelişmişti" bilgisi tarihçedir.
	ConflictsWith string `json:"conflictsWith,omitempty" bson:"conflictsWith,omitempty"`

	// Fields tipli türlerin (proje, görev, personel, pdks...) yapılandırılmış alanlarıdır; şema
	// internal/claim/kinds altındadır. Değerler KANONİK biçimde saklanır (tarih YYYY-MM-DD, saat HH:MM,
	// enum küçük harf ASCII): sorgu ve hesaplama kodda yapılır, model yapmaz. Şema öncesi kayıtlarda boştur.
	Fields map[string]string `json:"fields,omitempty" bson:"fields,omitempty"`
	// SubjectID özel türlerde (personel, pdks) kaydın İLGİLİ KİŞİSİNİN hesap kimliğidir: o kişi kaydı
	// görebilir. Boşsa kaydı yalnızca patron görür. Yalnızca patron atar (bkz. Service.Edit).
	SubjectID string `json:"subjectId,omitempty" bson:"subjectId,omitempty"`

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

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

// Clone derin kopya döndürür (depolar iç durumu sızdırmasın).
func (c Claim) Clone() Claim {
	cp := c
	cp.Embedding = append([]float32(nil), c.Embedding...)
	cp.Fields = cloneFields(c.Fields)
	return cp
}

func cloneFields(m map[string]string) map[string]string {
	if len(m) == 0 {
		return nil
	}
	cp := make(map[string]string, len(m))
	for k, v := range m {
		cp[k] = v
	}
	return cp
}

// Snapshot denetim kaydı için vektörsüz kopyadır (4096 boyutlu vektör ~32 KB'dır; geçmişe taşınmaz).
func (c Claim) Snapshot() *Claim {
	cp := c
	cp.Embedding = nil
	cp.EmbedModel = ""
	cp.Fields = cloneFields(c.Fields)
	return &cp
}

// Hata değerleri.
var (
	ErrNotFound  = errors.New("claim: kayıt bulunamadı")
	ErrDuplicate = errors.New("claim: aynı kimlikli kayıt zaten var")
)

// 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 "claim: geçersiz kayıt: " + strings.Join(parts, "; ")
}

// turkishFold Türkçe harfleri ASCII karşılıklarına indirger ("Çelik Tezgâh" ↔ "celik tezgah").
// İ/I → i çevirisi ToLower'DAN ÖNCE yapılır: Go'da ToLower("İ") birleşik noktalı bir i üretir.
var turkishFold = strings.NewReplacer(
	"ı", "i", "İ", "i", "ş", "s", "Ş", "s", "ğ", "g", "Ğ", "g",
	"ü", "u", "Ü", "u", "ö", "o", "Ö", "o", "ç", "c", "Ç", "c",
	"â", "a", "Â", "a", "î", "i", "Î", "i", "û", "u", "Û", "u",
)

// NormalizeText boşlukları tek aralığa indirir, satır sonlarını ve baş/son boşluğu atar.
func NormalizeText(s string) string { return strings.Join(strings.Fields(s), " ") }

// NormalizeKey bir adı birleştirme anahtarına çevirir: Türkçe katlama, küçük harf, harf/rakam
// dışındaki her şey tek boşluk. "ACME  Kalıp A.Ş." → "acme kalip a s".
// Aynı varlığın yazım farkları (büyük/küçük harf, aksan, noktalama) aynı anahtarı verir.
func NormalizeKey(s string) string {
	folded := strings.ToLower(turkishFold.Replace(s))
	return strings.Join(strings.FieldsFunc(folded, func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsDigit(r)
	}), " ")
}

// Validate bir kaydın yazılabilir olup olmadığını denetler. Depolar yalnızca saklar; bu denetimi
// servis çağırır (memory paketiyle aynı iş bölümü). Kaydı DEĞİŞTİRMEZ: normalleştirme çağıranındır.
func (c *Claim) Validate() error {
	var errs []FieldError
	add := func(field, msg string) { errs = append(errs, FieldError{Field: field, Message: msg}) }

	if c.ID == "" {
		add("id", "boş olamaz")
	}
	if strings.TrimSpace(c.Text) == "" {
		add("text", "boş olamaz")
	} else if utf8.RuneCountInString(c.Text) > MaxTextRunes {
		add("text", fmt.Sprintf("en fazla %d karakter olabilir", MaxTextRunes))
	}
	if !kindPattern.MatchString(c.Kind) {
		add("kind", "küçük harf ASCII, rakam ve alt çizgiden oluşmalı, harfle başlamalı (en fazla 32)")
	}
	if utf8.RuneCountInString(c.Key) > MaxKeyRunes {
		add("key", fmt.Sprintf("en fazla %d karakter olabilir", MaxKeyRunes))
	}
	if IsListKind(c.Kind) && c.Key == "" {
		add("key", "listelik türde (firma, kisi, urun) zorunludur")
	}
	if c.Key != "" && c.Key != NormalizeKey(c.Key) {
		add("key", "NormalizeKey ile normalleştirilmiş olmalı")
	}
	if c.AuthorID == "" {
		add("authorId", "boş olamaz")
	}
	if utf8.RuneCountInString(c.AuthorName) > MaxNameRunes {
		add("authorName", fmt.Sprintf("en fazla %d karakter olabilir", MaxNameRunes))
	}
	if !c.Status.Valid() {
		add("status", "aktif, guncellendi, celiskili ya da gecersiz olmalı")
	}
	if c.Supersedes != "" && c.Supersedes == c.ID {
		add("supersedes", "kayıt kendisinin yerini alamaz")
	}
	if c.ConflictsWith != "" && c.ConflictsWith == c.ID {
		add("conflictsWith", "kayıt kendisiyle çelişemez")
	}
	if len(c.Fields) > 0 {
		if sc, ok := kinds.Default().Get(c.Kind); !ok {
			add("fields", "bu türün alan şeması yok")
		} else {
			for name, msg := range sc.Check(c.Fields) {
				add("fields."+name, msg)
			}
		}
	}
	if c.SubjectID != "" {
		if !kinds.Default().IsPrivate(c.Kind) {
			add("subjectId", "yalnızca özel türlerde (personel, pdks) kullanılır")
		}
		if utf8.RuneCountInString(c.SubjectID) > MaxKeyRunes {
			add("subjectId", fmt.Sprintf("en fazla %d karakter olabilir", MaxKeyRunes))
		}
	}
	if len(errs) > 0 {
		return &ValidationError{Errors: errs}
	}
	return nil
}
