use super::{AppConfig, GitProfile, TokenConfig}; 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, } 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, }) } /// 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, }) } /// 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) { self.config.llm.provider = provider; self.modified = true; } /// Get OpenAI API key pub fn openai_api_key(&self) -> Option<&String> { self.config.llm.openai.api_key.as_ref() } /// Set OpenAI API key pub fn set_openai_api_key(&mut self, key: String) { self.config.llm.openai.api_key = Some(key); self.modified = true; } /// Get Anthropic API key pub fn anthropic_api_key(&self) -> Option<&String> { self.config.llm.anthropic.api_key.as_ref() } /// Set Anthropic API key pub fn set_anthropic_api_key(&mut self, key: String) { self.config.llm.anthropic.api_key = Some(key); self.modified = true; } /// Get Kimi API key pub fn kimi_api_key(&self) -> Option<&String> { self.config.llm.kimi.api_key.as_ref() } /// Set Kimi API key pub fn set_kimi_api_key(&mut self, key: String) { self.config.llm.kimi.api_key = Some(key); self.modified = true; } /// Get Kimi base URL pub fn kimi_base_url(&self) -> &str { &self.config.llm.kimi.base_url } /// Set Kimi base URL pub fn set_kimi_base_url(&mut self, url: String) { self.config.llm.kimi.base_url = url; self.modified = true; } /// Get DeepSeek API key pub fn deepseek_api_key(&self) -> Option<&String> { self.config.llm.deepseek.api_key.as_ref() } /// Set DeepSeek API key pub fn set_deepseek_api_key(&mut self, key: String) { self.config.llm.deepseek.api_key = Some(key); self.modified = true; } /// Get DeepSeek base URL pub fn deepseek_base_url(&self) -> &str { &self.config.llm.deepseek.base_url } /// Set DeepSeek base URL pub fn set_deepseek_base_url(&mut self, url: String) { self.config.llm.deepseek.base_url = url; self.modified = true; } /// Get OpenRouter API key pub fn openrouter_api_key(&self) -> Option<&String> { self.config.llm.openrouter.api_key.as_ref() } /// Set OpenRouter API key pub fn set_openrouter_api_key(&mut self, key: String) { self.config.llm.openrouter.api_key = Some(key); self.modified = true; } /// Get OpenRouter base URL pub fn openrouter_base_url(&self) -> &str { &self.config.llm.openrouter.base_url } /// Set OpenRouter base URL pub fn set_openrouter_base_url(&mut self, url: String) { self.config.llm.openrouter.base_url = url; self.modified = true; } // 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, } } }