// voice/replier.go — reactive reply phrasing.
//
// A SEPARATE seam from internal/phraser.Phraser:
//
//   - Phraser phrases DELIVERIES — the loop's nudges + reminders. Its output
//     (Body + Summary) is consumed by the dispatcher and shipped to ALL the
//     routed channels (voice gets Body, away channels get Summary). The
//     Phraser owns the multiple-of-many-channel output contract.
//
//   - Replier phrases REPLIES — the one text a voice round-trip says back to
//     the user who just spoke. Reactive, single-channel (voice), no
//     dispatcher involvement. The reply text goes through TTS to a single
//     audio clip played on the originating client; nothing routes elsewhere.
//
// Both seams feed TTS in production but at different times: Phraser for
// proactive nudges (loop tick → voicesink → tts → push), Replier for
// reactive round-trips (client audio → stt → router → action → replier →
// tts → response). Splitting the seam keeps the Phraser interface stable
// (the project's tests mock it; adding a method would break them) and
// keeps the LLM-backed impl cleanly focused: production phraser =
// personality-prompted "nudge tone", production replier = "chat tone",
// different prompts in the same model server.
//
// The Stub is the deterministic floor; production swaps in an LLM impl at
// the daemon seam (config wiring, no CoreAPI or voice-package change).
package voice

import (
	"github.com/kami/maven/internal/phraser"
	"github.com/kami/maven/internal/router"
)

// Replier — the reactive reply phrasing seam. The daemon's reactive handler
// calls Reply with the router's Decision; the impl produces a terse reply
// the handler passes to TTS and ships back to the originating client.
//
// The reply is per-Decision (one per reactive turn); the impl sees the
// decision's Intent + Slots + Clarify. The Intent largely names the reply
// shape (act/reminder/fact/note/query/clarify); the Slots carry the
// specifics that personalise it ("got it: water at 14:00").
type Replier interface {
	Reply(d router.Decision) string
}

// StubReplier — the deterministic, no-model floor. Canned per intent;
// slots incorporated as plain strings (the LLM impl will natural-language
// them; the Stub keeps it readable). The same instinct as the phraser
// Stub: a SCAFFOLD GROUND TRUTH so tests + the daemon end-to-end have
// deterministic replies; the LLM impl swaps in at the daemon wiring.
type StubReplier struct{}

// NewStubReplier builds the floor replier.
func NewStubReplier() *StubReplier { return &StubReplier{} }

// Reply dispatches on Intent + Clarify. Each branch is short; the LLM impl
// will replace this with prompted text and the same dispatch shape.
func (s *StubReplier) Reply(d router.Decision) string {
	if d.Clarify {
		return "не совсем поняла — можешь переформулировать?"
	}
	switch d.Intent {
	case router.IntentAct:
		if !d.Slots.HasFn {
			return "не могу это сделать — не разобрала действие."
		}
		return phraser.Ack(phraser.AckAct, map[string]string{"fn": d.Slots.Fn})
	case router.IntentReminder:
		return phraser.Ack(phraser.AckReminder, nil)
	case router.IntentFact:
		if d.Slots.HasKey {
			if d.Slots.Value != "" {
				return phraser.Ack(phraser.AckFactValue, map[string]string{"key": d.Slots.Key, "value": d.Slots.Value})
			}
			return phraser.Ack(phraser.AckFactKey, map[string]string{"key": d.Slots.Key})
		}
		return phraser.Ack(phraser.AckFact, nil)
	case router.IntentNote:
		return phraser.Ack(phraser.AckNote, nil)
	case router.IntentQuery:
		return "поискала в заметках — ничего не нашла."
	case router.IntentChat:
		return "поговорили." // stub — LLMReplier replaces this
	default:
		return phraser.Ack(phraser.AckGeneric, nil)
	}
}
