// ttsnorm/pronounce.go — how piper says the latin words that turn up inside a
// Russian sentence: service names, hostnames, acronyms (Vikunja #458).
//
// The RU voice reads latin letters one at a time or guesses, so "netdata" comes
// out as noise and "homesrv" comes out as nothing. The fix is spelling the
// sound in Cyrillic, and the mapping is data — nothing in the code knows any of
// these names, and adding one is an edit to pronounce_ru_v1.json.
//
// This is a rewrite over a latin token, not over Russian morphology: the
// pattern matches [a-z0-9] runs and asks the table. A word the table does not
// hold is left exactly as it was, so a miss is the current behaviour rather
// than a guess.
package ttsnorm

import (
	_ "embed"
	"encoding/json"
	"log"
	"regexp"
	"strings"
)

//go:embed pronounce_ru_v1.json
var pronounceJSON []byte

// pronounceSchemaVersion — the version this loader understands.
const pronounceSchemaVersion = 1

// latinToken — one run of latin letters and digits. Cyrillic is untouched, so a
// Russian word next to an English one is never rewritten by accident.
var latinToken = regexp.MustCompile(`[A-Za-z][A-Za-z0-9]*`)

// pronounce — lowercase word to its Russian spelling. Empty when the file
// failed to load, which leaves speech exactly as it was before this existed.
var pronounce = loadPronounce()

func loadPronounce() map[string]string {
	var f struct {
		SchemaVersion int               `json:"schema_version"`
		Entries       map[string]string `json:"entries"`
	}
	if err := json.Unmarshal(pronounceJSON, &f); err != nil {
		// Speech must not stop because a dictionary is malformed.
		log.Printf("ttsnorm: pronunciation dictionary: %v", err)
		return map[string]string{}
	}
	if f.SchemaVersion != pronounceSchemaVersion {
		log.Printf("ttsnorm: pronunciation dictionary schema_version %d, want %d — not loading it",
			f.SchemaVersion, pronounceSchemaVersion)
		return map[string]string{}
	}
	return f.Entries
}

// Pronounce rewrites every latin word the dictionary knows. Case-insensitive on
// the way in ("Netdata", "NETDATA" and "netdata" are one word) and lowercase on
// the way out, because the value is a sound and not a name.
func Pronounce(s string) string {
	if len(pronounce) == 0 {
		return s
	}
	return latinToken.ReplaceAllStringFunc(s, func(w string) string {
		if say, ok := pronounce[strings.ToLower(w)]; ok {
			return say
		}
		return w
	})
}
