// Package kinds claim türlerinin (kind) alan şemalarını ve görünürlük kurallarını tanımlar (Faz 8).
//
// Yeni bir ihtiyaç (izin, stok, sevkiyat, bakım...) = bu dosyaya yeni bir Schema; claim, depo, API ve
// okuma kodu DEĞİŞMEZ. Paket claim'i içe aktarmaz (claim bu paketi kullanır), bu yüzden metin
// normalleştirme burada küçük ve bağımsızdır.
//
// GÖRÜNÜRLÜK:
//
//	Team    ekipteki herkes görür (firma, proje, görev...)
//	Private yalnızca PATRON ve kaydın İLGİLİ KİŞİSİ görür (personel, pdks: kişisel veri, KVKK).
//	        İlgili kişi, kayıttaki `subjectId` (hesap kimliği) ile belirlenir; subjectId boşsa
//	        kaydı yalnızca patron görür (belirsizlikte güvenli varsayılan: görme).
//
// Şemada tanımlı OLMAYAN türler (serbest etiket) ekip görünürlüğündedir; kişisel veri taşıyacak her
// yeni tür burada Private olarak tanımlanmalıdır.
package kinds

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
	"time"
	"unicode/utf8"
)

// Visibility bir türün kayıtlarını kimin göreceğidir.
type Visibility string

const (
	Team    Visibility = "ekip"
	Private Visibility = "ozel"
)

// FieldType alan değerinin türüdür. Tüm değerler string saklanır; tür yalnızca doğrulama/normalleştirme içindir.
type FieldType string

const (
	TypeText FieldType = "text" // serbest metin
	TypeDate FieldType = "date" // YYYY-MM-DD
	TypeTime FieldType = "time" // HH:MM
	TypeEnum FieldType = "enum" // Values'tan biri
)

// MaxFieldRunes tek alan değerinin üst sınırıdır.
const MaxFieldRunes = 200

// Field bir yapılandırılmış alandır. Name küçük harf ASCII + alt çizgidir.
type Field struct {
	Name     string
	Label    string // kullanıcıya gösterilen ad
	Type     FieldType
	Required bool     // yazılırken zorunlu: yoksa kayıt "eksik bilgi" sayılır
	Values   []string // TypeEnum için izinli değerler (küçük harf ASCII)
	Default  string   // yoksa konacak değer (isteğe bağlı)
}

// Schema bir türün şemasıdır.
type Schema struct {
	Kind       string
	Label      string
	Visibility Visibility
	// ListKind true ise kayıtlar adla (KeyField) tanınır: birleştirme yalnızca anahtarın birebir
	// eşleşmesine göre yapılır ve ad zorunludur.
	ListKind bool
	// KeyField birleştirme anahtarının türetildiği alandır ("ad"). Boş olabilir.
	KeyField string
	Fields   []Field
}

// Field adıyla alanı döner.
func (s Schema) Field(name string) (Field, bool) {
	for _, f := range s.Fields {
		if f.Name == name {
			return f, true
		}
	}
	return Field{}, false
}

// Registry şema kümesidir. Değiştirilemez; eşzamanlı okumaya güvenlidir.
type Registry struct {
	byKind map[string]Schema
	order  []string
}

var fieldName = regexp.MustCompile(`^[a-z][a-z0-9_]{0,31}$`)

// NewRegistry şemaları doğrulayıp kayıt defteri kurar (yinelenen tür/alan ya da geçersiz ad hata verir).
func NewRegistry(schemas ...Schema) (*Registry, error) {
	r := &Registry{byKind: make(map[string]Schema, len(schemas))}
	for _, s := range schemas {
		if !fieldName.MatchString(s.Kind) {
			return nil, fmt.Errorf("kinds: geçersiz tür adı %q", s.Kind)
		}
		if _, dup := r.byKind[s.Kind]; dup {
			return nil, fmt.Errorf("kinds: %q iki kez tanımlı", s.Kind)
		}
		if s.Visibility != Team && s.Visibility != Private {
			return nil, fmt.Errorf("kinds: %q için görünürlük geçersiz", s.Kind)
		}
		seen := map[string]bool{}
		for _, f := range s.Fields {
			if !fieldName.MatchString(f.Name) || seen[f.Name] {
				return nil, fmt.Errorf("kinds: %q alanı geçersiz ya da yinelenmiş: %q", s.Kind, f.Name)
			}
			seen[f.Name] = true
			if f.Type == TypeEnum && len(f.Values) == 0 {
				return nil, fmt.Errorf("kinds: %s.%s enum değer listesi boş", s.Kind, f.Name)
			}
		}
		if s.KeyField != "" && !seen[s.KeyField] {
			return nil, fmt.Errorf("kinds: %q KeyField %q alan değil", s.Kind, s.KeyField)
		}
		r.byKind[s.Kind] = s
		r.order = append(r.order, s.Kind)
	}
	return r, nil
}

// Get türün şemasını döner; tanımsız tür serbest etikettir (ok=false).
func (r *Registry) Get(kind string) (Schema, bool) {
	s, ok := r.byKind[kind]
	return s, ok
}

// Kinds tanımlı türleri tanım sırasıyla döner.
func (r *Registry) Kinds() []string { return append([]string(nil), r.order...) }

// IsPrivate tür kişisel veri görünürlüğünde mi? Tanımsız tür ekip görünürlüğündedir.
func (r *Registry) IsPrivate(kind string) bool {
	s, ok := r.byKind[kind]
	return ok && s.Visibility == Private
}

// IsList tür adıyla tanınan (listelik) bir tür mü?
func (r *Registry) IsList(kind string) bool {
	s, ok := r.byKind[kind]
	return ok && s.ListKind
}

// PrivateKinds Private türlerin sıralı listesidir (yetki süzgecinin kaynağı).
func (r *Registry) PrivateKinds() []string {
	var out []string
	for _, k := range r.order {
		if r.byKind[k].Visibility == Private {
			out = append(out, k)
		}
	}
	sort.Strings(out)
	return out
}

// Normalize ham alanları şemaya göre temizler: bilinmeyen alanlar atılır, tarih/saat/enum kanonik
// biçime çevrilir (tarihler KODDA çözülür: "cuma" → gerçek tarih), geçersiz değer yok sayılır, boş
// alanlara Default verilir. missing, Required olup yok/geçersiz olan alanların adlarıdır.
func (s Schema) Normalize(raw map[string]string, now time.Time) (clean map[string]string, missing []string) {
	return s.NormalizeKeyed(raw, "", now)
}

// NormalizeKeyed Normalize gibidir; KeyField (ör. "ad") boşsa keyHint (modelin yazdığı ad) o alana konur.
func (s Schema) NormalizeKeyed(raw map[string]string, keyHint string, now time.Time) (clean map[string]string, missing []string) {
	byName := make(map[string]string, len(raw))
	for k, v := range raw {
		byName[fieldKey(k)] = v
	}
	clean = make(map[string]string, len(s.Fields))
	for _, f := range s.Fields {
		in := byName[f.Name]
		if strings.TrimSpace(in) == "" && f.Name == s.KeyField {
			in = keyHint
		}
		v, _ := f.normalize(in, now)
		if v == "" {
			v = f.Default
		}
		if v == "" {
			if f.Required {
				missing = append(missing, f.Name)
			}
			continue
		}
		clean[f.Name] = v
	}
	return clean, missing
}

// Check saklı alanları doğrular (kanonik biçimde mi, alan tanımlı mı). Zorunluluğa BAKMAZ: şema
// öncesi kayıtlar alansız olabilir; zorunluluk yazma sırasında Normalize ile uygulanır.
// Dönen anahtar alan adı, değer sorun metnidir.
func (s Schema) Check(fields map[string]string) map[string]string {
	var bad map[string]string
	add := func(k, msg string) {
		if bad == nil {
			bad = map[string]string{}
		}
		bad[k] = msg
	}
	for k, v := range fields {
		f, ok := s.Field(k)
		if !ok {
			add(k, "bu türde böyle bir alan yok")
			continue
		}
		if utf8.RuneCountInString(v) > MaxFieldRunes {
			add(k, fmt.Sprintf("en fazla %d karakter olabilir", MaxFieldRunes))
			continue
		}
		if got, ok := f.normalize(v, time.Time{}); !ok || got != v {
			add(k, "geçersiz değer")
		}
	}
	return bad
}

// normalize tek değeri kanonik biçime çevirir; ok=false geçersiz demektir (boş değer geçerlidir).
func (f Field) normalize(v string, now time.Time) (string, bool) {
	v = strings.Join(strings.Fields(v), " ")
	if v == "" {
		return "", true
	}
	switch f.Type {
	case TypeDate:
		if d, ok := ParseDate(v, now); ok {
			return d, true
		}
		return "", false
	case TypeTime:
		if t, ok := ParseClock(v); ok {
			return t, true
		}
		return "", false
	case TypeEnum:
		fv := Fold(v)
		for _, opt := range f.Values {
			if fv == opt {
				return opt, true
			}
		}
		if len([]rune(fv)) >= 3 { // "musteriler" → "musteri", "tamam" → "tamamlandi"
			for _, opt := range f.Values {
				if strings.HasPrefix(opt, fv) || strings.HasPrefix(fv, opt) {
					return opt, true
				}
			}
		}
		return "", false
	}
	if utf8.RuneCountInString(v) > MaxFieldRunes {
		v = string([]rune(v)[:MaxFieldRunes])
	}
	return v, true
}

// fieldKey modelin yazdığı alan adını kanonik adla eşler: "Son Tarih" → "son_tarih".
func fieldKey(k string) string {
	return strings.Join(strings.Fields(Fold(strings.ReplaceAll(k, "_", " "))), "_")
}

// Prompt çıkarıcı istemine eklenecek tür/alan açıklamasıdır.
func (r *Registry) Prompt() string {
	var b strings.Builder
	for _, k := range r.order {
		s := r.byKind[k]
		if len(s.Fields) == 0 {
			continue
		}
		fmt.Fprintf(&b, "- %s:", k)
		for i, f := range s.Fields {
			if i > 0 {
				b.WriteByte(',')
			}
			b.WriteByte(' ')
			b.WriteString(f.Name)
			switch f.Type {
			case TypeEnum:
				b.WriteString(" (" + strings.Join(f.Values, "|") + ")")
			case TypeDate:
				b.WriteString(" (tarih)")
			case TypeTime:
				b.WriteString(" (saat)")
			}
			if f.Required {
				b.WriteByte('*')
			}
		}
		b.WriteByte('\n')
	}
	return b.String()
}

var turkishFold = strings.NewReplacer(
	"ı", "i", "İ", "i", "ş", "s", "Ş", "s", "ğ", "g", "Ğ", "g",
	"ü", "u", "Ü", "u", "ö", "o", "Ö", "o", "ç", "c", "Ç", "c",
	"â", "a", "Â", "a", "î", "i", "Î", "i", "û", "u", "Û", "u",
)

// Fold Türkçe harfleri ASCII'ye indirip küçültür ("Çarşamba" → "carsamba").
func Fold(s string) string { return strings.ToLower(turkishFold.Replace(strings.TrimSpace(s))) }

var defaultRegistry *Registry

func init() {
	r, err := NewRegistry(defaultSchemas()...)
	if err != nil {
		panic(err) // şema tanımı programcı hatasıdır; açılışta yakalanır ve testte denetlenir
	}
	defaultRegistry = r
}

// Default uygulamanın şema kayıt defteridir.
func Default() *Registry { return defaultRegistry }

func defaultSchemas() []Schema {
	durumProje := []string{"planlandi", "devam", "beklemede", "tamamlandi", "iptal"}
	durumGorev := []string{"acik", "devam", "beklemede", "tamamlandi", "iptal"}
	return []Schema{
		{Kind: "firma", Label: "Firma", Visibility: Team, ListKind: true, KeyField: "ad", Fields: []Field{
			{Name: "ad", Label: "Ad", Type: TypeText, Required: true},
			{Name: "tur", Label: "Tür", Type: TypeEnum, Values: []string{"musteri", "tedarikci"}},
			{Name: "iletisim", Label: "İletişim", Type: TypeText},
		}},
		{Kind: "kisi", Label: "Kişi", Visibility: Team, ListKind: true},
		{Kind: "urun", Label: "Ürün", Visibility: Team, ListKind: true},
		{Kind: "genel", Label: "Genel", Visibility: Team},
		{Kind: "proje", Label: "Proje", Visibility: Team, ListKind: true, KeyField: "ad", Fields: []Field{
			{Name: "ad", Label: "Ad", Type: TypeText, Required: true},
			{Name: "sorumlu", Label: "Sorumlu", Type: TypeText},
			{Name: "durum", Label: "Durum", Type: TypeEnum, Values: durumProje},
			{Name: "baslangic", Label: "Başlangıç", Type: TypeDate},
			{Name: "bitis", Label: "Bitiş", Type: TypeDate},
			{Name: "oncelik", Label: "Öncelik", Type: TypeEnum, Values: []string{"dusuk", "normal", "yuksek"}},
		}},
		{Kind: "gorev", Label: "Görev", Visibility: Team, Fields: []Field{
			{Name: "proje", Label: "Proje", Type: TypeText},
			{Name: "atanan", Label: "Atanan", Type: TypeText},
			{Name: "son_tarih", Label: "Son tarih", Type: TypeDate},
			{Name: "durum", Label: "Durum", Type: TypeEnum, Values: durumGorev, Default: "acik"},
		}},
		{Kind: "personel", Label: "Personel", Visibility: Private, ListKind: true, KeyField: "ad", Fields: []Field{
			{Name: "ad", Label: "Ad", Type: TypeText, Required: true},
			{Name: "bolum", Label: "Bölüm", Type: TypeText},
			{Name: "unvan", Label: "Unvan", Type: TypeText},
			{Name: "vardiya", Label: "Vardiya", Type: TypeText},
		}},
		{Kind: "pdks", Label: "PDKS", Visibility: Private, Fields: []Field{
			{Name: "personel", Label: "Personel", Type: TypeText, Required: true},
			{Name: "tarih", Label: "Tarih", Type: TypeDate, Required: true},
			{Name: "giris", Label: "Giriş", Type: TypeTime},
			{Name: "cikis", Label: "Çıkış", Type: TypeTime},
			{Name: "kaynak", Label: "Kaynak", Type: TypeEnum, Values: []string{"elle", "cihaz", "csv"}, Default: "elle"},
		}},
	}
}
