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!(
355 status.render(),
356 "Field 1/6 — Type: A | Press Space to cycle types"
357 );
358 }
359
360 #[test]
361 fn test_render_form_field_name() {
362 let status = StatusMessage::FormFieldHelp {
363 context: FormFieldContext::Name,
364 form_type: "A".to_string(),
365 form_proxied: "false".to_string(),
366 is_editing: false,
367 };
368 assert_eq!(status.render(), "Field 2/6 — Name: e.g. nginx");
369 }
370
371 #[test]
372 fn test_render_form_field_content() {
373 let status = StatusMessage::FormFieldHelp {
374 context: FormFieldContext::Content,
375 form_type: "A".to_string(),
376 form_proxied: "false".to_string(),
377 is_editing: false,
378 };
379 assert_eq!(
380 status.render(),
381 "Field 3/6 — IP Address | Press Space to use an existing address | Type: enter IP"
382 );
383 }
384
385 #[test]
386 fn test_render_form_field_ttl() {
387 let status = StatusMessage::FormFieldHelp {
388 context: FormFieldContext::Ttl,
389 form_type: "A".to_string(),
390 form_proxied: "false".to_string(),
391 is_editing: false,
392 };
393 assert_eq!(status.render(), "Field 4/6 — TTL: seconds (1 = auto)");
394 }
395
396 #[test]
397 fn test_render_form_field_proxied_true() {
398 let status = StatusMessage::FormFieldHelp {
399 context: FormFieldContext::Proxied,
400 form_type: "A".to_string(),
401 form_proxied: "true".to_string(),
402 is_editing: false,
403 };
404 assert_eq!(
405 status.render(),
406 "Field 5/6 — Proxied: Orange cloud ON | Press Space to toggle"
407 );
408 }
409
410 #[test]
411 fn test_render_form_field_proxied_false() {
412 let status = StatusMessage::FormFieldHelp {
413 context: FormFieldContext::Proxied,
414 form_type: "A".to_string(),
415 form_proxied: "false".to_string(),
416 is_editing: false,
417 };
418 assert_eq!(
419 status.render(),
420 "Field 5/6 — Proxied: Grey cloud OFF | Press Space to toggle"
421 );
422 }
423
424 #[test]
425 fn test_render_form_field_submit_create() {
426 let status = StatusMessage::FormFieldHelp {
427 context: FormFieldContext::Submit,
428 form_type: "A".to_string(),
429 form_proxied: "false".to_string(),
430 is_editing: false,
431 };
432 assert_eq!(status.render(), "Field 6/6 — Press Enter to Create record");
433 }
434
435 #[test]
436 fn test_render_form_field_submit_edit() {
437 let status = StatusMessage::FormFieldHelp {
438 context: FormFieldContext::Submit,
439 form_type: "A".to_string(),
440 form_proxied: "false".to_string(),
441 is_editing: true,
442 };
443 assert_eq!(status.render(), "Field 6/6 — Press Enter to Save record");
444 }
445
446 #[test]
447 fn test_render_record_list_help() {
448 let status = StatusMessage::RecordListHelp {
449 position: 3,
450 total: 10,
451 record_name: "test.example.com".to_string(),
452 };
453 assert_eq!(
454 status.render(),
455 "3 of 10 — test.example.com | e: edit | d: delete | r: refresh | c: create | q: quit"
456 );
457 }
458
459 #[test]
460 fn test_render_empty_list_help() {
461 let status = StatusMessage::EmptyListHelp;
462 assert_eq!(
463 status.render(),
464 "No records | c: create your first DNS record | q: quit"
465 );
466 }
467
468 #[test]
469 fn test_generate_contextual_status_delete_view() {
470 let result = generate_contextual_status(
471 &AppView::Delete,
472 0,
473 "A",
474 "false",
475 false,
476 5,
477 0,
478 Some("example.com"),
479 );
480 assert!(matches!(
481 result,
482 StatusMessage::ViewHelp(ViewHelpContext::DeleteConfirmation)
483 ));
484 }
485
486 #[test]
487 fn test_generate_contextual_status_ip_selector_view() {
488 let result = generate_contextual_status(
489 &AppView::IpSelect,
490 0,
491 "A",
492 "false",
493 false,
494 5,
495 0,
496 Some("example.com"),
497 );
498 assert!(matches!(
499 result,
500 StatusMessage::ViewHelp(ViewHelpContext::IpSelector)
501 ));
502 }
503
504 #[test]
505 fn test_generate_contextual_status_create_view_first_field() {
506 let result = generate_contextual_status(
507 &AppView::Create,
508 0,
509 "A",
510 "false",
511 false,
512 5,
513 0,
514 Some("example.com"),
515 );
516 match result {
517 StatusMessage::FormFieldHelp {
518 context: FormFieldContext::Type,
519 form_type,
520 ..
521 } => {
522 assert_eq!(form_type, "A");
523 }
524 _ => panic!("Expected FormFieldHelp with Type context"),
525 }
526 }
527
528 #[test]
529 fn test_generate_contextual_status_create_view_name_field() {
530 let result = generate_contextual_status(
531 &AppView::Create,
532 1,
533 "AAAA",
534 "true",
535 false,
536 5,
537 0,
538 Some("example.com"),
539 );
540 match result {
541 StatusMessage::FormFieldHelp {
542 context: FormFieldContext::Name,
543 form_type,
544 form_proxied,
545 ..
546 } => {
547 assert_eq!(form_type, "AAAA");
548 assert_eq!(form_proxied, "true");
549 }
550 _ => panic!("Expected FormFieldHelp with Name context"),
551 }
552 }
553
554 #[test]
555 fn test_generate_contextual_status_edit_view_submit_field() {
556 let result = generate_contextual_status(
557 &AppView::Edit,
558 5,
559 "CNAME",
560 "false",
561 true,
562 5,
563 0,
564 Some("example.com"),
565 );
566 match result {
567 StatusMessage::FormFieldHelp {
568 context: FormFieldContext::Submit,
569 is_editing,
570 ..
571 } => {
572 assert!(is_editing);
573 }
574 _ => panic!("Expected FormFieldHelp with Submit context"),
575 }
576 }
577
578 #[test]
579 fn test_generate_contextual_status_list_view_with_records() {
580 let result = generate_contextual_status(
581 &AppView::List,
582 0,
583 "A",
584 "false",
585 false,
586 5,
587 2,
588 Some("test.example.com"),
589 );
590 match result {
591 StatusMessage::RecordListHelp {
592 position,
593 total,
594 record_name,
595 } => {
596 assert_eq!(position, 3); assert_eq!(total, 5);
598 assert_eq!(record_name, "test.example.com");
599 }
600 _ => panic!("Expected RecordListHelp"),
601 }
602 }
603
604 #[test]
605 fn test_generate_contextual_status_list_view_empty() {
606 let result = generate_contextual_status(&AppView::List, 0, "A", "false", false, 0, 0, None);
607 assert!(matches!(result, StatusMessage::EmptyListHelp));
608 }
609
610 #[test]
611 fn test_generate_contextual_status_list_view_invalid_selection() {
612 let result = generate_contextual_status(
614 &AppView::List,
615 0,
616 "A",
617 "false",
618 false,
619 3,
620 5, Some("example.com"),
622 );
623 assert!(matches!(result, StatusMessage::EmptyListHelp));
624 }
625
626 #[test]
627 fn test_generate_contextual_status_list_view_unknown_record_name() {
628 let result = generate_contextual_status(
629 &AppView::List,
630 0,
631 "A",
632 "false",
633 false,
634 1,
635 0,
636 None, );
638 match result {
639 StatusMessage::RecordListHelp { record_name, .. } => {
640 assert_eq!(record_name, "Unknown");
641 }
642 _ => panic!("Expected RecordListHelp"),
643 }
644 }
645
646 #[test]
647 fn test_generate_contextual_status_form_fallback() {
648 let result = generate_contextual_status(
650 &AppView::Create,
651 10, "A",
653 "false",
654 false,
655 5,
656 0,
657 Some("example.com"),
658 );
659 match result {
660 StatusMessage::FormFieldHelp {
661 context: FormFieldContext::Name,
662 ..
663 } => {
664 }
666 _ => panic!("Expected FormFieldHelp with Name context as fallback"),
667 }
668 }
669}