dampen_core/codegen/
mod.rs

1//! Code generation for production builds
2//!
3//! This module generates static Rust code from Dampen UI definitions.
4//! The generated code has zero runtime overhead compared to hand-written Iced code.
5//!
6//! # Overview
7//!
8//! Code generation is used in production mode to eliminate runtime parsing:
9//! 1. Parse `.dampen` files at build time
10//! 2. Generate Rust code with `generate_application()`
11//! 3. Include generated code via `include!(concat!(env!("OUT_DIR"), "/ui_generated.rs"))`
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! // At build time:
17//! let doc = parse(ui_xml)?;
18//! let output = generate_application(&doc, "Model", "Message", &handlers)?;
19//! fs::write("ui_generated.rs", output.code)?;
20//!
21//! // At runtime:
22//! include!("ui_generated.rs");
23//! // No XML parsing, no runtime overhead!
24//! ```
25//!
26//! # Generated Code Structure
27//!
28//! The generated code includes:
29//! - Message enum from handler signatures
30//! - `impl Application for Model` with `view()` and `update()` methods
31//! - Inlined widget tree with evaluated bindings
32
33pub mod application;
34pub mod bindings;
35pub mod config;
36pub mod handlers;
37pub mod inventory;
38pub mod status_mapping;
39pub mod subscription;
40pub mod theme;
41pub mod update;
42pub mod view;
43
44use crate::DampenDocument;
45use crate::HandlerSignature;
46use proc_macro2::TokenStream;
47use quote::quote;
48use std::path::PathBuf;
49use std::time::SystemTime;
50
51/// Handler signature classification for code generation
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum HandlerSignatureType {
54    /// fn(&mut Model) -> ()
55    /// Simple handler with no additional parameters
56    Simple,
57
58    /// fn(&mut Model, T) -> ()
59    /// Handler that receives a value from the UI element
60    WithValue,
61
62    /// fn(&mut Model) -> `Command<Message>`
63    /// Handler that returns a command for side effects
64    WithCommand,
65}
66
67/// Metadata structure emitted by `#[ui_handler]` macro for build-time registration
68#[derive(Debug, Clone)]
69pub struct HandlerInfo {
70    /// Unique handler name referenced in XML
71    pub name: &'static str,
72
73    /// Handler signature classification
74    pub signature_type: HandlerSignatureType,
75
76    /// Parameter type names for validation
77    pub param_types: &'static [&'static str],
78
79    /// Return type name
80    pub return_type: &'static str,
81
82    /// Source file location for error messages
83    pub source_file: &'static str,
84
85    /// Source line number
86    pub source_line: u32,
87}
88
89impl HandlerInfo {
90    /// Convert HandlerInfo to HandlerSignature for code generation.
91    ///
92    /// This bridges the metadata emitted by `#[ui_handler]` with the signature
93    /// format expected by the code generator.
94    ///
95    /// # Returns
96    ///
97    /// A HandlerSignature suitable for use with generate_application()
98    pub fn to_signature(&self) -> HandlerSignature {
99        // Determine param_type based on signature type
100        let param_type = match self.signature_type {
101            HandlerSignatureType::Simple => None,
102            HandlerSignatureType::WithValue => {
103                // Extract the second parameter type (first is &mut Model)
104                if self.param_types.len() > 1 {
105                    Some(self.param_types[1].to_string())
106                } else {
107                    None
108                }
109            }
110            HandlerSignatureType::WithCommand => None,
111        };
112
113        let returns_command = matches!(self.signature_type, HandlerSignatureType::WithCommand);
114
115        HandlerSignature {
116            name: self.name.to_string(),
117            param_type,
118            returns_command,
119        }
120    }
121}
122
123/// Result of code generation
124///
125/// Contains the generated Rust code and any warnings or notes.
126#[derive(Debug)]
127pub struct CodegenOutput {
128    /// Generated Rust code
129    pub code: String,
130    /// Any warnings or notes
131    pub warnings: Vec<String>,
132}
133
134/// Output structure from code generation
135#[derive(Debug, Clone)]
136pub struct GeneratedApplication {
137    /// Generated Rust code as string
138    pub code: String,
139
140    /// List of handler names discovered
141    pub handlers: Vec<String>,
142
143    /// List of widget types used
144    pub widgets: Vec<String>,
145
146    /// Any warnings generated during code gen
147    pub warnings: Vec<String>,
148}
149
150/// Container for generated Rust code with metadata
151///
152/// This structure holds generated code along with metadata about the source
153/// file and validation status. It provides methods for validating and formatting
154/// the generated code.
155#[derive(Debug, Clone)]
156pub struct GeneratedCode {
157    /// Generated Rust source code
158    pub code: String,
159
160    /// Module path (e.g., "ui_window")
161    pub module_name: String,
162
163    /// Source .dampen file path
164    pub source_file: PathBuf,
165
166    /// Generated at timestamp
167    pub timestamp: SystemTime,
168
169    /// Validation status
170    pub validated: bool,
171}
172
173impl GeneratedCode {
174    /// Create a new GeneratedCode instance
175    ///
176    /// # Arguments
177    /// * `code` - The generated Rust source code
178    /// * `module_name` - Module name (e.g., "ui_window")
179    /// * `source_file` - Path to the source .dampen file
180    ///
181    /// # Returns
182    /// A new GeneratedCode instance with validated set to false
183    pub fn new(code: String, module_name: String, source_file: PathBuf) -> Self {
184        Self {
185            code,
186            module_name,
187            source_file,
188            timestamp: SystemTime::now(),
189            validated: false,
190        }
191    }
192
193    /// Validate syntax by parsing with syn
194    ///
195    /// # Returns
196    /// Ok(()) if the code is valid Rust, Err with message otherwise
197    pub fn validate(&mut self) -> Result<(), String> {
198        match syn::parse_file(&self.code) {
199            Ok(_) => {
200                self.validated = true;
201                Ok(())
202            }
203            Err(e) => Err(format!("Syntax validation failed: {}", e)),
204        }
205    }
206
207    /// Format code with prettyplease
208    ///
209    /// # Returns
210    /// Ok(()) if formatting succeeded, Err with message otherwise
211    pub fn format(&mut self) -> Result<(), String> {
212        // First parse the code
213        match syn::parse_file(&self.code) {
214            Ok(syntax_tree) => {
215                // Format using prettyplease
216                self.code = prettyplease::unparse(&syntax_tree);
217                Ok(())
218            }
219            Err(e) => Err(format!("Failed to parse code for formatting: {}", e)),
220        }
221    }
222
223    /// Write to output directory
224    ///
225    /// # Arguments
226    /// * `path` - Path to write the generated code to
227    ///
228    /// # Returns
229    /// Ok(()) if write succeeded, Err with IO error otherwise
230    pub fn write_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
231        std::fs::write(path, &self.code)
232    }
233}
234
235/// Generate complete application code from a Dampen document
236///
237/// This is the main entry point for code generation. It orchestrates
238/// the generation of all necessary components.
239///
240/// # Arguments
241///
242/// * `document` - Parsed Dampen document
243/// * `model_name` - Name of the model struct (e.g., "Model")
244/// * `message_name` - Name of the message enum (e.g., "Message")
245/// * `handlers` - List of handler signatures
246///
247/// # Returns
248///
249/// `Ok(CodegenOutput)` with generated code and warnings
250///
251/// # Errors
252///
253/// Returns `CodegenError` if:
254/// - Handler validation fails
255/// - Widget generation fails
256pub fn generate_application(
257    document: &DampenDocument,
258    model_name: &str,
259    message_name: &str,
260    handlers: &[HandlerSignature],
261) -> Result<CodegenOutput, CodegenError> {
262    let warnings = Vec::new();
263
264    let message_enum = generate_message_enum(handlers);
265
266    let view_fn = view::generate_view(document, model_name, message_name)?;
267
268    let update_arms = update::generate_arms(handlers, message_name)?;
269
270    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
271    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
272
273    let combined = quote! {
274        use iced::{Element, Task};
275        use crate::ui::window::*;
276
277        #message_enum
278
279        pub fn new_model() -> (#model_ident, Task<#message_ident>) {
280            (#model_ident::default(), Task::none())
281        }
282
283        pub fn update_model(model: &mut #model_ident, message: #message_ident) -> Task<#message_ident> {
284            match message {
285                #update_arms
286            }
287        }
288
289        pub fn view_model(model: &#model_ident) -> Element<'_, #message_ident> {
290            #view_fn
291        }
292    };
293
294    Ok(CodegenOutput {
295        code: combined.to_string(),
296        warnings,
297    })
298}
299
300use crate::ir::theme::ThemeDocument;
301pub use config::PersistenceConfig;
302
303/// Generate complete application code with theme and subscription support
304///
305/// This is the full-featured entry point for code generation that includes:
306/// - Message enum with system theme variant (if follow_system is enabled)
307/// - Subscription function for system theme detection
308/// - Theme code generation
309/// - View and update functions
310///
311/// # Arguments
312///
313/// * `document` - Parsed Dampen document
314/// * `model_name` - Name of the model struct (e.g., "Model")
315/// * `message_name` - Name of the message enum (e.g., "Message")
316/// * `handlers` - List of handler signatures
317/// * `theme_document` - Optional theme document for theme code generation
318///
319/// # Returns
320///
321/// `Ok(CodegenOutput)` with generated code and warnings
322pub fn generate_application_with_theme_and_subscriptions(
323    document: &DampenDocument,
324    model_name: &str,
325    message_name: &str,
326    handlers: &[HandlerSignature],
327    theme_document: Option<&ThemeDocument>,
328) -> Result<CodegenOutput, CodegenError> {
329    let warnings = Vec::new();
330
331    // Create subscription config from theme document
332    let sub_config =
333        subscription::SubscriptionConfig::from_theme_document(theme_document, message_name);
334
335    // Generate message enum with system theme variant if needed
336    let message_enum = generate_message_enum_with_subscription(handlers, Some(&sub_config));
337
338    let view_fn = view::generate_view(document, model_name, message_name)?;
339
340    let update_arms = update::generate_arms(handlers, message_name)?;
341
342    // Generate system theme update arm if needed
343    let system_theme_arm = subscription::generate_system_theme_update_arm(&sub_config);
344
345    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
346    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
347
348    // Generate theme code if theme document is provided
349    let theme_code = if let Some(theme_doc) = theme_document {
350        match theme::generate_theme_code(theme_doc, &document.style_classes, "app") {
351            Ok(generated) => {
352                let code_str = generated.code;
353                syn::parse_str::<TokenStream>(&code_str).unwrap_or_default()
354            }
355            Err(e) => {
356                return Err(CodegenError::ThemeError(e));
357            }
358        }
359    } else {
360        TokenStream::new()
361    };
362
363    // Generate subscription function
364    let subscription_fn = subscription::generate_subscription_function(&sub_config);
365
366    let has_theme = theme_document.is_some();
367    let theme_method = if has_theme {
368        quote! {
369            pub fn theme(_model: &#model_ident) -> iced::Theme {
370                app_theme()
371            }
372        }
373    } else {
374        quote! {}
375    };
376
377    let combined = quote! {
378        use iced::{Element, Task, Theme};
379        use crate::ui::window::*;
380        use std::collections::HashMap;
381
382        #theme_code
383
384        #message_enum
385
386        pub fn new_model() -> (#model_ident, Task<#message_ident>) {
387            (#model_ident::default(), Task::none())
388        }
389
390        pub fn update_model(model: &mut #model_ident, message: #message_ident) -> Task<#message_ident> {
391            match message {
392                #update_arms
393                #system_theme_arm
394            }
395        }
396
397        pub fn view_model(model: &#model_ident) -> Element<'_, #message_ident> {
398            #view_fn
399        }
400
401        #theme_method
402
403        #subscription_fn
404    };
405
406    Ok(CodegenOutput {
407        code: combined.to_string(),
408        warnings,
409    })
410}
411
412/// Generate complete application code with theme, subscription, and persistence support
413///
414/// This is the most complete entry point for code generation that includes:
415/// - Message enum with system theme variant (if follow_system is enabled)
416/// - Subscription function for system theme detection
417/// - Theme code generation
418/// - View and update functions
419/// - Window settings function for persistence (if persistence config is provided)
420/// - Full window event handling for persistence (tracking size, position, save on close)
421///
422/// # Arguments
423///
424/// * `document` - Parsed Dampen document
425/// * `model_name` - Name of the model struct (e.g., "Model")
426/// * `message_name` - Name of the message enum (e.g., "Message")
427/// * `handlers` - List of handler signatures
428/// * `theme_document` - Optional theme document for theme code generation
429/// * `persistence` - Optional persistence configuration for window state
430///
431/// # Returns
432///
433/// `Ok(CodegenOutput)` with generated code and warnings
434pub fn generate_application_full(
435    document: &DampenDocument,
436    model_name: &str,
437    message_name: &str,
438    handlers: &[HandlerSignature],
439    theme_document: Option<&ThemeDocument>,
440    persistence: Option<&PersistenceConfig>,
441) -> Result<CodegenOutput, CodegenError> {
442    let warnings = Vec::new();
443
444    // Create subscription config from theme document
445    let sub_config =
446        subscription::SubscriptionConfig::from_theme_document(theme_document, message_name);
447
448    // Determine if we need window events (for persistence)
449    let has_persistence = persistence.is_some();
450
451    // Generate message enum with system theme variant and window events if needed
452    let message_enum = generate_message_enum_full(handlers, Some(&sub_config), has_persistence);
453
454    let view_fn = view::generate_view(document, model_name, message_name)?;
455
456    let update_arms = update::generate_arms(handlers, message_name)?;
457
458    // Generate system theme update arm if needed
459    let system_theme_arm = subscription::generate_system_theme_update_arm(&sub_config);
460
461    let model_ident = syn::Ident::new(model_name, proc_macro2::Span::call_site());
462    let message_ident = syn::Ident::new(message_name, proc_macro2::Span::call_site());
463
464    // Generate theme code if theme document is provided
465    let theme_code = if let Some(theme_doc) = theme_document {
466        match theme::generate_theme_code(theme_doc, &document.style_classes, "app") {
467            Ok(generated) => {
468                let code_str = generated.code;
469                syn::parse_str::<TokenStream>(&code_str).unwrap_or_default()
470            }
471            Err(e) => {
472                return Err(CodegenError::ThemeError(e));
473            }
474        }
475    } else {
476        TokenStream::new()
477    };
478
479    let has_theme = theme_document.is_some();
480    let theme_method = if has_theme {
481        quote! {
482            pub fn theme(_model: &AppModel) -> iced::Theme {
483                app_theme()
484            }
485        }
486    } else {
487        quote! {}
488    };
489
490    // Generate persistence-related code
491    let (
492        wrapper_struct,
493        new_model_fn,
494        update_model_fn,
495        view_model_fn,
496        subscription_fn,
497        window_settings_fn,
498    ) = if let Some(config) = persistence {
499        let app_name = &config.app_name;
500
501        // Generate wrapper struct that includes persisted_window_state
502        let wrapper = quote! {
503            /// Application model wrapper with persistence state
504            pub struct AppModel {
505                /// User's model
506                pub inner: #model_ident,
507                /// Persisted window state for saving on close
508                persisted_window_state: dampen_dev::persistence::WindowState,
509            }
510        };
511
512        // Generate new_model that initializes the wrapper
513        let new_model = quote! {
514            pub fn new_model() -> (AppModel, Task<#message_ident>) {
515                let persisted_state = dampen_dev::persistence::load_or_default(#app_name, 800, 600);
516                (
517                    AppModel {
518                        inner: #model_ident::default(),
519                        persisted_window_state: persisted_state,
520                    },
521                    Task::none(),
522                )
523            }
524        };
525
526        // Generate update arms that access model.inner for user handlers
527        let update_arms_inner = generate_update_arms_for_inner(handlers, message_name)?;
528
529        // Generate update_model with window event handling
530        let update_model = quote! {
531            pub fn update_model(model: &mut AppModel, message: #message_ident) -> Task<#message_ident> {
532                match message {
533                    #update_arms_inner
534                    #system_theme_arm
535                    #message_ident::Window(id, event) => {
536                        match event {
537                            iced::window::Event::Opened { .. } => {
538                                // Window is already created with correct size via window_settings().
539                                // Only need to maximize if that was the saved state.
540                                if model.persisted_window_state.maximized {
541                                    iced::window::maximize(id, true)
542                                } else {
543                                    Task::none()
544                                }
545                            }
546                            iced::window::Event::Resized(size) => {
547                                // Update persisted state with new size
548                                model.persisted_window_state.width = size.width as u32;
549                                model.persisted_window_state.height = size.height as u32;
550                                Task::none()
551                            }
552                            iced::window::Event::Moved(position) => {
553                                model.persisted_window_state.x = Some(position.x as i32);
554                                model.persisted_window_state.y = Some(position.y as i32);
555                                Task::none()
556                            }
557                            iced::window::Event::CloseRequested => {
558                                let _ = dampen_dev::persistence::save_window_state(
559                                    #app_name,
560                                    &model.persisted_window_state,
561                                );
562                                iced::window::close(id)
563                            }
564                            _ => Task::none(),
565                        }
566                    }
567                }
568            }
569        };
570
571        // Generate view_model that accesses inner model
572        // Note: view_fn uses `model` as the variable name, so we shadow it with the inner model
573        let view_model = quote! {
574            pub fn view_model(app_model: &AppModel) -> Element<'_, #message_ident> {
575                let model = &app_model.inner;
576                #view_fn
577            }
578        };
579
580        // Generate subscription with window events
581        let subscription = if sub_config.system_theme_variant.is_some() {
582            let variant_name = sub_config
583                .system_theme_variant
584                .as_ref()
585                .map(|v| syn::Ident::new(v, proc_macro2::Span::call_site()));
586            quote! {
587                pub fn subscription_model(_model: &AppModel) -> iced::Subscription<#message_ident> {
588                    let window_events = iced::window::events()
589                        .map(|(id, e)| #message_ident::Window(id, e));
590
591                    if app_follows_system() {
592                        let system_theme = dampen_iced::watch_system_theme()
593                            .map(#message_ident::#variant_name);
594                        iced::Subscription::batch(vec![window_events, system_theme])
595                    } else {
596                        window_events
597                    }
598                }
599            }
600        } else {
601            quote! {
602                pub fn subscription_model(_model: &AppModel) -> iced::Subscription<#message_ident> {
603                    iced::window::events()
604                        .map(|(id, e)| #message_ident::Window(id, e))
605                }
606            }
607        };
608
609        // Generate window_settings function
610        let window_settings = quote! {
611            /// Returns a WindowSettingsBuilder for configuring the initial window state.
612            ///
613            /// This function loads persisted window state if available, or uses defaults
614            /// for first launch.
615            ///
616            /// # Example
617            ///
618            /// ```ignore
619            /// iced::application(...)
620            ///     .window(window::window_settings()
621            ///         .default_size(800, 600)
622            ///         .min_size(400, 300)
623            ///         .build())
624            ///     .run()
625            /// ```
626            pub fn window_settings() -> dampen_dev::persistence::WindowSettingsBuilder {
627                dampen_dev::persistence::WindowSettingsBuilder::new(#app_name)
628            }
629        };
630
631        (
632            wrapper,
633            new_model,
634            update_model,
635            view_model,
636            subscription,
637            window_settings,
638        )
639    } else {
640        // No persistence - generate simpler code without wrapper
641        let wrapper = TokenStream::new();
642
643        let new_model = quote! {
644            pub fn new_model() -> (#model_ident, Task<#message_ident>) {
645                (#model_ident::default(), Task::none())
646            }
647        };
648
649        let update_model = quote! {
650            pub fn update_model(model: &mut #model_ident, message: #message_ident) -> Task<#message_ident> {
651                match message {
652                    #update_arms
653                    #system_theme_arm
654                }
655            }
656        };
657
658        let view_model = quote! {
659            pub fn view_model(model: &#model_ident) -> Element<'_, #message_ident> {
660                #view_fn
661            }
662        };
663
664        let subscription = subscription::generate_subscription_function(&sub_config);
665
666        let window_settings = TokenStream::new();
667
668        (
669            wrapper,
670            new_model,
671            update_model,
672            view_model,
673            subscription,
674            window_settings,
675        )
676    };
677
678    let combined = quote! {
679        use iced::{Element, Task, Theme};
680        use crate::ui::window::*;
681        use std::collections::HashMap;
682
683        #theme_code
684
685        #message_enum
686
687        #wrapper_struct
688
689        #new_model_fn
690
691        #update_model_fn
692
693        #view_model_fn
694
695        #theme_method
696
697        #subscription_fn
698
699        #window_settings_fn
700    };
701
702    Ok(CodegenOutput {
703        code: combined.to_string(),
704        warnings,
705    })
706}
707
708/// Generate Message enum from handler signatures
709fn generate_message_enum(handlers: &[HandlerSignature]) -> TokenStream {
710    generate_message_enum_with_subscription(handlers, None)
711}
712
713/// Generate Message enum from handler signatures with optional subscription config
714fn generate_message_enum_with_subscription(
715    handlers: &[HandlerSignature],
716    sub_config: Option<&subscription::SubscriptionConfig>,
717) -> TokenStream {
718    generate_message_enum_full(handlers, sub_config, false)
719}
720
721/// Generate Message enum with all optional variants
722fn generate_message_enum_full(
723    handlers: &[HandlerSignature],
724    sub_config: Option<&subscription::SubscriptionConfig>,
725    include_window_events: bool,
726) -> TokenStream {
727    let handler_variants: Vec<_> = handlers
728        .iter()
729        .map(|h| {
730            // Convert snake_case to UpperCamelCase
731            let variant_name = to_upper_camel_case(&h.name);
732            let ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
733
734            if let Some(param_type) = &h.param_type {
735                let type_ident = syn::Ident::new(param_type, proc_macro2::Span::call_site());
736                quote! { #ident(#type_ident) }
737            } else {
738                quote! { #ident }
739            }
740        })
741        .collect();
742
743    // Add system theme variant if configured
744    let system_theme_variant = sub_config.and_then(subscription::generate_system_theme_variant);
745
746    // Add window events variant if persistence is enabled
747    let window_variant = if include_window_events {
748        Some(quote! {
749            /// Window events for persistence
750            Window(iced::window::Id, iced::window::Event)
751        })
752    } else {
753        None
754    };
755
756    let all_variants: Vec<TokenStream> = handler_variants
757        .into_iter()
758        .chain(system_theme_variant)
759        .chain(window_variant)
760        .collect();
761
762    if all_variants.is_empty() {
763        return quote! {
764            #[derive(Clone, Debug)]
765            pub enum Message {}
766        };
767    }
768
769    quote! {
770        #[derive(Clone, Debug)]
771        pub enum Message {
772            #(#all_variants),*
773        }
774    }
775}
776
777/// Convert snake_case to UpperCamelCase
778fn to_upper_camel_case(s: &str) -> String {
779    let mut result = String::new();
780    let mut capitalize_next = true;
781    for c in s.chars() {
782        if c == '_' {
783            capitalize_next = true;
784        } else if capitalize_next {
785            result.push(c.to_ascii_uppercase());
786            capitalize_next = false;
787        } else {
788            result.push(c);
789        }
790    }
791    result
792}
793
794/// Generate update match arms that access `model.inner` instead of `model`
795///
796/// This is used when the model is wrapped in an AppModel struct for persistence.
797/// The handlers expect the user's model type, not the wrapper.
798fn generate_update_arms_for_inner(
799    handlers: &[HandlerSignature],
800    message_name: &str,
801) -> Result<TokenStream, CodegenError> {
802    use quote::format_ident;
803
804    let message_ident = format_ident!("{}", message_name);
805
806    let match_arms: Vec<TokenStream> = handlers
807        .iter()
808        .map(|handler| {
809            let handler_name = format_ident!("{}", handler.name);
810            let variant_name = to_upper_camel_case(&handler.name);
811            let variant_ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
812
813            if let Some(_param_type) = &handler.param_type {
814                quote! {
815                    #message_ident::#variant_ident(value) => {
816                        #handler_name(&mut model.inner, value);
817                        iced::Task::none()
818                    }
819                }
820            } else if handler.returns_command {
821                quote! {
822                    #message_ident::#variant_ident => {
823                        #handler_name(&mut model.inner)
824                    }
825                }
826            } else {
827                quote! {
828                    #message_ident::#variant_ident => {
829                        #handler_name(&mut model.inner);
830                        iced::Task::none()
831                    }
832                }
833            }
834        })
835        .collect();
836
837    Ok(quote! {
838        #(#match_arms)*
839    })
840}
841
842/// Basic constant folding optimizations for generated code
843///
844/// Performs simple optimizations:
845/// - Removes consecutive blank lines
846/// - Trims trailing whitespace from lines
847/// - Normalizes multiple spaces to single space
848pub fn constant_folding(code: &str) -> String {
849    let mut result = String::with_capacity(code.len());
850
851    for line in code.lines() {
852        let trimmed = line.trim_end();
853        if !trimmed.is_empty() {
854            result.push_str(trimmed);
855            result.push('\n');
856        }
857    }
858
859    result
860}
861
862/// Validate that all handlers referenced in the document exist
863pub fn validate_handlers(
864    document: &DampenDocument,
865    available_handlers: &[HandlerSignature],
866) -> Result<(), CodegenError> {
867    let handler_names: Vec<_> = available_handlers.iter().map(|h| h.name.clone()).collect();
868
869    // Collect all handler references from the document
870    fn collect_handlers(node: &crate::WidgetNode, handlers: &mut Vec<String>) {
871        for event in &node.events {
872            handlers.push(event.handler.clone());
873        }
874        for child in &node.children {
875            collect_handlers(child, handlers);
876        }
877    }
878
879    let mut referenced_handlers = Vec::new();
880    collect_handlers(&document.root, &mut referenced_handlers);
881
882    // Check each referenced handler exists
883    for handler in referenced_handlers {
884        if !handler_names.contains(&handler) {
885            return Err(CodegenError::MissingHandler(handler));
886        }
887    }
888
889    Ok(())
890}
891
892/// Errors that can occur during code generation
893#[derive(Debug, thiserror::Error)]
894pub enum CodegenError {
895    #[error("Handler '{0}' is referenced but not defined")]
896    MissingHandler(String),
897
898    #[error("Invalid widget kind for code generation: {0}")]
899    InvalidWidget(String),
900
901    #[error("Binding expression error: {0}")]
902    BindingError(String),
903
904    #[error("Theme code generation error: {0}")]
905    ThemeError(String),
906
907    #[error("IO error: {0}")]
908    IoError(#[from] std::io::Error),
909}
910
911#[cfg(test)]
912mod tests {
913    use super::*;
914    use crate::parse;
915
916    #[test]
917    fn test_message_enum_generation() {
918        let handlers = vec![
919            HandlerSignature {
920                name: "increment".to_string(),
921                param_type: None,
922                returns_command: false,
923            },
924            HandlerSignature {
925                name: "update_value".to_string(),
926                param_type: Some("String".to_string()),
927                returns_command: false,
928            },
929        ];
930
931        let tokens = generate_message_enum(&handlers);
932        let code = tokens.to_string();
933
934        assert!(code.contains("Increment"));
935        assert!(code.contains("UpdateValue"));
936    }
937
938    #[test]
939    fn test_handler_validation() {
940        let xml = r#"<column><button on_click="increment" /></column>"#;
941        let doc = parse(xml).unwrap();
942
943        let handlers = vec![HandlerSignature {
944            name: "increment".to_string(),
945            param_type: None,
946            returns_command: false,
947        }];
948
949        assert!(validate_handlers(&doc, &handlers).is_ok());
950
951        // Missing handler should fail
952        let handlers_empty: Vec<HandlerSignature> = vec![];
953        assert!(validate_handlers(&doc, &handlers_empty).is_err());
954    }
955}