package router

import (
	"strings"

	"github.com/kami/maven/internal/lexicon"
	"github.com/kami/maven/internal/morph"
)

// Money questions, matched deterministically (Vikunja #125).
//
// No new intent, for the same reason as tasks: the intent enum is a contract
// with the relabelling prompt. "сколько я потратил?" is a query; which figure
// it asks for is a lookup, not something to ask a 1.7B — and a model asked to
// invent a spending total will happily do it.

// MoneyWindow — which period a money question asks about.
type MoneyWindow int

const (
	MoneyNone MoneyWindow = iota
	MoneyToday
	MoneyMonth
	// MoneyUnsupported — a money question over a window nothing is stored for
	// ("вчера", "на прошлой неделе"). Claimed, not answered: the poller keeps
	// today and the month, and answering a question about yesterday with the
	// month-to-date total is worse than saying she does not keep it.
	MoneyUnsupported
)

// MoneyQuery — a parsed money question. Income is set when he asked what he
// EARNED rather than what he spent; the two read the same fact and differ only
// in which half of it leads the answer.
type MoneyQuery struct {
	Window MoneyWindow
	Income bool
}

// The word lists below are DICTIONARY FORMS, matched through internal/morph
// (Vikunja #529). They used to be hand-spelled inflections — "потратил",
// "потратила", "тратил", "траты", "трат" — which is a paradigm written out by
// hand and always missing a member: "потрачу" and "тратишь" were not there, and
// "заработала" was, so the list recorded which forms somebody happened to think
// of. Russian aspect pairs are two separate verbs, so both are still listed.
//
// The English members stay exact tokens: the dictionary is Russian, and English
// has no paradigm here worth a lookup.

// incomeWords — the words that make a money question be about income.
var (
	incomeWords   = []string{"заработать", "получить", "доход"}
	incomeWordsEN = []string{"earned", "income"}
)

// moneyWords — the words that make a question be about his money.
var (
	moneyWords = []string{
		"потратить", "тратить", "трата", "расход", "деньги",
		"заработать", "доход",
	}
	moneyWordsEN = []string{"spend", "spent", "expenses", "earned", "income"}
)

// notMoneyWords — what else he spends. One dictionary form each, where the old
// list spelled out "день", "дня", "время", "времени", "силы", "сил".
var notMoneyWords = []string{"день", "время", "сила", "нервы"}

// hasWord reports whether any token is one of the given dictionary forms.
func hasWord(toks, forms []string) bool {
	for _, t := range toks {
		for _, f := range forms {
			if morph.SameWord(t, f) {
				return true
			}
		}
	}
	return false
}

// hasAnyTok reports whether any token matches exactly. For the English members,
// which are not declined.
func hasAnyTok(toks, words []string) bool {
	for _, w := range words {
		if hasTok(toks, w) {
			return true
		}
	}
	return false
}

// ParseMoneyQuery reports whether an utterance asks about spending or income,
// and over which window. Defaults to the month: "сколько я потратил?" without a
// period is the month-to-date question, which is the one worth answering.
//
// Narrow on purpose. A money noun alone is not enough — "я потратил весь день
// на это" is him talking about his day, so an amount word or an explicit
// question word has to be there too. The two evidence halves are INDEPENDENT:
// "траты" and "расходы" used to sit in both lists, so either word alone
// satisfied the whole gate and "у меня в этом месяце большие траты", a
// statement, came back with a figure.
func ParseMoneyQuery(text string) (MoneyQuery, bool) {
	toks := planTokens(text)
	if len(toks) == 0 {
		return MoneyQuery{}, false
	}
	if !hasWord(toks, moneyWords) && !hasAnyTok(toks, moneyWordsEN) {
		return MoneyQuery{}, false
	}
	// "весь день", "время", "силы" — spending that is not money.
	if hasWord(toks, notMoneyWords) {
		return MoneyQuery{}, false
	}
	// The ask half. Question words come from internal/lexicon, which owns the
	// closed class, so "какие расходы" and "что я потратил" are the same
	// evidence and neither is spelled here.
	asking := hasWord(toks, lexicon.Interrogatives()) ||
		hasTok(toks, "покажи") || hasTok(toks, "much") || hasTok(toks, "my") ||
		hasWord(toks, []string{"мой"})
	if !asking {
		return MoneyQuery{}, false
	}
	income := hasWord(toks, incomeWords) || hasAnyTok(toks, incomeWordsEN)
	lower := strings.ToLower(text)
	// Which window. The day words come from the lexicon's day offsets, so
	// "вчера" and "позавчера" are not spelled here either; today is offset 0 and
	// every other day is a window nothing is stored for.
	if off, ok := lexicon.DayOffsetIn(text); ok {
		if off == 0 {
			return MoneyQuery{Window: MoneyToday, Income: income}, true
		}
		return MoneyQuery{Window: MoneyUnsupported, Income: income}, true
	}
	switch {
	// The remaining unsupported windows, claimed so they are refused rather
	// than silently answered with the month.
	case hasWord(toks, []string{"неделя", "год"}),
		strings.Contains(lower, "yesterday") || strings.Contains(lower, "week") ||
			strings.Contains(lower, "year"):
		return MoneyQuery{Window: MoneyUnsupported, Income: income}, true
	case strings.Contains(lower, "today"):
		return MoneyQuery{Window: MoneyToday, Income: income}, true
	}
	return MoneyQuery{Window: MoneyMonth, Income: income}, true
}
