// src/components/admin/add_logic.rs use crate::config::colors::themes::Theme; use crate::state::app::highlight::HighlightState; use crate::state::app::state::AppState; use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; use crate::state::pages::canvas_state::CanvasState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, text::{Line, Span}, widgets::{Block, BorderType, Borders, Paragraph}, Frame, }; use crate::components::handlers::canvas::render_canvas; use crate::components::common::{dialog, autocomplete}; // Added autocomplete use crate::config::binds::config::EditorKeybindingMode; pub fn render_add_logic( f: &mut Frame, area: Rect, theme: &Theme, app_state: &AppState, add_logic_state: &mut AddLogicState, is_edit_mode: bool, highlight_state: &HighlightState, ) { let main_block = Block::default() .title(" Add New Logic Script ") .title_alignment(Alignment::Center) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(theme.border)) .style(Style::default().bg(theme.bg)); let inner_area = main_block.inner(area); f.render_widget(main_block, area); // Handle full-screen script editing if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent { let mut editor_ref = add_logic_state.script_content_editor.borrow_mut(); let border_style_color = if is_edit_mode { theme.highlight } else { theme.secondary }; let border_style = Style::default().fg(border_style_color); editor_ref.set_cursor_line_style(Style::default()); editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); let script_title_hint = match add_logic_state.editor_keybinding_mode { EditorKeybindingMode::Vim => { let vim_mode_status = crate::components::common::text_editor::TextEditor::get_vim_mode_status(&add_logic_state.vim_state); format!("Script {}", vim_mode_status) } EditorKeybindingMode::Emacs | EditorKeybindingMode::Default => { if is_edit_mode { "Script (Editing)".to_string() } else { "Script".to_string() } } }; editor_ref.set_block( Block::default() .title(Span::styled(script_title_hint, Style::default().fg(theme.fg))) .title_alignment(Alignment::Center) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(border_style), ); f.render_widget(&*editor_ref, inner_area); // Drop the editor borrow before accessing autocomplete state drop(editor_ref); // === SCRIPT EDITOR AUTOCOMPLETE RENDERING === if add_logic_state.script_editor_autocomplete_active && !add_logic_state.script_editor_suggestions.is_empty() { // Get the current cursor position from textarea let current_cursor = { let editor_borrow = add_logic_state.script_content_editor.borrow(); editor_borrow.cursor() // Returns (row, col) as (usize, usize) }; let (cursor_line, cursor_col) = current_cursor; // Account for TextArea's block borders (1 for each side) let block_offset_x = 1; let block_offset_y = 1; // Position autocomplete at current cursor position // Add 1 to column to position dropdown right after the cursor let autocomplete_x = cursor_col + 1; let autocomplete_y = cursor_line; let input_rect = Rect { x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)), y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)), width: 1, // Minimum width for positioning height: 1, }; // Render autocomplete dropdown autocomplete::render_autocomplete_dropdown( f, input_rect, f.area(), // Full frame area for clamping theme, &add_logic_state.script_editor_suggestions, add_logic_state.script_editor_selected_suggestion_index, ); } return; // Exit early for fullscreen mode } // Regular layout with preview let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), // Top info Constraint::Length(9), // Canvas for 3 inputs (each 1 line + 1 padding = 2 lines * 3 + 2 border = 8, +1 for good measure) Constraint::Min(5), // Script preview Constraint::Length(3), // Buttons ]) .split(inner_area); let top_info_area = main_chunks[0]; let canvas_area = main_chunks[1]; let script_content_area = main_chunks[2]; let buttons_area = main_chunks[3]; // Top info let profile_text = Paragraph::new(vec![ Line::from(Span::styled( format!("Profile: {}", add_logic_state.profile_name), Style::default().fg(theme.fg), )), Line::from(Span::styled( format!( "Table: {}", add_logic_state .selected_table_name .clone() .unwrap_or_else(|| add_logic_state.selected_table_id .map(|id| format!("ID {}", id)) .unwrap_or_else(|| "Global (Not Selected)".to_string())) ), Style::default().fg(theme.fg), )), ]) .block( Block::default() .borders(Borders::BOTTOM) .border_style(Style::default().fg(theme.secondary)), ); f.render_widget(profile_text, top_info_area); // Canvas let focus_on_canvas_inputs = matches!( add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); // Call render_canvas and get the active_field_rect let active_field_rect = render_canvas( f, canvas_area, add_logic_state, // Pass the whole state as it impl CanvasState &add_logic_state.fields(), &add_logic_state.current_field(), &add_logic_state.inputs(), theme, is_edit_mode && focus_on_canvas_inputs, // is_edit_mode for canvas fields highlight_state, ); // --- Render Autocomplete for Target Column --- // `is_edit_mode` here refers to the general edit mode of the EventHandler if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field if let Some(suggestions) = add_logic_state.get_suggestions() { // Uses CanvasState impl let selected = add_logic_state.get_selected_suggestion_index(); if !suggestions.is_empty() { // Only render if there are suggestions to show if let Some(input_rect) = active_field_rect { autocomplete::render_autocomplete_dropdown( f, input_rect, f.area(), // Full frame area for clamping theme, suggestions, selected, ); } } } } // Script content preview { let mut editor_ref = add_logic_state.script_content_editor.borrow_mut(); editor_ref.set_cursor_line_style(Style::default()); let is_script_preview_focused = add_logic_state.current_focus == AddLogicFocus::ScriptContentPreview; if is_script_preview_focused { editor_ref.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); } else { let underscore_cursor_style = Style::default() .add_modifier(Modifier::UNDERLINED) .fg(theme.secondary); editor_ref.set_cursor_style(underscore_cursor_style); } let border_style_color = if is_script_preview_focused { theme.highlight } else { theme.secondary }; let title_text = "Script Preview"; // Title doesn't need to change based on focus here let title_style = if is_script_preview_focused { Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD) } else { Style::default().fg(theme.fg) }; editor_ref.set_block( Block::default() .title(Span::styled(title_text, title_style)) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(border_style_color)), ); f.render_widget(&*editor_ref, script_content_area); } // Buttons let get_button_style = |button_focus: AddLogicFocus, current_focus_state: AddLogicFocus| { let is_focused = current_focus_state == button_focus; let base_style = Style::default().fg(if is_focused { theme.highlight } else { theme.secondary }); if is_focused { base_style.add_modifier(Modifier::BOLD) } else { base_style } }; let get_button_border_style = |is_focused: bool, current_theme: &Theme| { if is_focused { Style::default().fg(current_theme.highlight) } else { Style::default().fg(current_theme.secondary) } }; let button_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(50), Constraint::Percentage(50), ]) .split(buttons_area); let save_button = Paragraph::new(" Save Logic ") .style(get_button_style( AddLogicFocus::SaveButton, add_logic_state.current_focus, )) .alignment(Alignment::Center) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(get_button_border_style( add_logic_state.current_focus == AddLogicFocus::SaveButton, theme, )), ); f.render_widget(save_button, button_chunks[0]); let cancel_button = Paragraph::new(" Cancel ") .style(get_button_style( AddLogicFocus::CancelButton, add_logic_state.current_focus, )) .alignment(Alignment::Center) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(get_button_border_style( add_logic_state.current_focus == AddLogicFocus::CancelButton, theme, )), ); f.render_widget(cancel_button, button_chunks[1]); // Dialog if app_state.ui.dialog.dialog_show { dialog::render_dialog( f, f.area(), theme, &app_state.ui.dialog.dialog_title, &app_state.ui.dialog.dialog_message, &app_state.ui.dialog.dialog_buttons, app_state.ui.dialog.dialog_active_button_index, app_state.ui.dialog.is_loading, ); } }