1package usecase
2
3import (
4 "context"
5
6 "github.com/bnema/dumber/internal/domain/autocomplete"
7 "github.com/bnema/dumber/internal/logging"
8)
9
10// AutocompleteUseCase handles inline autocomplete suggestions.
11type AutocompleteUseCase struct {
12 historyUC *SearchHistoryUseCase
13 favoritesUC *ManageFavoritesUseCase
14 shortcutsUC *SearchShortcutsUseCase
15}
16
17// NewAutocompleteUseCase creates a new autocomplete use case.
18func NewAutocompleteUseCase(
19 historyUC *SearchHistoryUseCase,
20 favoritesUC *ManageFavoritesUseCase,
21 shortcutsUC *SearchShortcutsUseCase,
22) *AutocompleteUseCase {
23 return &AutocompleteUseCase{
24 historyUC: historyUC,
25 favoritesUC: favoritesUC,
26 shortcutsUC: shortcutsUC,
27 }
28}
29
30// GetSuggestionInput contains parameters for getting an autocomplete suggestion.
31type GetSuggestionInput struct {
32 Input string // Current user input
33}
34
35// GetSuggestionOutput contains the best autocomplete suggestion.
36type GetSuggestionOutput struct {
37 Suggestion *autocomplete.Suggestion
38 Found bool
39}
40
41// GetSuggestion returns the best autocomplete suggestion for the given input.
42// Priority: Favorites > History (by frecency) > Bang shortcuts.
43func (uc *AutocompleteUseCase) GetSuggestion(ctx context.Context, input GetSuggestionInput) *GetSuggestionOutput {
44 log := logging.FromContext(ctx)
45
46 if input.Input == "" {
47 return &GetSuggestionOutput{Found: false}
48 }
49
50 // Try favorites first (highest priority)
51 if suggestion := uc.findFavoriteSuggestion(ctx, input.Input); suggestion != nil {
52 log.Debug().
53 Str("input", input.Input).
54 Str("match", suggestion.FullText).
55 Msg("autocomplete: matched favorite")
56 return &GetSuggestionOutput{Suggestion: suggestion, Found: true}
57 }
58
59 // Try history (second priority)
60 if suggestion := uc.findHistorySuggestion(ctx, input.Input); suggestion != nil {
61 log.Debug().
62 Str("input", input.Input).
63 Str("match", suggestion.FullText).
64 Msg("autocomplete: matched history")
65 return &GetSuggestionOutput{Suggestion: suggestion, Found: true}
66 }
67
68 // Try bang shortcuts (lowest priority for URL-style completion)
69 if suggestion := uc.findBangSuggestion(ctx, input.Input); suggestion != nil {
70 log.Debug().
71 Str("input", input.Input).
72 Str("match", suggestion.FullText).
73 Msg("autocomplete: matched bang shortcut")
74 return &GetSuggestionOutput{Suggestion: suggestion, Found: true}
75 }
76
77 return &GetSuggestionOutput{Found: false}
78}
79
80// findFavoriteSuggestion finds the best matching favorite for the input.
81func (uc *AutocompleteUseCase) findFavoriteSuggestion(ctx context.Context, input string) *autocomplete.Suggestion {
82 if uc.favoritesUC == nil {
83 return nil
84 }
85
86 favorites, err := uc.favoritesUC.GetAll(ctx)
87 if err != nil {
88 return nil
89 }
90
91 for _, fav := range favorites {
92 suffix, matchedURL, ok := autocomplete.ComputeURLCompletionSuffix(input, fav.URL)
93 if ok {
94 return &autocomplete.Suggestion{
95 FullText: matchedURL,
96 Suffix: suffix,
97 Source: autocomplete.SourceFavorite,
98 Title: fav.Title,
99 }
100 }
101 }
102
103 return nil
104}
105
106// findHistorySuggestion finds the best matching history entry for the input.
107func (uc *AutocompleteUseCase) findHistorySuggestion(ctx context.Context, input string) *autocomplete.Suggestion {
108 if uc.historyUC == nil {
109 return nil
110 }
111
112 // Search history for matches
113 output, err := uc.historyUC.Search(ctx, SearchInput{
114 Query: input,
115 Limit: 10, // Get top 10 to find best prefix match
116 })
117 if err != nil || output == nil {
118 return nil
119 }
120
121 // Find the first result that is a prefix match
122 for _, match := range output.Matches {
123 suffix, matchedURL, ok := autocomplete.ComputeURLCompletionSuffix(input, match.Entry.URL)
124 if ok {
125 return &autocomplete.Suggestion{
126 FullText: matchedURL,
127 Suffix: suffix,
128 Source: autocomplete.SourceHistory,
129 Title: match.Entry.Title,
130 }
131 }
132 }
133
134 return nil
135}
136
137// findBangSuggestion finds the best matching bang shortcut for the input.
138func (uc *AutocompleteUseCase) findBangSuggestion(ctx context.Context, input string) *autocomplete.Suggestion {
139 if uc.shortcutsUC == nil || len(input) < 1 || input[0] != '!' {
140 return nil
141 }
142
143 // Get filtered bang suggestions
144 output := uc.shortcutsUC.FilterBangs(ctx, FilterBangsInput{Query: input})
145 if len(output.Suggestions) == 0 {
146 return nil
147 }
148
149 // Use the first (best) match
150 bang := output.Suggestions[0]
151 fullBang := "!" + bang.Key
152
153 suffix, ok := autocomplete.ComputeCompletionSuffix(input, fullBang)
154 if ok {
155 return &autocomplete.Suggestion{
156 FullText: fullBang,
157 Suffix: suffix,
158 Source: autocomplete.SourceBangShortcut,
159 Title: bang.Description,
160 }
161 }
162
163 return nil
164}
165
166// GetSuggestionForURL returns an autocomplete suggestion specifically for a URL.
167// This is used when the user selects a row from the list to update ghost text.
168func (*AutocompleteUseCase) GetSuggestionForURL(_ context.Context, input, targetURL string) *GetSuggestionOutput {
169 if input == "" || targetURL == "" {
170 return &GetSuggestionOutput{Found: false}
171 }
172
173 suffix, matchedURL, ok := autocomplete.ComputeURLCompletionSuffix(input, targetURL)
174 if !ok {
175 return &GetSuggestionOutput{Found: false}
176 }
177
178 return &GetSuggestionOutput{
179 Suggestion: &autocomplete.Suggestion{
180 FullText: matchedURL,
181 Suffix: suffix,
182 Source: autocomplete.SourceHistory, // Default to history for selected rows
183 },
184 Found: true,
185 }
186}