use std::collections::HashMap;
use super::profile_store::{ProfileStore, ProfileStoreError};
use super::scenario_profile::{ProfileState, ScenarioProfile, ScenarioProfileId};
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("Store error: {0}")]
Store(#[from] ProfileStoreError),
#[error("Profile not found: {0}")]
NotFound(String),
#[error("No usable profile found")]
NoUsableProfile,
#[error("Profile not active: {0}")]
NotActive(String),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TaskMatcher {
pub keywords: Vec<String>,
pub profile_id: ScenarioProfileId,
pub priority: i32,
}
impl TaskMatcher {
pub fn new(profile_id: ScenarioProfileId, keywords: Vec<String>) -> Self {
Self {
keywords,
profile_id,
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn matches(&self, task: &str) -> bool {
let task_lower = task.to_lowercase();
self.keywords
.iter()
.any(|kw| task_lower.contains(&kw.to_lowercase()))
}
}
pub struct ScenarioRegistry {
store: ProfileStore,
profiles: HashMap<ScenarioProfileId, ScenarioProfile>,
matchers: Vec<TaskMatcher>,
}
impl ScenarioRegistry {
pub fn new(store: ProfileStore) -> Self {
Self {
store,
profiles: HashMap::new(),
matchers: Vec::new(),
}
}
pub fn load_all(&mut self) -> Result<(), RegistryError> {
let profiles = self.store.load_all()?;
for profile in profiles {
self.profiles.insert(profile.id.clone(), profile);
}
Ok(())
}
pub fn register(&mut self, profile: ScenarioProfile) -> Result<(), RegistryError> {
self.store.save(&profile)?;
self.profiles.insert(profile.id.clone(), profile);
Ok(())
}
pub fn get(&self, id: &ScenarioProfileId) -> Option<&ScenarioProfile> {
self.profiles.get(id)
}
pub fn get_mut(&mut self, id: &ScenarioProfileId) -> Option<&mut ScenarioProfile> {
self.profiles.get_mut(id)
}
pub fn remove(&mut self, id: &ScenarioProfileId) -> Result<(), RegistryError> {
self.store.delete(id)?;
self.profiles.remove(id);
self.matchers.retain(|m| &m.profile_id != id);
Ok(())
}
pub fn all_profiles(&self) -> impl Iterator<Item = &ScenarioProfile> {
self.profiles.values()
}
pub fn usable_profiles(&self) -> impl Iterator<Item = &ScenarioProfile> {
self.profiles.values().filter(|p| p.is_usable())
}
pub fn save(&self, id: &ScenarioProfileId) -> Result<(), RegistryError> {
if let Some(profile) = self.profiles.get(id) {
self.store.save(profile)?;
}
Ok(())
}
pub fn add_matcher(&mut self, matcher: TaskMatcher) {
self.matchers.push(matcher);
self.matchers.sort_by(|a, b| b.priority.cmp(&a.priority));
}
pub fn select_for_task(&self, task: &str) -> Result<&ScenarioProfile, RegistryError> {
for matcher in &self.matchers {
if matcher.matches(task) {
if let Some(profile) = self.profiles.get(&matcher.profile_id) {
if profile.is_usable() {
return Ok(profile);
}
}
}
}
self.usable_profiles()
.next()
.ok_or(RegistryError::NoUsableProfile)
}
pub fn candidates_for_task(&self, task: &str) -> Vec<&ScenarioProfile> {
let mut candidates: Vec<_> = self
.matchers
.iter()
.filter(|m| m.matches(task))
.filter_map(|m| self.profiles.get(&m.profile_id))
.filter(|p| p.is_usable())
.collect();
candidates.dedup_by_key(|p| &p.id);
candidates
}
pub fn profile_count(&self) -> usize {
self.profiles.len()
}
pub fn usable_count(&self) -> usize {
self.profiles.values().filter(|p| p.is_usable()).count()
}
pub fn matcher_count(&self) -> usize {
self.matchers.len()
}
pub fn count_by_state(&self) -> HashMap<ProfileState, usize> {
let mut counts = HashMap::new();
for profile in self.profiles.values() {
*counts.entry(profile.state).or_insert(0) += 1;
}
counts
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn create_test_registry() -> (ScenarioRegistry, TempDir) {
let temp_dir = TempDir::new().unwrap();
let store = ProfileStore::new(temp_dir.path());
let registry = ScenarioRegistry::new(store);
(registry, temp_dir)
}
#[test]
fn test_register_and_get() {
let (mut registry, _temp) = create_test_registry();
let profile = ScenarioProfile::from_file("test", "/path/to/scenario.toml");
registry.register(profile).unwrap();
let loaded = registry.get(&ScenarioProfileId::new("test"));
assert!(loaded.is_some());
}
#[test]
fn test_task_matcher() {
let matcher = TaskMatcher::new(
ScenarioProfileId::new("troubleshooting"),
vec!["ログ".to_string(), "調査".to_string(), "エラー".to_string()],
);
assert!(matcher.matches("ログを調査してください"));
assert!(matcher.matches("エラーの原因を特定"));
assert!(!matcher.matches("新機能を追加"));
}
#[test]
fn test_select_for_task() {
let (mut registry, _temp) = create_test_registry();
let mut profile1 = ScenarioProfile::from_file("troubleshooting", "/path/to/1.toml");
profile1.state = ProfileState::Active;
let mut profile2 = ScenarioProfile::from_file("deep_search", "/path/to/2.toml");
profile2.state = ProfileState::Active;
registry.register(profile1).unwrap();
registry.register(profile2).unwrap();
registry.add_matcher(TaskMatcher::new(
ScenarioProfileId::new("troubleshooting"),
vec!["ログ".to_string(), "エラー".to_string()],
));
registry.add_matcher(TaskMatcher::new(
ScenarioProfileId::new("deep_search"),
vec!["検索".to_string(), "探索".to_string()],
));
let selected = registry.select_for_task("ログを調査").unwrap();
assert_eq!(selected.id.0, "troubleshooting");
let selected = registry.select_for_task("ファイルを検索").unwrap();
assert_eq!(selected.id.0, "deep_search");
}
#[test]
fn test_usable_profiles() {
let (mut registry, _temp) = create_test_registry();
let mut active = ScenarioProfile::from_file("active", "/path/to/1.toml");
active.state = ProfileState::Active;
let draft = ScenarioProfile::from_file("draft", "/path/to/2.toml");
registry.register(active).unwrap();
registry.register(draft).unwrap();
assert_eq!(registry.profile_count(), 2);
assert_eq!(registry.usable_count(), 1);
}
#[test]
fn test_count_by_state() {
let (mut registry, _temp) = create_test_registry();
let mut active1 = ScenarioProfile::from_file("active1", "/path/to/1.toml");
active1.state = ProfileState::Active;
let mut active2 = ScenarioProfile::from_file("active2", "/path/to/2.toml");
active2.state = ProfileState::Active;
let draft = ScenarioProfile::from_file("draft", "/path/to/3.toml");
registry.register(active1).unwrap();
registry.register(active2).unwrap();
registry.register(draft).unwrap();
let counts = registry.count_by_state();
assert_eq!(counts.get(&ProfileState::Active), Some(&2));
assert_eq!(counts.get(&ProfileState::Draft), Some(&1));
}
}