use std::{collections::HashMap, path::Path}; use crate::{ cli::InitArgs, error::PakkerError, model::{Config, LockFile, ResolvedCredentials, Target}, ui_utils::{ prompt_curseforge_api_key, prompt_input, prompt_select, prompt_yes_no, }, }; pub fn execute( args: InitArgs, global_yes: bool, lockfile_path: &Path, config_path: &Path, ) -> Result<(), PakkerError> { let skip_prompts = global_yes; if lockfile_path.exists() { return Err(PakkerError::AlreadyExists( "Lock file already exists".into(), )); } // Interactive mode: prompt for values not provided via CLI and --yes not set let is_interactive = !skip_prompts && args.name.is_none(); // Get modpack name let name = if let Some(name) = args.name.clone() { name } else if is_interactive { prompt_input("Modpack name", Some("My Modpack")) .map_err(|e| PakkerError::InvalidInput(e.to_string()))? } else { "My Modpack".to_string() }; // Get modpack version let version = if let Some(version) = args.version.clone() { version } else if is_interactive { prompt_input("Version", Some("1.0.0")) .map_err(|e| PakkerError::InvalidInput(e.to_string()))? } else { "1.0.0".to_string() }; // Get target platform let target = if let Some(target) = args.target.clone() { target } else if is_interactive { let targets = ["multiplatform", "curseforge", "modrinth"]; let idx = prompt_select("Target platform", &targets) .map_err(|e| PakkerError::InvalidInput(e.to_string()))?; targets[idx].to_string() } else { "multiplatform".to_string() }; let target_enum = match target.as_str() { "curseforge" => Target::CurseForge, "modrinth" => Target::Modrinth, "multiplatform" => Target::Multiplatform, _ => { return Err(PakkerError::InvalidInput(format!( "Invalid target: {target}" ))); }, }; // Get Minecraft versions (supports multiple) let mc_versions = if let Some(versions) = args.mc_versions.clone() { versions } else if is_interactive { let input = prompt_input("Minecraft versions (space-separated)", Some("1.20.1")) .map_err(|e| PakkerError::InvalidInput(e.to_string()))?; input.split_whitespace().map(String::from).collect() } else { vec!["1.20.1".to_string()] }; // Get mod loaders (supports multiple in name=version format) let loaders: HashMap = if let Some(loader_strs) = args.loaders { let mut map = HashMap::new(); for loader_str in loader_strs { let parts: Vec<&str> = loader_str.splitn(2, '=').collect(); if parts.len() == 2 { map.insert(parts[0].to_string(), parts[1].to_string()); } else { // If no version specified, use "latest" map.insert(loader_str, "latest".to_string()); } } map } else if is_interactive { let loader_options = ["fabric", "forge", "neoforge", "quilt"]; let idx = prompt_select("Mod loader", &loader_options) .map_err(|e| PakkerError::InvalidInput(e.to_string()))?; let loader = loader_options[idx].to_string(); let loader_version = prompt_input("Loader version", Some("latest")) .map_err(|e| PakkerError::InvalidInput(e.to_string()))?; let mut map = HashMap::new(); map.insert(loader, loader_version); map } else { let mut map = HashMap::new(); map.insert("fabric".to_string(), "latest".to_string()); map }; let lockfile = LockFile { target: Some(target_enum), mc_versions, loaders, projects: Vec::new(), lockfile_version: 2, }; // Save expects directory path, so get parent directory let lockfile_dir = lockfile_path.parent().unwrap_or_else(|| Path::new(".")); lockfile.save(lockfile_dir)?; let config = Config { name: name.clone(), version: version.clone(), description: None, author: None, overrides: vec!["overrides".to_string()], server_overrides: None, client_overrides: None, paths: HashMap::new(), projects: None, export_profiles: None, export_server_side_projects_to_client: None, file_count_preference: None, }; let config_dir = config_path.parent().unwrap_or_else(|| Path::new(".")); config.save(config_dir)?; println!("Initialized new modpack '{name}' v{version}"); println!(" Target: {target}"); println!(" Minecraft: {}", lockfile.mc_versions.join(", ")); println!( " Loaders: {}", lockfile .loaders .iter() .map(|(k, v)| format!("{k}={v}")) .collect::>() .join(", ") ); // Check if CurseForge API key is needed and prompt if interactive if is_interactive && (target == "curseforge" || target == "multiplatform") { let credentials = ResolvedCredentials::load(); let has_cf_key = credentials.curseforge_api_key().is_some(); if !has_cf_key { println!(); if prompt_yes_no( "Would you like to set up CurseForge API key now?", true, skip_prompts, ) .map_err(|e| PakkerError::InvalidInput(e.to_string()))? && let Ok(Some(api_key)) = prompt_curseforge_api_key(skip_prompts) { // Save to credentials file let creds_path = std::env::var("HOME").map_or_else( |_| Path::new(".pakku").to_path_buf(), |h| Path::new(&h).join(".pakku"), ); std::fs::create_dir_all(&creds_path).ok(); let creds_file = creds_path.join("credentials"); let content = format!("# Pakku/Pakker credentials\nCURSEFORGE_API_KEY={api_key}\n"); if std::fs::write(&creds_file, content).is_ok() { println!("CurseForge API key saved to ~/.pakku/credentials"); } } } } Ok(()) }