// src/tui/functions/common/add_table.rs use crate::state::pages::add_table::{ AddTableFocus, AddTableState, ColumnDefinition, IndexDefinition, }; use crate::services::GrpcClient; use anyhow::{anyhow, Result}; use common::proto::komp_ac::table_definition::{ PostTableDefinitionRequest, ColumnDefinition as ProtoColumnDefinition, TableLink as ProtoTableLink, }; use tracing::debug; /// Handles the logic for adding a column when the "Add" button is activated. /// /// Takes the mutable state and command message string. /// Returns `Some(AddTableFocus)` indicating the desired focus state after a successful add, /// or `None` if the action failed (e.g., validation error). pub fn handle_add_column_action( add_table_state: &mut AddTableState, command_message: &mut String, ) -> Option { // Trim and create owned Strings from inputs let table_name_in = add_table_state.table_name_input.trim(); let column_name_in = add_table_state.column_name_input.trim(); let column_type_in = add_table_state.column_type_input.trim(); // Validate all inputs needed for this combined action let has_table_name = !table_name_in.is_empty(); let has_column_name = !column_name_in.is_empty(); let has_column_type = !column_type_in.is_empty(); match (has_table_name, has_column_name, has_column_type) { // Case 1: Both column fields have input (Table name is optional here) (_, true, true) => { let mut msg = String::new(); // Optionally update table name if provided if has_table_name { add_table_state.table_name = table_name_in.to_string(); msg.push_str(&format!("Table name set to '{}'. ", add_table_state.table_name)); } // Add the column let new_column = ColumnDefinition { name: column_name_in.to_string(), data_type: column_type_in.to_string(), selected: false, }; add_table_state.columns.push(new_column.clone()); // Clone for msg msg.push_str(&format!("Column '{}' added.", new_column.name)); // Add corresponding index definition (initially unselected) let new_index = IndexDefinition { name: column_name_in.to_string(), selected: false, }; add_table_state.indexes.push(new_index); *command_message = msg; // Clear all inputs and reset cursors add_table_state.table_name_input.clear(); add_table_state.column_name_input.clear(); add_table_state.column_type_input.clear(); add_table_state.table_name_cursor_pos = 0; add_table_state.column_name_cursor_pos = 0; add_table_state.column_type_cursor_pos = 0; add_table_state.has_unsaved_changes = true; Some(AddTableFocus::InputColumnName) // Focus for next column } // Case 2: Only one column field has input (Error) (_, true, false) | (_, false, true) => { *command_message = "Both Column Name and Type are required to add a column.".to_string(); None // Indicate validation failure } // Case 3: Only Table name has input (No column input) (true, false, false) => { add_table_state.table_name = table_name_in.to_string(); *command_message = format!("Table name set to '{}'.", add_table_state.table_name); // Clear only table name input add_table_state.table_name_input.clear(); add_table_state.table_name_cursor_pos = 0; add_table_state.has_unsaved_changes = true; Some(AddTableFocus::InputTableName) // Keep focus here } // Case 4: All fields are empty (false, false, false) => { *command_message = "No input provided.".to_string(); None } } } /// Handles deleting columns marked as selected in the AddTableState. pub fn handle_delete_selected_columns( add_table_state: &mut AddTableState, ) -> String { let initial_count = add_table_state.columns.len(); // Keep only the columns that are NOT selected let initial_selected_indices: std::collections::HashSet = add_table_state .columns .iter() .filter(|col| col.selected) .map(|col| col.name.clone()) .collect(); add_table_state.columns.retain(|col| !col.selected); let deleted_count = initial_count - add_table_state.columns.len(); if deleted_count > 0 { add_table_state.indexes.retain(|index| !initial_selected_indices.contains(&index.name)); add_table_state.has_unsaved_changes = true; // Reset selection highlight as indices have changed add_table_state.column_table_state.select(None); // Optionally, select the first item if the list is not empty // if !add_table_state.columns.is_empty() { // add_table_state.column_table_state.select(Some(0)); // } add_table_state.index_table_state.select(None); format!("Deleted {} selected column(s).", deleted_count) } else { "No columns marked for deletion.".to_string() } } /// Prepares and sends the request to save the new table definition via gRPC. pub async fn handle_save_table_action( grpc_client: &mut GrpcClient, add_table_state: &AddTableState, ) -> Result { // --- Basic Validation --- if add_table_state.table_name.is_empty() { return Err(anyhow!("Table name cannot be empty.")); } if add_table_state.columns.is_empty() { return Err(anyhow!("Table must have at least one column.")); } // --- Prepare Proto Data --- let proto_columns: Vec = add_table_state .columns .iter() .map(|col| ProtoColumnDefinition { name: col.name.clone(), field_type: col.data_type.clone(), // Assuming data_type maps directly }) .collect(); let proto_indexes: Vec = add_table_state .indexes .iter() .filter(|idx| idx.selected) // Only include selected indexes .map(|idx| idx.name.clone()) .collect(); let proto_links: Vec = add_table_state .links .iter() .filter(|link| link.selected) // Only include selected links .map(|link| ProtoTableLink { linked_table_name: link.linked_table_name.clone(), // Assuming 'required' maps directly, adjust if needed // For now, the proto only seems to use linked_table_name based on example // If your proto evolves, map link.is_required here. required: false, // Set based on your proto definition/needs }) .collect(); // --- Create Request --- let request = PostTableDefinitionRequest { table_name: add_table_state.table_name.clone(), columns: proto_columns, indexes: proto_indexes, links: proto_links, profile_name: add_table_state.profile_name.clone(), }; debug!("Sending PostTableDefinitionRequest: {:?}", request); // --- Call gRPC Service --- match grpc_client.post_table_definition(request).await { Ok(response) => { if response.success { Ok(format!( "Table '{}' saved successfully.", add_table_state.table_name )) } else { // Use the SQL message from the response if available, otherwise generic error let error_message = if !response.sql.is_empty() { format!("Server failed to save table: {}", response.sql) } else { "Server failed to save table (unknown reason).".to_string() }; Err(anyhow!(error_message)) } } Err(e) => Err(anyhow!("gRPC call failed: {}", e)), } }