RUST: Multi-line user input

// --- Import necessary modules ---
// std::io is needed for handling input/output operations like printing to console
// and reading user input.
// The `Write` trait specifically is needed for `flush()` to ensure prompts appear
// before the program waits for input.
use std::io::{self, Write};

// --- Define the Data Structure ---
// For a fixed set of known fields like name, age, and height, a `struct`
// is generally more appropriate and idiomatic in Rust than a `Vec`.
// - It's type-safe: each field has a defined type (String, u32, f64).
// - It's descriptive: fields have meaningful names (name, age, height_meters).
// - It clearly represents a single logical entity (a user's profile).
// A `Vec` is better suited for collections of items of the *same* type, or
// when the number of items isn't known at compile time.
// We use `#[derive(Debug)]` to allow easy printing of the whole struct for debugging.
#[derive(Debug)]
struct UserProfile {
    name: String,
    age: u32,           // Using u32 for age, as it's typically a non-negative whole number.
    height_meters: f64, // Using f64 for standard floating-point precision.
}

// --- Helper Function to Read and Validate String Input ---
// This function simplifies getting a non-empty string from the user.
// It takes the prompt message as input and returns a `Result` containing
// the valid `String` or an `io::Error` if reading fails.
fn get_non_empty_string(prompt: &str) -> io::Result<String> {
    // Loop indefinitely until valid input is received.
    loop {
        // Print the prompt message to the console without a newline.
        print!("{}", prompt);
        // Ensure the prompt is displayed immediately before waiting for input.
        // `flush()` returns a Result, so we use `?` to propagate potential errors.
        io::stdout().flush()?;

        // Create an empty mutable string to store the user's input.
        let mut buffer = String::new();
        // Read a line of text from standard input (the console).
        // `read_line` appends to the buffer. It also returns a Result.
        io::stdin().read_line(&mut buffer)?;

        // Remove leading/trailing whitespace (like the newline character).
        let trimmed_input = buffer.trim();
        dbg!(trimmed_input);
        // Check if the input is empty after trimming.
        if trimmed_input.is_empty() {
            // If empty, inform the user and loop again.
            println!("Input cannot be empty. Please try again.");
        } else {
            // If not empty, convert the trimmed string slice (`&str`)
            // into an owned `String` and return it wrapped in `Ok`.
            // This exits the loop.
            return Ok(trimmed_input.to_string());
        }
    }
}

// --- Helper Function to Read and Validate u32 Integer Input ---
// This function simplifies getting a valid `u32` integer from the user.
// It takes the prompt message and returns a `Result` containing the
// valid `u32` or an `io::Error`.
fn get_u32(prompt: &str) -> io::Result<u32> {
    // Loop until valid input is received.
    loop {
        // Print prompt and flush output buffer.
        print!("{}", prompt);
        io::stdout().flush()?;

        // Read the line from the user.
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer)?;

        // Trim whitespace and attempt to parse the input as a u32 integer.
        // `parse::<u32>()` returns a `Result<u32, ParseIntError>`.
        match buffer.trim().parse::<u32>() {
            // If parsing is successful (`Ok`), return the value wrapped in `Ok`.
            Ok(value) => return Ok(value),
            // If parsing fails (`Err`), inform the user and the loop continues.
            Err(_) => {
                println!("Invalid input. Please enter a positive whole number (e.g., 30).");
            }
        }
    }
}

// --- Helper Function to Read and Validate f64 Float Input ---
// This function simplifies getting a valid `f64` float from the user.
// It takes the prompt message and returns a `Result` containing the
// valid `f64` or an `io::Error`.
fn get_f64(prompt: &str) -> io::Result<f64> {
    // Loop until valid input is received.
    loop {
        // Print prompt and flush output buffer.
        print!("{}", prompt);
        io::stdout().flush()?;

        // Read the line from the user.
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer)?;

        // Trim whitespace and attempt to parse the input as an f64 float.
        // `parse::<f64>()` returns a `Result<f64, ParseFloatError>`.
        match buffer.trim().parse::<f64>() {
            // If parsing is successful (`Ok`), return the value wrapped in `Ok`.
            Ok(value) => return Ok(value),
            // If parsing fails (`Err`), inform the user and the loop continues.
            Err(_) => {
                println!("Invalid input. Please enter a number (e.g., 1.75).");
            }
        }
    }
}

// --- Main Function ---
// The entry point of the program.
// It returns `io::Result<()>` which allows using the `?` operator
// for concise error handling within the function for I/O operations.
fn main() -> io::Result<()> {
    println!("--- Please Enter Your Details ---");

    // Call the helper function to get the name. The `?` operator will return
    // early with the error if `get_non_empty_string` fails.
    let name = get_non_empty_string("1. Enter your Name: ")?;

    // Call the helper function to get the age.
    let age = get_u32("2. Enter your Age: ")?;

    // Call the helper function to get the height.
    let height = get_f64("3. Enter your Height (in meters, e.g., 1.75): ")?;

    // Create an instance of the UserProfile struct using the collected data.
    // We can use the field init shorthand if the variable name matches the field name.
    let user_profile = UserProfile {
        name,                  // Shorthand for name: name
        age,                   // Shorthand for age: age
        height_meters: height, // Variable name `height` doesn't match field `height_meters`
    };

    // --- Print the Collected Data ---
    println!("\n--- Thank you! Data Collected: ---");

    // Method 1: Using the Debug trait (`{:?}`) for a quick struct representation.
    // Useful for developers during debugging.
    println!("Debug Output: {:?}", user_profile);

    // Method 2: Printing fields individually for formatted user output.
    println!("\nFormatted Output:");
    println!("  Name: {}", user_profile.name);
    println!("  Age: {} years old", user_profile.age);
    // Use {:.2} in the format string to display the float with 2 decimal places.
    println!("  Height: {:.2} meters", user_profile.height_meters);

    // If all operations were successful, return `Ok(())`.
    // `()` is the unit type, indicating success with no specific value to return.
    Ok(())
}

"Life does not start and stop at your convenience, you miserable piece of shit"