rush_sync_server/ui/
screen.rs

1// =====================================================
2// FILE: src/ui/screen.rs - MIT TERMINAL-CURSOR-SUPPORT
3// =====================================================
4
5use crate::commands::history::HistoryKeyboardHandler;
6use crate::commands::lang::LanguageService;
7use crate::commands::theme::ThemeSystem;
8use crate::core::prelude::*;
9use crate::input::{
10    input::InputState,
11    keyboard::{KeyAction, KeyboardManager},
12};
13use crate::input::{AppEvent, EventHandler};
14use crate::output::{display::MessageDisplay, logging::AppLogger};
15use crate::ui::{
16    color::AppColor, terminal::TerminalManager, viewport::ScrollDirection, widget::Widget,
17};
18
19use crossterm::event::KeyEvent;
20use ratatui::{backend::CrosstermBackend, Terminal};
21use std::io::{self, Stdout};
22
23pub type TerminalBackend = Terminal<CrosstermBackend<Stdout>>;
24
25use crossterm::execute;
26
27pub struct ScreenManager {
28    terminal: TerminalBackend,
29    message_display: MessageDisplay,
30    input_state: Box<dyn Widget>,
31    config: Config,
32    terminal_mgr: TerminalManager,
33    events: EventHandler,
34    keyboard_manager: KeyboardManager,
35    waiting_for_restart_confirmation: bool,
36}
37
38impl ScreenManager {
39    pub async fn new(config: &Config) -> Result<Self> {
40        let mut terminal_mgr = TerminalManager::new().await?;
41        terminal_mgr.setup().await?;
42
43        let backend = CrosstermBackend::new(io::stdout());
44        let terminal = Terminal::new(backend)?;
45        let size = terminal.size()?;
46
47        let message_display = MessageDisplay::new(config, size.width, size.height);
48
49        log::info!(
50            "{}",
51            t!(
52                "screen.initialized",
53                &size.width.to_string(),
54                &size.height.to_string(),
55                &message_display.viewport().debug_info()
56            )
57        );
58
59        let owned_config = config.clone();
60
61        Ok(Self {
62            terminal,
63            terminal_mgr,
64            message_display,
65            input_state: Box::new(InputState::new(config)),
66            config: owned_config,
67            events: EventHandler::new(config.poll_rate),
68            keyboard_manager: KeyboardManager::new(),
69            waiting_for_restart_confirmation: false,
70        })
71    }
72
73    pub async fn run(&mut self) -> Result<()> {
74        let result = loop {
75            if let Some(event) = self.events.next().await {
76                match event {
77                    AppEvent::Input(key) => {
78                        if self.handle_input_event(key).await? {
79                            self.events.shutdown().await;
80                            break Ok(());
81                        }
82                    }
83                    AppEvent::Resize(width, height) => {
84                        self.handle_resize_event(width, height).await?;
85                    }
86                    AppEvent::Tick => {
87                        self.handle_tick_event().await?;
88                    }
89                }
90            }
91
92            self.process_pending_logs().await;
93            self.render().await?;
94        };
95
96        self.terminal_mgr.cleanup().await?;
97        result
98    }
99
100    async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
101        // HISTORY HANDLING ZUERST
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) = LanguageService::process_save_message(&new_input).await {
105                    self.message_display.add_message(processed);
106                    return Ok(false);
107                }
108
109                if let Some(processed) = self.process_live_theme_update(&new_input).await {
110                    self.message_display.add_message(processed);
111                    return Ok(false);
112                }
113
114                self.message_display.add_message(new_input.clone());
115
116                if new_input.starts_with("__CLEAR__") {
117                    self.message_display.clear_messages();
118                } else if new_input.starts_with("__EXIT__") {
119                    return Ok(true);
120                }
121            }
122            return Ok(false);
123        }
124
125        // SCROLL-HANDLING MIT VIEWPORT
126        match self.keyboard_manager.get_action(&key) {
127            KeyAction::ScrollUp => {
128                self.message_display.handle_scroll(ScrollDirection::Up, 1);
129                return Ok(false);
130            }
131            KeyAction::ScrollDown => {
132                self.message_display.handle_scroll(ScrollDirection::Down, 1);
133                return Ok(false);
134            }
135            KeyAction::PageUp => {
136                self.message_display
137                    .handle_scroll(ScrollDirection::PageUp, 0);
138                return Ok(false);
139            }
140            KeyAction::PageDown => {
141                self.message_display
142                    .handle_scroll(ScrollDirection::PageDown, 0);
143                return Ok(false);
144            }
145            KeyAction::Submit => {
146                if let Some(new_input) = self.input_state.handle_input(key) {
147                    let input_command = new_input.trim().to_lowercase();
148                    let is_performance_command = input_command == "perf"
149                        || input_command == "performance"
150                        || input_command == "stats";
151
152                    if let Some(processed) = LanguageService::process_save_message(&new_input).await
153                    {
154                        self.message_display.add_message(processed);
155                        return Ok(false);
156                    }
157
158                    if let Some(processed) = self.process_live_theme_update(&new_input).await {
159                        self.message_display.add_message(processed);
160                        return Ok(false);
161                    }
162
163                    self.message_display.add_message(new_input.clone());
164
165                    if is_performance_command {
166                        log::debug!(
167                            "{}",
168                            t!("screen.performance_command_detected", &input_command)
169                        );
170
171                        tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
172                        self.message_display.viewport_mut().force_auto_scroll();
173
174                        log::debug!(
175                            "{}",
176                            t!("screen.performance_command_viewport_reset_applied")
177                        );
178                    }
179
180                    if new_input.starts_with("__CLEAR__") {
181                        self.message_display.clear_messages();
182                    } else if new_input.starts_with("__EXIT__") {
183                        return Ok(true);
184                    } else if new_input.starts_with("__RESTART_WITH_MSG__") {
185                        let feedback_msg = new_input
186                            .replace("__RESTART_WITH_MSG__", "")
187                            .trim()
188                            .to_string();
189
190                        if !feedback_msg.is_empty() {
191                            self.message_display.add_message(feedback_msg);
192                            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
193                        }
194
195                        if let Err(e) = self.perform_restart().await {
196                            self.message_display
197                                .add_message(format!("Restart failed: {}", e));
198                        }
199                    } else if new_input.starts_with("__RESTART_FORCE__")
200                        || new_input == "__RESTART__"
201                    {
202                        if let Err(e) = self.perform_restart().await {
203                            self.message_display
204                                .add_message(format!("Restart failed: {}", e));
205                        }
206                    }
207                }
208            }
209            KeyAction::Quit => return Ok(true),
210            _ => {
211                if let Some(new_input) = self.input_state.handle_input(key) {
212                    if let Some(processed) = LanguageService::process_save_message(&new_input).await
213                    {
214                        self.message_display.add_message(processed);
215                        return Ok(false);
216                    }
217
218                    if let Some(processed) = self.process_live_theme_update(&new_input).await {
219                        self.message_display.add_message(processed);
220                        return Ok(false);
221                    }
222
223                    self.message_display.add_message(new_input);
224                }
225            }
226        }
227        Ok(false)
228    }
229
230    async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
231        if !message.starts_with("__LIVE_THEME_UPDATE__") {
232            return None;
233        }
234
235        let parts: Vec<&str> = message.split("__MESSAGE__").collect();
236        if parts.len() != 2 {
237            log::error!("{}", t!("screen.theme.invalid_format"));
238            return None;
239        }
240
241        let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
242        let display_message = parts[1];
243
244        log::info!(
245            "🎨 LIVE THEME UPDATE STARTING: '{}' β†’ '{}'",
246            self.config.current_theme_name,
247            theme_part
248        );
249
250        let theme_system = match ThemeSystem::load() {
251            Ok(system) => system,
252            Err(e) => {
253                log::error!("{} {}", t!("screen.theme.load_failed"), e);
254                return Some(tc!("screen.theme.failed", &e.to_string()));
255            }
256        };
257
258        if let Some(theme_def) = theme_system.get_theme(&theme_part) {
259            // βœ… DETAILED LOGGING: Jetzt mit theme_def im Scope
260            log::info!(
261                "πŸ” THEME DETAILS: prefix: '{}' β†’ '{}' | input_cursor: '{}' β†’ '{}'",
262                self.config.theme.input_cursor_prefix,
263                theme_def.input_cursor_prefix,
264                self.config.theme.input_cursor,
265                theme_def.input_cursor
266            );
267            match self.create_theme_from_definition(theme_def) {
268                Ok(new_theme) => {
269                    let backup = self.input_state.get_backup_data().unwrap_or_default();
270
271                    // βœ… DETAILED LOGGING: Show exact cursor transition
272                    log::info!(
273                        "πŸ”„ THEME TRANSITION: old='{}'/prefix='{}'/input_cursor='{}'/output_cursor='{}' β†’ new='{}'/prefix='{}'/input_cursor='{}'/output_cursor='{}'",
274                        self.config.current_theme_name,
275                        self.config.theme.input_cursor_prefix,
276                        self.config.theme.input_cursor,
277                        self.config.theme.output_cursor,
278                        theme_part,
279                        theme_def.input_cursor_prefix,
280                        theme_def.input_cursor,
281                        theme_def.output_cursor
282                    );
283
284                    // βœ… CRITICAL: Clear ALL UI state first
285                    self.message_display.clear_messages();
286
287                    // βœ… UPDATE CONFIG COMPLETELY
288                    self.config.theme = new_theme;
289                    self.config.current_theme_name = theme_part.clone();
290
291                    // βœ… FORCE COMPLETE UI RECREATION mit zentraler Cursor-API
292                    log::info!("πŸ”„ FORCING MessageDisplay config update...");
293                    self.message_display.update_config(&self.config);
294
295                    log::info!("πŸ”„ RECREATING InputState with central cursor API...");
296                    self.input_state = Box::new(InputState::new(&self.config));
297                    self.input_state.restore_backup_data(backup.clone());
298
299                    // βœ… FINAL VERIFICATION
300                    log::info!(
301                        "βœ… LIVE THEME APPLIED: theme='{}' | prefix='{}' | input_cursor='{}' | output_cursor='{}' | output_cursor_color='{}' | history={}",
302                        theme_part.to_uppercase(),
303                        self.config.theme.input_cursor_prefix,
304                        self.config.theme.input_cursor,
305                        self.config.theme.output_cursor,
306                        self.config.theme.output_cursor_color.to_name(),
307                        backup.history.len()
308                    );
309
310                    Some(display_message.to_string())
311                }
312                Err(e) => {
313                    log::error!("{} {}", t!("screen.theme.load_failed"), e);
314                    Some(tc!("screen.theme.failed", &e.to_string()))
315                }
316            }
317        } else {
318            log::error!("{} {}", t!("screen.theme.not_found"), theme_part);
319            Some(tc!("screen.theme.not_found_feedback", theme_part.as_str()))
320        }
321    }
322
323    fn create_theme_from_definition(
324        &self,
325        theme_def: &crate::commands::theme::ThemeDefinition,
326    ) -> Result<crate::core::config::Theme> {
327        use crate::ui::color::AppColor;
328
329        Ok(crate::core::config::Theme {
330            input_text: AppColor::from_string(&theme_def.input_text)?,
331            input_bg: AppColor::from_string(&theme_def.input_bg)?,
332            cursor: AppColor::from_string(&theme_def.cursor)?,
333            output_text: AppColor::from_string(&theme_def.output_text)?,
334            output_bg: AppColor::from_string(&theme_def.output_bg)?,
335
336            // βœ… PERFEKTE CURSOR-KONFIGURATION
337            input_cursor_prefix: theme_def.input_cursor_prefix.clone(),
338            input_cursor_color: AppColor::from_string(&theme_def.input_cursor_color)?,
339            input_cursor: theme_def.input_cursor.clone(),
340            output_cursor: theme_def.output_cursor.clone(),
341            output_cursor_color: AppColor::from_string(&theme_def.output_cursor_color)
342                .unwrap_or_default(),
343        })
344    }
345
346    async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
347        log::info!(
348            "{}",
349            t!(
350                "screen.resize_event",
351                &self
352                    .message_display
353                    .viewport()
354                    .terminal_size()
355                    .0
356                    .to_string(),
357                &self
358                    .message_display
359                    .viewport()
360                    .terminal_size()
361                    .1
362                    .to_string(),
363                &width.to_string(),
364                &height.to_string()
365            )
366        );
367
368        let changed = self.message_display.handle_resize(width, height);
369
370        if changed {
371            log::info!(
372                "{}",
373                t!(
374                    "screen.resize_completed",
375                    &self.message_display.viewport().debug_info()
376                )
377            );
378        }
379
380        Ok(())
381    }
382
383    async fn handle_tick_event(&mut self) -> Result<()> {
384        // βœ… TYPEWRITER-CURSOR UPDATE: Blinken + Progression
385        self.message_display.update_typewriter();
386
387        // βœ… INPUT-CURSOR UPDATE: Nur blinken (zentrale API)
388        if let Some(input_state) = self.input_state.as_input_state() {
389            input_state.update_cursor_blink();
390        }
391        Ok(())
392    }
393
394    async fn process_pending_logs(&mut self) {
395        match AppLogger::get_messages() {
396            Ok(messages) => {
397                for log_msg in messages {
398                    self.message_display.add_message(log_msg.formatted());
399                }
400            }
401            Err(e) => {
402                self.message_display.add_message(
403                    AppColor::from_any("error")
404                        .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
405                );
406            }
407        }
408    }
409
410    /// βœ… RENDER mit korrektem Cursor-Hide/Show
411    async fn render(&mut self) -> Result<()> {
412        // βœ… 1. CURSOR-INFO VOR draw() holen
413        let (input_widget, cursor_pos) = self.input_state.render_with_cursor();
414
415        self.terminal.draw(|frame| {
416            let size = frame.size();
417
418            if size.width < 10 || size.height < 5 {
419                log::error!(
420                    "{}",
421                    t!(
422                        "screen.render.too_small_log",
423                        &size.width.to_string(),
424                        &size.height.to_string()
425                    )
426                );
427
428                let emergency_area = ratatui::layout::Rect {
429                    x: 0,
430                    y: 0,
431                    width: size.width.max(1),
432                    height: size.height.max(1),
433                };
434
435                let emergency_widget =
436                    ratatui::widgets::Paragraph::new(t!("screen.render.too_small.text"))
437                        .block(ratatui::widgets::Block::default());
438
439                frame.render_widget(emergency_widget, emergency_area);
440                return;
441            }
442
443            let viewport = self.message_display.viewport();
444
445            if !viewport.is_usable() {
446                log::error!("{}", t!("screen.render.viewport_not_usable_log"));
447
448                let error_area = ratatui::layout::Rect {
449                    x: 0,
450                    y: 0,
451                    width: size.width,
452                    height: size.height,
453                };
454
455                let error_widget =
456                    ratatui::widgets::Paragraph::new(t!("screen.render.viewport_error.text"))
457                        .block(ratatui::widgets::Block::default());
458
459                frame.render_widget(error_widget, error_area);
460                return;
461            }
462
463            let output_area = viewport.output_area();
464            let input_area = viewport.input_area();
465
466            if !output_area.is_valid() || !input_area.is_valid() {
467                log::error!(
468                    "{}",
469                    t!(
470                        "screen.render.invalid_layout_log",
471                        &output_area.width.to_string(),
472                        &output_area.height.to_string(),
473                        &output_area.x.to_string(),
474                        &output_area.y.to_string(),
475                        &input_area.width.to_string(),
476                        &input_area.height.to_string(),
477                        &input_area.x.to_string(),
478                        &input_area.y.to_string()
479                    )
480                );
481                return;
482            }
483
484            if output_area.x + output_area.width > size.width
485                || output_area.y + output_area.height > size.height
486                || input_area.x + input_area.width > size.width
487                || input_area.y + input_area.height > size.height
488            {
489                log::error!(
490                    "{}",
491                    t!(
492                        "screen.render.exceed_bounds_log",
493                        &size.width.to_string(),
494                        &size.height.to_string()
495                    )
496                );
497                return;
498            }
499
500            // βœ… TYPEWRITER-CURSOR AWARE RENDERING
501            let (visible_messages, config, output_layout, cursor_state) =
502                self.message_display.create_output_widget_for_rendering();
503
504            let message_refs: Vec<(String, usize, bool, bool, bool)> = visible_messages;
505
506            let output_widget = crate::output::display::create_output_widget(
507                &message_refs,
508                output_layout,
509                &config,
510                cursor_state,
511            );
512
513            frame.render_widget(output_widget, output_area.as_rect());
514            frame.render_widget(input_widget, input_area.as_rect());
515
516            // βœ… CURSOR POSITION setzen (cursor_pos ist hier verfΓΌgbar durch Closure-Capture)
517            if let Some((cursor_x, cursor_y)) = cursor_pos {
518                let absolute_x = input_area.x + cursor_x;
519                let absolute_y = input_area.y + cursor_y;
520                frame.set_cursor(absolute_x, absolute_y);
521            }
522        })?;
523
524        // βœ… 2. CURSOR-STIL setzen (NACH dem draw!)
525        if cursor_pos.is_some() {
526            // Cursor ist sichtbar β†’ Cursor-Stil setzen
527            let cursor_commands = self.get_terminal_cursor_commands();
528            for command in cursor_commands {
529                execute!(std::io::stdout(), crossterm::style::Print(command))?;
530            }
531        } else {
532            // Cursor ist unsichtbar (Blinken) β†’ Cursor verstecken
533            execute!(
534                std::io::stdout(),
535                crossterm::style::Print("\x1B[?25l") // Hide cursor
536            )?;
537        }
538
539        Ok(())
540    }
541
542    /// βœ… FIXED: Terminal-Cursor-Kommandos fΓΌr korrekte Layer-Darstellung
543    fn get_terminal_cursor_commands(&self) -> Vec<&'static str> {
544        match self.config.theme.input_cursor.to_uppercase().as_str() {
545            "PIPE" | "DEFAULT" => vec![
546                "\x1B[6 q",  // Blinking bar (pipe)
547                "\x1B[?25h", // Show cursor
548            ],
549            "UNDERSCORE" => vec![
550                "\x1B[4 q",  // Blinking underscore
551                "\x1B[?25h", // Show cursor
552            ],
553            "BLOCK" => vec![
554                "\x1B[2 q",  // Blinking block (fallback, sollte nie erreicht werden)
555                "\x1B[?25h", // Show cursor
556            ],
557            _ => vec![
558                "\x1B[6 q",  // Default: Blinking bar
559                "\x1B[?25h", // Show cursor
560            ],
561        }
562    }
563
564    async fn perform_restart(&mut self) -> Result<()> {
565        log::info!("{}", t!("screen.restart.start"));
566
567        self.terminal_mgr.cleanup().await?;
568        self.terminal_mgr = TerminalManager::new().await?;
569        self.terminal_mgr.setup().await?;
570
571        let backend = CrosstermBackend::new(io::stdout());
572        self.terminal = Terminal::new(backend)?;
573        let size = self.terminal.size()?;
574
575        self.message_display = MessageDisplay::new(&self.config, size.width, size.height);
576        self.input_state = Box::new(InputState::new(&self.config));
577        self.waiting_for_restart_confirmation = false;
578
579        self.message_display
580            .add_message(tc!("system.commands.restart.success"));
581
582        log::info!("{}", t!("screen.restart.done"));
583        Ok(())
584    }
585}
586
587/// βœ… i18n Integration Helper (unverΓ€ndert)
588impl ScreenManager {
589    pub fn validate_i18n_keys() -> Vec<String> {
590        let required_keys = [
591            "screen.performance_command_detected",
592            "screen.performance_command_viewport_reset_applied",
593            "screen.theme.invalid_format",
594            "screen.theme.processing",
595            "screen.theme.load_failed",
596            "screen.theme.failed",
597            "screen.theme.applied",
598            "screen.theme.not_found",
599            "screen.theme.not_found_feedback",
600            "screen.render.too_small_log",
601            "screen.render.too_small.text",
602            "screen.render.viewport_not_usable_log",
603            "screen.render.viewport_error.text",
604            "screen.render.invalid_layout_log",
605            "screen.render.exceed_bounds_log",
606            "screen.restart.start",
607            "screen.restart.done",
608            "system.commands.restart.success",
609        ];
610
611        let mut missing = Vec::new();
612        for key in required_keys {
613            if !crate::i18n::has_translation(key) {
614                missing.push(key.to_string());
615            }
616        }
617        missing
618    }
619
620    pub fn get_i18n_debug_info() -> HashMap<String, usize> {
621        let mut info = HashMap::new();
622        let stats = crate::i18n::get_translation_stats();
623
624        info.insert("screen_manager_keys".to_string(), 18);
625        info.extend(stats);
626
627        info
628    }
629}