stt-cli 0.2.1

Speech to text Cli using Groq API and OpenAI API
use anyhow::{anyhow, Context, Result};
use cpal::traits::{DeviceTrait, HostTrait};
use cpal::{Device, Host, SampleFormat, SupportedStreamConfigRange};
use dialoguer::{theme::ColorfulTheme, Select};
use std::collections::HashSet;
use std::io::{self, Write};
use std::sync::Arc;
use tracing::{debug, info, warn};

use crate::audio::device::AudioDevice;

/// Lists available audio devices (input and output) using the new AudioDevice methods.
pub fn list_audio_devices(host: &Host) -> Result<Vec<Arc<AudioDevice>>> {
    info!("Listing available audio devices...");
    let mut devices = Vec::new();

    match AudioDevice::list_input_devices() {
        Ok(inputs) => devices.extend(inputs),
        Err(e) => warn!("Failed to list input devices: {}", e),
    }

    if devices.is_empty() {
        warn!("No audio devices found for host: {:?}", host.id());
        Err(anyhow!("No audio devices found"))
    } else {
        debug!("Found {} audio devices", devices.len());
        Ok(devices)
    }
}

/// Prompts the user to select an audio input device from a list.
pub fn select_audio_device(host: &Host) -> Result<Arc<AudioDevice>> {
    let devices =
        AudioDevice::list_input_devices().context("Failed to list input devices for selection")?;

    if devices.is_empty() {
        return Err(anyhow!("No input devices available to select"));
    }

    let device_names: Vec<String> = devices.iter().map(|d| d.name.clone()).collect();

    info!("Please select an audio input device:");

    let selection = Select::with_theme(&ColorfulTheme::default())
        .items(&device_names)
        .default(0)
        .interact()
        .context("Failed to get user selection")?;

    info!("Selected device: {}", device_names[selection]);
    Ok(devices[selection].clone())
}

/// Finds a specific audio device by name (case-insensitive comparison).
pub fn find_device_by_name(host: &Host, name: &str) -> Result<Arc<AudioDevice>> {
    debug!("Searching for device: '{}'", name);
    let lower_name = name.to_lowercase();

    if let Ok(devices) = AudioDevice::list_input_devices() {
        if let Some(device) = devices
            .into_iter()
            .find(|d| d.name.to_lowercase() == lower_name)
        {
            info!("Found input device: {}", device.name);
            return Ok(device);
        }
    }

    warn!("Device '{}' not found.", name);
    Err(anyhow!("Device '{}' not found", name))
}

/// Gets and formats the capabilities of a specific CPAL device.
pub fn get_device_capabilities(device: &Device) -> Result<String> {
    let device_name = device.name().unwrap_or_else(|_| "Unknown".to_string());
    let mut capabilities = format!("Capabilities for device: {}\n", device_name);

    append_configs(&mut capabilities, device.supported_input_configs(), "Input")?;
    append_configs(
        &mut capabilities,
        device.supported_output_configs(),
        "Output",
    )?;

    Ok(capabilities)
}

fn append_configs(
    caps: &mut String,
    configs: Result<
        impl Iterator<Item = SupportedStreamConfigRange>,
        cpal::SupportedStreamConfigsError,
    >,
    config_type: &str,
) -> Result<()> {
    match configs {
        Ok(configs) => {
            let mut unique_configs = HashSet::new();
            for config in configs {
                let config_str = format!(
                    "  {} Channels: {}, Sample Rate: {}-{} Hz, Format: {:?}\n",
                    config_type,
                    config.channels(),
                    config.min_sample_rate().0,
                    config.max_sample_rate().0,
                    config.sample_format()
                );
                if unique_configs.insert(config_str.clone()) {
                    caps.push_str(&config_str);
                }
            }
            if unique_configs.is_empty() {
                caps.push_str(&format!(
                    "  No supported {} configurations found.\n",
                    config_type
                ));
            }
        }
        Err(e) => {
            let err_msg = format!("Error getting {} configs: {}\n", config_type, e);
            warn!("{}", err_msg);
        }
    }
    Ok(())
}