package router

import (
	"fmt"
	"strings"
	"time"
	"unicode"
)

// CalendarEventFormatter formats calendar events into a Russian reply string.
type CalendarEventFormatter struct{}

// CalendarEntry — one event to recite. Uncertain marks an event maven did not
// read off a calendar server: the work calendar arrives as relayed phone
// notifications (Vikunja #126), stored below full confidence, and she says so
// rather than reciting a guess as fact.
type CalendarEntry struct {
	Text      string
	Uncertain bool
}

// dayPlanWords — the tokens that ask for the day as a whole rather than for a
// calendar listing. Whole words, not substrings: "планёрка" is a MEETING, and a
// notification about one must not be mistaken for a request for the plan.
var dayPlanWords = []string{
	"план", "плана", "плану", "плане", "планом",
	"планы", "планов", "планам", "планах",
	"расписание", "расписании", "распорядок", "распорядке",
	"plan", "plans", "schedule", "agenda",
}

// otherDayWords — a span that is not the clock's own day. The plan can only be
// built for today, so an utterance naming another day, a weekday, a week or a
// weekend belongs to the calendar listing instead. Claiming it here would
// answer today and stamp it with today's date, which is a wrong answer where
// falling through is only a terse one.
//
// A weekday is a refusal too, and it is not in this list: IsDayPlanQuery asks
// WeekdayIndex, so every case of every name refuses rather than the nine forms
// that used to be written out here (V-581). "какие планы на понедельник?"
// carries no other-day token in the сегодня family and does carry "планы", so
// the plan used to claim it and recite today.
var otherDayWords = []string{
	"завтра", "послезавтра", "вчера", "позавчера",
	"tomorrow", "yesterday",
	"неделю", "неделя", "недели", "неделе",
	"выходные", "выходных", "выходным",
	"месяц", "месяца", "месяце",
	"week", "weekend", "month",
}

// IsDayPlanQuery reports whether an utterance asks for today's plan (Vikunja
// #128) — "какие планы на сегодня?", "что у меня по плану?", "что дальше?".
//
// Deliberately narrow. The calendar listing already answers "что у меня
// сегодня?" and a plan that hijacks every date-bearing question would bury the
// events under checklist lines. Only a plan-shaped ask, and only about today.
func IsDayPlanQuery(text string) bool {
	// A habit question is never a day plan, whatever words it shares with one.
	// "какие у меня обычно планы по вторникам?" carries "планы", so the plan
	// source claimed it and answered today's calendar stamped with today's
	// date, and the habit source never ran. Deciding it here rather than by
	// reordering the source table keeps one matcher from depending on the
	// other's position in a slice.
	if _, ok := ParseHabitQuery(text); ok {
		return false
	}
	toks := planTokens(text)
	for _, t := range toks {
		if _, ok := WeekdayIndex(t); ok {
			return false
		}
		for _, w := range otherDayWords {
			if t == w {
				return false
			}
		}
	}
	for _, t := range toks {
		for _, w := range dayPlanWords {
			if t == w {
				return true
			}
		}
	}
	// "что дальше?" / "what's next?" — the rest of the day, with no plan word
	// in it. Both tokens rather than adjacency, because "what's" splits into
	// "what" and "s" and because "и что потом дальше" is the same question.
	return (hasTok(toks, "что") && hasTok(toks, "дальше")) ||
		(hasTok(toks, "what") && hasTok(toks, "next"))
}

// IsRestOfDayQuery reports whether the utterance asks for what is left of the
// day rather than for the whole of it — "что дальше?" and its English form.
//
// Tokenized for the same reason IsDayPlanQuery is: the substring form matched
// "дальше" inside longer words and "next" inside "nextcloud", and the two
// predicates deciding the same utterance differently is worse than either
// being wrong on its own.
func IsRestOfDayQuery(text string) bool {
	toks := planTokens(text)
	return hasTok(toks, "дальше") || hasTok(toks, "next")
}

func hasTok(toks []string, w string) bool {
	for _, t := range toks {
		if t == w {
			return true
		}
	}
	return false
}

// planTokens lowercases and splits on everything that is not a letter or a
// digit, so "планы?" and "что-дальше" tokenize like the plain words do.
func planTokens(text string) []string {
	return strings.FieldsFunc(strings.ToLower(text), func(r rune) bool {
		return !unicode.IsLetter(r) && !unicode.IsDigit(r)
	})
}

// Format returns a Russian reply for the given calendar events on the given
// date. Every event is treated as certain — use FormatEntries when provenance
// differs between them.
func (f CalendarEventFormatter) Format(events []string, date time.Time) string {
	entries := make([]CalendarEntry, len(events))
	for i, e := range events {
		entries[i] = CalendarEntry{Text: e}
	}
	return f.FormatEntries(entries, date)
}

// FormatEntries returns a Russian reply, hedging the entries maven is not sure
// about. "похоже" and not "возможно": the notification did arrive, what is
// uncertain is whether it describes the meeting correctly.
func (CalendarEventFormatter) FormatEntries(entries []CalendarEntry, date time.Time) string {
	dateStr := date.Format("02.01.2006")
	if len(entries) == 0 {
		return fmt.Sprintf("на %s ничего нет.", dateStr)
	}
	parts := make([]string, len(entries))
	for i, e := range entries {
		if e.Uncertain {
			parts[i] = "похоже, " + e.Text
			continue
		}
		parts[i] = e.Text
	}
	return fmt.Sprintf("на %s: %s", dateStr, strings.Join(parts, "; "))
}
