// src/state/pages/add_logic.rs use crate::config::binds::config::{EditorConfig, EditorKeybindingMode}; use canvas::canvas::{CanvasState, ActionContext, CanvasAction, AppMode}; use crate::components::common::text_editor::{TextEditor, VimState}; use std::cell::RefCell; use std::rc::Rc; use tui_textarea::TextArea; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AddLogicFocus { #[default] InputLogicName, InputTargetColumn, InputDescription, ScriptContentPreview, InsideScriptContent, SaveButton, CancelButton, } #[derive(Clone, Debug)] pub struct AddLogicState { pub profile_name: String, pub selected_table_id: Option, pub selected_table_name: Option, pub logic_name_input: String, pub target_column_input: String, pub script_content_editor: Rc>>, pub description_input: String, pub current_focus: AddLogicFocus, pub last_canvas_field: usize, pub logic_name_cursor_pos: usize, pub target_column_cursor_pos: usize, pub description_cursor_pos: usize, pub has_unsaved_changes: bool, pub editor_keybinding_mode: EditorKeybindingMode, pub vim_state: VimState, // New fields for Target Column Autocomplete pub table_columns_for_suggestions: Vec, // All columns for the table pub target_column_suggestions: Vec, // Filtered suggestions pub show_target_column_suggestions: bool, pub selected_target_column_suggestion_index: Option, pub in_target_column_suggestion_mode: bool, // Script Editor Autocomplete pub script_editor_autocomplete_active: bool, pub script_editor_suggestions: Vec, pub script_editor_selected_suggestion_index: Option, pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column) pub all_table_names: Vec, pub script_editor_filter_text: String, // New fields for same-profile table names and column autocomplete pub same_profile_table_names: Vec, // Tables from same profile only pub script_editor_awaiting_column_autocomplete: Option, // Table name waiting for column fetch pub app_mode: AppMode, } impl AddLogicState { pub fn new(editor_config: &EditorConfig) -> Self { let editor = TextEditor::new_textarea(editor_config); AddLogicState { profile_name: "default".to_string(), selected_table_id: None, selected_table_name: None, logic_name_input: String::new(), target_column_input: String::new(), script_content_editor: Rc::new(RefCell::new(editor)), description_input: String::new(), current_focus: AddLogicFocus::InputLogicName, last_canvas_field: 2, logic_name_cursor_pos: 0, target_column_cursor_pos: 0, description_cursor_pos: 0, has_unsaved_changes: false, editor_keybinding_mode: editor_config.keybinding_mode.clone(), vim_state: VimState::default(), table_columns_for_suggestions: Vec::new(), target_column_suggestions: Vec::new(), show_target_column_suggestions: false, selected_target_column_suggestion_index: None, in_target_column_suggestion_mode: false, script_editor_autocomplete_active: false, script_editor_suggestions: Vec::new(), script_editor_selected_suggestion_index: None, script_editor_trigger_position: None, all_table_names: Vec::new(), script_editor_filter_text: String::new(), same_profile_table_names: Vec::new(), script_editor_awaiting_column_autocomplete: None, app_mode: AppMode::Edit, } } pub const INPUT_FIELD_COUNT: usize = 3; /// Updates the target_column_suggestions based on current input. pub fn update_target_column_suggestions(&mut self) { let current_input = self.target_column_input.to_lowercase(); if self.table_columns_for_suggestions.is_empty() { self.target_column_suggestions.clear(); self.show_target_column_suggestions = false; self.selected_target_column_suggestion_index = None; return; } if current_input.is_empty() { self.target_column_suggestions = self.table_columns_for_suggestions.clone(); } else { self.target_column_suggestions = self .table_columns_for_suggestions .iter() .filter(|name| name.to_lowercase().contains(¤t_input)) .cloned() .collect(); } self.show_target_column_suggestions = !self.target_column_suggestions.is_empty(); if self.show_target_column_suggestions { if let Some(selected_idx) = self.selected_target_column_suggestion_index { if selected_idx >= self.target_column_suggestions.len() { self.selected_target_column_suggestion_index = Some(0); } } else { self.selected_target_column_suggestion_index = Some(0); } } else { self.selected_target_column_suggestion_index = None; } } /// Updates script editor suggestions based on current filter text pub fn update_script_editor_suggestions(&mut self) { let mut suggestions = vec!["sql".to_string()]; if self.selected_table_name.is_some() { suggestions.extend(self.table_columns_for_suggestions.clone()); } let current_selected_table_name = self.selected_table_name.as_deref(); suggestions.extend( self.same_profile_table_names .iter() .filter(|tn| Some(tn.as_str()) != current_selected_table_name) .cloned() ); if self.script_editor_filter_text.is_empty() { self.script_editor_suggestions = suggestions; } else { let filter_lower = self.script_editor_filter_text.to_lowercase(); self.script_editor_suggestions = suggestions .into_iter() .filter(|suggestion| suggestion.to_lowercase().contains(&filter_lower)) .collect(); } // Update selection index if self.script_editor_suggestions.is_empty() { self.script_editor_selected_suggestion_index = None; self.script_editor_autocomplete_active = false; } else if let Some(selected_idx) = self.script_editor_selected_suggestion_index { if selected_idx >= self.script_editor_suggestions.len() { self.script_editor_selected_suggestion_index = Some(0); } } else { self.script_editor_selected_suggestion_index = Some(0); } } /// Checks if a suggestion is a table name (for triggering column autocomplete) pub fn is_table_name_suggestion(&self, suggestion: &str) -> bool { // Not "sql" if suggestion == "sql" { return false; } if self.table_columns_for_suggestions.contains(&suggestion.to_string()) { return false; } self.same_profile_table_names.contains(&suggestion.to_string()) } /// Sets table columns for autocomplete suggestions pub fn set_table_columns(&mut self, columns: Vec) { self.table_columns_for_suggestions = columns.clone(); if !columns.is_empty() { self.update_target_column_suggestions(); } } /// Sets all available table names for autocomplete suggestions pub fn set_all_table_names(&mut self, table_names: Vec) { self.all_table_names = table_names; } /// Sets table names from the same profile for autocomplete suggestions pub fn set_same_profile_table_names(&mut self, table_names: Vec) { self.same_profile_table_names = table_names; } /// Triggers waiting for column autocomplete for a specific table pub fn trigger_column_autocomplete_for_table(&mut self, table_name: String) { self.script_editor_awaiting_column_autocomplete = Some(table_name); } /// Updates autocomplete with columns for a specific table pub fn set_columns_for_table_autocomplete(&mut self, columns: Vec) { self.script_editor_suggestions = columns; self.script_editor_selected_suggestion_index = if self.script_editor_suggestions.is_empty() { None } else { Some(0) }; self.script_editor_autocomplete_active = !self.script_editor_suggestions.is_empty(); self.script_editor_awaiting_column_autocomplete = None; } /// Deactivates script editor autocomplete and clears related state pub fn deactivate_script_editor_autocomplete(&mut self) { self.script_editor_autocomplete_active = false; self.script_editor_suggestions.clear(); self.script_editor_selected_suggestion_index = None; self.script_editor_trigger_position = None; self.script_editor_filter_text.clear(); } /// Helper method to validate and save logic pub fn save_logic(&mut self) -> Option { if self.logic_name_input.trim().is_empty() { return Some("Logic name is required".to_string()); } if self.target_column_input.trim().is_empty() { return Some("Target column is required".to_string()); } let script_content = { let editor_borrow = self.script_content_editor.borrow(); editor_borrow.lines().join("\n") }; if script_content.trim().is_empty() { return Some("Script content is required".to_string()); } // Here you would typically save to database/storage // For now, just clear the form and mark as saved self.has_unsaved_changes = false; Some(format!("Logic '{}' saved successfully", self.logic_name_input.trim())) } /// Helper method to clear the form pub fn clear_form(&mut self) -> Option { let profile = self.profile_name.clone(); let table_id = self.selected_table_id; let table_name = self.selected_table_name.clone(); let editor_config = EditorConfig::default(); // You might want to preserve the actual config *self = Self::new(&editor_config); self.profile_name = profile; self.selected_table_id = table_id; self.selected_table_name = table_name; Some("Form cleared".to_string()) } } impl Default for AddLogicState { fn default() -> Self { let mut state = Self::new(&EditorConfig::default()); state.app_mode = AppMode::Edit; state } } // Implement external library's CanvasState for AddLogicState impl CanvasState for AddLogicState { fn current_field(&self) -> usize { match self.current_focus { AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputDescription => 2, // If focus is elsewhere, return the last canvas field used _ => self.last_canvas_field, } } fn set_current_field(&mut self, index: usize) { let new_focus = match index { 0 => AddLogicFocus::InputLogicName, 1 => AddLogicFocus::InputTargetColumn, 2 => AddLogicFocus::InputDescription, _ => return, }; if self.current_focus != new_focus { if self.current_focus == AddLogicFocus::InputTargetColumn { self.in_target_column_suggestion_mode = false; self.show_target_column_suggestions = false; } self.current_focus = new_focus; self.last_canvas_field = index; } } fn current_cursor_pos(&self) -> usize { match self.current_focus { AddLogicFocus::InputLogicName => self.logic_name_cursor_pos, AddLogicFocus::InputTargetColumn => self.target_column_cursor_pos, AddLogicFocus::InputDescription => self.description_cursor_pos, _ => 0, } } fn set_current_cursor_pos(&mut self, pos: usize) { match self.current_focus { AddLogicFocus::InputLogicName => { self.logic_name_cursor_pos = pos.min(self.logic_name_input.len()); } AddLogicFocus::InputTargetColumn => { self.target_column_cursor_pos = pos.min(self.target_column_input.len()); } AddLogicFocus::InputDescription => { self.description_cursor_pos = pos.min(self.description_input.len()); } _ => {} } } fn get_current_input(&self) -> &str { match self.current_focus { AddLogicFocus::InputLogicName => &self.logic_name_input, AddLogicFocus::InputTargetColumn => &self.target_column_input, AddLogicFocus::InputDescription => &self.description_input, _ => "", // Should not happen if called correctly } } fn get_current_input_mut(&mut self) -> &mut String { match self.current_focus { AddLogicFocus::InputLogicName => &mut self.logic_name_input, AddLogicFocus::InputTargetColumn => &mut self.target_column_input, AddLogicFocus::InputDescription => &mut self.description_input, _ => &mut self.logic_name_input, // Fallback } } fn inputs(&self) -> Vec<&String> { vec![ &self.logic_name_input, &self.target_column_input, &self.description_input, ] } fn fields(&self) -> Vec<&str> { vec!["Logic Name", "Target Column", "Description"] } fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { match action { // Handle saving logic script CanvasAction::Custom(action_str) if action_str == "save_logic" => { self.save_logic() } // Handle clearing the form CanvasAction::Custom(action_str) if action_str == "clear_form" => { self.clear_form() } // Handle target column autocomplete activation CanvasAction::Custom(action_str) if action_str == "activate_autocomplete" => { if self.current_field() == 1 { // Target Column field self.in_target_column_suggestion_mode = true; self.update_target_column_suggestions(); Some("Autocomplete activated".to_string()) } else { None } } // Handle target column suggestion selection CanvasAction::Custom(action_str) if action_str == "select_suggestion" => { if self.current_field() == 1 && self.in_target_column_suggestion_mode { if let Some(selected_idx) = self.selected_target_column_suggestion_index { if let Some(suggestion) = self.target_column_suggestions.get(selected_idx) { self.target_column_input = suggestion.clone(); self.target_column_cursor_pos = suggestion.len(); self.in_target_column_suggestion_mode = false; self.show_target_column_suggestions = false; self.has_unsaved_changes = true; return Some(format!("Selected: {}", suggestion)); } } } None } // Custom validation when moving between fields CanvasAction::NextField => { match self.current_field() { 0 => { // Logic Name field if self.logic_name_input.trim().is_empty() { Some("Logic name cannot be empty".to_string()) } else { None // Let canvas library handle the normal field movement } } 1 => { // Target Column field // Update suggestions when entering target column field self.update_target_column_suggestions(); None } _ => None, } } // Handle character insertion with validation CanvasAction::InsertChar(c) => { if self.current_field() == 1 { // Target Column field // Update suggestions after character insertion // Note: Canvas library will handle the actual insertion // This is just for triggering suggestion updates None // Let canvas handle insertion, then we'll update suggestions } else { None // Let canvas handle normally } } // Let canvas library handle everything else _ => None, } } fn current_mode(&self) -> AppMode { self.app_mode } }