// service.go oturum (WS bağlantısı) başına ses biriktirir ve "stop" anında metne çevirir.
//
// Neden servis? Ses parçaları KARE KARE gelir; tanıma ise kayıt BİTİNCE bir kez yapılır.
// Arada durum tutmak gerekir: hangi oturum kayıt yapıyor, ne kadar veri toplandı, süre doldu mu.
package stt

import (
	"context"
	"sync"
	"time"
)

// Limits kayıt sınırlarıdır. Bunlar OLMAYAN ŞEYLERİ korur: bellek şişmesi,
// sonsuz dinleme, tek istekte devasa yükleme.
type Limits struct {
	MaxSeconds int   // en uzun kayıt (varsayılan 30)
	MaxBytes   int64 // en çok biriktirilecek ses (varsayılan 8 MiB ≈ 4 dk 16 kHz mono)
}

// Varsayılan sınırlar.
const (
	DefaultMaxSeconds = 30
	DefaultMaxBytes   = 8 << 20
)

// Recording, tek oturumun açık kaydıdır.
type Recording struct {
	Mime      string
	startedAt time.Time
	data      []byte
	truncated bool
}

// Service oturum başına kayıt tutar ve tanıma çağrısını yapar.
// Eşzamanlı kullanıma güvenlidir (aynı oturuma paralel kare gelirse veri kaybolmaz).
type Service struct {
	rec Recognizer
	lim Limits

	mu    sync.Mutex
	recs  map[string]*Recording
	clock func() time.Time
}

// New servis kurar. rec nil ise motor yapılandırılmamıştır (Enabled false).
func New(rec Recognizer, lim Limits) *Service {
	if lim.MaxSeconds <= 0 {
		lim.MaxSeconds = DefaultMaxSeconds
	}
	if lim.MaxBytes <= 0 {
		lim.MaxBytes = DefaultMaxBytes
	}
	return &Service{rec: rec, lim: lim, recs: map[string]*Recording{}, clock: time.Now}
}

// Enabled motor var mı?
func (s *Service) Enabled() bool { return s != nil && s.rec != nil }

// Start yeni kayıt başlatır (önceki varsa atılır).
func (s *Service) Start(sessionID, mime string) {
	if s == nil || !s.Enabled() {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	s.recs[sessionID] = &Recording{Mime: mime, startedAt: s.clock()}
}

// Append ses parçasını ekler. Kayıt yoksa yoksayılır (yarım kare paketi).
// Sınır aşılırsa veri KESİLİR ve Stop ErrTooLong döner; bellek sonsuza kadar büyümez.
func (s *Service) Append(sessionID string, chunk []byte) {
	if s == nil || len(chunk) == 0 {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	r, ok := s.recs[sessionID]
	if !ok {
		return
	}
	room := s.lim.MaxBytes - int64(len(r.data))
	if room <= 0 {
		r.truncated = true
		return
	}
	if int64(len(chunk)) > room {
		r.data = append(r.data, chunk[:room]...)
		r.truncated = true
		return
	}
	r.data = append(r.data, chunk...)
}

// Stop kaydı kapatır, metne çevirir ve oturumu temizler.
// Kayıt yoksa ErrNotStarted, ses boşsa ErrEmptyAudio döner.
func (s *Service) Stop(ctx context.Context, sessionID string) (string, time.Duration, error) {
	if s == nil || !s.Enabled() {
		return "", 0, ErrNoRecognizer
	}
	s.mu.Lock()
	r, ok := s.recs[sessionID]
	delete(s.recs, sessionID)
	s.mu.Unlock()
	if !ok {
		return "", 0, ErrNotStarted
	}
	dur := s.clock().Sub(r.startedAt)
	if len(r.data) == 0 {
		return "", dur, ErrEmptyAudio
	}
	if r.truncated || dur > time.Duration(s.lim.MaxSeconds)*time.Second {
		return "", dur, ErrTooLong
	}

	text, err := s.rec.Transcribe(ctx, r.data, r.Mime)
	if err != nil {
		return "", dur, err
	}
	return Sanitize(text), dur, nil
}

// Cancel açık kaydı sessizce atar (istemci bağlantıyı kapattığında sızmayı önler).
func (s *Service) Cancel(sessionID string) {
	if s == nil {
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.recs, sessionID)
}
