// Elliptical follow-ups — "а завтра?" after "какие напоминания на сегодня".
//
// These carry no intent of their own. Two words, one of them a particle, and
// everything that makes the utterance meaningful lives in the turn before it.
// Sent to the router they get whatever the model guesses, which on a 1.7B is
// close to a coin flip, and the guess costs ~2.7s to obtain.
//
// followUpMerge (followup.go) cannot help: it inherits SLOTS once the intent is
// known, and here the intent is the missing part. So this runs before the
// router and answers from the previous turn directly, which is both correct by
// construction and free.
package main

import (
	"time"

	"github.com/kami/maven/internal/dialogue"
	"github.com/kami/maven/internal/router"
)

// continuationMaxTokens — an ellipsis is short by definition. Past four tokens
// the utterance carries enough of its own content to be routed on its merits,
// and inheriting an intent for it would be overreach.
const continuationMaxTokens = 4

// continuationParticles — the words that open a follow-up. A leading particle
// is one of the two ways in; the other is an utterance that is nothing but a
// date ("завтра?").
var continuationParticles = map[string]bool{
	"а": true, "и": true, "ну": true,
	"what": true, "and": true, "how": true,
}

// continuableIntents — which intents an ellipsis may inherit.
//
// query and system are questions: asking the same question about a different
// day is exactly what "а завтра?" means, and re-aiming the Time slot answers it
// completely.
//
// The rest are excluded on purpose. fact and note would write something he did
// not say — "поужинал" then "а вчера?" is a question about yesterday, not a
// claim about it. chat has no slot to re-aim. act is the dangerous one: an
// allowlisted fn inherited by a two-word utterance is a way to run a
// destructive command nobody typed, and no follow-up is worth that.
//
// reminder was in this list and came out after a live check on 01-08-2026. A
// reminder's payload is its Text, and the Text embeds the day word it was
// created with: continuing "напомни сегодня о событиях" with "а завтра?" fires
// tomorrow with the text still reading "сегодня". Re-aiming Time is not enough
// when the day is also written into the payload, and rewriting the payload
// needs the date's span in the string, which ParseCalendarDate does not report.
var continuableIntents = map[dialogue.Intent]bool{
	dialogue.IntentQuery:  true,
	dialogue.IntentSystem: true,
}

// continuationDecision reads an utterance as "the previous question, but for
// this other day". Returns ok=false whenever anything is uncertain, which
// hands the turn back to the ordinary router path.
//
// The date is what makes this safe. An ellipsis with no parseable day is just
// a short utterance, and short utterances are the router's job.
func continuationDecision(prev *dialogue.Session, text string, now time.Time) (router.Decision, bool) {
	if prev == nil || prev.IsExpired(now) || !continuableIntents[prev.Intent] {
		return router.Decision{}, false
	}
	tokens := quietTokens(text)
	if len(tokens) == 0 || len(tokens) > continuationMaxTokens {
		return router.Decision{}, false
	}
	day, ok := router.ParseCalendarDate(text, now)
	if !ok {
		return router.Decision{}, false
	}
	// Either it opens with a particle, or the whole utterance is the date.
	if !continuationParticles[tokens[0]] && !isBareDate(tokens, day, now) {
		return router.Decision{}, false
	}

	dec := router.Decision{
		Utterance:  text,
		Intent:     router.Intent(prev.Intent),
		Confidence: 1.0,
		Stage:      0,
		Continued:  true,
		Slots: router.Slots{
			Key:    prev.Slots.Key,
			HasKey: prev.Slots.HasKey,
			Value:  prev.Slots.Value,
			Text:   prev.Slots.Text,
			// Fn/Args are deliberately not carried: continuableIntents
			// excludes act, so there is never one to carry.
			Time:    day,
			HasTime: true,
		},
	}
	return dec, true
}

// isBareDate reports whether the utterance is nothing but its date expression.
// "завтра" and "на выходных" qualify; "напомни завтра" does not, because the
// verb is content of its own and belongs to the router.
//
// Implemented by re-parsing each token: if every token that is not part of a
// date expression is a preposition or a question mark's leftovers, the
// utterance is bare. Cheap enough at four tokens.
func isBareDate(tokens []string, day time.Time, now time.Time) bool {
	for _, t := range tokens {
		if continuationFillers[t] {
			continue
		}
		if d, ok := router.ParseCalendarDate(t, now); ok && d.Equal(day) {
			continue
		}
		return false
	}
	return true
}

// continuationFillers — tokens that carry no content of their own inside a
// date expression ("на выходных", "в среду").
var continuationFillers = map[string]bool{
	"на": true, "в": true, "во": true, "за": true, "про": true,
	"about": true, "on": true, "for": true,
}
