// src/ui/handlers/ui.rs use crate::tui::terminal::TerminalCore; use crate::services::grpc_client::GrpcClient; use crate::services::ui_service::UiService; use crate::tui::terminal::EventReader; use crate::modes::common::commands::CommandHandler; use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; use crate::config::colors::themes::Theme; use crate::config::binds::config::Config; use crate::ui::handlers::render::render_ui; use crate::state::pages::form::FormState; use crate::state::pages::auth::AuthState; use crate::state::canvas_state::CanvasState; use crate::modes::handlers::event::EventHandler; use crate::state::state::AppState; use crossterm::cursor::SetCursorStyle; pub async fn run_ui() -> Result<(), Box> { let config = Config::load()?; let mut terminal = TerminalCore::new()?; let mut grpc_client = GrpcClient::new().await?; // let auth_client = AuthClient::new().await?; // AuthClient is now inside EventHandler let mut command_handler = CommandHandler::new(); let theme = Theme::from_str(&config.colors.theme); let mut auth_state = AuthState::default(); // The single source of truth for AuthState // Initialize app_state first let mut app_state = AppState::new()?; // Initialize app state with profile tree and table structure let column_names = UiService::initialize_app_state(&mut grpc_client, &mut app_state).await?; // Initialize FormState with dynamic fields let mut form_state = FormState::new(column_names); // Initialize EventHandler (which now contains AuthClient) let mut event_handler = EventHandler::new().await?; let event_reader = EventReader::new(); // Fetch the total count of Adresar entries UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?; form_state.reset_to_empty(); loop { // Determine edit mode based on EventHandler state let is_edit_mode = event_handler.is_edit_mode; terminal.draw(|f| { render_ui( f, &mut form_state, &mut auth_state, // Pass the single AuthState instance &theme, is_edit_mode, // Use determined edit mode app_state.total_count, app_state.current_position, &app_state.current_dir, &event_handler.command_input, event_handler.command_mode, &event_handler.command_message, &app_state, ); })?; // --- Cursor Visibility Logic --- let current_mode = ModeManager::derive_mode(&app_state, &event_handler); match current_mode { AppMode::Edit => { terminal.show_cursor()?; } AppMode::ReadOnly => { if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; } else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; } terminal.show_cursor()?; // Ensure visible } AppMode::General | AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; // Ensure visible (though might not be positioned meaningfully) } } // --- End Cursor Visibility Logic --- let total_count = app_state.total_count; let mut current_position = app_state.current_position; // Store position before event handling to detect navigation let position_before_event = current_position; let event = event_reader.read_event()?; let (should_exit, message) = event_handler.handle_event( event, &config, &mut terminal, &mut grpc_client, &mut command_handler, &mut form_state, &mut auth_state, // Pass the single AuthState instance here too &mut app_state, total_count, &mut current_position, ).await?; app_state.current_position = current_position; let position_changed = app_state.current_position != position_before_event; // Handle position changes and update form state (Only when form is shown) if app_state.ui.show_form { if position_changed && !event_handler.is_edit_mode { let current_input = form_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 // Limit to last character in readonly mode } else { 0 }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); // Ensure position never exceeds total_count + 1 if app_state.current_position > total_count + 1 { app_state.current_position = total_count + 1; } if app_state.current_position > total_count { // New entry - reset form form_state.reset_to_empty(); form_state.current_field = 0; } else if app_state.current_position >= 1 && app_state.current_position <= total_count { // Existing entry - load data let current_position_to_load = app_state.current_position; // Use a copy let load_message = UiService::load_adresar_by_position( &mut grpc_client, &mut app_state, // Pass app_state mutably if needed by the service &mut form_state, current_position_to_load ).await?; let current_input = form_state.get_current_input(); let max_cursor_pos = if !event_handler.is_edit_mode && !current_input.is_empty() { current_input.len() - 1 // In readonly mode, limit to last character } else { current_input.len() }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); // Don't overwrite message from handle_event if load_message is simple success if !load_message.starts_with("Loaded entry") || message.is_empty() { event_handler.command_message = load_message; } } else { // Invalid position (e.g., 0) - reset to first entry or new entry mode app_state.current_position = 1.min(total_count + 1); // Go to 1 or new entry if empty if app_state.current_position > total_count { form_state.reset_to_empty(); form_state.current_field = 0; } } } else if !position_changed && !event_handler.is_edit_mode { // If position didn't change but we are in read-only, just adjust cursor let current_input = form_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); } } else if app_state.ui.show_login { // Handle cursor updates for AuthState if needed, similar to FormState if !event_handler.is_edit_mode { let current_input = auth_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; auth_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); } } // Only update command message if handle_event provided one if !message.is_empty() { event_handler.command_message = message; } if should_exit { return Ok(()); } } }