1use crate::constants::{MAX_MESSAGE_LENGTH, MESSAGE_PREVIEW_LENGTH};
2use freemobile_api::FreeMobileError;
3use inquire::Text;
4use std::fs;
5use std::io::{self, Read};
6use std::path::Path;
7
8pub struct InputHandler;
9
10impl InputHandler {
11 pub async fn get_message_from_file<P: AsRef<Path>>(path: P) -> Result<String, FreeMobileError> {
12 let content = fs::read_to_string(path).map_err(FreeMobileError::IoError)?;
13
14 if content.trim().is_empty() {
15 return Err(FreeMobileError::EmptyMessage);
16 }
17
18 Ok(content.trim().to_string())
19 }
20
21 pub async fn get_message_from_stdin() -> Result<String, FreeMobileError> {
22 let mut buffer = String::new();
23 io::stdin()
24 .read_to_string(&mut buffer)
25 .map_err(FreeMobileError::IoError)?;
26
27 if buffer.trim().is_empty() {
28 return Err(FreeMobileError::EmptyMessage);
29 }
30
31 Ok(buffer.trim().to_string())
32 }
33
34 pub async fn get_message_interactive() -> Result<String, FreeMobileError> {
35 println!("Enter your message (press Enter to send, Ctrl+C to cancel):");
36
37 let message = Text::new("")
38 .with_placeholder("Type your message here...")
39 .prompt()
40 .map_err(|e| {
41 FreeMobileError::ConfigError(format!("Interactive input failed: {}", e))
42 })?;
43
44 if message.trim().is_empty() {
45 return Err(FreeMobileError::EmptyMessage);
46 }
47
48 Ok(message.trim().to_string())
49 }
50
51 pub fn validate_message(message: &str) -> Result<(), FreeMobileError> {
52 if message.trim().is_empty() {
53 return Err(FreeMobileError::EmptyMessage);
54 }
55
56 if message.len() > MAX_MESSAGE_LENGTH {
58 return Err(FreeMobileError::InvalidMessage(format!(
59 "Message too long (maximum {} characters)",
60 MAX_MESSAGE_LENGTH
61 )));
62 }
63
64 Ok(())
65 }
66
67 pub fn preview_message(message: &str, verbose: bool) {
68 if !verbose {
69 return;
70 }
71
72 println!("📄 Message preview:");
73 println!("Length: {} characters", message.len());
74
75 if message.len() > MESSAGE_PREVIEW_LENGTH {
76 use unicode_segmentation::UnicodeSegmentation;
77 let truncated: String = message
78 .graphemes(true)
79 .take(MESSAGE_PREVIEW_LENGTH)
80 .collect();
81 println!(
82 "Content (first {} graphemes): {}",
83 MESSAGE_PREVIEW_LENGTH, truncated
84 );
85 println!("... (truncated for preview)");
86 } else {
87 println!("Content: {}", message);
88 }
89 println!();
90 }
91
92 pub fn has_stdin_input() -> bool {
93 use is_terminal::IsTerminal;
94 !io::stdin().is_terminal()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::io::Write;
102 use tempfile::NamedTempFile;
103
104 #[tokio::test]
105 async fn test_read_from_file() {
106 let mut temp_file = NamedTempFile::new().unwrap();
107 writeln!(temp_file, "Test message from file").unwrap();
108
109 let message = InputHandler::get_message_from_file(temp_file.path())
110 .await
111 .unwrap();
112 assert_eq!(message, "Test message from file");
113 }
114
115 #[tokio::test]
116 async fn test_read_from_empty_file() {
117 let temp_file = NamedTempFile::new().unwrap();
118
119 let result = InputHandler::get_message_from_file(temp_file.path()).await;
120 assert!(result.is_err());
121 assert!(matches!(result.unwrap_err(), FreeMobileError::EmptyMessage));
122 }
123
124 #[test]
125 fn test_validate_message() {
126 assert!(InputHandler::validate_message("Valid message").is_ok());
127 assert!(InputHandler::validate_message("").is_err());
128 assert!(InputHandler::validate_message(" ").is_err());
129
130 let valid_message = "a".repeat(MAX_MESSAGE_LENGTH);
132 assert!(InputHandler::validate_message(&valid_message).is_ok());
133
134 let too_long_message = "a".repeat(MAX_MESSAGE_LENGTH + 1);
135 assert!(InputHandler::validate_message(&too_long_message).is_err());
136 }
137
138 #[test]
139 fn test_message_preview() {
140 InputHandler::preview_message("Test message", false);
142
143 InputHandler::preview_message("Test message", true);
145
146 let long_message = "a".repeat(150);
147 InputHandler::preview_message(&long_message, true);
148 }
149}