1#[cfg(test)]
4use async_trait::async_trait;
5use std::collections::HashMap;
6use std::sync::Arc;
7use wm_core::{Gana, Tool};
8
9struct RegistryInner {
11 tools: Vec<Arc<dyn Tool>>,
12 by_gana: HashMap<Gana, Vec<usize>>,
13 by_name: HashMap<String, usize>,
14}
15
16pub struct ToolRegistry {
21 inner: Arc<RegistryInner>,
22}
23
24impl ToolRegistry {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 inner: Arc::new(RegistryInner {
30 tools: vec![],
31 by_gana: HashMap::new(),
32 by_name: HashMap::new(),
33 }),
34 }
35 }
36
37 #[must_use]
39 pub fn with_capacity(cap: usize) -> Self {
40 Self {
41 inner: Arc::new(RegistryInner {
42 tools: Vec::with_capacity(cap),
43 by_gana: HashMap::new(),
44 by_name: HashMap::new(),
45 }),
46 }
47 }
48
49 #[must_use]
55 pub fn register(&self, tool: Arc<dyn Tool>) -> Self {
56 let mut inner = RegistryInner {
57 tools: self.inner.tools.clone(),
58 by_gana: self.inner.by_gana.clone(),
59 by_name: self.inner.by_name.clone(),
60 };
61 let idx = inner.tools.len();
62 let gana = tool.gana();
63 let name = tool.name().to_string();
64 inner.by_gana.entry(gana).or_default().push(idx);
65 inner.by_name.insert(name, idx);
66 inner.tools.push(tool);
67 Self {
68 inner: Arc::new(inner),
69 }
70 }
71
72 #[must_use]
74 pub fn get(&self, name: &str) -> Option<Arc<dyn Tool>> {
75 self.inner
76 .by_name
77 .get(name)
78 .map(|&i| Arc::clone(&self.inner.tools[i]))
79 }
80
81 #[must_use]
83 pub fn by_gana(&self, gana: Gana) -> Vec<Arc<dyn Tool>> {
84 self.inner
85 .by_gana
86 .get(&gana)
87 .map(|indices| {
88 indices
89 .iter()
90 .map(|&i| Arc::clone(&self.inner.tools[i]))
91 .collect()
92 })
93 .unwrap_or_default()
94 }
95
96 #[must_use]
98 pub fn all(&self) -> Vec<Arc<dyn Tool>> {
99 self.inner.tools.clone()
100 }
101
102 #[must_use]
104 pub fn all_ref(&self) -> &[Arc<dyn Tool>] {
105 &self.inner.tools
106 }
107
108 #[must_use]
110 pub fn len(&self) -> usize {
111 self.inner.tools.len()
112 }
113
114 #[must_use]
116 pub fn is_empty(&self) -> bool {
117 self.inner.tools.is_empty()
118 }
119
120 #[must_use]
125 pub fn available_in(&self, brain_wave: wm_core::BrainWave) -> Vec<Arc<dyn Tool>> {
126 self.inner
127 .tools
128 .iter()
129 .filter(|t| t.effects().is_available_in(brain_wave))
130 .cloned()
131 .collect()
132 }
133
134 #[must_use]
136 pub fn available_count(&self, brain_wave: wm_core::BrainWave) -> usize {
137 self.inner
138 .tools
139 .iter()
140 .filter(|t| t.effects().is_available_in(brain_wave))
141 .count()
142 }
143}
144
145impl Default for ToolRegistry {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl Clone for ToolRegistry {
152 fn clone(&self) -> Self {
153 Self {
154 inner: Arc::clone(&self.inner),
155 }
156 }
157}
158
159pub struct ToolRegistryBuilder {
161 tools: Vec<Arc<dyn Tool>>,
162 by_gana: HashMap<Gana, Vec<usize>>,
163 by_name: HashMap<String, usize>,
164}
165
166impl ToolRegistryBuilder {
167 #[must_use]
169 pub fn new() -> Self {
170 Self {
171 tools: vec![],
172 by_gana: HashMap::new(),
173 by_name: HashMap::new(),
174 }
175 }
176
177 pub fn register(&mut self, tool: Arc<dyn Tool>) -> &mut Self {
183 let idx = self.tools.len();
184 let gana = tool.gana();
185 let name = tool.name().to_string();
186 if self.by_name.contains_key(&name) {
187 tracing::warn!(
188 tool_name = %name,
189 "Duplicate tool registration — new tool will shadow existing one"
190 );
191 }
192 self.by_gana.entry(gana).or_default().push(idx);
193 self.by_name.insert(name, idx);
194 self.tools.push(tool);
195 self
196 }
197
198 #[must_use]
200 pub fn build(self) -> ToolRegistry {
201 ToolRegistry {
202 inner: Arc::new(RegistryInner {
203 tools: self.tools,
204 by_gana: self.by_gana,
205 by_name: self.by_name,
206 }),
207 }
208 }
209}
210
211impl Default for ToolRegistryBuilder {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use wm_core::{EffectRow, ToolStats};
221
222 struct MockTool {
223 name: String,
224 gana: Gana,
225 effects: EffectRow,
226 stats: ToolStats,
227 }
228
229 #[async_trait]
230 impl Tool for MockTool {
231 fn name(&self) -> &str {
232 &self.name
233 }
234 fn gana(&self) -> Gana {
235 self.gana
236 }
237 fn effects(&self) -> &EffectRow {
238 &self.effects
239 }
240 fn stats(&self) -> &ToolStats {
241 &self.stats
242 }
243 async fn call(
244 &self,
245 _ctx: &mut wm_core::Context,
246 _args: wm_core::Args,
247 ) -> wm_core::Result<wm_core::Output> {
248 Ok(serde_json::json!({"ok": true}))
249 }
250 }
251
252 fn make_tool(name: &str) -> Arc<dyn Tool> {
253 Arc::new(MockTool {
254 name: name.into(),
255 gana: Gana::Horn,
256 effects: EffectRow::default(),
257 stats: ToolStats::default(),
258 })
259 }
260
261 #[test]
262 fn duplicate_registration_shadows_and_warns() {
263 let mut builder = ToolRegistryBuilder::new();
264 builder.register(make_tool("dup"));
265 builder.register(make_tool("dup"));
266
267 let registry = builder.build();
268 let tool = registry.get("dup").unwrap();
269 assert_eq!(tool.name(), "dup");
270 }
271
272 #[test]
273 fn unique_registration_no_warning() {
274 let mut builder = ToolRegistryBuilder::new();
275 builder.register(make_tool("tool_a"));
276 builder.register(make_tool("tool_b"));
277
278 let registry = builder.build();
279 assert!(registry.get("tool_a").is_some());
280 assert!(registry.get("tool_b").is_some());
281 }
282}