use super::{filter::Filter, search::SearchState, sort::SortCriteria};
use crate::priority::UnifiedAnalysis;
#[derive(Debug)]
pub struct QueryState {
filtered_indices: Vec<usize>,
search: SearchState,
filters: Vec<Filter>,
sort_by: SortCriteria,
}
impl QueryState {
pub fn new(item_count: usize) -> Self {
Self {
filtered_indices: (0..item_count).collect(),
search: SearchState::new(),
filters: Vec::new(),
sort_by: SortCriteria::Score,
}
}
pub fn filtered_indices(&self) -> &[usize] {
&self.filtered_indices
}
pub fn filtered_indices_mut(&mut self) -> &mut Vec<usize> {
&mut self.filtered_indices
}
pub fn search(&self) -> &SearchState {
&self.search
}
pub fn search_mut(&mut self) -> &mut SearchState {
&mut self.search
}
pub fn filters(&self) -> &[Filter] {
&self.filters
}
pub fn sort_by(&self) -> SortCriteria {
self.sort_by
}
pub fn set_sort_by(&mut self, criteria: SortCriteria, analysis: &UnifiedAnalysis) {
self.sort_by = criteria;
self.apply_sort(analysis);
}
pub fn apply_search(&mut self, analysis: &UnifiedAnalysis) {
let query = self.search.query();
self.filtered_indices = if query.is_empty() {
(0..analysis.items.len()).collect()
} else {
super::search::filter_items(analysis, query)
};
self.apply_filters(analysis);
self.apply_sort(analysis);
}
pub fn add_filter(&mut self, filter: Filter, analysis: &UnifiedAnalysis) {
self.filters.push(filter);
self.reapply_all(analysis);
}
pub fn remove_filter(&mut self, index: usize, analysis: &UnifiedAnalysis) {
if index < self.filters.len() {
self.filters.remove(index);
self.reapply_all(analysis);
}
}
pub fn clear_filters(&mut self, analysis: &UnifiedAnalysis) {
self.filters.clear();
self.reapply_all(analysis);
}
fn apply_filters(&mut self, analysis: &UnifiedAnalysis) {
if self.filters.is_empty() {
return;
}
self.filtered_indices.retain(|&idx| {
analysis
.items
.get(idx)
.map(|item| self.filters.iter().all(|f| f.matches(item)))
.unwrap_or(false)
});
}
fn apply_sort(&mut self, analysis: &UnifiedAnalysis) {
super::sort::sort_indices(&mut self.filtered_indices, analysis, self.sort_by);
}
fn reapply_all(&mut self, analysis: &UnifiedAnalysis) {
let query = self.search.query();
self.filtered_indices = if query.is_empty() {
(0..analysis.items.len()).collect()
} else {
super::search::filter_items(analysis, query)
};
self.apply_filters(analysis);
self.apply_sort(analysis);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_empty_analysis() -> UnifiedAnalysis {
UnifiedAnalysis::new(crate::priority::call_graph::CallGraph::new())
}
#[test]
fn test_new_query_state() {
let state = QueryState::new(10);
assert_eq!(state.filtered_indices().len(), 10);
assert_eq!(state.sort_by(), SortCriteria::Score);
assert!(state.filters().is_empty());
}
#[test]
fn test_new_query_state_empty() {
let state = QueryState::new(0);
assert!(state.filtered_indices().is_empty());
}
#[test]
fn test_sort_by_change() {
let analysis = create_empty_analysis();
let mut state = QueryState::new(0);
state.set_sort_by(SortCriteria::Complexity, &analysis);
assert_eq!(state.sort_by(), SortCriteria::Complexity);
}
#[test]
fn test_search_state_access() {
let mut state = QueryState::new(5);
state.search_mut().set_query("test".to_string());
assert_eq!(state.search().query(), "test");
}
#[test]
fn test_filtered_indices_mut() {
let mut state = QueryState::new(10);
state.filtered_indices_mut().clear();
assert!(state.filtered_indices().is_empty());
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
fn create_empty_analysis() -> UnifiedAnalysis {
UnifiedAnalysis::new(crate::priority::call_graph::CallGraph::new())
}
proptest! {
#[test]
fn new_state_has_sequential_indices(count in 0usize..1000) {
let state = QueryState::new(count);
let indices = state.filtered_indices();
prop_assert_eq!(indices.len(), count);
for (i, &idx) in indices.iter().enumerate() {
prop_assert_eq!(idx, i);
}
}
#[test]
fn new_state_has_no_filters(count in 0usize..100) {
let state = QueryState::new(count);
prop_assert!(state.filters().is_empty());
}
#[test]
fn sort_criteria_preserved(criteria_idx in 0usize..5) {
let criteria = match criteria_idx {
0 => SortCriteria::Score,
1 => SortCriteria::Complexity,
2 => SortCriteria::FilePath,
3 => SortCriteria::FunctionName,
_ => SortCriteria::Coverage,
};
let analysis = create_empty_analysis();
let mut state = QueryState::new(0);
state.set_sort_by(criteria, &analysis);
prop_assert_eq!(state.sort_by(), criteria);
}
#[test]
fn no_duplicate_indices(count in 0usize..100) {
let state = QueryState::new(count);
let indices = state.filtered_indices();
let mut seen = std::collections::HashSet::new();
for &idx in indices {
prop_assert!(
seen.insert(idx),
"Duplicate index {} found",
idx
);
}
}
#[test]
fn indices_within_bounds(count in 1usize..100) {
let state = QueryState::new(count);
for &idx in state.filtered_indices() {
prop_assert!(idx < count, "Index {} >= count {}", idx, count);
}
}
#[test]
fn search_query_roundtrip(query in ".*") {
let mut state = QueryState::new(0);
state.search_mut().set_query(query.clone());
prop_assert_eq!(state.search().query(), &query);
}
}
}