// Spoken snooze — "не сейчас", "потом", "отложи" said out loud after a nudge
// resolves it as `snoozed`, the same outcome the Telegram buttons and the web
// UI write. Until this existed, a nudge could only be deferred by touching a
// screen: the voice path had no way to reach store.ResolveNudge at all, so the
// one channel she nudges on hardest was the one channel he could not answer.
package main

import (
	"context"
	"log"
	"time"

	"github.com/kami/maven/internal/ipc"
	"github.com/kami/maven/internal/phraser"
	"github.com/kami/maven/internal/store"
)

// snoozeWindow — how long after a send "потом" still means "that nudge".
//
// A window is what makes this safe to run before the router. "потом" is an
// ordinary Russian word; eating every one of them would break real sentences.
// Bounded to the minutes right after she spoke, the word is almost always an
// answer to what she just said, and outside the window the utterance falls
// through and routes normally.
//
// Twenty minutes rather than the two hours of store.SnoozeDuration: those
// measure different things. SnoozeDuration is how long the quiet lasts,
// snoozeWindow is how long an unanswered nudge stays the topic of the
// conversation.
const snoozeWindow = 20 * time.Minute

// snoozeScan — how many recent nudges to look at when finding the target. The
// newest pending one is nearly always the first row; a handful of resolved
// rows can sit in front of it when he acked a few in a row.
const snoozeScan = 10

// resolveSnooze — pre-route keyword check, run after the quiet toggle. Returns
// (reply, true) when the utterance defers a nudge she recently sent.
//
// It returns ("", false) in two different situations, on purpose: the words do
// not read as a deferral, or they do but there is nothing pending to defer. In
// both the turn keeps routing, so "потом посмотрю что там с бэкапом" is still
// a query when no nudge is outstanding.
func (h *reactiveHandler) resolveSnooze(ctx context.Context, text string, src turnSource) (string, bool) {
	if !classifySnooze(text) {
		return "", false
	}
	now := h.now()
	target, ok := h.pendingNudge(ctx, now)
	if !ok {
		return "", false
	}
	if err := h.api.ResolveNudge(ctx, target.ID, store.NudgeSnoozed, now); err != nil {
		log.Printf("voice: snooze nudge %d (%s, %s): %v", target.ID, target.Rule, src, err)
		return phraser.Ack(phraser.FailSnooze, nil), true
	}
	log.Printf("voice: snoozed nudge %d (rule %s) from %s", target.ID, target.Rule, src)
	return phraser.Ack(phraser.AckSnooze, nil), true
}

// pendingNudge — the newest still-pending nudge sent inside snoozeWindow.
//
// Channel is deliberately not filtered. A nudge that went to Telegram is still
// the thing he is answering when he says "потом" at the microphone, and making
// the reply channel decide which nudges are answerable would mean the ops page
// he actually read could not be dismissed by voice.
func (h *reactiveHandler) pendingNudge(ctx context.Context, now time.Time) (ipc.Nudge, bool) {
	recent, err := h.api.RecentNudges(ctx, snoozeScan)
	if err != nil {
		log.Printf("voice: recent nudges for snooze: %v", err)
		return ipc.Nudge{}, false
	}
	for _, n := range recent {
		if n.Outcome != store.NudgePending {
			continue
		}
		if now.Sub(n.Ts) > snoozeWindow || n.Ts.After(now) {
			continue
		}
		return n, true
	}
	return ipc.Nudge{}, false
}

// snoozePhrases — the deferral vocabulary. Matched by quietPhrase and quietStem
// (quiet_toggle.go), so an adverb is matched through the dictionary and a verb
// exactly, prefixed with "=": "напомни" is a request and "напомнил" is a report
// about earlier, and the dictionary files both under напомнить (Vikunja #526).
// The verbs used to be truncated stems — "напомн", "отлож" — which is what the
// deleted ending list existed to complete.
//
// quietPhrase carries the rule that matters here:
// a single-word pattern matches only a single-word utterance. Bare "потом" is
// an answer; "потом схожу за водой" is a plan, and reporting a plan must not
// silence the rule that prompted it.
var snoozePhrases = [][]string{
	{"не", "сейчас"}, {"не", "могу", "сейчас"}, {"не", "до", "этого"},
	{"=напомни|=напоминай", "позже"}, {"=напомни|=напоминай", "потом"},
	{"=спроси|=спрашивай", "позже"},
	{"=отложи|=отложим|=откладывай"}, {"позже"}, {"потом"}, {"попозже"},
	{"=погоди|=погодите"},
	{"not", "now"}, {"later"}, {"snooze"}, {"remind", "me", "later"},
}

// classifySnooze reads an utterance as a deferral. Unlike the quiet toggle
// there is no negation arm: "не потом" is not something anyone says, and the
// leading "не" of "не сейчас" is part of the phrase itself.
func classifySnooze(text string) bool {
	tokens := quietTokens(text)
	for _, p := range snoozePhrases {
		if quietPhrase(tokens, p) {
			return true
		}
	}
	return false
}
