1#![allow(dead_code)]
2
3use crate::ui::state::AppView;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum StatusType {
9 Transient,
11 Contextual,
13}
14
15#[derive(Debug, Clone)]
17pub enum FormFieldContext {
18 Type,
19 Name,
20 Content,
21 Ttl,
22 Proxied,
23 Submit,
24}
25
26#[derive(Debug, Clone)]
28pub enum StatusMessage {
29 Initializing,
31 LoadingRecords,
33 Error(String),
35 OperationResult(String),
37 ViewHelp(ViewHelpContext),
39 FormFieldHelp {
41 context: FormFieldContext,
42 form_type: String,
43 form_proxied: String,
44 is_editing: bool,
45 },
46 RecordListHelp {
48 position: usize,
49 total: usize,
50 record_name: String,
51 },
52 EmptyListHelp,
54}
55
56#[derive(Debug, Clone)]
58pub enum ViewHelpContext {
59 DeleteConfirmation,
60 IpSelector,
61}
62
63impl StatusMessage {
64 pub fn status_type(&self) -> StatusType {
66 match self {
67 StatusMessage::Error(_)
68 | StatusMessage::OperationResult(_)
69 | StatusMessage::Initializing
70 | StatusMessage::LoadingRecords => StatusType::Transient,
71 _ => StatusType::Contextual,
72 }
73 }
74
75 pub fn is_transient(status_str: &str) -> bool {
77 status_str.starts_with("Error:")
78 || status_str.starts_with("Refreshing...")
79 || status_str.starts_with("Created")
80 || status_str.starts_with("Updated")
81 || status_str.starts_with("Deleted")
82 || status_str.starts_with("Failed")
83 || status_str.starts_with("Selected")
84 || status_str.starts_with("Cancelled")
85 }
86
87 pub fn render(&self) -> String {
89 match self {
90 StatusMessage::Initializing => "Initializing...".to_string(),
91 StatusMessage::LoadingRecords => "Loading DNS records...".to_string(),
92 StatusMessage::Error(msg) => format!("Error: {}", msg),
93 StatusMessage::OperationResult(msg) => msg.clone(),
94 StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation) => {
95 "Enter: confirm deletion | Esc: cancel".to_string()
96 }
97 StatusMessage::ViewHelp(ViewHelpContext::IpSelector) => {
98 "↑↓: navigate | Enter: select IP | Esc: back to form".to_string()
99 }
100 StatusMessage::FormFieldHelp {
101 context,
102 form_type,
103 form_proxied,
104 is_editing,
105 } => match context {
106 FormFieldContext::Type => {
107 format!(
108 "Field 1/6 — Type: {} | Press Space to cycle types",
109 form_type
110 )
111 }
112 FormFieldContext::Name => "Field 2/6 — Name: e.g. nginx".to_string(),
113 FormFieldContext::Content => {
114 "Field 3/6 — IP Address | Press Space to use an existing address | Type: enter IP"
115 .to_string()
116 }
117 FormFieldContext::Ttl => "Field 4/6 — TTL: seconds (1 = auto)".to_string(),
118 FormFieldContext::Proxied => {
119 let proxied_status = if form_proxied == "true" {
120 "Orange cloud ON"
121 } else {
122 "Grey cloud OFF"
123 };
124 format!(
125 "Field 5/6 — Proxied: {} | Press Space to toggle",
126 proxied_status
127 )
128 }
129 FormFieldContext::Submit => {
130 let action = if *is_editing { "Save" } else { "Create" };
131 format!("Field 6/6 — Press Enter to {} record", action)
132 }
133 },
134 StatusMessage::RecordListHelp {
135 position,
136 total,
137 record_name,
138 } => {
139 format!(
140 "{} of {} — {} | e: edit | d: delete | r: refresh | c: create | q: quit",
141 position, total, record_name
142 )
143 }
144 StatusMessage::EmptyListHelp => {
145 "No records | c: create your first DNS record | q: quit".to_string()
146 }
147 }
148 }
149}
150
151#[allow(clippy::too_many_arguments)]
153pub fn generate_contextual_status(
154 view: &AppView,
155 form_focus: usize,
156 form_type: &str,
157 form_proxied: &str,
158 is_editing: bool,
159 record_count: usize,
160 selected_record_idx: usize,
161 selected_record_name: Option<&str>,
162) -> StatusMessage {
163 match view {
164 AppView::Delete => StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation),
165 AppView::IpSelect => StatusMessage::ViewHelp(ViewHelpContext::IpSelector),
166 AppView::Create | AppView::Edit => {
167 let context = match form_focus {
168 0 => FormFieldContext::Type,
169 1 => FormFieldContext::Name,
170 2 => FormFieldContext::Content,
171 3 => FormFieldContext::Ttl,
172 4 => FormFieldContext::Proxied,
173 5 => FormFieldContext::Submit,
174 _ => FormFieldContext::Name, };
176 StatusMessage::FormFieldHelp {
177 context,
178 form_type: form_type.to_string(),
179 form_proxied: form_proxied.to_string(),
180 is_editing,
181 }
182 }
183 AppView::List => {
184 if record_count > 0 && selected_record_idx < record_count {
185 StatusMessage::RecordListHelp {
186 position: selected_record_idx + 1,
187 total: record_count,
188 record_name: selected_record_name.unwrap_or("Unknown").to_string(),
189 }
190 } else {
191 StatusMessage::EmptyListHelp
192 }
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_status_type_transient_error() {
203 let status = StatusMessage::Error("test error".to_string());
204 assert_eq!(status.status_type(), StatusType::Transient);
205 }
206
207 #[test]
208 fn test_status_type_transient_operation_result() {
209 let status = StatusMessage::OperationResult("Created A for example".to_string());
210 assert_eq!(status.status_type(), StatusType::Transient);
211 }
212
213 #[test]
214 fn test_status_type_transient_initializing() {
215 let status = StatusMessage::Initializing;
216 assert_eq!(status.status_type(), StatusType::Transient);
217 }
218
219 #[test]
220 fn test_status_type_transient_loading_records() {
221 let status = StatusMessage::LoadingRecords;
222 assert_eq!(status.status_type(), StatusType::Transient);
223 }
224
225 #[test]
226 fn test_status_type_contextual_view_help() {
227 let status = StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation);
228 assert_eq!(status.status_type(), StatusType::Contextual);
229 }
230
231 #[test]
232 fn test_status_type_contextual_form_field_help() {
233 let status = StatusMessage::FormFieldHelp {
234 context: FormFieldContext::Name,
235 form_type: "A".to_string(),
236 form_proxied: "false".to_string(),
237 is_editing: false,
238 };
239 assert_eq!(status.status_type(), StatusType::Contextual);
240 }
241
242 #[test]
243 fn test_status_type_contextual_record_list_help() {
244 let status = StatusMessage::RecordListHelp {
245 position: 1,
246 total: 5,
247 record_name: "example.com".to_string(),
248 };
249 assert_eq!(status.status_type(), StatusType::Contextual);
250 }
251
252 #[test]
253 fn test_status_type_contextual_empty_list_help() {
254 let status = StatusMessage::EmptyListHelp;
255 assert_eq!(status.status_type(), StatusType::Contextual);
256 }
257
258 #[test]
259 fn test_is_transient_error() {
260 assert!(StatusMessage::is_transient("Error: something went wrong"));
261 }
262
263 #[test]
264 fn test_is_transient_created() {
265 assert!(StatusMessage::is_transient("Created A for example"));
266 }
267
268 #[test]
269 fn test_is_transient_updated() {
270 assert!(StatusMessage::is_transient("Updated A for example"));
271 }
272
273 #[test]
274 fn test_is_transient_deleted() {
275 assert!(StatusMessage::is_transient("Deleted A for example"));
276 }
277
278 #[test]
279 fn test_is_transient_failed() {
280 assert!(StatusMessage::is_transient("Failed: API error"));
281 }
282
283 #[test]
284 fn test_is_transient_selected() {
285 assert!(StatusMessage::is_transient("Selected IP address"));
286 }
287
288 #[test]
289 fn test_is_transient_cancelled() {
290 assert!(StatusMessage::is_transient("Cancelled operation"));
291 }
292
293 #[test]
294 fn test_is_not_transient_contextual() {
295 assert!(!StatusMessage::is_transient(
296 "Enter: confirm deletion | Esc: cancel"
297 ));
298 }
299
300 #[test]
301 fn test_is_not_transient_record_list() {
302 assert!(!StatusMessage::is_transient(
303 "1 of 5 — example.com | E: edit | D: delete | R: refresh | C: create | Q: quit"
304 ));
305 }
306
307 #[test]
308 fn test_render_initializing() {
309 let status = StatusMessage::Initializing;
310 assert_eq!(status.render(), "Initializing...");
311 }
312
313 #[test]
314 fn test_render_loading_records() {
315 let status = StatusMessage::LoadingRecords;
316 assert_eq!(status.render(), "Loading DNS records...");
317 }
318
319 #[test]
320 fn test_render_error() {
321 let status = StatusMessage::Error("connection failed".to_string());
322 assert_eq!(status.render(), "Error: connection failed");
323 }
324
325 #[test]
326 fn test_render_operation_result() {
327 let status = StatusMessage::OperationResult("Created A record".to_string());
328 assert_eq!(status.render(), "Created A record");
329 }
330
331 #[test]
332 fn test_render_delete_confirmation_help() {
333 let status = StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation);
334 assert_eq!(status.render(), "Enter: confirm deletion | Esc: cancel");
335 }
336
337 #[test]
338 fn test_render_ip_selector_help() {
339 let status = StatusMessage::ViewHelp(ViewHelpContext::IpSelector);
340 assert_eq!(
341 status.render(),
342 "↑↓: navigate | Enter: select IP | Esc: back to form"
343 );
344 }
345
346 #[test]
347 fn test_render_form_field_type() {
348 let status = StatusMessage::FormFieldHelp {
349 context: FormFieldContext::Type,
350 form_type: "A".to_string(),
351 form_proxied: "false".to_string(),
352 is_editing: false,
353 };
354 assert_eq!(status.render(), "Field 1/6 — Type: A | Press Space to cycle types");
355 }
356
357 #[test]
358 fn test_render_form_field_name() {
359 let status = StatusMessage::FormFieldHelp {
360 context: FormFieldContext::Name,
361 form_type: "A".to_string(),
362 form_proxied: "false".to_string(),
363 is_editing: false,
364 };
365 assert_eq!(status.render(), "Field 2/6 — Name: e.g. nginx");
366 }
367
368 #[test]
369 fn test_render_form_field_content() {
370 let status = StatusMessage::FormFieldHelp {
371 context: FormFieldContext::Content,
372 form_type: "A".to_string(),
373 form_proxied: "false".to_string(),
374 is_editing: false,
375 };
376 assert_eq!(
377 status.render(),
378 "Field 3/6 — IP Address | Press Space to use an existing address | Type: enter IP"
379 );
380 }
381
382 #[test]
383 fn test_render_form_field_ttl() {
384 let status = StatusMessage::FormFieldHelp {
385 context: FormFieldContext::Ttl,
386 form_type: "A".to_string(),
387 form_proxied: "false".to_string(),
388 is_editing: false,
389 };
390 assert_eq!(status.render(), "Field 4/6 — TTL: seconds (1 = auto)");
391 }
392
393 #[test]
394 fn test_render_form_field_proxied_true() {
395 let status = StatusMessage::FormFieldHelp {
396 context: FormFieldContext::Proxied,
397 form_type: "A".to_string(),
398 form_proxied: "true".to_string(),
399 is_editing: false,
400 };
401 assert_eq!(status.render(), "Field 5/6 — Proxied: Orange cloud ON | Press Space to toggle");
402 }
403
404 #[test]
405 fn test_render_form_field_proxied_false() {
406 let status = StatusMessage::FormFieldHelp {
407 context: FormFieldContext::Proxied,
408 form_type: "A".to_string(),
409 form_proxied: "false".to_string(),
410 is_editing: false,
411 };
412 assert_eq!(status.render(), "Field 5/6 — Proxied: Grey cloud OFF | Press Space to toggle");
413 }
414
415 #[test]
416 fn test_render_form_field_submit_create() {
417 let status = StatusMessage::FormFieldHelp {
418 context: FormFieldContext::Submit,
419 form_type: "A".to_string(),
420 form_proxied: "false".to_string(),
421 is_editing: false,
422 };
423 assert_eq!(status.render(), "Field 6/6 — Press Enter to Create record");
424 }
425
426 #[test]
427 fn test_render_form_field_submit_edit() {
428 let status = StatusMessage::FormFieldHelp {
429 context: FormFieldContext::Submit,
430 form_type: "A".to_string(),
431 form_proxied: "false".to_string(),
432 is_editing: true,
433 };
434 assert_eq!(status.render(), "Field 6/6 — Press Enter to Save record");
435 }
436
437 #[test]
438 fn test_render_record_list_help() {
439 let status = StatusMessage::RecordListHelp {
440 position: 3,
441 total: 10,
442 record_name: "test.example.com".to_string(),
443 };
444 assert_eq!(
445 status.render(),
446 "3 of 10 — test.example.com | e: edit | d: delete | r: refresh | c: create | q: quit"
447 );
448 }
449
450 #[test]
451 fn test_render_empty_list_help() {
452 let status = StatusMessage::EmptyListHelp;
453 assert_eq!(
454 status.render(),
455 "No records | c: create your first DNS record | q: quit"
456 );
457 }
458
459 #[test]
460 fn test_generate_contextual_status_delete_view() {
461 let result = generate_contextual_status(
462 &AppView::Delete,
463 0,
464 "A",
465 "false",
466 false,
467 5,
468 0,
469 Some("example.com"),
470 );
471 assert!(matches!(
472 result,
473 StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation)
474 ));
475 }
476
477 #[test]
478 fn test_generate_contextual_status_ip_selector_view() {
479 let result = generate_contextual_status(
480 &AppView::IpSelect,
481 0,
482 "A",
483 "false",
484 false,
485 5,
486 0,
487 Some("example.com"),
488 );
489 assert!(matches!(
490 result,
491 StatusMessage::ViewHelp(ViewHelpContext::IpSelector)
492 ));
493 }
494
495 #[test]
496 fn test_generate_contextual_status_create_view_first_field() {
497 let result = generate_contextual_status(
498 &AppView::Create,
499 0,
500 "A",
501 "false",
502 false,
503 5,
504 0,
505 Some("example.com"),
506 );
507 match result {
508 StatusMessage::FormFieldHelp {
509 context: FormFieldContext::Type,
510 form_type,
511 ..
512 } => {
513 assert_eq!(form_type, "A");
514 }
515 _ => panic!("Expected FormFieldHelp with Type context"),
516 }
517 }
518
519 #[test]
520 fn test_generate_contextual_status_create_view_name_field() {
521 let result = generate_contextual_status(
522 &AppView::Create,
523 1,
524 "AAAA",
525 "true",
526 false,
527 5,
528 0,
529 Some("example.com"),
530 );
531 match result {
532 StatusMessage::FormFieldHelp {
533 context: FormFieldContext::Name,
534 form_type,
535 form_proxied,
536 ..
537 } => {
538 assert_eq!(form_type, "AAAA");
539 assert_eq!(form_proxied, "true");
540 }
541 _ => panic!("Expected FormFieldHelp with Name context"),
542 }
543 }
544
545 #[test]
546 fn test_generate_contextual_status_edit_view_submit_field() {
547 let result = generate_contextual_status(
548 &AppView::Edit,
549 5,
550 "CNAME",
551 "false",
552 true,
553 5,
554 0,
555 Some("example.com"),
556 );
557 match result {
558 StatusMessage::FormFieldHelp {
559 context: FormFieldContext::Submit,
560 is_editing,
561 ..
562 } => {
563 assert!(is_editing);
564 }
565 _ => panic!("Expected FormFieldHelp with Submit context"),
566 }
567 }
568
569 #[test]
570 fn test_generate_contextual_status_list_view_with_records() {
571 let result = generate_contextual_status(
572 &AppView::List,
573 0,
574 "A",
575 "false",
576 false,
577 5,
578 2,
579 Some("test.example.com"),
580 );
581 match result {
582 StatusMessage::RecordListHelp {
583 position,
584 total,
585 record_name,
586 } => {
587 assert_eq!(position, 3); assert_eq!(total, 5);
589 assert_eq!(record_name, "test.example.com");
590 }
591 _ => panic!("Expected RecordListHelp"),
592 }
593 }
594
595 #[test]
596 fn test_generate_contextual_status_list_view_empty() {
597 let result = generate_contextual_status(&AppView::List, 0, "A", "false", false, 0, 0, None);
598 assert!(matches!(result, StatusMessage::EmptyListHelp));
599 }
600
601 #[test]
602 fn test_generate_contextual_status_list_view_invalid_selection() {
603 let result = generate_contextual_status(
605 &AppView::List,
606 0,
607 "A",
608 "false",
609 false,
610 3,
611 5, Some("example.com"),
613 );
614 assert!(matches!(result, StatusMessage::EmptyListHelp));
615 }
616
617 #[test]
618 fn test_generate_contextual_status_list_view_unknown_record_name() {
619 let result = generate_contextual_status(
620 &AppView::List,
621 0,
622 "A",
623 "false",
624 false,
625 1,
626 0,
627 None, );
629 match result {
630 StatusMessage::RecordListHelp { record_name, .. } => {
631 assert_eq!(record_name, "Unknown");
632 }
633 _ => panic!("Expected RecordListHelp"),
634 }
635 }
636
637 #[test]
638 fn test_generate_contextual_status_form_fallback() {
639 let result = generate_contextual_status(
641 &AppView::Create,
642 10, "A",
644 "false",
645 false,
646 5,
647 0,
648 Some("example.com"),
649 );
650 match result {
651 StatusMessage::FormFieldHelp {
652 context: FormFieldContext::Name,
653 ..
654 } => {
655 }
657 _ => panic!("Expected FormFieldHelp with Name context as fallback"),
658 }
659 }
660}