// src/modes/canvas/edit.rs use crate::config::binds::config::Config; use crate::functions::modes::edit::{ add_logic_e, add_table_e, form_e, }; 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}; 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 if let Some(canvas_action) = CanvasAction::from_key(key_event.code) { 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()) } /// NEW: Unified canvas action handler for any CanvasState (LoginState, RegisterState, etc.) /// This replaces the old auth_e::execute_edit_action calls with the new canvas library async fn handle_canvas_state_edit( key: KeyEvent, config: &Config, state: &mut S, ideal_cursor_column: &mut usize, ) -> Result { // Try direct key mapping first (same pattern as FormState) if let Some(canvas_action) = CanvasAction::from_key(key.code) { 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(_) => { // Fall through to try config mapping } } } // Try config-mapped action (same pattern as FormState) if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) { 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)); } } } 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" }; // FIX: Pass &mut event_handler.ideal_cursor_column form_e::execute_edit_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" { // FIX: Pass &mut event_handler.ideal_cursor_column let msg = form_e::execute_edit_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 { // FIX: Pass &mut event_handler.ideal_cursor_column add_table_e::execute_edit_action( action_str, key, &mut admin_state.add_table_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_logic { // FIX: Pass &mut event_handler.ideal_cursor_column add_logic_e::execute_edit_action( action_str, key, &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 { // FIX: Pass &mut event_handler.ideal_cursor_column form_e::execute_edit_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 { // FIX: Pass &mut event_handler.ideal_cursor_column add_table_e::execute_edit_action( "insert_char", key, &mut admin_state.add_table_state, &mut event_handler.ideal_cursor_column, ) .await? } else if app_state.ui.show_add_logic { // FIX: Pass &mut event_handler.ideal_cursor_column add_logic_e::execute_edit_action( "insert_char", key, &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 { // FIX: Pass &mut event_handler.ideal_cursor_column form_e::execute_edit_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 }