package router

import (
	"strconv"
	"strings"

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

// hourNouns — the two words that are the hour noun as often as they are the
// number one. "в час дня" means one o'clock, so rewriting it to "в 1 дня" is
// right either way.
//
// They are not cardinals and do not belong in the lexicon's number set: nobody
// counts "час яблок". Everything else this file reads comes from
// lexicon.Cardinal, which is where the number words live complete, oblique forms
// included (Vikunja #530). The table here used to be a second copy that stopped
// at fifty and disagreed with the lexicon about its own members.
var hourNouns = map[string]string{"час": "1", "часу": "1"}

// numeralDigit reports the digits a spoken number is written as, for a clock
// hour or the minutes after it.
func numeralDigit(word string) (string, bool) {
	if d, ok := hourNouns[word]; ok {
		return d, true
	}
	n, ok := lexicon.Cardinal(word)
	if !ok {
		return "", false
	}
	return strconv.Itoa(n), true
}

// numeralContext — the words that make a numeral a time. A numeral is only
// rewritten when one of these sits next to it, so "три яблока" in a note is
// left alone and "в три часа" is not.
var numeralContext = map[string]bool{
	"в": true, "во": true, "к": true, "около": true, "на": true,
	"часа": true, "часов": true, "час": true, "часу": true,
	"утра": true, "вечера": true, "дня": true, "ночи": true,
	"минут": true, "минуты": true, "минуту": true,
	"at": true, "by": true,
}

// SpellOutDigits rewrites spoken numbers as digits so the date parsers see the
// shape they know.
//
// "напомни мне позвонить маме в семь вечера" parsed to nothing, while "в 19:00"
// parsed fine (Vikunja #469). Speech is where reminders come from, and speech
// says the hour in words, so this is not a long-tail case — it is the ordinary
// one. dateparser reads "в 7 вечера" through the qualifier rewrite the python
// script already does; it does not read "в семь вечера".
//
// Conservative by construction: a numeral is only rewritten when a time word
// stands beside it. "три часа" becomes "3 часа"; "три яблока" stays as it is,
// and a note or a fact carrying a spoken number is untouched.
func SpellOutDigits(text string) string {
	// A half hour and a quarter-to hour are phrases rather than numerals, and
	// the hour they name is one less than the word in them (V-538). They are
	// rewritten whole, before the token pass, and come out as digits it leaves
	// alone.
	text = rewriteHalfPast(text)
	toks := strings.Fields(text)
	if len(toks) == 0 {
		return text
	}
	out := make([]string, len(toks))
	copy(out, toks)
	for i, tok := range toks {
		key := strings.ToLower(strings.Trim(tok, ".,!?;:«»\"'"))
		digit, ok := numeralDigit(key)
		if !ok {
			continue
		}
		// "час" and "часу" are the hour noun as often as they are the number
		// one, and rewriting "в час дня" to "в 1 дня" is right either way. What
		// must not happen is rewriting the noun that gives another numeral its
		// context: "в семь часов" must keep "часов".
		if !hasTimeNeighbour(toks, i) {
			continue
		}
		out[i] = digit
	}
	return strings.Join(out, " ")
}

// hasTimeNeighbour reports whether the token before or after i is a time word.
func hasTimeNeighbour(toks []string, i int) bool {
	for _, j := range []int{i - 1, i + 1} {
		if j < 0 || j >= len(toks) {
			continue
		}
		if numeralContext[strings.ToLower(strings.Trim(toks[j], ".,!?;:«»\"'"))] {
			return true
		}
	}
	return false
}
