use crate::app::managers::queue_stats_manager::QueueStatsManager;
use crate::app::updates::messages::MessagePaginationState;
use quetty_server::bulk_operations::MessageIdentifier;
use quetty_server::model::MessageModel;
use quetty_server::service_bus_manager::QueueType;
use std::collections::HashSet;
#[derive(Debug, Default)]
pub struct BulkSelectionState {
pub selected_messages: HashSet<MessageIdentifier>,
pub selection_mode: bool,
pub last_selected_index: Option<usize>,
pub selected_indices: HashSet<usize>,
}
impl BulkSelectionState {
pub fn toggle_selection(&mut self, message_id: MessageIdentifier, index: usize) -> bool {
if self.selected_messages.contains(&message_id) {
self.selected_messages.remove(&message_id);
self.selected_indices.remove(&index);
self.last_selected_index = self.selected_indices.iter().max().copied();
false
} else {
self.selected_messages.insert(message_id);
self.selected_indices.insert(index);
self.last_selected_index = self.selected_indices.iter().max().copied();
true
}
}
pub fn select_all(&mut self, messages: &[MessageModel]) {
for (index, message) in messages.iter().enumerate() {
self.selected_messages
.insert(MessageIdentifier::from_message(message));
self.selected_indices.insert(index);
}
self.last_selected_index = self.selected_indices.iter().max().copied();
if !messages.is_empty() {
self.selection_mode = true;
}
}
pub fn clear_all(&mut self) {
self.selected_messages.clear();
self.selected_indices.clear();
self.last_selected_index = None;
self.selection_mode = false;
}
pub fn selection_count(&self) -> usize {
self.selected_messages.len()
}
pub fn has_selections(&self) -> bool {
!self.selected_messages.is_empty()
}
pub fn enter_selection_mode(&mut self) {
self.selection_mode = true;
}
pub fn exit_selection_mode(&mut self) {
self.clear_all();
}
pub fn get_selected_messages(&self) -> Vec<MessageIdentifier> {
self.selected_messages.iter().cloned().collect()
}
pub fn get_highest_selected_position(&self) -> Option<usize> {
self.selected_indices.iter().max().map(|&index| index + 1)
}
pub fn are_selections_contiguous_from_start(&self) -> bool {
if self.selected_indices.is_empty() {
return true; }
let min_index = self.selected_indices.iter().min().unwrap_or(&0);
let max_index = self.selected_indices.iter().max().unwrap_or(&0);
if *min_index != 0 {
return false;
}
for i in 0..=*max_index {
if !self.selected_indices.contains(&i) {
return false; }
}
true }
pub fn remove_messages(&mut self, message_ids: &[MessageIdentifier]) {
for id in message_ids {
self.selected_messages.remove(id);
}
if self.selected_messages.is_empty() {
self.selection_mode = false;
self.last_selected_index = None;
self.selected_indices.clear();
}
}
pub fn select_all_with_offset(&mut self, messages: &[MessageModel], start_index_offset: usize) {
for (local_idx, message) in messages.iter().enumerate() {
let global_idx = start_index_offset + local_idx;
self.selected_messages
.insert(MessageIdentifier::from_message(message));
self.selected_indices.insert(global_idx);
}
self.last_selected_index = self.selected_indices.iter().max().copied();
if !messages.is_empty() {
self.selection_mode = true;
}
}
pub fn calculate_gap_sum(&self) -> usize {
if self.selected_indices.len() <= 1 {
return 0;
}
let mut sorted_indices: Vec<usize> = self.selected_indices.iter().copied().collect();
sorted_indices.sort_unstable();
let min_index = sorted_indices.first().copied().unwrap_or(0);
let max_index = sorted_indices.last().copied().unwrap_or(0);
let total_span = max_index - min_index + 1;
let gap_sum = total_span.saturating_sub(sorted_indices.len());
log::debug!(
"Gap calculation: min_index={}, max_index={}, total_span={}, selected_count={}, gap_sum={}",
min_index,
max_index,
total_span,
sorted_indices.len(),
gap_sum
);
gap_sum
}
}
#[derive(Debug)]
pub struct QueueState {
pub pending_queue: Option<String>,
pub current_queue_name: Option<String>,
pub current_queue_type: QueueType,
pub messages: Option<Vec<MessageModel>>,
pub message_pagination: MessagePaginationState,
pub stats_manager: QueueStatsManager,
pub bulk_selection: BulkSelectionState,
pub message_repeat_count: usize,
}
impl Default for QueueState {
fn default() -> Self {
Self {
pending_queue: None,
current_queue_name: None,
current_queue_type: QueueType::Main,
messages: None,
message_pagination: MessagePaginationState::default(),
stats_manager: QueueStatsManager::new(),
bulk_selection: BulkSelectionState::default(),
message_repeat_count: 1, }
}
}
impl QueueState {
pub fn new() -> Self {
Self::default()
}
pub fn set_selected_queue(&mut self, queue_name: String) {
self.pending_queue = Some(queue_name.clone());
self.current_queue_name = Some(queue_name);
self.current_queue_type = QueueType::Main;
self.messages = None;
self.message_pagination.reset();
}
pub fn toggle_queue_type(&mut self) -> Option<String> {
if let Some(current_queue_name) = &self.current_queue_name {
let new_queue_type = match self.current_queue_type {
QueueType::Main => QueueType::DeadLetter,
QueueType::DeadLetter => QueueType::Main,
};
let base_queue_name = if current_queue_name.ends_with("/$deadletterqueue") {
current_queue_name
.strip_suffix("/$deadletterqueue")
.map(|s| s.to_string())
.unwrap_or_else(|| {
log::warn!(
"Failed to strip DLQ suffix from queue name: {current_queue_name}"
);
current_queue_name.clone()
})
} else {
current_queue_name.clone()
};
let target_queue = match new_queue_type {
QueueType::Main => base_queue_name,
QueueType::DeadLetter => format!("{base_queue_name}/$deadletterqueue"),
};
self.pending_queue = Some(target_queue.clone());
self.current_queue_name = Some(target_queue.clone());
self.current_queue_type = new_queue_type;
self.messages = None;
self.message_pagination.reset();
log::info!(
"Queue toggle: cleared all message cache, switching from {:?} to {:?} ({})",
match self.current_queue_type {
QueueType::Main => QueueType::DeadLetter,
QueueType::DeadLetter => QueueType::Main,
},
self.current_queue_type,
target_queue
);
self.bulk_selection.clear_all();
Some(target_queue)
} else {
None
}
}
}