package phraser

// The query answers and gaps — what a query source says when it answers from
// something other than the model, and what it says when it has nothing.
//
// Third family on the shared deck (deck.go), after the fallbacks and the
// acknowledgements. They were literals spread across actions_query.go and
// netscan.go, where the largest single site held two dozen of them.
//
// QueryUnknown is not the phraser's UnknownFallback, even though the two read
// the same today. Here she looked and found nothing; there she failed to phrase
// an answer she had. Two files, two entries, so rewording one leaves the other.

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

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

//go:embed query_ru_v1.json
var queryJSON []byte

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

// The entry keys.
const (
	QueryUnknown      = "query_unknown"
	QueryOtherDay     = "other_day"
	QueryPersonalNone = "personal_none"
	QueryFactWhen     = "fact_when"
	QueryFactValue    = "fact_value"
	QueryFound        = "found"
	QueryPageText     = "page_text"
	QueryPageBlocked  = "page_blocked"
	QueryPageEmpty    = "page_empty"
	QueryFeedsOff     = "feeds_off"
	QueryFeedsNew     = "feeds_new"
	QueryFeedsEmpty   = "feeds_empty"
	QueryFeedsTopic   = "feeds_empty_topic"
	QueryWeatherNow   = "weather_now"
	QueryWeatherOff   = "weather_off"
	QueryWeatherWhere = "weather_nolocation"
	QueryNetEmpty     = "net_empty"
	QueryNetOff       = "net_off"
	QueryPageOff      = "page_off"

	QueryFailPlan     = "fail_plan"
	QueryFailNotes    = "fail_notes"
	QueryFailFeeds    = "fail_feeds"
	QueryFailCalendar = "fail_calendar"
	QueryFailWeather  = "fail_weather"
	QueryFailAnswer   = "fail_answer"
	QueryFailPage     = "fail_page"
	QueryFailNetscan  = "fail_netscan"
)

var queryKeys = []string{
	QueryUnknown, QueryOtherDay, QueryPersonalNone, QueryFactWhen, QueryFactValue,
	QueryFound, QueryPageText, QueryPageBlocked, QueryPageEmpty,
	QueryFeedsOff, QueryFeedsNew, QueryFeedsEmpty, QueryFeedsTopic,
	QueryWeatherNow, QueryWeatherOff, QueryWeatherWhere, QueryNetEmpty, QueryNetOff, QueryPageOff,
	QueryFailPlan, QueryFailNotes, QueryFailFeeds, QueryFailCalendar,
	QueryFailWeather, QueryFailAnswer, QueryFailPage, QueryFailNetscan,
}

// queryFloor — the literal each key falls back to when the file is unusable.
// It started as the exact strings that lived in Go before this file existed and
// now tracks the file's first variant instead, because a floor that keeps the
// wording review threw out would say it back on the one turn nobody is watching.
var queryFloor = map[string]string{
	QueryUnknown:      "ничего не нашла.",
	QueryOtherDay:     "про другой день так не отвечу — спроси целиком.",
	QueryPersonalNone: "не знаю — не нашла у тебя такой записи.",
	QueryFactWhen:     "записала это {when}",
	QueryFactValue:    "у меня записано: {key} — {value}",
	QueryFound:        "вот что я нашла: {text}",
	QueryPageText:     "вот что на странице: {text}",
	QueryPageBlocked:  "эта страница закрыта для чтения — сам сайт это запрещает.",
	QueryPageEmpty:    "страница открылась, но читать там нечего.",
	QueryFeedsOff:     "ленты не настроены.",
	QueryFeedsNew:     "вот что нового: {items}",
	QueryFeedsEmpty:   "в лентах пока ничего нового.",
	QueryFeedsTopic:   "по этой теме в лентах пока ничего.",
	QueryWeatherNow:   "в {location} сейчас {temp} {word}, {condition}.",
	QueryWeatherOff:   "погода не настроена.",
	QueryWeatherWhere: "для какого города?",
	QueryNetEmpty:     "в сети никого не нашла.",
	QueryNetOff:       "сканирование сети не настроено.",
	QueryPageOff:      "я не читаю страницы — это не настроено.",

	QueryFailPlan:     "не получилось собрать план.",
	QueryFailNotes:    "не получилось посмотреть записи.",
	QueryFailFeeds:    "не получилось посмотреть ленты.",
	QueryFailCalendar: "не получилось проверить календарь.",
	QueryFailWeather:  "не получилось узнать погоду.",
	QueryFailAnswer:   "не получилось найти ответ.",
	QueryFailPage:     "не получилось прочитать страницу.",
	QueryFailNetscan:  "не получилось просканировать сеть.",
}

// Queries picks a hand-written Russian query line. Safe for concurrent use.
type Queries struct{ d *say.Deck }

// LoadQueries reads the embedded file. Pass a source to make the picking
// reproducible in tests; nil seeds from the clock.
func LoadQueries(src rand.Source) (*Queries, error) {
	d, err := say.Load(queryJSON, QuerySchemaVersion, queryKeys, queryFloor, src)
	if err != nil {
		return nil, err
	}
	// The entries that exist to read something back. A variant without the
	// placeholder would answer the question by dropping the answer.
	for _, req := range []struct{ key, ph string }{
		{QueryFactWhen, "{when}"}, {QueryFactValue, "{key}"}, {QueryFactValue, "{value}"},
		{QueryFound, "{text}"}, {QueryPageText, "{text}"}, {QueryFeedsNew, "{items}"},
		{QueryWeatherNow, "{location}"}, {QueryWeatherNow, "{temp}"},
		{QueryWeatherNow, "{word}"}, {QueryWeatherNow, "{condition}"},
	} {
		if err := d.RequirePlaceholder(req.key, req.ph); err != nil {
			return nil, err
		}
	}
	return &Queries{d: d}, nil
}

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

// Say returns one line for key, with the values filled into the frame.
func (q *Queries) Say(key string, vars map[string]string) string {
	return q.deck().Text(key, vars)
}

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

var (
	queryOnce sync.Once
	queries   *Queries
)

// DefaultQueries returns the shared instance, loading it on first use. A broken
// file logs once and leaves a nil *Queries, which still answers from queryFloor.
func DefaultQueries() *Queries {
	queryOnce.Do(func() {
		q, err := LoadQueries(nil)
		if err != nil {
			log.Printf("phraser: query lines unavailable, using the built-in ones: %v", err)
			return
		}
		queries = q
	})
	return queries
}

// Q — one query line, the way every caller says it.
func Q(key string, vars map[string]string) string { return DefaultQueries().Say(key, vars) }

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