// src/ui/handlers/ui.rs use crate::config::binds::config::Config; use crate::config::colors::themes::Theme; use crate::services::grpc_client::GrpcClient; use crate::services::ui_service::UiService; use crate::config::storage::storage::load_auth_data; use crate::modes::common::commands::CommandHandler; use crate::modes::handlers::event::{EventHandler, EventOutcome}; use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::form::FormState; use crate::state::pages::auth::AuthState; use crate::state::pages::auth::LoginState; use crate::state::pages::auth::RegisterState; use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminFocus; use crate::state::pages::intro::IntroState; use crate::state::app::buffer::BufferState; use crate::state::app::buffer::AppView; use crate::state::app::state::AppState; use crate::tui::terminal::{EventReader, TerminalCore}; use crate::ui::handlers::render::render_ui; use crate::tui::functions::common::login::LoginResult; use crate::tui::functions::common::register::RegisterResult; // Removed: use crate::tui::functions::common::add_table::handle_save_table_action; // Removed: use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender; use crate::ui::handlers::context::DialogPurpose; // UiContext removed if not used directly use crate::tui::functions::common::login; use crate::tui::functions::common::register; use std::time::Instant; use anyhow::{Context, Result}; use crossterm::cursor::SetCursorStyle; use crossterm::event as crossterm_event; use tracing::{error, info, warn}; // Added warn use tokio::sync::mpsc; pub async fn run_ui() -> Result<()> { let config = Config::load().context("Failed to load configuration")?; let theme = Theme::from_str(&config.colors.theme); let mut terminal = TerminalCore::new().context("Failed to initialize terminal")?; let mut grpc_client = GrpcClient::new().await?; let mut command_handler = CommandHandler::new(); // --- Channel for Login Results --- let (login_result_sender, mut login_result_receiver) = mpsc::channel::(1); let (register_result_sender, mut register_result_receiver) = mpsc::channel::(1); let (save_table_result_sender, mut save_table_result_receiver) = mpsc::channel::>(1); let (save_logic_result_sender, _save_logic_result_receiver) = // Prefixed and removed mut mpsc::channel::>(1); let mut event_handler = EventHandler::new( login_result_sender.clone(), register_result_sender.clone(), save_table_result_sender.clone(), save_logic_result_sender.clone(), ).await.context("Failed to create event handler")?; let event_reader = EventReader::new(); let mut auth_state = AuthState::default(); let mut login_state = LoginState::default(); let mut register_state = RegisterState::default(); let mut intro_state = IntroState::default(); let mut admin_state = AdminState::default(); let mut buffer_state = BufferState::default(); let mut app_state = AppState::new().context("Failed to create initial app state")?; // --- DATA: Load auth data from file at startup --- let mut auto_logged_in = false; match load_auth_data() { Ok(Some(stored_data)) => { auth_state.auth_token = Some(stored_data.access_token); auth_state.user_id = Some(stored_data.user_id); auth_state.role = Some(stored_data.role); auth_state.decoded_username = Some(stored_data.username); auto_logged_in = true; info!("Auth data loaded from file. User is auto-logged in."); } Ok(None) => { info!("No stored auth data found. User will see intro/login."); } Err(e) => { error!("Failed to load auth data: {}", e); } } // --- END DATA --- let column_names = UiService::initialize_app_state(&mut grpc_client, &mut app_state) .await.context("Failed to initialize app state from UI service")?; let mut form_state = FormState::new(column_names); UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?; form_state.reset_to_empty(); if auto_logged_in { buffer_state.history = vec![AppView::Form]; buffer_state.active_index = 0; info!("Initial view set to Form due to auto-login."); } let mut last_frame_time = Instant::now(); let mut current_fps = 0.0; let mut needs_redraw = true; loop { // --- Synchronize UI View from Active Buffer --- if let Some(active_view) = buffer_state.get_active_view() { app_state.ui.show_intro = false; app_state.ui.show_login = false; app_state.ui.show_register = false; app_state.ui.show_admin = false; app_state.ui.show_add_table = false; app_state.ui.show_add_logic = false; app_state.ui.show_form = false; match active_view { AppView::Intro => app_state.ui.show_intro = true, AppView::Login => app_state.ui.show_login = true, AppView::Register => app_state.ui.show_register = true, AppView::Admin => { info!("Active view is Admin, refreshing profile tree..."); match grpc_client.get_profile_tree().await { Ok(refreshed_tree) => { app_state.profile_tree = refreshed_tree; } Err(e) => { error!("Failed to refresh profile tree for Admin panel: {}", e); event_handler.command_message = format!("Error refreshing admin data: {}", e); } } app_state.ui.show_admin = true; let profile_names = app_state.profile_tree.profiles.iter() .map(|p| p.name.clone()) .collect(); admin_state.set_profiles(profile_names); if admin_state.current_focus == AdminFocus::default() || !matches!(admin_state.current_focus, AdminFocus::InsideProfilesList | AdminFocus::Tables | AdminFocus::InsideTablesList | AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) { admin_state.current_focus = AdminFocus::ProfilesPane; } if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() { admin_state.profile_list_state.select(Some(0)); } } AppView::AddTable => app_state.ui.show_add_table = true, AppView::AddLogic => app_state.ui.show_add_logic = true, AppView::Form => app_state.ui.show_form = true, AppView::Scratch => {} } } // --- End Synchronization --- // --- Handle Pending Table Structure Fetches --- if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() { if app_state.ui.show_add_logic { // Ensure admin_state.add_logic_state matches the pending fetch if admin_state.add_logic_state.profile_name == profile_name && admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) { info!("Fetching table structure for {}.{}", profile_name, table_name); let fetch_message = UiService::initialize_add_logic_table_data( &mut grpc_client, &mut admin_state.add_logic_state, ).await.unwrap_or_else(|e| { error!("Error initializing add_logic_table_data: {}", e); format!("Error fetching table structure: {}", e) }); if !fetch_message.contains("Error") && !fetch_message.contains("Warning") { info!("{}", fetch_message); // Optionally update command message on success if desired // event_handler.command_message = fetch_message; } else { event_handler.command_message = fetch_message; // Show error/warning to user } needs_redraw = true; } else { error!( "Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}", profile_name, table_name, admin_state.add_logic_state.profile_name, admin_state.add_logic_state.selected_table_name ); // Cleared by .take(), no need to set to None explicitly unless re-queueing } } else { warn!( "Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.", profile_name, table_name ); // If you need to re-queue: // app_state.pending_table_structure_fetch = Some((profile_name, table_name)); } } // --- 3. Draw UI --- if needs_redraw { terminal.draw(|f| { render_ui( f, &mut form_state, &mut auth_state, &login_state, ®ister_state, &intro_state, &mut admin_state, &buffer_state, &theme, event_handler.is_edit_mode, &event_handler.highlight_state, app_state.total_count, app_state.current_position, &app_state.current_dir, &event_handler.command_input, event_handler.command_mode, &event_handler.command_message, current_fps, &app_state, ); }).context("Terminal draw call failed")?; needs_redraw = false; } // --- Cursor Visibility Logic --- let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state); match current_mode { AppMode::Edit => { terminal.show_cursor()?; } AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; 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().context("Failed to show cursor in ReadOnly mode")?; } AppMode::General => { if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; } else { terminal.hide_cursor()?; } } AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; } } // --- End Cursor Visibility Logic --- let total_count = app_state.total_count; let mut current_position = app_state.current_position; let position_before_event = current_position; if app_state.ui.dialog.is_loading { needs_redraw = true; } // --- 1. Handle Terminal Events --- let mut event_outcome_result = Ok(EventOutcome::Ok(String::new())); let mut event_processed = false; if crossterm_event::poll(std::time::Duration::from_millis(1))? { let event = event_reader.read_event().context("Failed to read terminal event")?; event_processed = true; event_outcome_result = event_handler.handle_event( event, &config, &mut terminal, &mut grpc_client, &mut command_handler, &mut form_state, &mut auth_state, &mut login_state, &mut register_state, &mut intro_state, &mut admin_state, &mut buffer_state, &mut app_state, total_count, &mut current_position, ).await; } if event_processed { needs_redraw = true; } app_state.current_position = current_position; // --- Check for Login Results from Channel --- match login_result_receiver.try_recv() { Ok(result) => { if login::handle_login_result(result, &mut app_state, &mut auth_state, &mut login_state) { needs_redraw = true; } } Err(mpsc::error::TryRecvError::Empty) => { /* No message waiting */ } Err(mpsc::error::TryRecvError::Disconnected) => { error!("Login result channel disconnected unexpectedly."); } } // --- Check for Register Results from Channel --- match register_result_receiver.try_recv() { Ok(result) => { if register::handle_registration_result(result, &mut app_state, &mut register_state) { needs_redraw = true; } } Err(mpsc::error::TryRecvError::Empty) => { /* No message waiting */ } Err(mpsc::error::TryRecvError::Disconnected) => { error!("Register result channel disconnected unexpectedly."); } } // --- Check for Save Table Results --- match save_table_result_receiver.try_recv() { Ok(result) => { app_state.hide_dialog(); match result { Ok(ref success_message) => { app_state.show_dialog( "Save Successful", success_message, vec!["OK".to_string()], DialogPurpose::SaveTableSuccess, ); admin_state.add_table_state.has_unsaved_changes = false; } Err(e) => { event_handler.command_message = format!("Save failed: {}", e); } } needs_redraw = true; } Err(mpsc::error::TryRecvError::Empty) => {} Err(mpsc::error::TryRecvError::Disconnected) => { error!("Save table result channel disconnected unexpectedly."); } } // --- Centralized Consequence Handling --- let mut should_exit = false; match event_outcome_result { Ok(outcome) => match outcome { EventOutcome::Ok(_message) => { // Message is often set directly in event_handler.command_message } EventOutcome::Exit(message) => { event_handler.command_message = message; should_exit = true; } EventOutcome::DataSaved(save_outcome, message) => { event_handler.command_message = message; if let Err(e) = UiService::handle_save_outcome( save_outcome, &mut grpc_client, &mut app_state, &mut form_state, ) .await { event_handler.command_message = format!("Error handling save outcome: {}", e); } } EventOutcome::ButtonSelected { context: _, index: _ } => { // Handled within event_handler or specific navigation modules } }, Err(e) => { event_handler.command_message = format!("Error: {}", e); } } // --- End Consequence Handling --- // --- Position Change Handling --- let position_changed = app_state.current_position != position_before_event; let current_total_count = app_state.total_count; // Use current total_count let mut position_logic_needs_redraw = false; 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 } else { 0 }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); position_logic_needs_redraw = true; if app_state.current_position > current_total_count + 1 { app_state.current_position = current_total_count + 1; } if app_state.current_position > current_total_count { form_state.reset_to_empty(); form_state.current_field = 0; } else if app_state.current_position >= 1 && app_state.current_position <= current_total_count { let current_position_to_load = app_state.current_position; let load_message = UiService::load_adresar_by_position( &mut grpc_client, &mut app_state, &mut form_state, current_position_to_load, ) .await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?; let current_input_after_load = form_state.get_current_input(); let max_cursor_pos_after_load = if !event_handler.is_edit_mode && !current_input_after_load.is_empty() { current_input_after_load.len() - 1 } else { current_input_after_load.len() }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_after_load); if !load_message.starts_with("Loaded entry") || event_handler.command_message.is_empty() { event_handler.command_message = load_message; } } else { // current_position is 0 or invalid app_state.current_position = 1.min(current_total_count + 1); if app_state.current_position > current_total_count { // Handles empty db case form_state.reset_to_empty(); form_state.current_field = 0; } // If db is not empty, this will trigger load in next iteration if position changed to 1 } } else 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 } else { 0 }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); } } else if app_state.ui.show_register { if !event_handler.is_edit_mode { let current_input = register_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); } } else if app_state.ui.show_login { if !event_handler.is_edit_mode { let current_input = login_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); } } if position_logic_needs_redraw { needs_redraw = true; } // --- End Position Change Handling --- if should_exit { return Ok(()); } // --- FPS Calculation --- let now = Instant::now(); let frame_duration = now.duration_since(last_frame_time); last_frame_time = now; if frame_duration.as_secs_f64() > 1e-6 { // Avoid division by zero current_fps = 1.0 / frame_duration.as_secs_f64(); } } // End main loop }