// src/modes/canvas/edit.rs use crate::config::binds::config::Config; use crate::modes::handlers::event::EventHandler; use crate::services::grpc_client::GrpcClient; use crate::state::app::state::AppState; use crate::state::pages::admin::AdminState; use crate::state::pages::{ auth::{LoginState, RegisterState}, form::FormState, }; use canvas::canvas::CanvasState; use canvas::{canvas::CanvasAction, dispatcher::ActionDispatcher, canvas::ActionResult}; use anyhow::Result; use common::proto::komp_ac::search::search_response::Hit; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use tokio::sync::mpsc; use tracing::info; #[derive(Debug, Clone, PartialEq, Eq)] pub enum EditEventOutcome { Message(String), ExitEditMode, } /// Helper function to spawn a non-blocking search task for autocomplete. async fn trigger_form_autocomplete_search( form_state: &mut FormState, grpc_client: &mut GrpcClient, sender: mpsc::UnboundedSender>, ) { if let Some(field_def) = form_state.fields.get(form_state.current_field) { if field_def.is_link { if let Some(target_table) = &field_def.link_target_table { // 1. Update state for immediate UI feedback form_state.autocomplete_loading = true; form_state.autocomplete_active = true; form_state.autocomplete_suggestions.clear(); form_state.selected_suggestion_index = None; // 2. Clone everything needed for the background task let query = form_state.get_current_input().to_string(); let table_to_search = target_table.clone(); let mut grpc_client_clone = grpc_client.clone(); info!( "[Autocomplete] Spawning search in '{}' for query: '{}'", table_to_search, query ); // 3. Spawn the non-blocking task tokio::spawn(async move { match grpc_client_clone .search_table(table_to_search, query) .await { Ok(response) => { // Send results back through the channel let _ = sender.send(response.hits); } Err(e) => { tracing::error!( "[Autocomplete] Search failed: {:?}", e ); // Send an empty vec on error so the UI can stop loading let _ = sender.send(vec![]); } } }); } } } } pub async fn handle_form_edit_with_canvas( key_event: KeyEvent, config: &Config, form_state: &mut FormState, ideal_cursor_column: &mut usize, ) -> Result { // Try canvas action from key first let canvas_config = canvas::config::CanvasConfig::load(); if let Some(action_name) = canvas_config.get_edit_action(key_event.code, key_event.modifiers) { let canvas_action = CanvasAction::from_string(action_name); match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => { return Ok(msg.unwrap_or_default()); } Ok(ActionResult::HandledByFeature(msg)) => { return Ok(msg); } Ok(ActionResult::Error(msg)) => { return Ok(format!("Error: {}", msg)); } Ok(ActionResult::RequiresContext(msg)) => { return Ok(format!("Context needed: {}", msg)); } Err(_) => { // Fall through to try config mapping } } } // Try config-mapped action if let Some(action_str) = config.get_edit_action_for_key(key_event.code, key_event.modifiers) { let canvas_action = CanvasAction::from_string(&action_str); match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => { return Ok(msg.unwrap_or_default()); } Ok(ActionResult::HandledByFeature(msg)) => { return Ok(msg); } Ok(ActionResult::Error(msg)) => { return Ok(format!("Error: {}", msg)); } Ok(ActionResult::RequiresContext(msg)) => { return Ok(format!("Context needed: {}", msg)); } Err(e) => { return Ok(format!("Action failed: {}", e)); } } } Ok(String::new()) } /// Helper function to execute a specific action using canvas library async fn execute_canvas_action( action: &str, key: KeyEvent, form_state: &mut FormState, ideal_cursor_column: &mut usize, ) -> Result { let canvas_action = CanvasAction::from_string(action); match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => Ok(msg.unwrap_or_default()), Ok(ActionResult::HandledByFeature(msg)) => Ok(msg), Ok(ActionResult::Error(msg)) => Ok(format!("Error: {}", msg)), Ok(ActionResult::RequiresContext(msg)) => Ok(format!("Context needed: {}", msg)), Err(e) => Ok(format!("Action failed: {}", e)), } } /// FIXED: Unified canvas action handler with proper priority order for edit mode async fn handle_canvas_state_edit( key: KeyEvent, config: &Config, state: &mut S, ideal_cursor_column: &mut usize, ) -> Result { // println!("DEBUG: Key pressed: {:?}", key); // DEBUG // PRIORITY 1: Character insertion in edit mode comes FIRST if let KeyCode::Char(c) = key.code { // Only insert if no modifiers or just shift (for uppercase) if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT { // println!("DEBUG: Using character insertion priority for: {}", c); // DEBUG let canvas_action = CanvasAction::InsertChar(c); match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => { return Ok(msg.unwrap_or_default()); } Ok(ActionResult::HandledByFeature(msg)) => { return Ok(msg); } Ok(ActionResult::Error(msg)) => { return Ok(format!("Error: {}", msg)); } Ok(ActionResult::RequiresContext(msg)) => { return Ok(format!("Context needed: {}", msg)); } Err(e) => { // println!("DEBUG: Character insertion failed: {:?}, trying config", e); // Fall through to try config mappings } } } } // PRIORITY 2: Check canvas config for special keys/combinations let canvas_config = canvas::config::CanvasConfig::load(); if let Some(action_name) = canvas_config.get_edit_action(key.code, key.modifiers) { // println!("DEBUG: Canvas config mapped to: {}", action_name); // DEBUG let canvas_action = CanvasAction::from_string(action_name); match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => { return Ok(msg.unwrap_or_default()); } Ok(ActionResult::HandledByFeature(msg)) => { return Ok(msg); } Ok(ActionResult::Error(msg)) => { return Ok(format!("Error: {}", msg)); } Ok(ActionResult::RequiresContext(msg)) => { return Ok(format!("Context needed: {}", msg)); } Err(_) => { // println!("DEBUG: Canvas action failed, trying client config"); // DEBUG } } } else { // println!("DEBUG: No canvas config mapping found"); // DEBUG } // PRIORITY 3: Check client config ONLY for non-character keys or modified keys if !matches!(key.code, KeyCode::Char(_)) || !key.modifiers.is_empty() { if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) { // println!("DEBUG: Client config mapped to: {} (for non-char key)", action_str); // DEBUG let canvas_action = CanvasAction::from_string(&action_str); match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { Ok(ActionResult::Success(msg)) => { return Ok(msg.unwrap_or_default()); } Ok(ActionResult::HandledByFeature(msg)) => { return Ok(msg); } Ok(ActionResult::Error(msg)) => { return Ok(format!("Error: {}", msg)); } Ok(ActionResult::RequiresContext(msg)) => { return Ok(format!("Context needed: {}", msg)); } Err(e) => { return Ok(format!("Action failed: {}", e)); } } } else { // println!("DEBUG: No client config mapping found for non-char key"); // DEBUG } } else { // println!("DEBUG: Skipping client config for character key in edit mode"); // DEBUG } // println!("DEBUG: No action taken for key: {:?}", key); // DEBUG Ok(String::new()) } #[allow(clippy::too_many_arguments)] pub async fn handle_edit_event( key: KeyEvent, config: &Config, form_state: &mut FormState, login_state: &mut LoginState, register_state: &mut RegisterState, admin_state: &mut AdminState, current_position: &mut u64, total_count: u64, event_handler: &mut EventHandler, app_state: &AppState, ) -> Result { // --- AUTOCOMPLETE-SPECIFIC KEY HANDLING --- if app_state.ui.show_form && form_state.autocomplete_active { if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) { match action { "suggestion_down" => { if !form_state.autocomplete_suggestions.is_empty() { let current = form_state.selected_suggestion_index.unwrap_or(0); let next = (current + 1) % form_state.autocomplete_suggestions.len(); form_state.selected_suggestion_index = Some(next); } return Ok(EditEventOutcome::Message(String::new())); } "suggestion_up" => { if !form_state.autocomplete_suggestions.is_empty() { let current = form_state.selected_suggestion_index.unwrap_or(0); let prev = if current == 0 { form_state.autocomplete_suggestions.len() - 1 } else { current - 1 }; form_state.selected_suggestion_index = Some(prev); } return Ok(EditEventOutcome::Message(String::new())); } "exit" => { form_state.deactivate_autocomplete(); return Ok(EditEventOutcome::Message( "Autocomplete cancelled".to_string(), )); } "enter_decider" => { if let Some(selected_idx) = form_state.selected_suggestion_index { if let Some(selection) = form_state .autocomplete_suggestions .get(selected_idx) .cloned() { // --- THIS IS THE CORE LOGIC CHANGE --- // 1. Get the friendly display name for the UI let display_name = form_state.get_display_name_for_hit(&selection); // 2. Store the REAL ID in the form's values let current_input = form_state.get_current_input_mut(); *current_input = selection.id.to_string(); // 3. Set the persistent display override in the map form_state.link_display_map.insert( form_state.current_field, display_name, ); // 4. Finalize state form_state.deactivate_autocomplete(); form_state.set_has_unsaved_changes(true); return Ok(EditEventOutcome::Message( "Selection made".to_string(), )); } } form_state.deactivate_autocomplete(); // Fall through to default 'enter' behavior } _ => {} // Let other keys fall through to the live search logic } } } // --- LIVE AUTOCOMPLETE TRIGGER LOGIC --- let mut trigger_search = false; if app_state.ui.show_form { // Manual trigger if let Some("trigger_autocomplete") = config.get_edit_action_for_key(key.code, key.modifiers) { if !form_state.autocomplete_active { trigger_search = true; } } // Live search trigger while typing else if form_state.autocomplete_active { if let KeyCode::Char(_) | KeyCode::Backspace = key.code { let action = if let KeyCode::Backspace = key.code { "delete_char_backward" } else { "insert_char" }; // FIXED: Use canvas library instead of form_e::execute_edit_action execute_canvas_action( action, key, form_state, &mut event_handler.ideal_cursor_column, ) .await?; trigger_search = true; } } } if trigger_search { trigger_form_autocomplete_search( form_state, &mut event_handler.grpc_client, event_handler.autocomplete_result_sender.clone(), ) .await; return Ok(EditEventOutcome::Message("Searching...".to_string())); } // --- GENERAL EDIT MODE EVENT HANDLING (IF NOT AUTOCOMPLETE) --- if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) { // Handle Enter key (next field) if action_str == "enter_decider" { // FIXED: Use canvas library instead of form_e::execute_edit_action let msg = execute_canvas_action( "next_field", key, form_state, &mut event_handler.ideal_cursor_column, ) .await?; return Ok(EditEventOutcome::Message(msg)); } // Handle exiting edit mode if action_str == "exit" { return Ok(EditEventOutcome::ExitEditMode); } // Handle all other edit actions - NOW USING CANVAS LIBRARY let msg = if app_state.ui.show_login { // NEW: Use unified canvas handler instead of auth_e::execute_edit_action handle_canvas_state_edit( key, config, login_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_table { // NEW: Use unified canvas handler instead of add_table_e::execute_edit_action handle_canvas_state_edit( key, config, &mut admin_state.add_table_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_logic { // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action handle_canvas_state_edit( key, config, &mut admin_state.add_logic_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_register { // NEW: Use unified canvas handler instead of auth_e::execute_edit_action handle_canvas_state_edit( key, config, register_state, &mut event_handler.ideal_cursor_column, ) .await? } else { // FIXED: Use canvas library instead of form_e::execute_edit_action execute_canvas_action( action_str, key, form_state, &mut event_handler.ideal_cursor_column, ) .await? }; return Ok(EditEventOutcome::Message(msg)); } // --- FALLBACK FOR CHARACTER INSERTION (IF NO OTHER BINDING MATCHED) --- if let KeyCode::Char(_) = key.code { let msg = if app_state.ui.show_login { // NEW: Use unified canvas handler instead of auth_e::execute_edit_action handle_canvas_state_edit( key, config, login_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_table { // NEW: Use unified canvas handler instead of add_table_e::execute_edit_action handle_canvas_state_edit( key, config, &mut admin_state.add_table_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_logic { // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action handle_canvas_state_edit( key, config, &mut admin_state.add_logic_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_register { // NEW: Use unified canvas handler instead of auth_e::execute_edit_action handle_canvas_state_edit( key, config, register_state, &mut event_handler.ideal_cursor_column, ) .await? } else { // FIXED: Use canvas library instead of form_e::execute_edit_action execute_canvas_action( "insert_char", key, form_state, &mut event_handler.ideal_cursor_column, ) .await? }; return Ok(EditEventOutcome::Message(msg)); } Ok(EditEventOutcome::Message(String::new())) // No action taken }