1use anyhow::Result;
2use fuzzy_matcher::skim::SkimMatcherV2;
3use fuzzy_matcher::FuzzyMatcher;
4use regex::Regex;
5use serde_json::Value;
6
7pub struct SearchFilter;
9
10impl SearchFilter {
11 pub fn perform_search(data: &[Vec<String>], pattern: &str) -> Result<Vec<(usize, usize)>> {
13 let mut matches = Vec::new();
14 let regex = Regex::new(pattern)?;
15
16 for (row_idx, row) in data.iter().enumerate() {
17 for (col_idx, cell) in row.iter().enumerate() {
18 if regex.is_match(cell) {
19 matches.push((row_idx, col_idx));
20 }
21 }
22 }
23
24 Ok(matches)
25 }
26
27 pub fn apply_regex_filter(data: &[Value], pattern: &str) -> Result<Vec<Value>> {
29 let regex = Regex::new(pattern)?;
30 let mut filtered = Vec::new();
31
32 for item in data {
33 if let Some(obj) = item.as_object() {
34 let mut matches = false;
35 for (_key, value) in obj {
36 let value_str = match value {
37 Value::String(s) => s.clone(),
38 Value::Number(n) => n.to_string(),
39 Value::Bool(b) => b.to_string(),
40 Value::Null => String::from("null"),
41 _ => value.to_string(),
42 };
43
44 if regex.is_match(&value_str) {
45 matches = true;
46 break;
47 }
48 }
49
50 if matches {
51 filtered.push(item.clone());
52 }
53 }
54 }
55
56 Ok(filtered)
57 }
58
59 #[must_use]
61 pub fn apply_fuzzy_filter(data: &[Value], pattern: &str, score_threshold: i64) -> Vec<usize> {
62 let matcher = SkimMatcherV2::default();
63 let mut filtered_indices = Vec::new();
64
65 for (idx, item) in data.iter().enumerate() {
66 if let Some(obj) = item.as_object() {
67 let mut best_score = 0i64;
68
69 for (_key, value) in obj {
70 let value_str = match value {
71 Value::String(s) => s.clone(),
72 Value::Number(n) => n.to_string(),
73 Value::Bool(b) => b.to_string(),
74 Value::Null => String::from("null"),
75 _ => value.to_string(),
76 };
77
78 if let Some(score) = matcher.fuzzy_match(&value_str, pattern) {
79 best_score = best_score.max(score);
80 }
81 }
82
83 if best_score > score_threshold {
84 filtered_indices.push(idx);
85 }
86 }
87 }
88
89 filtered_indices
90 }
91
92 #[must_use]
94 pub fn find_matching_columns(headers: &[&str], pattern: &str) -> Vec<(usize, String)> {
95 let pattern_lower = pattern.to_lowercase();
96 let mut matching = Vec::new();
97
98 for (idx, &header) in headers.iter().enumerate() {
99 if header.to_lowercase().contains(&pattern_lower) {
100 matching.push((idx, header.to_string()));
101 }
102 }
103
104 matching
105 }
106
107 #[must_use]
109 pub fn next_match(matches: &[(usize, usize)], current_index: usize) -> Option<(usize, usize)> {
110 if matches.is_empty() {
111 return None;
112 }
113
114 let next_index = (current_index + 1) % matches.len();
115 Some(matches[next_index])
116 }
117
118 #[must_use]
120 pub fn previous_match(
121 matches: &[(usize, usize)],
122 current_index: usize,
123 ) -> Option<(usize, usize)> {
124 if matches.is_empty() {
125 return None;
126 }
127
128 let prev_index = if current_index == 0 {
129 matches.len() - 1
130 } else {
131 current_index - 1
132 };
133
134 Some(matches[prev_index])
135 }
136}