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 pub fn apply_fuzzy_filter(data: &[Value], pattern: &str, score_threshold: i64) -> Vec<usize> {
61 let matcher = SkimMatcherV2::default();
62 let mut filtered_indices = Vec::new();
63
64 for (idx, item) in data.iter().enumerate() {
65 if let Some(obj) = item.as_object() {
66 let mut best_score = 0i64;
67
68 for (_key, value) in obj {
69 let value_str = match value {
70 Value::String(s) => s.clone(),
71 Value::Number(n) => n.to_string(),
72 Value::Bool(b) => b.to_string(),
73 Value::Null => String::from("null"),
74 _ => value.to_string(),
75 };
76
77 if let Some(score) = matcher.fuzzy_match(&value_str, pattern) {
78 best_score = best_score.max(score);
79 }
80 }
81
82 if best_score > score_threshold {
83 filtered_indices.push(idx);
84 }
85 }
86 }
87
88 filtered_indices
89 }
90
91 pub fn find_matching_columns(headers: &[&str], pattern: &str) -> Vec<(usize, String)> {
93 let pattern_lower = pattern.to_lowercase();
94 let mut matching = Vec::new();
95
96 for (idx, &header) in headers.iter().enumerate() {
97 if header.to_lowercase().contains(&pattern_lower) {
98 matching.push((idx, header.to_string()));
99 }
100 }
101
102 matching
103 }
104
105 pub fn next_match(matches: &[(usize, usize)], current_index: usize) -> Option<(usize, usize)> {
107 if matches.is_empty() {
108 return None;
109 }
110
111 let next_index = (current_index + 1) % matches.len();
112 Some(matches[next_index])
113 }
114
115 pub fn previous_match(
117 matches: &[(usize, usize)],
118 current_index: usize,
119 ) -> Option<(usize, usize)> {
120 if matches.is_empty() {
121 return None;
122 }
123
124 let prev_index = if current_index == 0 {
125 matches.len() - 1
126 } else {
127 current_index - 1
128 };
129
130 Some(matches[prev_index])
131 }
132}