// Package protocol WebSocket üzerinden istemci ↔ sunucu arasında konuşulan olay şemalarını tanımlar.
// Frontend'deki models/protocol.ts bu dosyayla birebir uyumlu tutulmalıdır.
package protocol

import (
	"encoding/json"
	"time"
)

// ClientType istemciden sunucuya giden olay türleridir.
type ClientType string

const (
	ClientVoiceStart ClientType = "voice.start"
	ClientVoiceAudio ClientType = "voice.audio"
	ClientVoiceStop  ClientType = "voice.stop"
	// TTS (Phase 7): tts.mode sesli modu açar/kapatır, tts.stop konuşmayı keser.
	ClientTTSMode            ClientType = "tts.mode"
	ClientTTSStop            ClientType = "tts.stop"
	ClientTextMessage        ClientType = "text.message"
	ClientConversationCancel ClientType = "conversation.cancel"
	ClientInterrupt          ClientType = "interrupt"
	ClientPing               ClientType = "ping"
	// Kalıcı bellek onayı: kullanıcı, LLM'in emin olmadığı bir bilgi için sorduğu
	// soruya yanıt verir (memory.Question). Kararı İNSAN verir.
	ClientMemoryAnswer ClientType = "memory.answer"
	// notify: alıcı, kendisine sorulan soruyu yanıtlar. Kararı İNSAN verir —
	// model değil. WS'tir çünkü yanıt anında zil de tazelenmelidir.
	ClientNotifyAnswer ClientType = "notify.answer"
)

// Valid bilinen bir istemci olayı mı?
func (t ClientType) Valid() bool {
	switch t {
	case ClientVoiceStart, ClientVoiceAudio, ClientVoiceStop,
		ClientTTSMode, ClientTTSStop,
		ClientTextMessage, ClientConversationCancel, ClientInterrupt, ClientPing,
		ClientMemoryAnswer, ClientToolConfirmAnswer, ClientNotifyAnswer:
		return true
	}
	return false
}

// ServerType sunucudan istemciye giden olay türleridir.
type ServerType string

const (
	ServerVoiceDetected ServerType = "voice.detected"
	// TTS olayları: her cümle için tts.audio, dizi sonunda tts.done.
	ServerTTSAudio             ServerType = "tts.audio"
	ServerTTSDone              ServerType = "tts.done"
	ServerTranscriptionPartial ServerType = "transcription.partial"
	ServerTranscriptionFinal   ServerType = "transcription.final"
	ServerAssistantThinking    ServerType = "assistant.thinking"
	ServerAssistantTextDelta   ServerType = "assistant.text.delta"
	ServerAssistantTextFinal   ServerType = "assistant.text.final"
	ServerAssistantAudioChunk  ServerType = "assistant.audio.chunk"
	ServerToolStart            ServerType = "tool.start"
	ServerToolProgress         ServerType = "tool.progress"
	ServerToolResult           ServerType = "tool.result"
	ServerConversationState    ServerType = "conversation.state"
	ServerAssistantInterrupted ServerType = "assistant.interrupted"
	ServerError                ServerType = "error"

	// Spec dışı yardımcı olaylar (Phase 1 ihtiyaçları).
	ServerPong                ServerType = "pong"
	ServerConversationUpdated ServerType = "conversation.updated"

	// Kalıcı bellek onay sorusu: LLM karar veremedi, kullanıcıya soruyoruz.
	// Frontend'deki dinamik soru bileşeni bu olayı dinler.
	ServerMemoryQuestion ServerType = "memory.question"
	// Bellek değişikliği bildirimleri. Bellek ONAY SORUSU cevaplandığında sunucu
	// kaydı yazar/ekler; frontend bu olaylarla (a) kalıcı bellek listesini tazeler ve
	// (b) soru kartını "kaydedildi" durumuna geçirir.
	ServerMemoryAdded   ServerType = "memory.added"
	ServerMemoryUpdated ServerType = "memory.updated"
	// ServerClaimSummary Iddia Hafızası'na yazılan bilgilerin kısa özetidir ("X kayıt eklendi,
	// Y çelişki var, Z kayıtta eksik bilgi var"). Yazma arka planda biter; özet sonradan,
	// yalnızca bir şey kaydedildiyse ya da dikkat gerektiren bir durum varsa gönderilir.
	ServerClaimSummary ServerType = "claim.summary"

	// ---- YAZMA/SİLME ONAY FORMU ----
	// Model yazma/silme aracı çağırdığında sunucu işlemi YAPMAZ; onay formu gönderir
	// (bkz. internal/tool/builtin/confirm.go). Kullanıcı formu Onayla/Vazgeç ile
	// cevaplar. "Yazı yazmak yerine bileşende sormak" ilkesinin tool işlemleri için
	// karşılığı: karar ekranda verilir, konuşma tek turda bozulmaz.
	ServerToolConfirm ServerType = "tool.confirm"
	// Onay cevabı: kullanıcı formu onayladı/vazgeçti ve araç gerçekten çalıştı.
	ServerToolConfirmResolved ServerType = "tool.confirm.resolved"

	// ---- KULLANICILAR ARASI BİLDİRİM (notify) ----
	// Biri diğerine bildirim/soru gönderdiğinde ALICIYA anında ulaşır. Olay
	// sohbete BAĞLI DEĞİLDİR: kullanıcı hangi sekmede olursa olsun görür.
	//
	// İKİ AYRI OLAY NEDEN? `notify.received` alıcının gelen kutusuna düşer
	// (bildirim/soru). `notify.answered` ise SORANIN gönendilerine düşer (alıcının
	// verdiği yanıt). Tek olay olsaydı arayüz "bana bildirim mi geldi, soruma mı
	// cevap geldi" ayrımını yapamazdı ve zil yanlış çalardı.
	//
	// AD ÇAKIŞMASI (bilinçli): İstemci türü `notify.answer` (KULLANICI cevap
	// veriyor), sunucu türü `notify.answered` (YANIT GELDİ bildirimi). İkisi
	// farklı yönlerde aynı adı taşıyamaz — protokol testi bunu ayrı tutmayı
	// zorunlu kılar (TestNoDuplicateWireNames): karışırsa istemci olayını
	// sunucu olayı sanan kod yazılır ve bildirim sessizce kaybolur.
	ServerNotifyReceived ServerType = "notify.received"
	ServerNotifyAnswered ServerType = "notify.answered"
	// ServerNotifyUnread zil sayacını günceller (yeni bildirim geldi / okundu).
	ServerNotifyUnread ServerType = "notify.unread"
)

// İstemciden gelen onay cevabı (ClientTextMessage gibi ayrı bir olay değil:
// sohbet akışını bozmadan, konuşmanın kendi kanalında gitmesi gerekir).
const ClientToolConfirmAnswer ClientType = "tool.confirm.answer"

// State orb / konuşma durumunu tanımlar.
type State string

const (
	StateIdle        State = "IDLE"
	StateListening   State = "LISTENING"
	StateThinking    State = "THINKING"
	StateSpeaking    State = "SPEAKING"
	StateExecuting   State = "EXECUTING"
	StateInterrupted State = "INTERRUPTED"
	StateError       State = "ERROR"
)

// Inbound istemciden gelen zarftır. Payload türe göre ayrıca çözülür.
type Inbound struct {
	Type           ClientType      `json:"type"`
	ID             string          `json:"id,omitempty"` // istemci istek kimliği; yanıtlarda replyTo olarak döner
	ConversationID string          `json:"conversationId,omitempty"`
	Payload        json.RawMessage `json:"payload,omitempty"`
}

// Outbound sunucudan istemciye giden zarftır.
type Outbound struct {
	Type           ServerType `json:"type"`
	ConversationID string     `json:"conversationId,omitempty"`
	ReplyTo        string     `json:"replyTo,omitempty"`
	At             time.Time  `json:"at"`
	Payload        any        `json:"payload,omitempty"`
}

// NewOutbound zaman damgalı bir Outbound üretir.
func NewOutbound(t ServerType, conversationID string, payload any) Outbound {
	return Outbound{Type: t, ConversationID: conversationID, At: time.Now().UTC(), Payload: payload}
}

// ---- İstemci payload'ları ----

type TextMessagePayload struct {
	Text  string `json:"text"`
	Model string `json:"model,omitempty"` // boşsa sunucu varsayılanı
}

type CancelPayload struct {
	Reason string `json:"reason,omitempty"`
}

// MemoryQuestionPayload, LLM'in emin olmadığı bir bilgi için kullanıcıya sorulan
// sorudur. Frontend'deki dinamik soru bileşeni bu alanları olduğu gibi gösterir:
// metin, seçenekler ve önceki kayıt MODELden gelir, sabit şablon YOKTUR (özelleştirilebilirlik).
type MemoryQuestionPayload struct {
	QuestionID string `json:"questionId"`
	Text       string `json:"text"` // soru cümlesi (doğal Türkçe)
	// Options: her seçenek kararını KENDİSİ taşır. Arayüz metinden tahmin YAPMAZ —
	// "Hayır, farklı bir yıl" bir onay olabilir, metin bunu söylemez.
	Options []MemoryQuestionOption `json:"options,omitempty"`
	// OptionTexts: yalnızca eski istemciler için düz metinler.
	OptionTexts []string `json:"optionTexts,omitempty"`
	// Previous önceki kaydın metnidir; boşsa yeni kayıt soruluyor demektir.
	Previous  string `json:"previous,omitempty"`
	NewValue  string `json:"newValue"`  // uygulanacak yeni metin
	TargetID  string `json:"targetId"`  // güncellenecek kayıt (yoksa yeni kayıt)
	ExpiresAt string `json:"expiresAt"` // sorunun bitiş zamanı (ISO-8601)
}

// MemoryQuestionOption, tek bir seçenektir: metin + uygulanacak karar.
type MemoryQuestionOption struct {
	Text      string `json:"text"`      // butonda görünen metin (modelden gelir)
	Confirmed bool   `json:"confirmed"` // true ise seçilince kayıt/güncelleme uygulanır
	SaveAsNew bool   `json:"saveAsNew,omitempty"`
}

// MemoryAnswerPayload, kullanıcının soruya verdiği yanıttır.
type MemoryAnswerPayload struct {
	QuestionID string `json:"questionId"`
	// Confirmed false ise hiçbir şey yapılmaz (kullanıcı reddetti).
	Confirmed bool `json:"confirmed"`
	// SaveAsNew true ise güncelleme yerine ayrı kayıt açılır.
	SaveAsNew bool `json:"saveAsNew,omitempty"`
}

// ---- Kullanıcılar arası bildirim (notify) ----

// NotifyOption, bildirim sorusunun tek bir seçeneğidir.
//
// Arayüz buton METNİNDEN karar TAHMİN ETMEZ (bellek sorularındaki ders): seçenek
// düz metindir, karar kullanıcının tıklamasıdır.
type NotifyOption struct {
	Text string `json:"text"`
}

// NotifyPayload, alıcıya gönderilen bildirim veya sorudur (notify.received olayı).
//
// ALAN SEÇİMİ NEDEN BÖYLE? `fromUserId` GİDER: arayüz "Ali sana bildirdi" demeli.
// `text` MODEL YAZAR ama gönderen adı SUNUCUDA çözülür — model "Fiko gönderdi"
// diyemez, çünkü o alanı modele hiç açmıyoruz.
type NotifyPayload struct {
	// ID bildirimin kalıcı kimliğidir (REST okuma ve silme bununla çalışır).
	ID string `json:"id"`
	// QuestionId soru kimliğidir; yalnızca `question` türünde dolar. Kullanıcı
	// cevapladığında bu kimlik gönderilir.
	QuestionID string `json:"questionId,omitempty"`
	// Kind "info" | "question". Arayüz soruyu ayırt etmek için ZORUNLU: info ise
	// yalnızca metin gösterilir, question ise seçeneklerle yanıt beklenir.
	Kind string `json:"kind"`
	Text string `json:"text"`
	// FromUserID gönderenin kimliği, FromName görünen adı (arayüzde "X adına").
	FromUserID string `json:"fromUserId"`
	FromName   string `json:"fromName"`
	// Options yalnızca `question` türünde dolar.
	Options []NotifyOption `json:"options,omitempty"`
	// ExpiresAt sorunun bitiş zamanı (ISO-8601); info bildirimlerinde boştur.
	ExpiresAt string `json:"expiresAt,omitempty"`
	// Pending true ise hâlâ yanıt bekleniyor; false ise cevaplandı/arşivlendi.
	// Arayüz kartın durumunu buna göre çizer.
	Pending bool `json:"pending"`
	// At gönderim zamanı (ISO-8601); liste sıralaması için.
	At string `json:"at"`
}

// NotifyAnswerPayload, SORANIN gönendilerine düşen yanıttır (notify.answer olayı).
//
// Neden ayrı tip? Arayüz "bana yeni bildirim mi geldi" ile "soruma cevap mı geldi"
// durumlarını FARKLı göstermeli: ilki zil çalar, ikincisi zil çalmaz.
type NotifyAnswerPayload struct {
	// QuestionId cevaplanan sorunun kimliği (notify.ask çıktısındakiyle eşleşir).
	QuestionID string `json:"questionId"`
	// Question sorunun metni; arayüzde "…sorusuna cevap:" başlığıyla gösterilir.
	Question string `json:"question"`
	// Answer alıcının verdiği yanıtın metni.
	Answer string `json:"answer"`
	// FromUserID cevaplayanın kimliği, FromName görünen adı.
	FromUserID string `json:"fromUserId"`
	FromName   string `json:"fromName"`
	// At yanıt zamanı (ISO-8601).
	At string `json:"at"`
}

// NotifyUnreadPayload, zil sayacının güncel halidir (notify.unread olayı).
//
// Neden ayrı olay? Zil sayacı, bildirimin KENDİSİ görülmeden de değişir (okundu
// işaretlenince azalır). Arayüz her mesajda sayaç sorgulamak yerine bu olayı
// dinler; bağlantı yoksa REST'e düşer.
type NotifyUnreadPayload struct {
	// Unread okunmamış bildirim sayısıdır (yanıtlar sayılmaz).
	Unread int `json:"unread"`
	// Pending yanıt bekleyen soru sayısıdır; arayüz ayrı rozet gösterir.
	Pending int `json:"pending"`
}

// ClaimSummaryPayload, Iddia Hafızası'na yazılan bilgilerin kısa özetidir (claim.summary olayı).
//
// Yazma arka planda biter; bu olay sonradan, yalnızca bir şey kaydedildiyse ya da dikkat
// gerektiren bir durum varsa gider. Sayılar arayüzün ayrıca renk/rozet vermesi içindir;
// kullanıcıya gösterilecek cümle sunucuda üretilir (Text) ve nötr dildedir.
type ClaimSummaryPayload struct {
	// Text spec'teki özet cümlesidir: "X kayıt eklendi, Y çelişki var, Z kayıtta eksik bilgi var".
	Text string `json:"text"`
	// Added oluşturulan kayıt sayısı; Updated bunun kaçının eski bir kaydın yerini aldığı.
	Added   int `json:"added"`
	Updated int `json:"updated"`
	// Conflicts çelişkili işaretlenen kayıt çifti sayısı (karar patrondadır).
	Conflicts int `json:"conflicts"`
	// Incomplete eksik bilgi yüzünden yazılamayan aday sayısı.
	Incomplete int `json:"incomplete"`
	// FailedChunks > 0 ise sonuç EKSİKTİR: o parçalardaki bilgiler çıkarılamadı.
	FailedChunks int `json:"failedChunks,omitempty"`
}

// NotifyAnswerInput, istemciden gelen `notify.answer` olayının gövdesidir.
type NotifyAnswerInput struct {
	// QuestionID cevaplanacak soru; sunucu alıcılık denetimi yapar.
	QuestionID string `json:"questionId"`
	// Answer kullanıcının yazdığı metindir. Boş olabilir DEĞİL: boş yanıt
	// anlamsızdır, doğrulama 400 döner.
	Answer string `json:"answer"`
}

// ---- Yazma/silme onay formu ----

// ToolConfirmField, form üzerindeki tek bir alandır. Alan sunucudan gelir; arayüz
// yalnızca düzenlenebilir olanları düzenlenebilir yapar.
type ToolConfirmField struct {
	Key      string   `json:"key"`
	Label    string   `json:"label"`
	Value    string   `json:"value"`
	Editable bool     `json:"editable"`
	Required bool     `json:"required"`
	Missing  bool     `json:"missing"`
	Hint     string   `json:"hint,omitempty"`
	Options  []string `json:"options,omitempty"`
	// Type: text | textarea | number | date | enum | bool (boş = text).
	Type string `json:"type,omitempty"`
}

// ToolConfirmPayload, modelin yazma/silme isteği karşılığında gönderilen onay formudur.
//
// Alanlar KONUŞMADAN doldurulur (model ne anladıysa), eksikler "missing" işaretlidir ve
// kullanıcı form üstünde tamamlar. Böylece "hangi müşteri?" diye yazı yazmak yerine
// kullanıcı seçer.
type ToolConfirmPayload struct {
	ConfirmID string             `json:"confirmId"`
	CallID    string             `json:"callId"` // eşleşen tool çağrısı (varsa)
	Tool      string             `json:"tool"`
	Action    string             `json:"action"` // add | update | delete | ...
	Title     string             `json:"title"`
	Message   string             `json:"message,omitempty"`
	Fields    []ToolConfirmField `json:"fields"`
	// Eksik zorunlu alan anahtarları. Boşsa form doğrudan onaylanabilir.
	MissingFields []string `json:"missingFields,omitempty"`
	ConfirmLabel  string   `json:"confirmLabel"`
	CancelLabel   string   `json:"cancelLabel"`
	Destructive   bool     `json:"destructive"`
	// Tool, onaylandığında hangi girdiyle çalıştırılacak (kullanıcı düzenlemiş olabilir).
	ToolInput map[string]any `json:"toolInput"`
}

// ToolConfirmAnswerPayload, kullanıcının onay cevabıdır.
//
// Values, kullanıcının forma yazdığı alanları içerir (null = değiştirmedi). Sunucu
// toolInput'ı bu değerlerle günceller, `confirmed:true` ile aracı çalıştırır.
type ToolConfirmAnswerPayload struct {
	ConfirmID string            `json:"confirmId"`
	Confirmed bool              `json:"confirmed"`
	Values    map[string]string `json:"values,omitempty"`
	// Kullanıcı formu iptal etti mi? true ise araca hiç gidilmez.
	Cancelled bool `json:"cancelled,omitempty"`
}

// ToolConfirmResolvedPayload, onayın sonucunu bildirir (kart "tamamlandı" görünümüne geçer).
type ToolConfirmResolvedPayload struct {
	ConfirmID string `json:"confirmId"`
	Confirmed bool   `json:"confirmed"`
	Summary   string `json:"summary,omitempty"` // "3 müşteri kaydedildi" gibi
	Tool      string `json:"tool,omitempty"`
	// Action formun türüdür ("ask" = bilgi formu: araç çalışmaz, cevaplar sohbete döner).
	Action string `json:"action,omitempty"`
	// Values bilgi formunda (action=ask) kullanıcının gönderdiği nihai cevaplardır:
	// her alan anahtarı için bir değer (varsayılanlar + kullanıcı düzenlemeleri).
	// Arayüz bunu sohbete kullanıcı mesajı olarak yollar; böylece model cevabı görür.
	Values map[string]string `json:"values,omitempty"`
	// Expired true ise form artık geçerli değildir (süresi doldu ya da sunucu yeniden başladı);
	// işlem YAPILMADI. Kart "vazgeçildi" değil "süresi doldu" olarak gösterilir.
	Expired bool `json:"expired,omitempty"`
}

// ---- Sunucu payload'ları ----

type ThinkingPayload struct {
	MessageID string `json:"messageId"`
}

type TextDeltaPayload struct {
	MessageID string `json:"messageId"`
	Delta     string `json:"delta"`
}

type TextFinalPayload struct {
	MessageID        string `json:"messageId"`
	Content          string `json:"content"`
	Status           string `json:"status"` // complete | interrupted | error
	Model            string `json:"model,omitempty"`
	PromptTokens     int    `json:"promptTokens,omitempty"`
	CompletionTokens int    `json:"completionTokens,omitempty"`
	LatencyMs        int64  `json:"latencyMs,omitempty"`
}

type StatePayload struct {
	State State `json:"state"`
}

type InterruptedPayload struct {
	MessageID string `json:"messageId,omitempty"`
	Partial   string `json:"partial,omitempty"`
	Reason    string `json:"reason,omitempty"`
}

type ConversationUpdatedPayload struct {
	Title string `json:"title"`
}

// ToolStartPayload bir tool çağrısının BAŞLADIĞINI bildirir (istemci "çalışıyor" gösterir).
type ToolStartPayload struct {
	CallID string `json:"callId"`
	Name   string `json:"name"`
	Input  any    `json:"input,omitempty"` // modelin istediği argümanlar (gizli değil, tanım gereği)
}

// ToolProgressPayload tool'un ara ilerlemesidir (Executor'ın Progress geri çağrısından).
type ToolProgressPayload struct {
	CallID  string `json:"callId"`
	Step    string `json:"step,omitempty"`
	Percent int    `json:"percent"`
}

// ToolResultPayload bir tool çağrısının BİTİŞİDİR. Status her zaman "success" veya "error" olur;
// yetki reddi de "error"tur ve message içinde belirtilir (403 ayrı kodda taşınmaz).
type ToolResultPayload struct {
	CallID     string `json:"callId"`
	Name       string `json:"name"`
	Status     string `json:"status"` // success | error
	Output     any    `json:"output,omitempty"`
	Error      string `json:"error,omitempty"`
	DurationMs int64  `json:"durationMs"`
	// UI çıktının arayüzde hangi görünümde sunulacağıdır (json|table|file|list|code|metrics|form).
	// İstemci bu alana göre uygun bileşeni seçer; "auto" GÖNDERİLMEZ, çözülmüş değer gider.
	UI string `json:"ui,omitempty"`
}

// VoiceStartPayload kayıt başlatır (istemci mikrofonu açtı).
type VoiceStartPayload struct {
	ConversationID string `json:"conversationId"`
	// Mime ses biçimidir (örn. "audio/webm"); tanıma motoru uzantıya bakar.
	Mime string `json:"mime,omitempty"`
}

// VoiceAudioPayload bir ses parçasıdır.
//
// Neden base64? Bu sunucunun kare okuyucusu JSON'dur; ikili kare desteği eklemek ayrı bir
// altyapı işidir. Tek konuşma kareleri kısa olduğu için ~%33 ek yük kabul edilir.
type VoiceAudioPayload struct {
	ConversationID string `json:"conversationId"`
	// Data ses baytlarının base64 gösterimidir.
	Data string `json:"data"`
}

// VoiceStopPayload kaydı bitirir; sunucu metne çevirip voice.detected gönderir.
type VoiceStopPayload struct {
	ConversationID string `json:"conversationId"`
}

// VoiceDetectedPayload tanınan metni bildirir. Metin boşsa motor bir şey anlamadı demektir
// (kullanıcıya "anlaşılmadı" yazılır; boş mesaj gönderilmez).
type VoiceDetectedPayload struct {
	ConversationID string `json:"conversationId"`
	Text           string `json:"text,omitempty"`
	DurationMs     int64  `json:"durationMs"`
	// Sent true ise metin otomatik olarak kullanıcı mesajı gibi gönderildi.
	Sent bool `json:"sent"`
}

// TTSModePayload sesli modu açar/kapatır. Oturum bazlıdır: kapandığında sunucu
// yanıtları seslendirmez, metin akışı olduğu gibi devam eder.
type TTSModePayload struct {
	ConversationID string `json:"conversationId"`
	Enabled        bool   `json:"enabled"`
}

// TTSStopPayload konuşmayı keser (barge-in: kullanıcı konuşmaya başladığında).
type TTSStopPayload struct {
	ConversationID string `json:"conversationId"`
}

// TTSAudioPayload tek cümlenin sesidir. Cümleler SIRAYLA gönderilir; istemci
// kuyruğa alıp arka arkaya çalar (akış hissi).
type TTSAudioPayload struct {
	ConversationID string `json:"conversationId"`
	// MessageID seslendirilen asistan mesajıdır (arayüz eşleştirme için).
	MessageID string `json:"messageId,omitempty"`
	Index     int    `json:"index"`
	Mime      string `json:"mime"`
	// Data ses baytlarının base64 gösterimidir (WS kare okuyucusu JSON'dur).
	Data string `json:"data"`
}

// TTSDonePayload seslendirme dizisinin bittiğini bildirir.
type TTSDonePayload struct {
	ConversationID string `json:"conversationId"`
	MessageID      string `json:"messageId,omitempty"`
	Sentences      int    `json:"sentences"` // üretilen cümle sayısı
	Bytes          int64  `json:"bytes"`     // toplam ses boyutu
	DurationMs     int64  `json:"durationMs"`
	// Stopped true ise kullanıcı/oturum konuşmayı kesti (hata DEĞİLDİR).
	Stopped bool `json:"stopped,omitempty"`
}

type ErrorPayload struct {
	Code      string `json:"code"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable,omitempty"`
}

// Hata kodları. REST (internal/api) ve WS (ws) aynı sözlüğü paylaşır;
// frontend models/protocol.ts ile birebir uyumlu olmalıdır.
const (
	ErrCodeBadRequest       = "bad_request"
	ErrCodeNotFound         = "not_found"
	ErrCodeUnauthorized     = "unauthorized"
	ErrCodeUnknownEvent     = "unknown_event"
	ErrCodeNotImplemented   = "not_implemented"
	ErrCodeBusy             = "busy"
	ErrCodeInternal         = "internal"
	ErrCodeLLM              = "llm_error"
	ErrCodeTooLarge         = "too_large"
	ErrCodeConflict         = "conflict"          // REST: kaynak çakışması (ör. aynı adlı skill) → 409
	ErrCodeValidation       = "validation_error"  // REST: alan doğrulaması başarısız → 400, ayrıntı `fields`'ta
	ErrCodePermissionDenied = "permission_denied" // REST: tool çalıştırma yetkisi yok → 403
)
