1use crate::core::constants::{DEFAULT_BUFFER_SIZE, DEFAULT_POLL_RATE};
6use crate::core::prelude::*;
7use crate::ui::color::AppColor;
8use serde::{Deserialize, Serialize};
9use std::path::{Path, PathBuf};
10
11const MIN_POLL_RATE: u64 = 16; const MAX_POLL_RATE: u64 = 1000; const MAX_TYPEWRITER_DELAY: u64 = 2000; #[derive(Debug, Serialize, Deserialize)]
18struct ConfigFile {
19 general: GeneralConfig,
20 theme: ThemeConfig,
21 prompt: PromptConfig,
22 language: LanguageConfig,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26struct GeneralConfig {
27 max_messages: usize,
28 typewriter_delay: u64,
29 input_max_length: usize,
30 max_history: usize,
31 poll_rate: u64,
32 log_level: String,
33}
34
35#[derive(Debug, Serialize, Deserialize)]
36struct ThemeConfig {
37 input_text: String,
38 input_bg: String,
39 cursor: String,
40 output_text: String,
41 output_bg: String,
42}
43
44#[derive(Debug, Serialize, Deserialize)]
45struct PromptConfig {
46 text: String,
47 color: String,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51struct LanguageConfig {
52 current: String,
53}
54
55pub struct Config {
57 config_path: Option<String>,
58 pub max_messages: usize,
59 pub typewriter_delay: Duration,
60 pub input_max_length: usize,
61 pub max_history: usize,
62 pub poll_rate: Duration,
63 pub log_level: String,
64 pub theme: Theme,
65 pub prompt: Prompt,
66 pub language: String,
67 pub debug_info: Option<String>,
68}
69
70pub struct Theme {
71 pub input_text: AppColor,
72 pub input_bg: AppColor,
73 pub cursor: AppColor,
74 pub output_text: AppColor,
75 pub output_bg: AppColor,
76}
77
78pub struct Prompt {
79 pub text: String,
80 pub color: AppColor,
81}
82
83impl Config {
84 pub async fn load() -> Result<Self> {
85 Self::load_with_messages(true).await
86 }
87
88 pub async fn load_with_messages(show_messages: bool) -> Result<Self> {
89 for path in crate::setup::setup_toml::get_config_paths() {
91 if path.exists() {
92 match Self::from_file(&path).await {
93 Ok(config) => {
94 if config.poll_rate.as_millis() < 16 && show_messages {
96 log::warn!(
97 "⚡ PERFORMANCE: poll_rate sehr niedrig! {}",
98 config.get_performance_info()
99 );
100 }
101
102 if let Err(e) = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await {
104 if show_messages {
105 log::warn!(
106 "{}",
107 get_translation(
108 "system.config.language_set_failed",
109 &[&e.to_string()]
110 )
111 );
112 }
113 }
114
115 if show_messages {
117 crate::output::logging::AppLogger::log_plain(
118 crate::i18n::get_command_translation(
119 "system.startup.version",
120 &[crate::core::constants::VERSION],
121 ),
122 );
123 }
124
125 return Ok(config);
126 }
127 Err(_e) => {
128 continue;
129 }
130 }
131 }
132 }
133
134 if show_messages {
136 log::info!("{}", get_translation("system.config.no_existing", &[]));
137 }
138
139 match crate::setup::setup_toml::ensure_config_exists().await {
140 Ok(config_path) => {
141 match Self::from_file(&config_path).await {
142 Ok(mut config) => {
143 if show_messages {
145 let plain_msg = get_translation(
146 "system.config.new_default",
147 &[&config_path.display().to_string()],
148 );
149 log::info!("{}", plain_msg);
150 config.debug_info = Some(plain_msg);
151
152 crate::output::logging::AppLogger::log_plain(
153 crate::i18n::get_command_translation(
154 "system.startup.version",
155 &[crate::core::constants::VERSION],
156 ),
157 );
158 }
159
160 let _ = crate::commands::lang::config::LanguageConfig::load_and_apply_from_config(&config).await;
161
162 Ok(config)
163 }
164 Err(e) => {
165 if show_messages {
166 log::error!(
167 "{}",
168 get_translation("system.config.load_error", &[&format!("{:?}", e)])
169 );
170 }
171 Err(e)
172 }
173 }
174 }
175 Err(e) => {
176 if show_messages {
177 log::error!(
178 "{}",
179 get_translation("system.config.setup_failed", &[&format!("{:?}", e)])
180 );
181 }
182 Err(e)
183 }
184 }
185 }
186
187 pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
188 let content = tokio::fs::read_to_string(&path)
189 .await
190 .map_err(AppError::Io)?;
191
192 let config_file: ConfigFile = toml::from_str(&content)
193 .map_err(|e| AppError::Validation(format!("Ungültiges TOML-Format: {}", e)))?;
194
195 let original_poll_rate = config_file.general.poll_rate;
197 let original_typewriter_delay = config_file.general.typewriter_delay;
198
199 let poll_rate = Self::validate_poll_rate(original_poll_rate);
200 let typewriter_delay = Self::validate_typewriter_delay(original_typewriter_delay);
201
202 let config = Self {
203 config_path: Some(path.as_ref().to_string_lossy().into_owned()),
204 max_messages: config_file.general.max_messages,
205 typewriter_delay: Duration::from_millis(typewriter_delay),
206 input_max_length: config_file.general.input_max_length,
207 max_history: config_file.general.max_history,
208 poll_rate: Duration::from_millis(poll_rate),
209 log_level: config_file.general.log_level,
210 theme: Theme::from_config(&config_file.theme)?,
211 prompt: Prompt::from_config(&config_file.prompt)?,
212 language: config_file.language.current,
213 debug_info: None,
214 };
215
216 let values_changed =
218 original_poll_rate != poll_rate || original_typewriter_delay != typewriter_delay;
219
220 if values_changed {
221 log::warn!("🔧 Ungültige Config-Werte korrigiert und gespeichert:");
222 if original_poll_rate != poll_rate {
223 log::warn!(" poll_rate: {}ms → {}ms", original_poll_rate, poll_rate);
224 }
225 if original_typewriter_delay != typewriter_delay {
226 log::warn!(
227 " typewriter_delay: {}ms → {}ms",
228 original_typewriter_delay,
229 typewriter_delay
230 );
231 }
232
233 if let Err(e) = config.save().await {
235 log::warn!("Konnte korrigierte Config nicht speichern: {}", e);
236 } else {
237 log::info!("✅ Korrigierte Werte in Config-Datei gespeichert");
238 }
239 }
240
241 Ok(config)
242 }
243
244 fn validate_poll_rate(value: u64) -> u64 {
246 match value {
247 0 => {
248 log::warn!(
249 "poll_rate = 0 nicht erlaubt, verwende Minimum: {}ms",
250 MIN_POLL_RATE
251 );
252 MIN_POLL_RATE
253 }
254 v if v < MIN_POLL_RATE => {
255 log::warn!(
256 "poll_rate = {}ms zu schnell (Performance!), verwende Minimum: {}ms",
257 v,
258 MIN_POLL_RATE
259 );
260 MIN_POLL_RATE
261 }
262 v if v > MAX_POLL_RATE => {
263 log::warn!(
264 "poll_rate = {}ms zu langsam, verwende Maximum: {}ms",
265 v,
266 MAX_POLL_RATE
267 );
268 MAX_POLL_RATE
269 }
270 v => {
271 if v < 33 {
272 log::trace!("poll_rate = {}ms (sehr schnell, aber OK)", v);
273 }
274 v
275 }
276 }
277 }
278
279 fn validate_typewriter_delay(value: u64) -> u64 {
281 match value {
282 0 => {
283 log::info!("typewriter_delay = 0 → Typewriter-Effekt deaktiviert");
284 0 }
286 v if v > MAX_TYPEWRITER_DELAY => {
287 log::warn!(
288 "typewriter_delay = {}ms zu langsam, verwende Maximum: {}ms",
289 v,
290 MAX_TYPEWRITER_DELAY
291 );
292 MAX_TYPEWRITER_DELAY
293 }
294 v => v,
295 }
296 }
297
298 pub fn get_performance_info(&self) -> String {
300 let fps = 1000.0 / self.poll_rate.as_millis() as f64;
301 let typewriter_chars_per_sec = if self.typewriter_delay.as_millis() > 0 {
302 1000.0 / self.typewriter_delay.as_millis() as f64
303 } else {
304 f64::INFINITY
305 };
306
307 format!(
308 "Performance: {:.1} FPS, Typewriter: {:.1} chars/sec",
309 fps, typewriter_chars_per_sec
310 )
311 }
312
313 pub async fn save(&self) -> Result<()> {
314 if let Some(path) = &self.config_path {
315 let config_file = ConfigFile {
316 general: GeneralConfig {
317 max_messages: self.max_messages,
318 typewriter_delay: self.typewriter_delay.as_millis() as u64,
319 input_max_length: self.input_max_length,
320 max_history: self.max_history,
321 poll_rate: self.poll_rate.as_millis() as u64,
322 log_level: self.log_level.clone(),
323 },
324 theme: ThemeConfig {
325 input_text: self.theme.input_text.to_string(),
326 input_bg: self.theme.input_bg.to_string(),
327 cursor: self.theme.cursor.to_string(),
328 output_text: self.theme.output_text.to_string(),
329 output_bg: self.theme.output_bg.to_string(),
330 },
331 prompt: PromptConfig {
332 text: self.prompt.text.clone(),
333 color: self.prompt.color.to_string(),
334 },
335 language: LanguageConfig {
336 current: self.language.clone(),
337 },
338 };
339
340 let content = toml::to_string_pretty(&config_file)
341 .map_err(|e| AppError::Validation(format!("Serialisierungsfehler: {}", e)))?;
342
343 if let Some(parent) = PathBuf::from(path).parent() {
344 if !parent.exists() {
345 tokio::fs::create_dir_all(parent)
346 .await
347 .map_err(AppError::Io)?;
348 }
349 }
350
351 tokio::fs::write(path, content)
352 .await
353 .map_err(AppError::Io)?;
354 }
355 Ok(())
356 }
357}
358
359impl Theme {
360 fn from_config(config: &ThemeConfig) -> Result<Self> {
361 Ok(Self {
362 input_text: AppColor::from_string(&config.input_text)?,
363 input_bg: AppColor::from_string(&config.input_bg)?,
364 cursor: AppColor::from_string(&config.cursor)?,
365 output_text: AppColor::from_string(&config.output_text)?,
366 output_bg: AppColor::from_string(&config.output_bg)?,
367 })
368 }
369}
370
371impl Prompt {
372 fn from_config(config: &PromptConfig) -> Result<Self> {
373 Ok(Self {
374 text: config.text.clone(),
375 color: AppColor::from_string(&config.color)?,
376 })
377 }
378}
379
380crate::impl_default!(
381 Config,
382 Self {
383 config_path: None,
384 max_messages: DEFAULT_BUFFER_SIZE,
385 typewriter_delay: Duration::from_millis(50), input_max_length: DEFAULT_BUFFER_SIZE,
387 max_history: 30,
388 poll_rate: Duration::from_millis(DEFAULT_POLL_RATE), log_level: "info".to_string(),
390 theme: Theme::default(),
391 prompt: Prompt::default(),
392 language: crate::i18n::DEFAULT_LANGUAGE.to_string(),
393 debug_info: None,
394 }
395);
396
397crate::impl_default!(
398 Theme,
399 Self {
400 input_text: AppColor::new(Color::Black),
401 input_bg: AppColor::new(Color::Black),
402 cursor: AppColor::new(Color::Black),
403 output_text: AppColor::new(Color::White),
404 output_bg: AppColor::new(Color::White),
405 }
406);
407
408crate::impl_default!(
409 Prompt,
410 Self {
411 text: "/// ".to_string(),
412 color: AppColor::new(Color::Black),
413 }
414);
415
416#[cfg(debug_assertions)]
418impl Config {
419 pub fn debug_performance_warning(&self) {
420 if self.poll_rate.as_millis() < 16 {
421 log::warn!(
422 "🔥 PERFORMANCE WARNING: poll_rate = {}ms verursacht hohe CPU-Last!",
423 self.poll_rate.as_millis()
424 );
425 log::warn!("💡 EMPFEHLUNG: Setze poll_rate auf 16-33ms für bessere Performance");
426 }
427
428 if self.typewriter_delay.as_millis() < 10 {
429 log::warn!(
430 "⚡ PERFORMANCE INFO: typewriter_delay = {}ms (sehr schnell)",
431 self.typewriter_delay.as_millis()
432 );
433 }
434
435 log::info!("📊 AKTUELLE WERTE: {}", self.get_performance_info());
436 }
437}