package strimport ("regexp""strings""github.com/deluan/sanitize")// FTSPunctStrip matches any character that is not a letter or number. Index-time// normalization (NormalizeForFTS) and query-time processing in persistence share it// so both sides produce matching tokens.var FTSPunctStrip = regexp.MustCompile(`[^\p{L}\p{N}]`)// NormalizeForFTS takes multiple strings and returns a space-separated, deduplicated list of// alternative searchable forms for each word: punctuation-stripped (R.E.M. β REM, AC/DC β ACDC)// and ASCII-transliterated (BjΓΈrk β Bjork, Εuvre β oeuvre). The transliterated form is needed// because FTS5's `unicode61 remove_diacritics 2` only handles NFKD-decomposable diacritics β// atomic letters like ΓΈ/Γ¦/Ε/Γ survive tokenization, so the query side and index side disagree// without an explicit transliterated entry here.func NormalizeForFTS(values ...string) string {seen := make(map[string]struct{})var result []stringadd := func(orig, variant string) {if variant == "" || variant == orig {return}lower := strings.ToLower(variant)if _, ok := seen[lower]; ok {return}seen[lower] = struct{}{}result = append(result, variant)}for _, v := range values {for word := range strings.FieldsSeq(v) {transliterated := sanitize.Accents(word)// Concatenated ASCII form: R.E.M. β REM, AC/DC β ACDC, St-Γtienne β StEtienne.add(word, FTSPunctStrip.ReplaceAllString(transliterated, ""))// Accent-only transliteration for words without name-punctuation (BjΓΈrk β Bjork).add(word, transliterated)}}return strings.Join(result, " ")}