// Package ttsnorm rewrites machine-formatted dates/times/numbers into RU text
// a TTS voice speaks naturally — so "10.07.2026" is not read as "number dot
// number dot number". Pure, deterministic; runs on reply/nudge text before synth.
package ttsnorm

import (
	"regexp"
	"strconv"
	"strings"

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

// The month names are a closed class and live in internal/lexicon, 1-indexed,
// which is also where the voice reply path reads them. There used to be a second
// copy of the twelve names in cmd/mavend/ruwords.go (Vikunja #525).

var (
	reDateY = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\.(\d{4})\b`)
	reDate  = regexp.MustCompile(`\b(\d{1,2})\.(\d{1,2})\b`)
	reTime  = regexp.MustCompile(`\b(\d{1,2}):(\d{2})\b`)
	reDots  = regexp.MustCompile(`\b\d+(?:\.\d+){2,}\b`)
)

// Speakable rewrites d.m.y, d.m, h:mm, and residual dotted-number runs.
// Order matters: dates with year first, then times, then multi-dot numbers
// (3+ parts — never valid dates), then 2-part dates.
func Speakable(s string) string {
	s = reDateY.ReplaceAllStringFunc(s, func(m string) string {
		p := reDateY.FindStringSubmatch(m)
		return spokenDate(p[1], p[2], p[3])
	})
	s = reTime.ReplaceAllStringFunc(s, func(m string) string {
		p := reTime.FindStringSubmatch(m)
		return spokenTime(mustInt(p[1]), mustInt(p[2]))
	})
	s = reDots.ReplaceAllStringFunc(s, func(m string) string {
		return strings.Join(strings.Split(m, "."), " точка ")
	})
	s = reDate.ReplaceAllStringFunc(s, func(m string) string {
		p := reDate.FindStringSubmatch(m)
		return spokenDate(p[1], p[2], "")
	})
	// Last, so a hostname is spelled out after the numbers around it are
	// already words and no rewrite above can see Cyrillic it did not expect.
	return Pronounce(s)
}

// spokenTime reads a clock time the way it is said rather than the way it is
// written. Two things the written form gets wrong out loud. The noun after a
// numeral inflects, so 21:00 is "час" and 22:00 is "часа", where the old
// rewrite said "часов" for every hour and "минут" for every minute. And a
// leading zero is punctuation, not a word: 14:00 is "14 часов" and 9:05 is
// "9 часов 5 минут", never "00 минут" or "05 минут".
func spokenTime(h, m int) string {
	out := strconv.Itoa(h) + " " + say.CountWord(h, "час", "часа", "часов")
	if m == 0 {
		return out
	}
	return out + " " + strconv.Itoa(m) + " " + say.CountWord(m, "минута", "минуты", "минут")
}

func spokenDate(dd, mm, yyyy string) string {
	mi, _ := strconv.Atoi(mm)
	if mi < 1 || mi > 12 {
		return dd + " " + mm + gap(yyyy)
	}
	day := strconv.Itoa(mustInt(dd))
	out := day + " " + lexicon.MonthGenitive(mi)
	if yyyy != "" {
		out += " " + yyyy
	}
	return out
}

func mustInt(s string) int { n, _ := strconv.Atoi(s); return n }
func gap(y string) string {
	if y == "" {
		return ""
	}
	return " " + y
}
