dampen_core/handler/
mod.rs

1//! Handler system for event dispatch
2
3use std::any::Any;
4use std::collections::{HashMap, HashSet};
5use std::sync::{Arc, RwLock};
6
7/// Registry of event handlers
8#[derive(Clone, Debug)]
9pub struct HandlerRegistry {
10    handlers: Arc<RwLock<HashMap<String, HandlerEntry>>>,
11}
12
13/// Entry in the handler registry
14#[derive(Clone)]
15#[allow(clippy::type_complexity)]
16pub enum HandlerEntry {
17    // ============================================
18    // Existing variants (unchanged for compatibility)
19    // ============================================
20    /// Simple handler: `fn(&mut Model)`
21    Simple(Arc<dyn Fn(&mut dyn Any) + Send + Sync>),
22
23    /// Handler with value: `fn(&mut Model, T)`
24    WithValue(Arc<dyn Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync>),
25
26    /// Handler returning command: `fn(&mut Model) -> Command<Message>`
27    WithCommand(Arc<dyn Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync>),
28
29    // ============================================
30    // New variants for shared state access
31    // ============================================
32    /// Handler with shared context: `fn(&mut Model, &SharedContext<S>)`
33    ///
34    /// Use when the handler needs to read or write shared state.
35    WithShared(Arc<dyn Fn(&mut dyn Any, &dyn Any) + Send + Sync>),
36
37    /// Handler with value and shared: `fn(&mut Model, T, &SharedContext<S>)`
38    ///
39    /// Use when the handler receives input value and needs shared state.
40    WithValueAndShared(Arc<dyn Fn(&mut dyn Any, Box<dyn Any>, &dyn Any) + Send + Sync>),
41
42    /// Handler with command and shared: `fn(&mut Model, &SharedContext<S>) -> Command`
43    ///
44    /// Use when the handler needs shared state and returns a command.
45    WithCommandAndShared(Arc<dyn Fn(&mut dyn Any, &dyn Any) -> Box<dyn Any> + Send + Sync>),
46}
47
48impl std::fmt::Debug for HandlerEntry {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            HandlerEntry::Simple(_) => f.write_str("Simple(handler)"),
52            HandlerEntry::WithValue(_) => f.write_str("WithValue(handler)"),
53            HandlerEntry::WithCommand(_) => f.write_str("WithCommand(handler)"),
54            HandlerEntry::WithShared(_) => f.write_str("WithShared(handler)"),
55            HandlerEntry::WithValueAndShared(_) => f.write_str("WithValueAndShared(handler)"),
56            HandlerEntry::WithCommandAndShared(_) => f.write_str("WithCommandAndShared(handler)"),
57        }
58    }
59}
60
61impl HandlerRegistry {
62    /// Create a new empty handler registry
63    pub fn new() -> Self {
64        Self {
65            handlers: Arc::new(RwLock::new(HashMap::new())),
66        }
67    }
68
69    /// Register a simple handler
70    pub fn register_simple<F>(&self, name: &str, handler: F)
71    where
72        F: Fn(&mut dyn Any) + Send + Sync + 'static,
73    {
74        if let Ok(mut handlers) = self.handlers.write() {
75            handlers.insert(name.to_string(), HandlerEntry::Simple(Arc::new(handler)));
76        }
77    }
78
79    /// Register a handler with a value parameter
80    pub fn register_with_value<F>(&self, name: &str, handler: F)
81    where
82        F: Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync + 'static,
83    {
84        if let Ok(mut handlers) = self.handlers.write() {
85            handlers.insert(name.to_string(), HandlerEntry::WithValue(Arc::new(handler)));
86        }
87    }
88
89    /// Register a handler that returns a command
90    pub fn register_with_command<F>(&self, name: &str, handler: F)
91    where
92        F: Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync + 'static,
93    {
94        if let Ok(mut handlers) = self.handlers.write() {
95            handlers.insert(
96                name.to_string(),
97                HandlerEntry::WithCommand(Arc::new(handler)),
98            );
99        }
100    }
101
102    // ============================================
103    // New registration methods for shared state
104    // ============================================
105
106    /// Register a handler that receives shared context.
107    ///
108    /// Use this for handlers that need to read or modify shared state
109    /// that is accessible across multiple views.
110    ///
111    /// # Arguments
112    ///
113    /// * `name` - Handler name (referenced in XML `on_click="name"`)
114    /// * `handler` - Function that receives `(&mut Model, &SharedContext<S>)`
115    ///
116    /// # Example
117    ///
118    /// ```rust,ignore
119    /// use dampen_core::HandlerRegistry;
120    ///
121    /// let registry = HandlerRegistry::new();
122    /// registry.register_with_shared("update_theme", |model, shared| {
123    ///     let model = model.downcast_mut::<Model>().unwrap();
124    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
125    ///     shared.write().theme = model.selected_theme.clone();
126    /// });
127    /// ```
128    pub fn register_with_shared<F>(&self, name: &str, handler: F)
129    where
130        F: Fn(&mut dyn Any, &dyn Any) + Send + Sync + 'static,
131    {
132        if let Ok(mut handlers) = self.handlers.write() {
133            handlers.insert(
134                name.to_string(),
135                HandlerEntry::WithShared(Arc::new(handler)),
136            );
137        }
138    }
139
140    /// Register a handler with both a value parameter and shared context.
141    ///
142    /// Use this for handlers that receive input (like text field values)
143    /// and also need access to shared state.
144    ///
145    /// # Arguments
146    ///
147    /// * `name` - Handler name (referenced in XML `on_change="name"`)
148    /// * `handler` - Function that receives `(&mut Model, Box<dyn Any>, &SharedContext<S>)`
149    ///
150    /// # Example
151    ///
152    /// ```rust,ignore
153    /// use dampen_core::HandlerRegistry;
154    ///
155    /// let registry = HandlerRegistry::new();
156    /// registry.register_with_value_and_shared("set_username", |model, value, shared| {
157    ///     let model = model.downcast_mut::<Model>().unwrap();
158    ///     let name = value.downcast_ref::<String>().unwrap();
159    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
160    ///     shared.write().username = name.clone();
161    /// });
162    /// ```
163    pub fn register_with_value_and_shared<F>(&self, name: &str, handler: F)
164    where
165        F: Fn(&mut dyn Any, Box<dyn Any>, &dyn Any) + Send + Sync + 'static,
166    {
167        if let Ok(mut handlers) = self.handlers.write() {
168            handlers.insert(
169                name.to_string(),
170                HandlerEntry::WithValueAndShared(Arc::new(handler)),
171            );
172        }
173    }
174
175    /// Register a handler that receives shared context and returns a command.
176    ///
177    /// Use this for async handlers that need shared state access.
178    ///
179    /// # Arguments
180    ///
181    /// * `name` - Handler name (referenced in XML `on_click="name"`)
182    /// * `handler` - Function that receives `(&mut Model, &SharedContext<S>) -> Command`
183    ///
184    /// # Example
185    ///
186    /// ```rust,ignore
187    /// use dampen_core::HandlerRegistry;
188    ///
189    /// let registry = HandlerRegistry::new();
190    /// registry.register_with_command_and_shared("sync_settings", |model, shared| {
191    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
192    ///     let settings = shared.read().clone();
193    ///     Box::new(Task::perform(save_settings(settings), Message::SettingsSaved))
194    /// });
195    /// ```
196    pub fn register_with_command_and_shared<F>(&self, name: &str, handler: F)
197    where
198        F: Fn(&mut dyn Any, &dyn Any) -> Box<dyn Any> + Send + Sync + 'static,
199    {
200        if let Ok(mut handlers) = self.handlers.write() {
201            handlers.insert(
202                name.to_string(),
203                HandlerEntry::WithCommandAndShared(Arc::new(handler)),
204            );
205        }
206    }
207
208    /// Look up a handler by name
209    pub fn get(&self, name: &str) -> Option<HandlerEntry> {
210        self.handlers.read().ok()?.get(name).cloned()
211    }
212
213    /// Dispatches a handler by name, executing it with the provided model and optional value.
214    ///
215    /// This is a convenience method that combines `get()` and handler invocation.
216    /// For handlers that require shared state, use [`dispatch_with_shared`](Self::dispatch_with_shared)
217    /// instead.
218    ///
219    /// # Arguments
220    ///
221    /// * `handler_name` - Name of the handler to dispatch
222    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
223    /// * `value` - Optional string value passed to WithValue handlers
224    ///
225    /// # Note
226    ///
227    /// This method does NOT support `WithShared`, `WithValueAndShared`, or `WithCommandAndShared`
228    /// handlers. Those handlers will be silently ignored. Use `dispatch_with_shared` instead.
229    ///
230    /// # Example
231    ///
232    /// ```rust,ignore
233    /// use dampen_core::HandlerRegistry;
234    ///
235    /// let registry = HandlerRegistry::new();
236    /// registry.register_simple("greet", |model| {
237    ///     let model = model.downcast_mut::<MyModel>().unwrap();
238    ///     model.count += 1;
239    /// });
240    ///
241    /// let model = &mut MyModel { count: 0 } as &mut dyn std::any::Any;
242    /// registry.dispatch("greet", model, None);
243    /// ```
244    pub fn dispatch(&self, handler_name: &str, model: &mut dyn Any, value: Option<String>) {
245        if let Some(entry) = self.get(handler_name) {
246            match entry {
247                HandlerEntry::Simple(h) => h(model),
248                HandlerEntry::WithValue(h) => {
249                    let val = value.unwrap_or_default();
250                    h(model, Box::new(val));
251                }
252                HandlerEntry::WithCommand(h) => {
253                    h(model);
254                }
255                // Shared handlers require shared context - silently skip in dispatch()
256                HandlerEntry::WithShared(_)
257                | HandlerEntry::WithValueAndShared(_)
258                | HandlerEntry::WithCommandAndShared(_) => {
259                    // These handlers require shared context. Use dispatch_with_shared() instead.
260                }
261            }
262        }
263    }
264
265    /// Dispatches a handler by name and returns any command/task it produces.
266    ///
267    /// This method is similar to `dispatch()` but returns the command/task from
268    /// `WithCommand` handlers instead of discarding it. This is essential for
269    /// integrating with the Elm/MVU pattern where handlers can return tasks.
270    ///
271    /// For handlers that require shared state, use [`dispatch_with_shared`](Self::dispatch_with_shared)
272    /// instead.
273    ///
274    /// # Arguments
275    ///
276    /// * `handler_name` - Name of the handler to dispatch
277    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
278    /// * `value` - Optional string value passed to WithValue handlers
279    ///
280    /// # Returns
281    ///
282    /// * `Some(Box<dyn Any>)` - The command/task from a `WithCommand` handler
283    /// * `None` - For `Simple` and `WithValue` handlers, or if handler not found
284    ///
285    /// # Note
286    ///
287    /// This method does NOT support shared handlers. Use `dispatch_with_shared` instead.
288    ///
289    /// # Example
290    ///
291    /// ```rust,ignore
292    /// use dampen_core::HandlerRegistry;
293    /// use iced::Task;
294    ///
295    /// let registry = HandlerRegistry::new();
296    /// registry.register_with_command("navigate", |model| {
297    ///     let model = model.downcast_mut::<MyModel>().unwrap();
298    ///     Box::new(Task::done(Message::SwitchView))
299    /// });
300    ///
301    /// let model = &mut MyModel::default() as &mut dyn std::any::Any;
302    /// if let Some(boxed_task) = registry.dispatch_with_command("navigate", model, None) {
303    ///     if let Ok(task) = boxed_task.downcast::<Task<Message>>() {
304    ///         return *task;
305    ///     }
306    /// }
307    /// ```
308    pub fn dispatch_with_command(
309        &self,
310        handler_name: &str,
311        model: &mut dyn Any,
312        value: Option<String>,
313    ) -> Option<Box<dyn Any>> {
314        if let Some(entry) = self.get(handler_name) {
315            match entry {
316                HandlerEntry::Simple(h) => {
317                    h(model);
318                    None
319                }
320                HandlerEntry::WithValue(h) => {
321                    let val = value.unwrap_or_default();
322                    h(model, Box::new(val));
323                    None
324                }
325                HandlerEntry::WithCommand(h) => Some(h(model)),
326                // Shared handlers require shared context - not supported here
327                HandlerEntry::WithShared(_)
328                | HandlerEntry::WithValueAndShared(_)
329                | HandlerEntry::WithCommandAndShared(_) => None,
330            }
331        } else {
332            None
333        }
334    }
335
336    /// Dispatches a handler with shared context and returns any command it produces.
337    ///
338    /// This is the primary dispatch method for applications using shared state.
339    /// It handles all handler variants, passing the shared context to variants
340    /// that expect it.
341    ///
342    /// # Arguments
343    ///
344    /// * `handler_name` - Name of the handler to dispatch
345    /// * `model` - Mutable reference to the local model (as `&mut dyn Any`)
346    /// * `shared` - Reference to the shared context (as `&dyn Any`)
347    /// * `value` - Optional string value passed to WithValue/WithValueAndShared handlers
348    ///
349    /// # Returns
350    ///
351    /// * `Some(Box<dyn Any>)` - The command from `WithCommand` or `WithCommandAndShared` handlers
352    /// * `None` - For simple handlers, value handlers, or if handler not found
353    ///
354    /// # Example
355    ///
356    /// ```rust,ignore
357    /// use dampen_core::HandlerRegistry;
358    ///
359    /// let registry = HandlerRegistry::new();
360    /// registry.register_with_shared("toggle_theme", |model, shared| {
361    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
362    ///     let current = shared.read().dark_mode;
363    ///     shared.write().dark_mode = !current;
364    /// });
365    ///
366    /// let model = &mut Model::default() as &mut dyn std::any::Any;
367    /// let shared = &shared_context as &dyn std::any::Any;
368    /// registry.dispatch_with_shared("toggle_theme", model, shared, None);
369    /// ```
370    pub fn dispatch_with_shared(
371        &self,
372        handler_name: &str,
373        model: &mut dyn Any,
374        shared: &dyn Any,
375        value: Option<String>,
376    ) -> Option<Box<dyn Any>> {
377        let entry = self.get(handler_name)?;
378
379        match entry {
380            // Existing variants (backward compatible - ignore shared)
381            HandlerEntry::Simple(h) => {
382                h(model);
383                None
384            }
385            HandlerEntry::WithValue(h) => {
386                h(model, Box::new(value.unwrap_or_default()));
387                None
388            }
389            HandlerEntry::WithCommand(h) => Some(h(model)),
390
391            // New shared variants
392            HandlerEntry::WithShared(h) => {
393                h(model, shared);
394                None
395            }
396            HandlerEntry::WithValueAndShared(h) => {
397                h(model, Box::new(value.unwrap_or_default()), shared);
398                None
399            }
400            HandlerEntry::WithCommandAndShared(h) => Some(h(model, shared)),
401        }
402    }
403
404    /// Check if a handler exists
405    pub fn contains(&self, name: &str) -> bool {
406        if let Ok(handlers) = self.handlers.read() {
407            handlers.contains_key(name)
408        } else {
409            false
410        }
411    }
412}
413
414impl Default for HandlerRegistry {
415    fn default() -> Self {
416        Self::new()
417    }
418}
419
420/// Handler metadata for compile-time validation
421#[derive(Debug, Clone, PartialEq)]
422pub struct HandlerSignature {
423    /// Handler name
424    pub name: String,
425
426    /// Parameter type if applicable
427    pub param_type: Option<String>,
428
429    /// Whether handler returns Command
430    pub returns_command: bool,
431}
432
433/// Build-time analysis structure for circular dependency detection
434///
435/// **Feature T130 - Not Yet Integrated**
436///
437/// This structure is used for static analysis of handler dependencies to detect
438/// circular call chains at compile time. It is not yet integrated into the
439/// handler registration or dispatch system.
440///
441/// # Example
442///
443/// ```rust,ignore
444/// use dampen_core::handler::HandlerCallGraph;
445///
446/// let mut graph = HandlerCallGraph::new();
447/// graph.add_dependency("handler_a", "handler_b");
448/// graph.add_dependency("handler_b", "handler_c");
449///
450/// if let Some(cycle) = graph.detect_cycles() {
451///     println!("Circular dependency detected: {:?}", cycle);
452/// }
453/// ```
454#[allow(dead_code)]
455#[derive(Debug, Clone)]
456pub struct HandlerCallGraph {
457    /// Map of handler name to its dependencies
458    dependencies: HashMap<String, Vec<String>>,
459}
460
461impl HandlerCallGraph {
462    /// Create a new empty call graph
463    pub fn new() -> Self {
464        Self {
465            dependencies: HashMap::new(),
466        }
467    }
468
469    /// Add a dependency edge (from depends on to)
470    pub fn add_dependency(&mut self, from: &str, to: &str) {
471        self.dependencies
472            .entry(from.to_string())
473            .or_default()
474            .push(to.to_string());
475    }
476
477    /// Detect if adding edge would create a cycle
478    pub fn would_create_cycle(&self, from: &str, to: &str) -> bool {
479        // Check if 'to' can reach 'from' (which would create a cycle)
480        let mut visited = HashSet::new();
481        self.can_reach(to, from, &mut visited)
482    }
483
484    /// Check if 'from' can reach 'to' via dependencies
485    fn can_reach(&self, from: &str, to: &str, visited: &mut HashSet<String>) -> bool {
486        if from == to {
487            return true;
488        }
489
490        if visited.contains(from) {
491            return false;
492        }
493
494        visited.insert(from.to_string());
495
496        if let Some(deps) = self.dependencies.get(from) {
497            for dep in deps {
498                if self.can_reach(dep, to, visited) {
499                    return true;
500                }
501            }
502        }
503
504        false
505    }
506
507    /// Get all handlers that depend on the given handler
508    pub fn dependents_of(&self, handler: &str) -> Vec<String> {
509        self.dependencies
510            .iter()
511            .filter_map(|(k, v)| {
512                if v.contains(&handler.to_string()) {
513                    Some(k.clone())
514                } else {
515                    None
516                }
517            })
518            .collect()
519    }
520
521    /// Detect cycles in the call graph using DFS
522    pub fn detect_cycles(&self) -> Option<Vec<String>> {
523        let mut visited = HashSet::new();
524        let mut recursion_stack = HashSet::new();
525        let mut path = Vec::new();
526
527        for handler in self.dependencies.keys() {
528            if !visited.contains(handler) {
529                if let Some(cycle) =
530                    self.dfs_detect_cycle(handler, &mut visited, &mut recursion_stack, &mut path)
531                {
532                    return Some(cycle);
533                }
534            }
535        }
536
537        None
538    }
539
540    fn dfs_detect_cycle(
541        &self,
542        handler: &str,
543        visited: &mut HashSet<String>,
544        recursion_stack: &mut HashSet<String>,
545        path: &mut Vec<String>,
546    ) -> Option<Vec<String>> {
547        visited.insert(handler.to_string());
548        recursion_stack.insert(handler.to_string());
549        path.push(handler.to_string());
550
551        if let Some(deps) = self.dependencies.get(handler) {
552            for dep in deps {
553                if !visited.contains(dep) {
554                    if let Some(cycle) = self.dfs_detect_cycle(dep, visited, recursion_stack, path)
555                    {
556                        return Some(cycle);
557                    }
558                } else if recursion_stack.contains(dep) {
559                    // Found a cycle - construct the cycle path
560                    if let Some(cycle_start) = path.iter().position(|h| h == dep) {
561                        let mut cycle = path[cycle_start..].to_vec();
562                        cycle.push(dep.to_string());
563                        return Some(cycle);
564                    }
565                }
566            }
567        }
568
569        path.pop();
570        recursion_stack.remove(handler);
571        None
572    }
573}
574
575impl Default for HandlerCallGraph {
576    fn default() -> Self {
577        Self::new()
578    }
579}