use super::{AppConfig, GitProfile, TokenConfig}; use crate::utils::keyring::{KeyringManager, get_default_base_url, get_default_model, provider_needs_api_key}; use anyhow::{bail, Context, Result}; // use std::collections::HashMap; use std::path::{Path, PathBuf}; /// Configuration manager pub struct ConfigManager { config: AppConfig, config_path: PathBuf, modified: bool, keyring: KeyringManager, } impl ConfigManager { /// Create new config manager with default path pub fn new() -> Result { let config_path = AppConfig::default_path()?; Self::with_path(&config_path) } /// Create config manager with specific path pub fn with_path(path: &Path) -> Result { let config = if path.exists() { AppConfig::load(path)? } else { AppConfig::default() }; Ok(Self { config, config_path: path.to_path_buf(), modified: false, keyring: KeyringManager::new(), }) } /// Create config manager with fresh config (ignoring existing) pub fn with_path_fresh(path: &Path) -> Result { Ok(Self { config: AppConfig::default(), config_path: path.to_path_buf(), modified: true, keyring: KeyringManager::new(), }) } /// Get configuration reference pub fn config(&self) -> &AppConfig { &self.config } /// Get mutable configuration reference pub fn config_mut(&mut self) -> &mut AppConfig { self.modified = true; &mut self.config } /// Save configuration if modified pub fn save(&mut self) -> Result<()> { if self.modified { self.config.save(&self.config_path)?; self.modified = false; } Ok(()) } // /// Force save configuration // pub fn force_save(&self) -> Result<()> { // self.config.save(&self.config_path) // } /// Get configuration file path pub fn path(&self) -> &Path { &self.config_path } // Profile management /// Add a new profile pub fn add_profile(&mut self, name: String, profile: GitProfile) -> Result<()> { if self.config.profiles.contains_key(&name) { bail!("Profile '{}' already exists", name); } self.config.profiles.insert(name, profile); self.modified = true; Ok(()) } /// Remove a profile pub fn remove_profile(&mut self, name: &str) -> Result<()> { if !self.config.profiles.contains_key(name) { bail!("Profile '{}' does not exist", name); } if self.config.default_profile.as_ref() == Some(&name.to_string()) { self.config.default_profile = None; } self.config.repo_profiles.retain(|_, v| v != name); self.config.profiles.remove(name); self.modified = true; Ok(()) } /// Update a profile pub fn update_profile(&mut self, name: &str, profile: GitProfile) -> Result<()> { if !self.config.profiles.contains_key(name) { bail!("Profile '{}' does not exist", name); } self.config.profiles.insert(name.to_string(), profile); self.modified = true; Ok(()) } /// Get a profile pub fn get_profile(&self, name: &str) -> Option<&GitProfile> { self.config.profiles.get(name) } // /// Get mutable profile // pub fn get_profile_mut(&mut self, name: &str) -> Option<&mut GitProfile> { // self.modified = true; // self.config.profiles.get_mut(name) // } /// List all profile names pub fn list_profiles(&self) -> Vec<&String> { self.config.profiles.keys().collect() } /// Check if profile exists pub fn has_profile(&self, name: &str) -> bool { self.config.profiles.contains_key(name) } /// Set default profile pub fn set_default_profile(&mut self, name: Option) -> Result<()> { if let Some(ref n) = name { if !self.config.profiles.contains_key(n) { bail!("Profile '{}' does not exist", n); } } self.config.default_profile = name; self.modified = true; Ok(()) } /// Get default profile pub fn default_profile(&self) -> Option<&GitProfile> { self.config .default_profile .as_ref() .and_then(|name| self.config.profiles.get(name)) } /// Get default profile name pub fn default_profile_name(&self) -> Option<&String> { self.config.default_profile.as_ref() } /// Record profile usage pub fn record_profile_usage(&mut self, name: &str, repo_path: Option) -> Result<()> { if let Some(profile) = self.config.profiles.get_mut(name) { profile.record_usage(repo_path); self.modified = true; Ok(()) } else { bail!("Profile '{}' does not exist", name); } } // /// Get profile usage statistics // pub fn get_profile_usage(&self, name: &str) -> Option<&super::UsageStats> { // self.config.profiles.get(name).map(|p| &p.usage) // } // Token management /// Add a token to a profile pub fn add_token_to_profile(&mut self, profile_name: &str, service: String, token: TokenConfig) -> Result<()> { if let Some(profile) = self.config.profiles.get_mut(profile_name) { profile.add_token(service, token); self.modified = true; Ok(()) } else { bail!("Profile '{}' does not exist", profile_name); } } // /// Get a token from a profile // pub fn get_token_from_profile(&self, profile_name: &str, service: &str) -> Option<&TokenConfig> { // self.config.profiles.get(profile_name)?.get_token(service) // } /// Remove a token from a profile pub fn remove_token_from_profile(&mut self, profile_name: &str, service: &str) -> Result<()> { if let Some(profile) = self.config.profiles.get_mut(profile_name) { profile.remove_token(service); self.modified = true; Ok(()) } else { bail!("Profile '{}' does not exist", profile_name); } } // /// List all tokens in a profile // pub fn list_profile_tokens(&self, profile_name: &str) -> Option> { // self.config.profiles.get(profile_name).map(|p| p.tokens.keys().collect()) // } // Repository profile management // /// Get profile for repository // pub fn get_repo_profile(&self, repo_path: &str) -> Option<&GitProfile> { // self.config // .repo_profiles // .get(repo_path) // .and_then(|name| self.config.profiles.get(name)) // } /// Set profile for repository pub fn set_repo_profile(&mut self, repo_path: String, profile_name: String) -> Result<()> { if !self.config.profiles.contains_key(&profile_name) { bail!("Profile '{}' does not exist", profile_name); } self.config.repo_profiles.insert(repo_path, profile_name); self.modified = true; Ok(()) } // /// Remove repository profile mapping // pub fn remove_repo_profile(&mut self, repo_path: &str) { // self.config.repo_profiles.remove(repo_path); // self.modified = true; // } // /// List repository profile mappings // pub fn list_repo_profiles(&self) -> &HashMap { // &self.config.repo_profiles // } // /// Get effective profile for a repository (repo-specific -> default) // pub fn get_effective_profile(&self, repo_path: Option<&str>) -> Option<&GitProfile> { // if let Some(path) = repo_path { // if let Some(profile) = self.get_repo_profile(path) { // return Some(profile); // } // } // self.default_profile() // } /// Check and compare profile with git configuration pub fn check_profile_config(&self, profile_name: &str, repo: &git2::Repository) -> Result { let profile = self.get_profile(profile_name) .ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?; profile.compare_with_git_config(repo) } // LLM configuration /// Get LLM provider pub fn llm_provider(&self) -> &str { &self.config.llm.provider } /// Set LLM provider pub fn set_llm_provider(&mut self, provider: String) { let default_model = get_default_model(&provider); self.config.llm.provider = provider.clone(); if self.config.llm.model.is_empty() || self.config.llm.model == "llama2" { self.config.llm.model = default_model.to_string(); } self.modified = true; } /// Get model pub fn llm_model(&self) -> &str { &self.config.llm.model } /// Set model pub fn set_llm_model(&mut self, model: String) { self.config.llm.model = model; self.modified = true; } /// Get base URL (returns provider default if not set) pub fn llm_base_url(&self) -> String { match &self.config.llm.base_url { Some(url) => url.clone(), None => get_default_base_url(&self.config.llm.provider).to_string(), } } /// Set base URL pub fn set_llm_base_url(&mut self, url: Option) { self.config.llm.base_url = url; self.modified = true; } /// Get API key from configured storage method pub fn get_api_key(&self) -> Option { // First try environment variables (always checked) if let Some(key) = self.keyring.get_api_key(&self.config.llm.provider).unwrap_or(None) { return Some(key); } // Then try config file if configured if self.config.llm.api_key_storage == "config" { return self.config.llm.api_key.clone(); } None } /// Store API key in configured storage method pub fn set_api_key(&self, api_key: &str) -> Result<()> { match self.config.llm.api_key_storage.as_str() { "keyring" => { if !self.keyring.is_available() { bail!("Keyring is not available. Set QUICOMMIT_API_KEY environment variable instead or change api_key_storage to 'config'."); } self.keyring.store_api_key(&self.config.llm.provider, api_key) }, "config" => { // We can't modify self.config here since self is immutable // This will be handled by the caller updating the config Ok(()) }, "environment" => { bail!("API key storage set to 'environment'. Please set QUICOMMIT_{}_API_KEY environment variable.", self.config.llm.provider.to_uppercase()); }, _ => { bail!("Invalid API key storage method: {}", self.config.llm.api_key_storage); } } } /// Delete API key from configured storage method pub fn delete_api_key(&self) -> Result<()> { match self.config.llm.api_key_storage.as_str() { "keyring" => { if self.keyring.is_available() { self.keyring.delete_api_key(&self.config.llm.provider)?; } }, "config" => { // We can't modify self.config here since self is immutable // This will be handled by the caller updating the config }, "environment" => { // Environment variables are not managed by the app }, _ => { bail!("Invalid API key storage method: {}", self.config.llm.api_key_storage); } } Ok(()) } /// Check if API key is configured pub fn has_api_key(&self) -> bool { if !provider_needs_api_key(&self.config.llm.provider) { return true; } // Check environment variables if self.keyring.get_api_key(&self.config.llm.provider).unwrap_or(None).is_some() { return true; } // Check config file if configured if self.config.llm.api_key_storage == "config" { return self.config.llm.api_key.is_some(); } false } /// Get keyring manager reference pub fn keyring(&self) -> &KeyringManager { &self.keyring } // /// Configure LLM provider with all settings // pub fn configure_llm(&mut self, provider: String, model: Option, base_url: Option, api_key: Option<&str>) -> Result<()> { // self.set_llm_provider(provider.clone()); // if let Some(m) = model { // self.set_llm_model(m); // } // self.set_llm_base_url(base_url); // if let Some(key) = api_key { // if provider_needs_api_key(&provider) { // self.set_api_key(key)?; // } // } // Ok(()) // } // Commit configuration // /// Get commit format // pub fn commit_format(&self) -> super::CommitFormat { // self.config.commit.format // } /// Set commit format pub fn set_commit_format(&mut self, format: super::CommitFormat) { self.config.commit.format = format; self.modified = true; } // /// Check if auto-generate is enabled // pub fn auto_generate_commits(&self) -> bool { // self.config.commit.auto_generate // } /// Set auto-generate commits pub fn set_auto_generate_commits(&mut self, enabled: bool) { self.config.commit.auto_generate = enabled; self.modified = true; } // Tag configuration // /// Get version prefix // pub fn version_prefix(&self) -> &str { // &self.config.tag.version_prefix // } /// Set version prefix pub fn set_version_prefix(&mut self, prefix: String) { self.config.tag.version_prefix = prefix; self.modified = true; } // Changelog configuration // /// Get changelog path // pub fn changelog_path(&self) -> &str { // &self.config.changelog.path // } /// Set changelog path pub fn set_changelog_path(&mut self, path: String) { self.config.changelog.path = path; self.modified = true; } // Language configuration // /// Get output language // pub fn output_language(&self) -> &str { // &self.config.language.output_language // } /// Set output language pub fn set_output_language(&mut self, language: String) { self.config.language.output_language = language; self.modified = true; } /// Get language enum from config pub fn get_language(&self) -> Option { super::Language::from_str(&self.config.language.output_language) } /// Check if commit types should be kept in English pub fn keep_types_english(&self) -> bool { self.config.language.keep_types_english } /// Set keep types English flag pub fn set_keep_types_english(&mut self, keep: bool) { self.config.language.keep_types_english = keep; self.modified = true; } /// Check if changelog types should be kept in English pub fn keep_changelog_types_english(&self) -> bool { self.config.language.keep_changelog_types_english } /// Set keep changelog types English flag pub fn set_keep_changelog_types_english(&mut self, keep: bool) { self.config.language.keep_changelog_types_english = keep; self.modified = true; } /// Export configuration to TOML string pub fn export(&self) -> Result { toml::to_string_pretty(&self.config) .context("Failed to serialize config") } /// Import configuration from TOML string pub fn import(&mut self, toml_str: &str) -> Result<()> { self.config = toml::from_str(toml_str) .context("Failed to parse config")?; self.modified = true; Ok(()) } /// Reset to default configuration pub fn reset(&mut self) { self.config = AppConfig::default(); self.modified = true; } } impl Default for ConfigManager { fn default() -> Self { Self { config: AppConfig::default(), config_path: PathBuf::new(), modified: false, keyring: KeyringManager::new(), } } }