package web import ( "net/http" "strings" "music-server/internal/backend" "music-server/internal/logging" "go.uber.org/zap" ) // NewSearchHandler returns a handler that renders the soundtracks matching the posted search term. func NewSearchHandler(cache *backend.SearchCache) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, "Bad Request", http.StatusBadRequest) return } soundtracks, err := cache.Get(r.Context()) if err != nil { logging.GetLogger().Error("Failed to load search data", zap.Error(err)) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } results := search(r.FormValue("search_term"), soundtracks) if err := FoundSoundtracks(results).Render(r.Context(), w); err != nil { logging.GetLogger().Error("Failed to render search results", zap.Error(err)) http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }) } // search returns the soundtracks matching searchText: exact matches first, then matches // ignoring spaces and special characters, then matches with the letters in order. func search(searchText string, soundtrackData []backend.SoundtrackData) []string { var results []string seen := make(map[string]bool) add := func(match func(backend.SoundtrackData) bool) { for _, data := range soundtrackData { if !seen[data.Original] && match(data) { results = append(results, data.Original) seen[data.Original] = true } } } lowercase := strings.ToLower(searchText) cleaned := backend.CleanSearchTerm(searchText) add(func(d backend.SoundtrackData) bool { return strings.Contains(d.Lowercase, lowercase) }) add(func(d backend.SoundtrackData) bool { return strings.Contains(d.Cleaned, cleaned) }) add(func(d backend.SoundtrackData) bool { return backend.MatchesSubsequence(cleaned, d.Cleaned) }) return results }