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    // βœ… FIXED: Live-Theme-Update mit korrekter Cursor-Farb-Übertragung
231    async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
232        if !message.starts_with("__LIVE_THEME_UPDATE__") {
233            return None;
234        }
235
236        let parts: Vec<&str> = message.split("__MESSAGE__").collect();
237        if parts.len() != 2 {
238            log::error!("{}", t!("screen.theme.invalid_format"));
239            return None;
240        }
241
242        let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
243        let display_message = parts[1];
244
245        log::info!(
246            "🎨 LIVE THEME UPDATE STARTING: '{}' β†’ '{}'",
247            self.config.current_theme_name,
248            theme_part
249        );
250
251        let theme_system = match ThemeSystem::load() {
252            Ok(system) => system,
253            Err(e) => {
254                log::error!("{} {}", t!("screen.theme.load_failed"), e);
255                return Some(tc!("screen.theme.failed", &e.to_string()));
256            }
257        };
258
259        if let Some(theme_def) = theme_system.get_theme(&theme_part) {
260            // βœ… KRITISCHER FIX: Theme-Definition Details loggen
261            log::info!(
262                "πŸ“‹ THEME DEFINITION LOADED:\n  \
263                input_cursor_prefix: '{}'\n  \
264                input_cursor_color: '{}'\n  \
265                input_cursor: '{}'\n  \
266                output_cursor: '{}'\n  \
267                output_cursor_color: '{}'",
268                theme_def.input_cursor_prefix,
269                theme_def.input_cursor_color,
270                theme_def.input_cursor,
271                theme_def.output_cursor,
272                theme_def.output_cursor_color
273            );
274
275            match self.create_theme_from_definition(theme_def) {
276                Ok(new_theme) => {
277                    let backup = self.input_state.get_backup_data().unwrap_or_default();
278
279                    // βœ… KRITISCHER FIX: Theme-Konvertierung Details loggen
280                    log::info!(
281                        "πŸ”„ THEME CONVERSION COMPLETE:\n  \
282                        OLD Config: input_cursor='{}', input_cursor_color='{}'\n  \
283                        NEW Config: input_cursor='{}', input_cursor_color='{}'",
284                        self.config.theme.input_cursor,
285                        self.config.theme.input_cursor_color.to_name(),
286                        new_theme.input_cursor,
287                        new_theme.input_cursor_color.to_name()
288                    );
289
290                    // βœ… CRITICAL: Clear ALL UI state first
291                    self.message_display.clear_messages();
292
293                    // βœ… UPDATE CONFIG COMPLETELY
294                    self.config.theme = new_theme;
295                    self.config.current_theme_name = theme_part.clone();
296
297                    // βœ… FORCE COMPLETE UI RECREATION
298                    self.message_display.update_config(&self.config);
299
300                    log::info!("πŸ”„ RECREATING InputState with central cursor API...");
301                    self.input_state = Box::new(InputState::new(&self.config));
302
303                    // βœ… KRITISCHER FIX: Cursor-Details nach Recreation verifizieren
304                    if let Some(_input_widget) = self.input_state.as_input_state() {
305                        log::info!(
306                            "βœ… INPUT-CURSOR CREATED:\n  \
307                            Expected: cursor='{}' (color: {})\n  \
308                            Theme config: prefix='{}' (color: {})",
309                            self.config.theme.input_cursor,
310                            self.config.theme.input_cursor_color.to_name(),
311                            self.config.theme.input_cursor_prefix,
312                            self.config.theme.input_cursor_color.to_name()
313                        );
314                    }
315
316                    self.input_state.restore_backup_data(backup.clone());
317
318                    // βœ… FINAL VERIFICATION
319                    log::info!(
320                        "βœ… LIVE THEME APPLIED SUCCESSFULLY:\n  \
321                        theme='{}'\n  \
322                        prefix='{}'\n  \
323                        input_cursor='{}'\n  \
324                        input_cursor_color='{}'\n  \
325                        output_cursor='{}'\n  \
326                        output_cursor_color='{}'\n  \
327                        history={} entries",
328                        theme_part.to_uppercase(),
329                        self.config.theme.input_cursor_prefix,
330                        self.config.theme.input_cursor,
331                        self.config.theme.input_cursor_color.to_name(),
332                        self.config.theme.output_cursor,
333                        self.config.theme.output_cursor_color.to_name(),
334                        backup.history.len()
335                    );
336
337                    Some(display_message.to_string())
338                }
339                Err(e) => {
340                    log::error!("{} {}", t!("screen.theme.load_failed"), e);
341                    Some(tc!("screen.theme.failed", &e.to_string()))
342                }
343            }
344        } else {
345            log::error!("{} {}", t!("screen.theme.not_found"), theme_part);
346            Some(tc!("screen.theme.not_found_feedback", theme_part.as_str()))
347        }
348    }
349
350    // βœ… FIXED: Theme-Konvertierung mit detailliertem Logging
351    fn create_theme_from_definition(
352        &self,
353        theme_def: &crate::commands::theme::ThemeDefinition,
354    ) -> Result<crate::core::config::Theme> {
355        use crate::ui::color::AppColor;
356
357        log::debug!(
358            "πŸ”§ create_theme_from_definition STARTING:\n  \
359            input_cursor_prefix: '{}'\n  \
360            input_cursor_color: '{}'\n  \
361            input_cursor: '{}'\n  \
362            output_cursor: '{}'\n  \
363            output_cursor_color: '{}'",
364            theme_def.input_cursor_prefix,
365            theme_def.input_cursor_color,
366            theme_def.input_cursor,
367            theme_def.output_cursor,
368            theme_def.output_cursor_color
369        );
370
371        let input_cursor_color = AppColor::from_string(&theme_def.input_cursor_color)?;
372        let output_cursor_color = AppColor::from_string(&theme_def.output_cursor_color)?;
373
374        log::debug!(
375            "🎨 COLOR CONVERSION COMPLETE:\n  \
376            input_cursor_color: '{}' β†’ '{}'\n  \
377            output_cursor_color: '{}' β†’ '{}'",
378            theme_def.input_cursor_color,
379            input_cursor_color.to_name(),
380            theme_def.output_cursor_color,
381            output_cursor_color.to_name()
382        );
383
384        Ok(crate::core::config::Theme {
385            input_text: AppColor::from_string(&theme_def.input_text)?,
386            input_bg: AppColor::from_string(&theme_def.input_bg)?,
387            output_text: AppColor::from_string(&theme_def.output_text)?,
388            output_bg: AppColor::from_string(&theme_def.output_bg)?,
389
390            // βœ… PERFEKTE CURSOR-KONFIGURATION
391            input_cursor_prefix: theme_def.input_cursor_prefix.clone(),
392            input_cursor_color,
393            input_cursor: theme_def.input_cursor.clone(),
394            output_cursor: theme_def.output_cursor.clone(),
395            output_cursor_color,
396        })
397    }
398
399    async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
400        log::info!(
401            "{}",
402            t!(
403                "screen.resize_event",
404                &self
405                    .message_display
406                    .viewport()
407                    .terminal_size()
408                    .0
409                    .to_string(),
410                &self
411                    .message_display
412                    .viewport()
413                    .terminal_size()
414                    .1
415                    .to_string(),
416                &width.to_string(),
417                &height.to_string()
418            )
419        );
420
421        let changed = self.message_display.handle_resize(width, height);
422
423        if changed {
424            log::info!(
425                "{}",
426                t!(
427                    "screen.resize_completed",
428                    &self.message_display.viewport().debug_info()
429                )
430            );
431        }
432
433        Ok(())
434    }
435
436    async fn handle_tick_event(&mut self) -> Result<()> {
437        // βœ… TYPEWRITER-CURSOR UPDATE: Blinken + Progression
438        self.message_display.update_typewriter();
439
440        // βœ… INPUT-CURSOR UPDATE: Nur blinken (zentrale API)
441        if let Some(input_state) = self.input_state.as_input_state() {
442            input_state.update_cursor_blink();
443        }
444        Ok(())
445    }
446
447    async fn process_pending_logs(&mut self) {
448        match AppLogger::get_messages() {
449            Ok(messages) => {
450                for log_msg in messages {
451                    self.message_display.add_message(log_msg.formatted());
452                }
453            }
454            Err(e) => {
455                self.message_display.add_message(
456                    AppColor::from_any("error")
457                        .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
458                );
459            }
460        }
461    }
462
463    /// βœ… RENDER mit korrektem Cursor-Hide/Show
464    async fn render(&mut self) -> Result<()> {
465        // βœ… 1. CURSOR-INFO VOR draw() holen
466        let (input_widget, cursor_pos) = self.input_state.render_with_cursor();
467
468        self.terminal.draw(|frame| {
469            let size = frame.size();
470
471            if size.width < 10 || size.height < 5 {
472                log::error!(
473                    "{}",
474                    t!(
475                        "screen.render.too_small_log",
476                        &size.width.to_string(),
477                        &size.height.to_string()
478                    )
479                );
480
481                let emergency_area = ratatui::layout::Rect {
482                    x: 0,
483                    y: 0,
484                    width: size.width.max(1),
485                    height: size.height.max(1),
486                };
487
488                let emergency_widget =
489                    ratatui::widgets::Paragraph::new(t!("screen.render.too_small.text"))
490                        .block(ratatui::widgets::Block::default());
491
492                frame.render_widget(emergency_widget, emergency_area);
493                return;
494            }
495
496            let viewport = self.message_display.viewport();
497
498            if !viewport.is_usable() {
499                log::error!("{}", t!("screen.render.viewport_not_usable_log"));
500
501                let error_area = ratatui::layout::Rect {
502                    x: 0,
503                    y: 0,
504                    width: size.width,
505                    height: size.height,
506                };
507
508                let error_widget =
509                    ratatui::widgets::Paragraph::new(t!("screen.render.viewport_error.text"))
510                        .block(ratatui::widgets::Block::default());
511
512                frame.render_widget(error_widget, error_area);
513                return;
514            }
515
516            let output_area = viewport.output_area();
517            let input_area = viewport.input_area();
518
519            if !output_area.is_valid() || !input_area.is_valid() {
520                log::error!(
521                    "{}",
522                    t!(
523                        "screen.render.invalid_layout_log",
524                        &output_area.width.to_string(),
525                        &output_area.height.to_string(),
526                        &output_area.x.to_string(),
527                        &output_area.y.to_string(),
528                        &input_area.width.to_string(),
529                        &input_area.height.to_string(),
530                        &input_area.x.to_string(),
531                        &input_area.y.to_string()
532                    )
533                );
534                return;
535            }
536
537            if output_area.x + output_area.width > size.width
538                || output_area.y + output_area.height > size.height
539                || input_area.x + input_area.width > size.width
540                || input_area.y + input_area.height > size.height
541            {
542                log::error!(
543                    "{}",
544                    t!(
545                        "screen.render.exceed_bounds_log",
546                        &size.width.to_string(),
547                        &size.height.to_string()
548                    )
549                );
550                return;
551            }
552
553            // βœ… TYPEWRITER-CURSOR AWARE RENDERING
554            let (visible_messages, config, output_layout, cursor_state) =
555                self.message_display.create_output_widget_for_rendering();
556
557            let message_refs: Vec<(String, usize, bool, bool, bool)> = visible_messages;
558
559            let output_widget = crate::output::display::create_output_widget(
560                &message_refs,
561                output_layout,
562                &config,
563                cursor_state,
564            );
565
566            frame.render_widget(output_widget, output_area.as_rect());
567            frame.render_widget(input_widget, input_area.as_rect());
568
569            // βœ… CURSOR POSITION setzen (cursor_pos ist hier verfΓΌgbar durch Closure-Capture)
570            if let Some((cursor_x, cursor_y)) = cursor_pos {
571                let absolute_x = input_area.x + cursor_x;
572                let absolute_y = input_area.y + cursor_y;
573                frame.set_cursor(absolute_x, absolute_y);
574            }
575        })?;
576
577        // βœ… 2. CURSOR-STIL setzen (NACH dem draw!)
578        if cursor_pos.is_some() {
579            // Cursor ist sichtbar β†’ Cursor-Stil setzen
580            let cursor_commands = self.get_terminal_cursor_commands();
581            for command in cursor_commands {
582                execute!(std::io::stdout(), crossterm::style::Print(command))?;
583            }
584        } else {
585            // Cursor ist unsichtbar (Blinken) β†’ Cursor verstecken
586            execute!(
587                std::io::stdout(),
588                crossterm::style::Print("\x1B[?25l") // Hide cursor
589            )?;
590        }
591
592        Ok(())
593    }
594
595    /// βœ… BEREINIGTE Terminal-Cursor-Kommandos
596    fn get_terminal_cursor_commands(&self) -> Vec<&'static str> {
597        match self.config.theme.input_cursor.to_uppercase().as_str() {
598            "PIPE" => vec![
599                "\x1B[6 q",  // Blinking bar (pipe)
600                "\x1B[?25h", // Show cursor
601            ],
602            "UNDERSCORE" => vec![
603                "\x1B[4 q",  // Blinking underscore
604                "\x1B[?25h", // Show cursor
605            ],
606            "BLOCK" => vec![
607                "\x1B[2 q",  // Blinking block
608                "\x1B[?25h", // Show cursor
609            ],
610            _ => vec![
611                "\x1B[6 q",  // Default: Blinking bar
612                "\x1B[?25h", // Show cursor
613            ],
614        }
615    }
616
617    async fn perform_restart(&mut self) -> Result<()> {
618        log::info!("{}", t!("screen.restart.start"));
619
620        self.terminal_mgr.cleanup().await?;
621        self.terminal_mgr = TerminalManager::new().await?;
622        self.terminal_mgr.setup().await?;
623
624        let backend = CrosstermBackend::new(io::stdout());
625        self.terminal = Terminal::new(backend)?;
626        let size = self.terminal.size()?;
627
628        self.message_display = MessageDisplay::new(&self.config, size.width, size.height);
629        self.input_state = Box::new(InputState::new(&self.config));
630        self.waiting_for_restart_confirmation = false;
631
632        self.message_display
633            .add_message(tc!("system.commands.restart.success"));
634
635        log::info!("{}", t!("screen.restart.done"));
636        Ok(())
637    }
638}
639
640/// βœ… i18n Integration Helper (unverΓ€ndert)
641impl ScreenManager {
642    pub fn validate_i18n_keys() -> Vec<String> {
643        let required_keys = [
644            "screen.performance_command_detected",
645            "screen.performance_command_viewport_reset_applied",
646            "screen.theme.invalid_format",
647            "screen.theme.processing",
648            "screen.theme.load_failed",
649            "screen.theme.failed",
650            "screen.theme.applied",
651            "screen.theme.not_found",
652            "screen.theme.not_found_feedback",
653            "screen.render.too_small_log",
654            "screen.render.too_small.text",
655            "screen.render.viewport_not_usable_log",
656            "screen.render.viewport_error.text",
657            "screen.render.invalid_layout_log",
658            "screen.render.exceed_bounds_log",
659            "screen.restart.start",
660            "screen.restart.done",
661            "system.commands.restart.success",
662        ];
663
664        let mut missing = Vec::new();
665        for key in required_keys {
666            if !crate::i18n::has_translation(key) {
667                missing.push(key.to_string());
668            }
669        }
670        missing
671    }
672
673    pub fn get_i18n_debug_info() -> HashMap<String, usize> {
674        let mut info = HashMap::new();
675        let stats = crate::i18n::get_translation_stats();
676
677        info.insert("screen_manager_keys".to_string(), 18);
678        info.extend(stats);
679
680        info
681    }
682}