package phraser

// The confirmation answers — what she says once he has answered a confirm, and
// what she says when an act names a verb she may not run.
//
// Fifth family on the shared deck (deck.go). They were literals in
// cmd/mavend/confirm.go.
//
// The prompt is deliberately not here. act_confirm and act_confirm_entity are in
// acts_ru_v1.json, where they belong: the sentence he has to hear before he says
// yes is an act line, and it already loads with its {name} placeholder required.
// Vikunja #505 asked whether this family should move at all. It moves, but only
// the answers, and every entry is fixed. He answered a question about one
// specific thing, so variety here buys nothing and costs the one property that
// matters: the same act reports the same outcome every time.
//
// The tool-confirm success and failure lines are not here either. They said
// "готово." and "не получилось выполнить команду." in two places, which is the
// acts family word for word, so the confirmed run now reports through ActDone
// and ActFail rather than keeping a second copy that can drift.

import (
	_ "embed"
	"log"
	"math/rand"
	"sync"

	"github.com/kami/maven/internal/say"
)

//go:embed confirm_ru_v1.json
var confirmJSON []byte

// ConfirmSchemaVersion — this family's own version.
const ConfirmSchemaVersion = 1

// The entry keys.
const (
	// ConfirmCancelled — he said no. It names no capability, because the
	// prompt he answered named one and nothing ran.
	ConfirmCancelled = "confirm_cancelled"
	// ConfirmRoutineAuthed — he said yes to a proposed routine out loud, which
	// is not an acceptance. A room mic cannot hand the tick loop a standing new
	// reason to speak, so the row stays proposed and this line points at the
	// page where the accept button is gated (Vikunja #367).
	ConfirmRoutineAuthed = "confirm_routine_authed"
	ConfirmRoutineNo     = "confirm_routine_no"

	// The propose-gap lines: an act whose verb is not on the allowlist. She
	// drafts the registration and says so. She never enables it.
	ProposeNoVerb  = "propose_no_verb"
	ProposeFailed  = "propose_failed"
	ProposeNew     = "propose_new"
	ProposeAlready = "propose_already"
)

var confirmKeys = []string{
	ConfirmCancelled, ConfirmRoutineAuthed, ConfirmRoutineNo,
	ProposeNoVerb, ProposeFailed, ProposeNew, ProposeAlready,
}

// confirmFloor — the literal each key falls back to when the file is unusable.
// These are the exact strings that lived in cmd/mavend/confirm.go.
var confirmFloor = map[string]string{
	ConfirmCancelled:     "отменила.",
	ConfirmRoutineAuthed: "поняла — подтверди на странице рутин, и начну напоминать.",
	ConfirmRoutineNo:     "хорошо, не буду.",

	ProposeNoVerb:  "не разобрала команду — попробуй иначе.",
	ProposeFailed:  "команды «{name}» нет в списке разрешённых.",
	ProposeNew:     "команды «{name}» нет в списке. Предложила её добавить — включи через клиент.",
	ProposeAlready: "команды «{name}» пока нет в списке — она уже предложена, включи через клиент.",
}

// Confirms picks a hand-written Russian confirmation answer. Safe for
// concurrent use.
type Confirms struct{ d *say.Deck }

// LoadConfirms reads the embedded file. Pass a source to make the picking
// reproducible in tests; nil seeds from the clock.
func LoadConfirms(src rand.Source) (*Confirms, error) {
	d, err := say.Load(confirmJSON, ConfirmSchemaVersion, confirmKeys, confirmFloor, src)
	if err != nil {
		return nil, err
	}
	// Every propose line that names the verb must keep naming it. A variant that
	// dropped {name} would tell him a command is not allowed without saying
	// which one, and the whole point of the line is that he goes and enables it.
	for _, key := range []string{ProposeFailed, ProposeNew, ProposeAlready} {
		if err := d.RequirePlaceholder(key, "{name}"); err != nil {
			return nil, err
		}
	}
	return &Confirms{d: d}, nil
}

// deck reads through a nil *Confirms, which is the unloadable-file case.
func (c *Confirms) deck() *say.Deck {
	if c == nil {
		return say.FloorDeck(confirmFloor)
	}
	return c.d
}

// Say returns the line for key, with the verb filled into the frame.
func (c *Confirms) Say(key string, vars map[string]string) string {
	return c.deck().Text(key, vars)
}

// Variants returns every line the file can produce, for the persona scorer.
func (c *Confirms) Variants() []string { return c.deck().Variants() }

var (
	confirmOnce  sync.Once
	confirmsDeck *Confirms
)

// DefaultConfirms returns the shared instance, loading it on first use. A broken
// file logs once and leaves a nil *Confirms, which still answers from
// confirmFloor: a daemon must not fail to boot over its own copy deck.
func DefaultConfirms() *Confirms {
	confirmOnce.Do(func() {
		c, err := LoadConfirms(nil)
		if err != nil {
			log.Printf("phraser: confirmation answers unavailable, using the built-in lines: %v", err)
			return
		}
		confirmsDeck = c
	})
	return confirmsDeck
}

// C — one confirmation answer, the way every caller says it.
func C(key string, vars map[string]string) string { return DefaultConfirms().Say(key, vars) }

// IsC reports whether text is a line key could have produced, for the tests.
func IsC(key string, vars map[string]string, text string) bool {
	return DefaultConfirms().deck().Matches(key, vars, text)
}
