rush_sync_server/ui/
screen.rs

1// =====================================================
2// FILE: src/ui/screen.rs - VOLLSTÄNDIG mit LIVE UPDATE PROCESSING
3// =====================================================
4
5use crate::commands::history::HistoryKeyboardHandler;
6use crate::commands::lang::LanguageManager;
7use crate::commands::theme::TomlThemeLoader;
8use crate::core::prelude::*;
9use crate::input::{
10    event::{AppEvent, EventHandler},
11    input::InputState,
12    keyboard::{KeyAction, KeyboardManager},
13};
14use crate::output::{logging::AppLogger, message::MessageManager, output::create_output_widget};
15use crate::ui::{color::AppColor, terminal::TerminalManager, widget::Widget};
16
17use crossterm::event::KeyEvent;
18use ratatui::{
19    backend::CrosstermBackend,
20    layout::{Constraint, Direction, Layout},
21    Terminal,
22};
23use std::io::{self, Stdout};
24
25pub type TerminalBackend = Terminal<CrosstermBackend<Stdout>>;
26
27pub struct ScreenManager {
28    terminal: TerminalBackend,
29    message_manager: MessageManager,
30    input_state: Box<dyn Widget>,
31    terminal_size: (u16, u16),
32    config: Config, // ✅ OWNED statt &'a Config für Live-Updates!
33    terminal_mgr: TerminalManager,
34    events: EventHandler,
35    keyboard_manager: KeyboardManager,
36    waiting_for_restart_confirmation: bool,
37}
38
39impl ScreenManager {
40    /// ✅ CONSTRUCTOR mit owned config
41    pub async fn new(config: &Config) -> Result<Self> {
42        let mut terminal_mgr = TerminalManager::new().await?;
43        terminal_mgr.setup().await?;
44
45        let backend = CrosstermBackend::new(io::stdout());
46        let terminal = Terminal::new(backend)?;
47        let size = terminal.size()?;
48
49        let initial_height = size.height.saturating_sub(4) as usize;
50        let mut message_manager = MessageManager::new(config);
51        message_manager
52            .scroll_state
53            .update_dimensions(initial_height, 0);
54
55        // ✅ CLONE Config für owned ownership
56        let owned_config = config.clone();
57
58        Ok(Self {
59            terminal,
60            terminal_mgr,
61            message_manager,
62            input_state: Box::new(InputState::new(&config.prompt.text, config)),
63            terminal_size: (size.width, size.height),
64            config: owned_config, // ✅ OWNED Config
65            events: EventHandler::new(config.poll_rate),
66            keyboard_manager: KeyboardManager::new(),
67            waiting_for_restart_confirmation: false,
68        })
69    }
70
71    /// ✅ Hauptloop: Nur Dispatcher, schlank & lesbar
72    pub async fn run(&mut self) -> Result<()> {
73        let result = loop {
74            if let Some(event) = self.events.next().await {
75                match event {
76                    AppEvent::Input(key) => {
77                        if self.handle_input_event(key).await? {
78                            self.events.shutdown().await;
79                            break Ok(());
80                        }
81                    }
82                    AppEvent::Resize(width, height) => {
83                        self.handle_resize_event(width, height).await?;
84                    }
85                    AppEvent::Tick => {
86                        self.handle_tick_event().await?;
87                    }
88                }
89            }
90
91            self.process_pending_logs().await;
92            self.render().await?;
93        };
94
95        self.terminal_mgr.cleanup().await?;
96        result
97    }
98
99    /// ✅ Eingaben mit LIVE THEME UPDATE PROCESSING
100    async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
101        // History
102        if HistoryKeyboardHandler::get_history_action(&key).is_some() {
103            if let Some(new_input) = self.input_state.handle_input(key) {
104                if let Some(processed) = LanguageManager::process_save_message(&new_input).await {
105                    self.message_manager.add_message(processed);
106                    return Ok(false);
107                }
108
109                // ✅ NEU: LIVE THEME UPDATE PROCESSING
110                if let Some(processed) = self.process_live_theme_update(&new_input).await {
111                    self.message_manager.add_message(processed);
112                    return Ok(false);
113                }
114
115                self.message_manager.add_message(new_input.clone());
116
117                if new_input.starts_with("__CLEAR__") {
118                    self.message_manager.clear_messages();
119                } else if new_input.starts_with("__EXIT__") {
120                    return Ok(true);
121                }
122            }
123            return Ok(false);
124        }
125
126        // Normale Keys
127        match self.keyboard_manager.get_action(&key) {
128            KeyAction::ScrollUp
129            | KeyAction::ScrollDown
130            | KeyAction::PageUp
131            | KeyAction::PageDown => {
132                let window_height = self.get_content_height();
133                self.message_manager
134                    .handle_scroll(self.keyboard_manager.get_action(&key), window_height);
135            }
136            KeyAction::Submit => {
137                if let Some(new_input) = self.input_state.handle_input(key) {
138                    if let Some(processed) = LanguageManager::process_save_message(&new_input).await
139                    {
140                        self.message_manager.add_message(processed);
141                        return Ok(false);
142                    }
143
144                    // ✅ NEU: LIVE THEME UPDATE PROCESSING
145                    if let Some(processed) = self.process_live_theme_update(&new_input).await {
146                        self.message_manager.add_message(processed);
147                        return Ok(false);
148                    }
149
150                    self.message_manager.add_message(new_input.clone());
151                    if new_input.starts_with("__CLEAR__") {
152                        self.message_manager.clear_messages();
153                    } else if new_input.starts_with("__EXIT__") {
154                        return Ok(true);
155                    }
156                    // ✅ LEGACY: Restart support (falls noch verwendet)
157                    else if new_input.starts_with("__RESTART_WITH_MSG__") {
158                        let feedback_msg = new_input
159                            .replace("__RESTART_WITH_MSG__", "")
160                            .trim()
161                            .to_string();
162
163                        if !feedback_msg.is_empty() {
164                            self.message_manager.add_message(feedback_msg);
165                            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
166                        }
167
168                        if let Err(e) = self.perform_restart().await {
169                            self.message_manager
170                                .add_message(format!("Restart failed: {}", e));
171                        }
172                    } else if new_input.starts_with("__RESTART_FORCE__")
173                        || new_input == "__RESTART__"
174                    {
175                        if let Err(e) = self.perform_restart().await {
176                            self.message_manager
177                                .add_message(format!("Restart failed: {}", e));
178                        }
179                    }
180                }
181            }
182            KeyAction::Quit => return Ok(true),
183            _ => {
184                if let Some(new_input) = self.input_state.handle_input(key) {
185                    if let Some(processed) = LanguageManager::process_save_message(&new_input).await
186                    {
187                        self.message_manager.add_message(processed);
188                        return Ok(false);
189                    }
190
191                    // ✅ NEU: LIVE THEME UPDATE PROCESSING
192                    if let Some(processed) = self.process_live_theme_update(&new_input).await {
193                        self.message_manager.add_message(processed);
194                        return Ok(false);
195                    }
196
197                    self.message_manager.add_message(new_input);
198                }
199            }
200        }
201        Ok(false)
202    }
203
204    async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
205        if !message.starts_with("__LIVE_THEME_UPDATE__") {
206            return None;
207        }
208
209        let parts: Vec<&str> = message.split("__MESSAGE__").collect();
210        if parts.len() != 2 {
211            log::error!("Invalid live theme update format: {}", message);
212            return None;
213        }
214
215        let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
216        let display_message = parts[1];
217
218        log::debug!("🎨 Processing live theme update: {}", theme_part);
219
220        // ✅ Lade Theme aus TOML
221        if let Some(theme_def) = TomlThemeLoader::load_theme_by_name_sync(&theme_part) {
222            match self.create_theme_from_definition(&theme_def) {
223                Ok(new_theme) => {
224                    // ✅ STEP 1: BACKUP current InputState data
225                    let backup = self.input_state.get_backup_data().unwrap_or_default();
226                    log::debug!(
227                        "📦 Backed up {} history entries, content: '{}'",
228                        backup.history.len(),
229                        backup.content
230                    );
231
232                    // ✅ STEP 2: UPDATE config with new theme
233                    self.config.theme = new_theme;
234                    self.config.current_theme_name = theme_part.clone();
235
236                    // ✅ STEP 3: CREATE new InputState with new config
237                    self.input_state =
238                        Box::new(InputState::new(&self.config.prompt.text, &self.config));
239
240                    // ✅ STEP 4: RESTORE backed up data
241                    self.input_state.restore_backup_data(backup.clone());
242                    log::debug!(
243                        "🔄 Restored {} history entries to new InputState",
244                        backup.history.len()
245                    );
246
247                    // ✅ STEP 5: UPDATE MessageManager
248                    self.message_manager.update_config(&self.config);
249
250                    log::info!(
251                        "✅ Live theme '{}' applied from TOML - {} history entries preserved!",
252                        theme_part.to_uppercase(),
253                        backup.history.len()
254                    );
255                    Some(display_message.to_string())
256                }
257                Err(e) => {
258                    log::error!("❌ Failed to create theme: {}", e);
259                    Some(format!("❌ Theme update failed: {}", e))
260                }
261            }
262        } else {
263            log::error!("❌ Theme '{}' not found in TOML", theme_part);
264            Some(format!("❌ Theme '{}' not found in config", theme_part))
265        }
266    }
267
268    // ✅ HELPER: Theme aus TOML-Definition erstellen
269    fn create_theme_from_definition(
270        &self,
271        theme_def: &crate::commands::theme::ThemeDefinition,
272    ) -> Result<crate::core::config::Theme> {
273        use crate::ui::color::AppColor;
274
275        Ok(crate::core::config::Theme {
276            input_text: AppColor::from_string(&theme_def.input_text)?,
277            input_bg: AppColor::from_string(&theme_def.input_bg)?,
278            cursor: AppColor::from_string(&theme_def.cursor)?,
279            output_text: AppColor::from_string(&theme_def.output_text)?,
280            output_bg: AppColor::from_string(&theme_def.output_bg)?,
281        })
282    }
283
284    /// ✅ Fenstergröße anpassen
285    async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
286        self.terminal_size = (width, height);
287        let window_height = self.get_content_height();
288        self.message_manager
289            .scroll_state
290            .update_dimensions(window_height, self.message_manager.get_content_height());
291        Ok(())
292    }
293
294    /// ✅ Tick (Typewriter, Cursor-Blink)
295    async fn handle_tick_event(&mut self) -> Result<()> {
296        self.message_manager.update_typewriter();
297        if let Some(input_state) = self.input_state.as_input_state() {
298            input_state.update_cursor_blink();
299        }
300        Ok(())
301    }
302
303    fn get_content_height(&self) -> usize {
304        self.terminal_size.1.saturating_sub(4) as usize
305    }
306
307    async fn process_pending_logs(&mut self) {
308        match AppLogger::get_messages() {
309            Ok(messages) => {
310                for log_msg in messages {
311                    self.message_manager.add_message(log_msg.formatted());
312                }
313            }
314            Err(e) => {
315                self.message_manager.add_message(
316                    AppColor::from_any("error")
317                        .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
318                );
319            }
320        }
321    }
322
323    async fn render(&mut self) -> Result<()> {
324        self.terminal.draw(|frame| {
325            let size = frame.size();
326            if size.width < 20 || size.height < 10 {
327                return;
328            }
329
330            let chunks = Layout::default()
331                .direction(Direction::Vertical)
332                .margin(1)
333                .constraints([Constraint::Min(3), Constraint::Length(3)])
334                .split(size);
335
336            let available_height = chunks[0].height as usize;
337            self.message_manager
338                .scroll_state
339                .update_dimensions(available_height, self.message_manager.get_content_height());
340
341            let messages = self.message_manager.get_messages();
342            let output_widget =
343                create_output_widget(&messages, available_height as u16, &self.config);
344            frame.render_widget(output_widget, chunks[0]);
345
346            let input_widget = self.input_state.render();
347            frame.render_widget(input_widget, chunks[1]);
348        })?;
349        Ok(())
350    }
351
352    async fn perform_restart(&mut self) -> Result<()> {
353        // ✅ LEGACY restart function (falls noch benötigt)
354        self.terminal_mgr.cleanup().await?;
355        self.terminal_mgr = TerminalManager::new().await?;
356        self.terminal_mgr.setup().await?;
357
358        let backend = CrosstermBackend::new(io::stdout());
359        self.terminal = Terminal::new(backend)?;
360
361        self.message_manager.clear_messages();
362        self.input_state = Box::new(InputState::new(&self.config.prompt.text, &self.config));
363        self.waiting_for_restart_confirmation = false;
364
365        self.message_manager
366            .add_message(crate::i18n::get_command_translation(
367                "system.commands.restart.success",
368                &[],
369            ));
370        log::info!("Internal restart completed successfully");
371        Ok(())
372    }
373}