use crate::core::{Config, MappingMode};
use inquire::{Select, Text};
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::Path;
pub async fn create_config_interactive() -> Result<String, Box<dyn Error>> {
println!("\n📝 Creating a new configuration file...\n");
let filename = Text::new("Configuration filename (without .json):").prompt()?;
let config_filename = format!("configs/{}.json", filename);
if Path::new(&config_filename).exists() {
println!("\n⚠️ File already exists: {}", config_filename);
return Err("Configuration file already exists".into());
}
println!();
let modes = vec!["Notes", "Octaves"];
let selected_mode = Select::new("Choose mapping mode:", modes).prompt()?;
let mapping_mode = match selected_mode {
"Notes" => MappingMode::Notes,
"Octaves" => MappingMode::Octaves,
_ => MappingMode::Notes,
};
let velocity_threshold: u8 = loop {
let input = Text::new("Velocity threshold (0-127, 0 means accept all):").prompt()?;
match input.parse::<u8>() {
Ok(v) if v <= 127 => {
break v;
}
Ok(_) => println!("❌ Please enter a value between 0 and 127"),
Err(_) => println!("❌ Please enter a valid number"),
}
};
let config = if mapping_mode == MappingMode::Notes {
create_notes_mode_config(velocity_threshold).await?
} else {
create_octaves_mode_config(velocity_threshold).await?
};
fs::create_dir_all("configs")?;
let config_json = serde_json::to_string_pretty(&config)?;
fs::write(&config_filename, config_json)?;
println!("\n✅ Configuration file created successfully!");
println!("📂 Saved to: {}", config_filename);
println!(
"\n💡 You can now use it with: osynic-midi start -c {}",
config_filename
);
Ok(config_filename)
}
async fn create_notes_mode_config(velocity_threshold: u8) -> Result<Config, Box<dyn Error>> {
println!("\n🎹 Configuring Notes Mode");
println!("(Mapping individual MIDI note numbers to keyboard keys)\n");
let mut note_mappings = HashMap::new();
loop {
let note_input =
Text::new("Enter MIDI note number (0-127, or 'done' to finish):").prompt()?;
if note_input.to_lowercase() == "done" || note_input.is_empty() {
break;
}
match note_input.parse::<u8>() {
Ok(note) if note <= 127 => {
let key = Text::new(&format!(
"Enter keyboard key for MIDI note {} (e.g., A, Space, Left):",
note
))
.prompt()?;
note_mappings.insert(note, key);
println!("✓ Note {} -> {}", note, note_mappings.get(¬e).unwrap());
}
_ => println!("❌ Please enter a valid note number (0-127)"),
}
}
Ok(Config {
mapping_mode: Some(MappingMode::Notes),
octaves: HashMap::new(),
velocity_threshold,
note_mappings,
})
}
async fn create_octaves_mode_config(velocity_threshold: u8) -> Result<Config, Box<dyn Error>> {
println!("\n🎵 Configuring Octaves Mode");
println!("(Mapping by octave and pitch)\n");
let mut octaves = HashMap::new();
let pitches = vec![
"C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B",
];
loop {
let octave_input =
Text::new("Enter octave number (0-10, or 'done' to finish):").prompt()?;
if octave_input.to_lowercase() == "done" || octave_input.is_empty() {
break;
}
match octave_input.parse::<u8>() {
Ok(octave) if octave <= 10 => {
println!("\n🎹 Mapping octave {}:", octave);
let mut octave_map = HashMap::new();
for pitch in &pitches {
let key = Text::new(&format!(" {} {} -> ", octave, pitch)).prompt()?;
if !key.is_empty() {
octave_map.insert(pitch.to_string(), key);
}
}
if !octave_map.is_empty() {
octaves.insert(octave.to_string(), octave_map);
println!("✓ Octave {} configured", octave);
}
}
_ => println!("❌ Please enter a valid octave number (0-10)"),
}
}
Ok(Config {
mapping_mode: Some(MappingMode::Octaves),
octaves,
velocity_threshold,
note_mappings: HashMap::new(),
})
}
pub fn select_local_config() -> Result<String, Box<dyn Error>> {
println!();
let config_path =
Text::new("Enter configuration file path (absolute or relative):").prompt()?;
if !Path::new(&config_path).exists() {
return Err(format!("Configuration file not found: {}", config_path).into());
}
if !config_path.ends_with(".json") {
return Err("Configuration file must be a .json file".into());
}
match crate::Config::load(&config_path) {
Ok(_) => {
println!("✅ Configuration loaded successfully!");
Ok(config_path)
}
Err(e) => Err(format!("Failed to load configuration: {}", e).into()),
}
}