1use 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::{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_display: MessageDisplay,
30 input_state: Box<dyn Widget>,
31 terminal_size: (u16, u16),
32 config: Config,
33 terminal_mgr: TerminalManager,
34 events: EventHandler,
35 keyboard_manager: KeyboardManager,
36 waiting_for_restart_confirmation: bool,
37}
38
39impl ScreenManager {
40 pub async fn new(config: &Config) -> Result<Self> {
41 let mut terminal_mgr = TerminalManager::new().await?;
42 terminal_mgr.setup().await?;
43
44 let backend = CrosstermBackend::new(io::stdout());
45 let terminal = Terminal::new(backend)?;
46 let size = terminal.size()?;
47
48 let initial_height = size.height.saturating_sub(4) as usize;
49 let mut message_display = MessageDisplay::new(config);
50 message_display
51 .scroll_state
52 .update_dimensions(initial_height, 0);
53
54 let owned_config = config.clone();
55
56 Ok(Self {
57 terminal,
58 terminal_mgr,
59 message_display,
60 input_state: Box::new(InputState::new(config)),
62 terminal_size: (size.width, size.height),
63 config: owned_config,
64 events: EventHandler::new(config.poll_rate),
65 keyboard_manager: KeyboardManager::new(),
66 waiting_for_restart_confirmation: false,
67 })
68 }
69
70 pub async fn run(&mut self) -> Result<()> {
71 let result = loop {
72 if let Some(event) = self.events.next().await {
73 match event {
74 AppEvent::Input(key) => {
75 if self.handle_input_event(key).await? {
76 self.events.shutdown().await;
77 break Ok(());
78 }
79 }
80 AppEvent::Resize(width, height) => {
81 self.handle_resize_event(width, height).await?;
82 }
83 AppEvent::Tick => {
84 self.handle_tick_event().await?;
85 }
86 }
87 }
88
89 self.process_pending_logs().await;
90 self.render().await?;
91 };
92
93 self.terminal_mgr.cleanup().await?;
94 result
95 }
96
97 async fn handle_input_event(&mut self, key: KeyEvent) -> Result<bool> {
98 if HistoryKeyboardHandler::get_history_action(&key).is_some() {
99 if let Some(new_input) = self.input_state.handle_input(key) {
100 if let Some(processed) = LanguageService::process_save_message(&new_input).await {
101 self.message_display.add_message(processed);
102 return Ok(false);
103 }
104
105 if let Some(processed) = self.process_live_theme_update(&new_input).await {
106 self.message_display.add_message(processed);
107 return Ok(false);
108 }
109
110 self.message_display.add_message(new_input.clone());
111
112 if new_input.starts_with("__CLEAR__") {
113 self.message_display.clear_messages();
114 } else if new_input.starts_with("__EXIT__") {
115 return Ok(true);
116 }
117 }
118 return Ok(false);
119 }
120
121 match self.keyboard_manager.get_action(&key) {
122 KeyAction::ScrollUp
123 | KeyAction::ScrollDown
124 | KeyAction::PageUp
125 | KeyAction::PageDown => {
126 let window_height = self.get_content_height();
127 self.message_display
128 .handle_scroll(self.keyboard_manager.get_action(&key), window_height);
129 }
130 KeyAction::Submit => {
131 if let Some(new_input) = self.input_state.handle_input(key) {
132 let input_command = new_input.trim().to_lowercase();
134 let is_performance_command = input_command == "perf"
135 || input_command == "performance"
136 || input_command == "stats";
137
138 if let Some(processed) = LanguageService::process_save_message(&new_input).await
139 {
140 self.message_display.add_message(processed);
141 return Ok(false);
142 }
143
144 if let Some(processed) = self.process_live_theme_update(&new_input).await {
145 self.message_display.add_message(processed);
146 return Ok(false);
147 }
148
149 self.message_display.add_message(new_input.clone());
150
151 if is_performance_command {
153 log::info!("🔧 Performance command '{}' detected", input_command);
154
155 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
157
158 self.message_display.scroll_state.force_auto_scroll();
160
161 let window_height = self.get_content_height();
162 let content_height = self.message_display.get_content_height();
163 self.message_display
164 .scroll_state
165 .update_dimensions(window_height, content_height);
166
167 log::info!("✅ Performance command: scroll reset applied");
168 }
169
170 if new_input.starts_with("__CLEAR__") {
172 self.message_display.clear_messages();
173 } else if new_input.starts_with("__EXIT__") {
174 return Ok(true);
175 } else if new_input.starts_with("__RESTART_WITH_MSG__") {
176 let feedback_msg = new_input
177 .replace("__RESTART_WITH_MSG__", "")
178 .trim()
179 .to_string();
180
181 if !feedback_msg.is_empty() {
182 self.message_display.add_message(feedback_msg);
183 tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
184 }
185
186 if let Err(e) = self.perform_restart().await {
187 self.message_display
188 .add_message(format!("Restart failed: {}", e));
189 }
190 } else if new_input.starts_with("__RESTART_FORCE__")
191 || new_input == "__RESTART__"
192 {
193 if let Err(e) = self.perform_restart().await {
194 self.message_display
195 .add_message(format!("Restart failed: {}", e));
196 }
197 }
198 }
199 }
200 KeyAction::Quit => return Ok(true),
201 _ => {
202 if let Some(new_input) = self.input_state.handle_input(key) {
203 if let Some(processed) = LanguageService::process_save_message(&new_input).await
204 {
205 self.message_display.add_message(processed);
206 return Ok(false);
207 }
208
209 if let Some(processed) = self.process_live_theme_update(&new_input).await {
210 self.message_display.add_message(processed);
211 return Ok(false);
212 }
213
214 self.message_display.add_message(new_input);
215 }
216 }
217 }
218 Ok(false)
219 }
220
221 async fn process_live_theme_update(&mut self, message: &str) -> Option<String> {
222 if !message.starts_with("__LIVE_THEME_UPDATE__") {
223 return None;
224 }
225
226 let parts: Vec<&str> = message.split("__MESSAGE__").collect();
227 if parts.len() != 2 {
228 log::error!("Invalid live theme update format: {}", message);
229 return None;
230 }
231
232 let theme_part = parts[0].replace("__LIVE_THEME_UPDATE__", "");
233 let display_message = parts[1];
234
235 log::debug!("🎨 Processing live theme update: {}", theme_part);
236
237 let theme_system = match ThemeSystem::load() {
238 Ok(system) => system,
239 Err(e) => {
240 log::error!("Failed to load theme system: {}", e);
241 return Some(format!("❌ Theme update failed: {}", e));
242 }
243 };
244
245 if let Some(theme_def) = theme_system.get_theme(&theme_part) {
246 match self.create_theme_from_definition(theme_def) {
247 Ok(new_theme) => {
248 let backup = self.input_state.get_backup_data().unwrap_or_default();
249
250 self.config.theme = new_theme;
251 self.config.current_theme_name = theme_part.clone();
252
253 self.input_state = Box::new(InputState::new(&self.config));
255 self.input_state.restore_backup_data(backup.clone());
256
257 self.message_display.update_config(&self.config);
258
259 log::info!(
260 "✅ Live theme '{}' applied with prompt '{}' - {} history entries preserved!",
261 theme_part.to_uppercase(),
262 self.config.theme.prompt_text,
263 backup.history.len()
264 );
265 Some(display_message.to_string())
266 }
267 Err(e) => {
268 log::error!("❌ Failed to create theme: {}", e);
269 Some(format!("❌ Theme update failed: {}", e))
270 }
271 }
272 } else {
273 log::error!("❌ Theme '{}' not found in TOML", theme_part);
274 Some(format!("❌ Theme '{}' not found in config", theme_part))
275 }
276 }
277
278 fn create_theme_from_definition(
279 &self,
280 theme_def: &crate::commands::theme::ThemeDefinition,
281 ) -> Result<crate::core::config::Theme> {
282 use crate::ui::color::AppColor;
283
284 Ok(crate::core::config::Theme {
285 input_text: AppColor::from_string(&theme_def.input_text)?,
286 input_bg: AppColor::from_string(&theme_def.input_bg)?,
287 cursor: AppColor::from_string(&theme_def.cursor)?,
288 output_text: AppColor::from_string(&theme_def.output_text)?,
289 output_bg: AppColor::from_string(&theme_def.output_bg)?,
290 prompt_text: theme_def.prompt_text.clone(),
291 prompt_color: AppColor::from_string(&theme_def.prompt_color)?,
292 })
293 }
294
295 async fn handle_resize_event(&mut self, width: u16, height: u16) -> Result<()> {
296 eprintln!(
297 "🔄 RESIZE EVENT: {}x{} → {}x{}",
298 self.terminal_size.0, self.terminal_size.1, width, height
299 );
300
301 self.terminal_size = (width, height);
302 let window_height = self.get_content_height();
303 let content_height = self.message_display.get_content_height();
304
305 self.message_display
306 .scroll_state
307 .update_dimensions(window_height, content_height);
308
309 eprintln!(
310 " Window height: {}, Content height: {}",
311 window_height, content_height
312 );
313 Ok(())
314 }
315
316 async fn handle_tick_event(&mut self) -> Result<()> {
317 self.message_display.update_typewriter();
318 if let Some(input_state) = self.input_state.as_input_state() {
319 input_state.update_cursor_blink();
320 }
321 Ok(())
322 }
323
324 fn get_content_height(&self) -> usize {
325 let total_height = self.terminal_size.1 as usize;
327 let margin = 2; let input_area = 3; total_height
331 .saturating_sub(margin)
332 .saturating_sub(input_area)
333 }
334
335 async fn process_pending_logs(&mut self) {
336 match AppLogger::get_messages() {
337 Ok(messages) => {
338 for log_msg in messages {
339 self.message_display.add_message(log_msg.formatted());
340 }
341 }
342 Err(e) => {
343 self.message_display.add_message(
344 AppColor::from_any("error")
345 .format_message("ERROR", &format!("Logging-Fehler: {:?}", e)),
346 );
347 }
348 }
349 }
350
351 async fn render(&mut self) -> Result<()> {
352 self.terminal.draw(|frame| {
353 let size = frame.size();
354
355 if size.width < 30 || size.height < 8 {
356 return;
357 }
358
359 let total_available = size.height.saturating_sub(2); let input_needs = 3; let output_gets = total_available.saturating_sub(input_needs);
363
364 let chunks = Layout::default()
366 .direction(Direction::Vertical)
367 .margin(1)
368 .constraints([
369 Constraint::Length(output_gets), Constraint::Length(input_needs), ])
372 .split(size);
373
374 let output_chunk = chunks[0];
375 let input_chunk = chunks[1];
376
377 let total_used = output_chunk.height + input_chunk.height + 2; if total_used != size.height {
380 log::warn!(
382 "⚠️ Layout-Math ERROR: terminal={}, used={}, output={}, input={}",
383 size.height,
384 total_used,
385 output_chunk.height,
386 input_chunk.height
387 );
388 }
389
390 let total_lines = self.message_display.get_content_height();
392 self.message_display
393 .scroll_state
394 .update_dimensions(output_chunk.height as usize, total_lines);
395
396 let (messages_data, config) = self
398 .message_display
399 .create_output_widget_for_rendering(output_chunk.height);
400 let messages_refs: Vec<(&String, usize)> =
401 messages_data.iter().map(|(s, l)| (s, *l)).collect();
402
403 let output_widget = crate::output::display::create_output_widget(
404 &messages_refs,
405 output_chunk.height,
406 &config,
407 );
408 frame.render_widget(output_widget, output_chunk);
409
410 let input_widget = self.input_state.render();
411 frame.render_widget(input_widget, input_chunk);
412 })?;
413 Ok(())
414 }
415
416 async fn perform_restart(&mut self) -> Result<()> {
417 self.terminal_mgr.cleanup().await?;
418 self.terminal_mgr = TerminalManager::new().await?;
419 self.terminal_mgr.setup().await?;
420
421 let backend = CrosstermBackend::new(io::stdout());
422 self.terminal = Terminal::new(backend)?;
423
424 self.message_display.clear_messages();
425 self.input_state = Box::new(InputState::new(&self.config));
427 self.waiting_for_restart_confirmation = false;
428
429 self.message_display
430 .add_message(crate::i18n::get_command_translation(
431 "system.commands.restart.success",
432 &[],
433 ));
434 log::info!("Internal restart completed successfully");
435 Ok(())
436 }
437}