Skip to main content

trustformers_wasm/
auto_docs.rs

1//! Automatic documentation generator from TypeScript definitions
2//!
3//! This module provides comprehensive documentation generation capabilities including:
4//! - TypeScript definition parsing and analysis
5//! - Multi-format documentation output (HTML, Markdown, JSON)
6//! - API reference generation with examples
7//! - Interactive playground documentation
8//! - Theme support and customization
9
10use js_sys::Array;
11use serde::{Deserialize, Serialize};
12use std::format;
13use std::string::{String, ToString};
14use std::vec::Vec;
15use wasm_bindgen::prelude::*;
16
17/// Documentation output formats
18#[wasm_bindgen]
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum DocFormat {
21    /// HTML documentation with interactive features
22    HTML,
23    /// Markdown documentation
24    Markdown,
25    /// JSON API documentation
26    JSON,
27    /// OpenAPI/Swagger specification
28    OpenAPI,
29    /// TypeDoc compatible format
30    TypeDoc,
31}
32
33/// Documentation themes
34#[wasm_bindgen]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum DocTheme {
37    /// Default light theme
38    Default,
39    /// Dark theme
40    Dark,
41    /// Material design theme
42    Material,
43    /// Bootstrap theme
44    Bootstrap,
45    /// Minimal theme
46    Minimal,
47}
48
49/// TypeScript node types
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub enum TSNodeType {
52    Interface,
53    Class,
54    Function,
55    Method,
56    Property,
57    Parameter,
58    Enum,
59    Type,
60    Module,
61    Namespace,
62}
63
64/// TypeScript definition node
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TSNode {
67    pub name: String,
68    pub node_type: TSNodeType,
69    pub description: Option<String>,
70    pub parameters: Vec<TSParameter>,
71    pub return_type: Option<String>,
72    pub properties: Vec<TSProperty>,
73    pub examples: Vec<String>,
74    pub deprecated: bool,
75    pub since_version: Option<String>,
76    pub file_path: String,
77    pub line_number: u32,
78}
79
80/// TypeScript parameter
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct TSParameter {
83    pub name: String,
84    pub param_type: String,
85    pub optional: bool,
86    pub default_value: Option<String>,
87    pub description: Option<String>,
88}
89
90/// TypeScript property
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct TSProperty {
93    pub name: String,
94    pub property_type: String,
95    pub optional: bool,
96    pub readonly: bool,
97    pub description: Option<String>,
98}
99
100/// Documentation configuration
101#[wasm_bindgen]
102#[derive(Debug, Clone)]
103pub struct DocConfig {
104    format: DocFormat,
105    theme: DocTheme,
106    include_examples: bool,
107    include_source_links: bool,
108    include_private: bool,
109    generate_playground: bool,
110    output_directory: String,
111    base_url: String,
112    title: String,
113    version: String,
114}
115
116impl Default for DocConfig {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122#[wasm_bindgen]
123impl DocConfig {
124    /// Create a new documentation configuration
125    #[wasm_bindgen(constructor)]
126    pub fn new() -> Self {
127        Self {
128            format: DocFormat::HTML,
129            theme: DocTheme::Default,
130            include_examples: true,
131            include_source_links: true,
132            include_private: false,
133            generate_playground: true,
134            output_directory: "./docs".to_string(),
135            base_url: "/".to_string(),
136            title: "TrustFormer WASM API".to_string(),
137            version: "1.0.0".to_string(),
138        }
139    }
140
141    /// Set documentation format
142    pub fn set_format(&mut self, format: DocFormat) {
143        self.format = format;
144    }
145
146    /// Set documentation theme
147    pub fn set_theme(&mut self, theme: DocTheme) {
148        self.theme = theme;
149    }
150
151    /// Enable/disable examples
152    pub fn set_include_examples(&mut self, include: bool) {
153        self.include_examples = include;
154    }
155
156    /// Enable/disable source links
157    pub fn set_include_source_links(&mut self, include: bool) {
158        self.include_source_links = include;
159    }
160
161    /// Enable/disable private member documentation
162    pub fn set_include_private(&mut self, include: bool) {
163        self.include_private = include;
164    }
165
166    /// Enable/disable playground generation
167    pub fn set_generate_playground(&mut self, generate: bool) {
168        self.generate_playground = generate;
169    }
170
171    /// Set output directory
172    pub fn set_output_directory(&mut self, dir: String) {
173        self.output_directory = dir;
174    }
175
176    /// Set base URL
177    pub fn set_base_url(&mut self, url: String) {
178        self.base_url = url;
179    }
180
181    /// Set documentation title
182    pub fn set_title(&mut self, title: String) {
183        self.title = title;
184    }
185
186    /// Set version
187    pub fn set_version(&mut self, version: String) {
188        self.version = version;
189    }
190}
191
192/// Generated documentation
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct GeneratedDoc {
195    pub title: String,
196    pub version: String,
197    pub generated_at: f64,
198    pub format: DocFormat,
199    pub content: String,
200    pub assets: Vec<DocAsset>,
201    pub navigation: DocNavigation,
202}
203
204/// Documentation asset (CSS, JS, images)
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct DocAsset {
207    pub filename: String,
208    pub content: String,
209    pub mime_type: String,
210}
211
212/// Documentation navigation structure
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct DocNavigation {
215    pub sections: Vec<NavSection>,
216}
217
218/// Navigation section
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct NavSection {
221    pub title: String,
222    pub items: Vec<NavItem>,
223}
224
225/// Navigation item
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct NavItem {
228    pub title: String,
229    pub link: String,
230    pub children: Vec<NavItem>,
231}
232
233/// Automatic documentation generator
234#[wasm_bindgen]
235pub struct AutoDocGenerator {
236    config: DocConfig,
237    ts_nodes: Vec<TSNode>,
238    examples: Vec<CodeExample>,
239}
240
241/// Code example
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct CodeExample {
244    pub title: String,
245    pub description: String,
246    pub code: String,
247    pub language: String,
248    pub category: String,
249}
250
251#[wasm_bindgen]
252impl AutoDocGenerator {
253    /// Create a new documentation generator
254    #[wasm_bindgen(constructor)]
255    pub fn new(config: DocConfig) -> Self {
256        Self {
257            config,
258            ts_nodes: Vec::new(),
259            examples: Vec::new(),
260        }
261    }
262
263    /// Parse TypeScript definitions from string
264    pub fn parse_typescript(&mut self, typescript_content: &str) -> Result<(), JsValue> {
265        // Simplified TypeScript parsing
266        // In a real implementation, this would use a proper TypeScript parser
267
268        let lines: Vec<&str> = typescript_content.lines().collect();
269        let mut current_line = 0;
270
271        while current_line < lines.len() {
272            let line = lines[current_line].trim();
273
274            if line.starts_with("export interface") {
275                self.parse_interface(&lines, &mut current_line)?;
276            } else if line.starts_with("export class") {
277                self.parse_class(&lines, &mut current_line)?;
278            } else if line.starts_with("export function")
279                || line.starts_with("export declare function")
280            {
281                self.parse_function(&lines, &mut current_line)?;
282            } else if line.starts_with("export enum") {
283                self.parse_enum(&lines, &mut current_line)?;
284            }
285
286            current_line += 1;
287        }
288
289        Ok(())
290    }
291
292    /// Generate documentation in the configured format
293    pub fn generate_documentation(&self) -> Result<String, JsValue> {
294        match self.config.format {
295            DocFormat::HTML => self.generate_html(),
296            DocFormat::Markdown => self.generate_markdown(),
297            DocFormat::JSON => self.generate_json(),
298            DocFormat::OpenAPI => self.generate_openapi(),
299            DocFormat::TypeDoc => self.generate_typedoc(),
300        }
301    }
302
303    /// Add a code example
304    pub fn add_example(&mut self, example_json: &str) -> Result<(), JsValue> {
305        let example: CodeExample = serde_json::from_str(example_json)
306            .map_err(|e| JsValue::from_str(&format!("Invalid example: {e}")))?;
307
308        self.examples.push(example);
309        Ok(())
310    }
311
312    /// Generate navigation structure
313    pub fn generate_navigation(&self) -> String {
314        let nav = self.build_navigation();
315        serde_json::to_string_pretty(&nav).unwrap_or_else(|_| "{}".to_string())
316    }
317
318    /// Generate API reference
319    pub fn generate_api_reference(&self) -> String {
320        let mut content = String::new();
321
322        // Group nodes by type
323        let interfaces: Vec<_> = self
324            .ts_nodes
325            .iter()
326            .filter(|n| matches!(n.node_type, TSNodeType::Interface))
327            .collect();
328        let classes: Vec<_> = self
329            .ts_nodes
330            .iter()
331            .filter(|n| matches!(n.node_type, TSNodeType::Class))
332            .collect();
333        let functions: Vec<_> = self
334            .ts_nodes
335            .iter()
336            .filter(|n| matches!(n.node_type, TSNodeType::Function))
337            .collect();
338        let enums: Vec<_> = self
339            .ts_nodes
340            .iter()
341            .filter(|n| matches!(n.node_type, TSNodeType::Enum))
342            .collect();
343
344        // Generate sections
345        if !interfaces.is_empty() {
346            content.push_str("# Interfaces\n\n");
347            for interface in interfaces {
348                content.push_str(&self.format_interface_doc(interface));
349            }
350        }
351
352        if !classes.is_empty() {
353            content.push_str("# Classes\n\n");
354            for class in classes {
355                content.push_str(&self.format_class_doc(class));
356            }
357        }
358
359        if !functions.is_empty() {
360            content.push_str("# Functions\n\n");
361            for function in functions {
362                content.push_str(&self.format_function_doc(function));
363            }
364        }
365
366        if !enums.is_empty() {
367            content.push_str("# Enumerations\n\n");
368            for enum_node in enums {
369                content.push_str(&self.format_enum_doc(enum_node));
370            }
371        }
372
373        content
374    }
375
376    /// Generate playground documentation
377    pub fn generate_playground(&self) -> String {
378        if !self.config.generate_playground {
379            return "Playground generation disabled".to_string();
380        }
381
382        let mut playground = String::new();
383
384        playground.push_str(r#"
385<!DOCTYPE html>
386<html lang="en">
387<head>
388    <meta charset="UTF-8">
389    <meta name="viewport" content="width=device-width, initial-scale=1.0">
390    <title>TrustFormer WASM Playground</title>
391    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
392    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css">
393    <style>
394        body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
395        .playground { display: flex; height: 100vh; }
396        .editor-panel { flex: 1; display: flex; flex-direction: column; }
397        .output-panel { flex: 1; display: flex; flex-direction: column; border-left: 1px solid #ccc; }
398        .toolbar { padding: 10px; background: #f5f5f5; border-bottom: 1px solid #ccc; }
399        .editor { flex: 1; }
400        .output { flex: 1; padding: 10px; overflow: auto; background: #1e1e1e; color: #fff; font-family: monospace; }
401        button { padding: 8px 16px; margin-right: 8px; }
402        select { padding: 8px; margin-right: 8px; }
403    </style>
404</head>
405<body>
406    <div class="playground">
407        <div class="editor-panel">
408            <div class="toolbar">
409                <select id="example-select">
410                    <option value="">Select an example...</option>
411"#);
412
413        // Add examples to playground
414        for (i, example) in self.examples.iter().enumerate() {
415            playground.push_str(&format!(
416                r#"                    <option value="{}">{}</option>
417"#,
418                i, example.title
419            ));
420        }
421
422        playground.push_str(r#"
423                </select>
424                <button onclick="runCode()">Run</button>
425                <button onclick="clearOutput()">Clear</button>
426            </div>
427            <div class="editor">
428                <textarea id="code-editor"></textarea>
429            </div>
430        </div>
431        <div class="output-panel">
432            <div class="toolbar">
433                <strong>Output</strong>
434            </div>
435            <div class="output" id="output"></div>
436        </div>
437    </div>
438
439    <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
440    <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
441    <script>
442        const examples = [
443"#);
444
445        // Add example data
446        for example in &self.examples {
447            playground.push_str(&format!(
448                r#"            {{
449                title: "{}",
450                description: "{}",
451                code: `{}`
452            }},
453"#,
454                example.title,
455                example.description,
456                example.code.replace('`', "\\`")
457            ));
458        }
459
460        playground.push_str(r#"
461        ];
462
463        const editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), {
464            mode: 'javascript',
465            theme: 'monokai',
466            lineNumbers: true,
467            autoCloseBrackets: true,
468            matchBrackets: true
469        });
470
471        document.getElementById('example-select').addEventListener('change', function(e) {
472            const index = parseInt(e.target.value);
473            if (!isNaN(index) && examples[index]) {
474                editor.setValue(examples[index].code);
475            }
476        });
477
478        function runCode() {
479            const code = editor.getValue();
480            const output = document.getElementById('output');
481
482            try {
483                // This would execute the code with the WASM module
484                output.innerHTML += '> ' + code + '\n';
485                output.innerHTML += 'Note: This is a demo playground. Real execution would require the WASM module.\n\n';
486                output.scrollTop = output.scrollHeight;
487            } catch (error) {
488                output.innerHTML += 'Error: ' + error.message + '\n\n';
489                output.scrollTop = output.scrollHeight;
490            }
491        }
492
493        function clearOutput() {
494            document.getElementById('output').innerHTML = '';
495        }
496
497        // Load first example by default
498        if (examples.length > 0) {
499            editor.setValue(examples[0].code);
500        }
501    </script>
502</body>
503</html>
504"#);
505
506        playground
507    }
508
509    /// Get statistics about the parsed TypeScript
510    pub fn get_statistics(&self) -> String {
511        let interface_count = self
512            .ts_nodes
513            .iter()
514            .filter(|n| matches!(n.node_type, TSNodeType::Interface))
515            .count();
516        let class_count = self
517            .ts_nodes
518            .iter()
519            .filter(|n| matches!(n.node_type, TSNodeType::Class))
520            .count();
521        let function_count = self
522            .ts_nodes
523            .iter()
524            .filter(|n| matches!(n.node_type, TSNodeType::Function))
525            .count();
526        let enum_count =
527            self.ts_nodes.iter().filter(|n| matches!(n.node_type, TSNodeType::Enum)).count();
528
529        format!(
530            r#"{{
531  "total_nodes": {},
532  "interfaces": {},
533  "classes": {},
534  "functions": {},
535  "enums": {},
536  "examples": {}
537}}"#,
538            self.ts_nodes.len(),
539            interface_count,
540            class_count,
541            function_count,
542            enum_count,
543            self.examples.len()
544        )
545    }
546
547    // Private parsing methods
548
549    fn parse_interface(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
550        let line = lines[*current_line];
551
552        // Extract interface name
553        let name = self.extract_name_from_declaration(line, "interface")?;
554
555        let mut properties = Vec::new();
556        *current_line += 1;
557
558        // Parse properties until closing brace
559        while *current_line < lines.len() {
560            let prop_line = lines[*current_line].trim();
561            if prop_line == "}" {
562                break;
563            }
564
565            if !prop_line.is_empty() && !prop_line.starts_with("//") {
566                if let Ok(property) = self.parse_property(prop_line) {
567                    properties.push(property);
568                }
569            }
570
571            *current_line += 1;
572        }
573
574        let node = TSNode {
575            name,
576            node_type: TSNodeType::Interface,
577            description: self
578                .extract_description_comment(lines, *current_line - properties.len() - 1),
579            parameters: Vec::new(),
580            return_type: None,
581            properties,
582            examples: Vec::new(),
583            deprecated: false,
584            since_version: None,
585            file_path: "parsed.ts".to_string(),
586            line_number: *current_line as u32,
587        };
588
589        self.ts_nodes.push(node);
590        Ok(())
591    }
592
593    fn parse_class(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
594        let line = lines[*current_line];
595        let name = self.extract_name_from_declaration(line, "class")?;
596
597        // For simplicity, treat class similar to interface
598        // In a real implementation, this would parse methods, constructors, etc.
599        let node = TSNode {
600            name,
601            node_type: TSNodeType::Class,
602            description: self.extract_description_comment(lines, *current_line - 1),
603            parameters: Vec::new(),
604            return_type: None,
605            properties: Vec::new(),
606            examples: Vec::new(),
607            deprecated: false,
608            since_version: None,
609            file_path: "parsed.ts".to_string(),
610            line_number: *current_line as u32,
611        };
612
613        self.ts_nodes.push(node);
614        Ok(())
615    }
616
617    fn parse_function(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
618        let line = lines[*current_line];
619        let name = self.extract_name_from_declaration(line, "function")?;
620
621        // Extract parameters and return type
622        let (parameters, return_type) = self.parse_function_signature(line)?;
623
624        let node = TSNode {
625            name,
626            node_type: TSNodeType::Function,
627            description: self.extract_description_comment(lines, *current_line - 1),
628            parameters,
629            return_type,
630            properties: Vec::new(),
631            examples: Vec::new(),
632            deprecated: false,
633            since_version: None,
634            file_path: "parsed.ts".to_string(),
635            line_number: *current_line as u32,
636        };
637
638        self.ts_nodes.push(node);
639        Ok(())
640    }
641
642    fn parse_enum(&mut self, lines: &[&str], current_line: &mut usize) -> Result<(), JsValue> {
643        let line = lines[*current_line];
644        let name = self.extract_name_from_declaration(line, "enum")?;
645
646        let node = TSNode {
647            name,
648            node_type: TSNodeType::Enum,
649            description: self.extract_description_comment(lines, *current_line - 1),
650            parameters: Vec::new(),
651            return_type: None,
652            properties: Vec::new(),
653            examples: Vec::new(),
654            deprecated: false,
655            since_version: None,
656            file_path: "parsed.ts".to_string(),
657            line_number: *current_line as u32,
658        };
659
660        self.ts_nodes.push(node);
661        Ok(())
662    }
663
664    fn extract_name_from_declaration(&self, line: &str, keyword: &str) -> Result<String, JsValue> {
665        let parts: Vec<&str> = line.split_whitespace().collect();
666
667        for i in 0..parts.len() {
668            if parts[i] == keyword && i + 1 < parts.len() {
669                let name = parts[i + 1];
670                // Remove any generic parameters or extends clauses
671                let clean_name =
672                    name.split('<').next().unwrap_or(name).split('(').next().unwrap_or(name);
673                return Ok(clean_name.to_string());
674            }
675        }
676
677        Err(JsValue::from_str(&format!(
678            "Could not extract name from {} declaration",
679            keyword
680        )))
681    }
682
683    fn parse_property(&self, line: &str) -> Result<TSProperty, JsValue> {
684        // Simplified property parsing
685        let parts: Vec<&str> = line.split(':').collect();
686        if parts.len() != 2 {
687            return Err(JsValue::from_str("Invalid property syntax"));
688        }
689
690        let name_part = parts[0].trim();
691        let type_part = parts[1].trim().trim_end_matches(';');
692
693        let optional = name_part.ends_with('?');
694        let readonly = name_part.starts_with("readonly ");
695
696        let name = name_part
697            .trim_start_matches("readonly ")
698            .trim_end_matches('?')
699            .trim()
700            .to_string();
701
702        Ok(TSProperty {
703            name,
704            property_type: type_part.to_string(),
705            optional,
706            readonly,
707            description: None,
708        })
709    }
710
711    fn parse_function_signature(
712        &self,
713        _line: &str,
714    ) -> Result<(Vec<TSParameter>, Option<String>), JsValue> {
715        // Simplified function signature parsing
716        let parameters = Vec::new(); // Would parse actual parameters
717        let return_type = Some("any".to_string()); // Would parse actual return type
718
719        Ok((parameters, return_type))
720    }
721
722    fn extract_description_comment(&self, lines: &[&str], line_index: usize) -> Option<String> {
723        if line_index == 0 {
724            return None;
725        }
726
727        let comment_line = lines[line_index - 1].trim();
728        if comment_line.starts_with("//") {
729            Some(comment_line.trim_start_matches("//").trim().to_string())
730        } else {
731            None
732        }
733    }
734
735    // Documentation generation methods
736
737    fn generate_html(&self) -> Result<String, JsValue> {
738        let mut html = String::new();
739
740        html.push_str(&format!(
741            r#"
742<!DOCTYPE html>
743<html lang="en">
744<head>
745    <meta charset="UTF-8">
746    <meta name="viewport" content="width=device-width, initial-scale=1.0">
747    <title>{}</title>
748    {}
749</head>
750<body>
751    <div class="container">
752        <header>
753            <h1>{}</h1>
754            <p>Version: {}</p>
755        </header>
756        <nav>
757            {}
758        </nav>
759        <main>
760            {}
761        </main>
762    </div>
763    {}
764</body>
765</html>
766"#,
767            self.config.title,
768            self.get_html_styles(),
769            self.config.title,
770            self.config.version,
771            self.generate_html_navigation(),
772            self.generate_api_reference(),
773            self.get_html_scripts()
774        ));
775
776        Ok(html)
777    }
778
779    fn generate_markdown(&self) -> Result<String, JsValue> {
780        let mut md = String::new();
781
782        md.push_str(&format!("# {title}\n\n", title = self.config.title));
783        md.push_str(&format!(
784            "Version: {version}\n\n",
785            version = self.config.version
786        ));
787        md.push_str(&self.generate_api_reference());
788
789        Ok(md)
790    }
791
792    fn generate_json(&self) -> Result<String, JsValue> {
793        let doc = GeneratedDoc {
794            title: self.config.title.clone(),
795            version: self.config.version.clone(),
796            generated_at: js_sys::Date::now(),
797            format: self.config.format,
798            content: self.generate_api_reference(),
799            assets: Vec::new(),
800            navigation: self.build_navigation(),
801        };
802
803        serde_json::to_string_pretty(&doc).map_err(|e| JsValue::from_str(&e.to_string()))
804    }
805
806    fn generate_openapi(&self) -> Result<String, JsValue> {
807        // Generate OpenAPI specification
808        let mut spec = String::new();
809        spec.push_str("openapi: 3.0.0\n");
810        spec.push_str(&format!(
811            "info:\n  title: {}\n  version: {}\n",
812            self.config.title, self.config.version
813        ));
814        spec.push_str("paths:\n");
815
816        // Add API endpoints based on functions
817        for node in &self.ts_nodes {
818            if matches!(node.node_type, TSNodeType::Function) {
819                spec.push_str(&format!(
820                    "  /{}:\n    post:\n      summary: {}\n",
821                    node.name,
822                    node.description.as_deref().unwrap_or(&node.name)
823                ));
824            }
825        }
826
827        Ok(spec)
828    }
829
830    fn generate_typedoc(&self) -> Result<String, JsValue> {
831        // Generate TypeDoc compatible JSON
832        self.generate_json()
833    }
834
835    fn build_navigation(&self) -> DocNavigation {
836        let mut sections = Vec::new();
837
838        // Group by node type
839        let interface_items: Vec<_> = self
840            .ts_nodes
841            .iter()
842            .filter(|n| matches!(n.node_type, TSNodeType::Interface))
843            .map(|n| NavItem {
844                title: n.name.clone(),
845                link: format!("#{name}", name = n.name.to_lowercase()),
846                children: Vec::new(),
847            })
848            .collect();
849
850        if !interface_items.is_empty() {
851            sections.push(NavSection {
852                title: "Interfaces".to_string(),
853                items: interface_items,
854            });
855        }
856
857        let class_items: Vec<_> = self
858            .ts_nodes
859            .iter()
860            .filter(|n| matches!(n.node_type, TSNodeType::Class))
861            .map(|n| NavItem {
862                title: n.name.clone(),
863                link: format!("#{name}", name = n.name.to_lowercase()),
864                children: Vec::new(),
865            })
866            .collect();
867
868        if !class_items.is_empty() {
869            sections.push(NavSection {
870                title: "Classes".to_string(),
871                items: class_items,
872            });
873        }
874
875        DocNavigation { sections }
876    }
877
878    fn format_interface_doc(&self, interface: &TSNode) -> String {
879        let mut doc = String::new();
880        doc.push_str(&format!("## {name}\n\n", name = interface.name));
881
882        if let Some(ref description) = interface.description {
883            doc.push_str(&format!("{description}\n\n"));
884        }
885
886        if !interface.properties.is_empty() {
887            doc.push_str("### Properties\n\n");
888            for prop in &interface.properties {
889                doc.push_str(&format!(
890                    "- **{}**{}: `{}` {}\n",
891                    prop.name,
892                    if prop.optional { "?" } else { "" },
893                    prop.property_type,
894                    if prop.readonly { "(readonly)" } else { "" }
895                ));
896
897                if let Some(ref desc) = prop.description {
898                    doc.push_str(&format!("  {desc}\n"));
899                }
900            }
901            doc.push('\n');
902        }
903
904        doc
905    }
906
907    fn format_class_doc(&self, class: &TSNode) -> String {
908        let mut doc = String::new();
909        doc.push_str(&format!("## {name}\n\n", name = class.name));
910
911        if let Some(ref description) = class.description {
912            doc.push_str(&format!("{description}\n\n"));
913        }
914
915        doc
916    }
917
918    fn format_function_doc(&self, function: &TSNode) -> String {
919        let mut doc = String::new();
920        doc.push_str(&format!("## {name}\n\n", name = function.name));
921
922        if let Some(ref description) = function.description {
923            doc.push_str(&format!("{description}\n\n"));
924        }
925
926        if !function.parameters.is_empty() {
927            doc.push_str("### Parameters\n\n");
928            for param in &function.parameters {
929                doc.push_str(&format!(
930                    "- **{}**{}: `{}`\n",
931                    param.name,
932                    if param.optional { "?" } else { "" },
933                    param.param_type
934                ));
935
936                if let Some(ref desc) = param.description {
937                    doc.push_str(&format!("  {desc}\n"));
938                }
939            }
940            doc.push('\n');
941        }
942
943        if let Some(ref return_type) = function.return_type {
944            doc.push_str(&format!("### Returns\n\n`{return_type}`\n\n"));
945        }
946
947        doc
948    }
949
950    fn format_enum_doc(&self, enum_node: &TSNode) -> String {
951        let mut doc = String::new();
952        doc.push_str(&format!("## {name}\n\n", name = enum_node.name));
953
954        if let Some(ref description) = enum_node.description {
955            doc.push_str(&format!("{description}\n\n"));
956        }
957
958        doc
959    }
960
961    fn generate_html_navigation(&self) -> String {
962        let nav = self.build_navigation();
963        let mut html = String::new();
964
965        html.push_str("<ul>");
966        for section in &nav.sections {
967            html.push_str(&format!(
968                "<li><strong>{title}</strong><ul>",
969                title = section.title
970            ));
971            for item in &section.items {
972                html.push_str(&format!(
973                    "<li><a href=\"{}\">{}</a></li>",
974                    item.link, item.title
975                ));
976            }
977            html.push_str("</ul></li>");
978        }
979        html.push_str("</ul>");
980
981        html
982    }
983
984    fn get_html_styles(&self) -> String {
985        match self.config.theme {
986            DocTheme::Default => self.get_default_styles(),
987            DocTheme::Dark => self.get_dark_styles(),
988            DocTheme::Material => self.get_material_styles(),
989            DocTheme::Bootstrap => self.get_bootstrap_styles(),
990            DocTheme::Minimal => self.get_minimal_styles(),
991        }
992    }
993
994    fn get_default_styles(&self) -> String {
995        r#"
996<style>
997    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; background: #fff; }
998    .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
999    header { text-align: center; margin-bottom: 2rem; }
1000    nav { background: #f8f9fa; padding: 1rem; border-radius: 8px; margin-bottom: 2rem; }
1001    nav ul { list-style: none; padding: 0; }
1002    nav li { margin: 0.5rem 0; }
1003    nav a { text-decoration: none; color: #0366d6; }
1004    h1, h2, h3 { color: #24292e; }
1005    code { background: #f6f8fa; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1006    pre { background: #f6f8fa; padding: 1rem; border-radius: 6px; overflow-x: auto; }
1007</style>
1008"#.to_string()
1009    }
1010
1011    fn get_dark_styles(&self) -> String {
1012        r#"
1013<style>
1014    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; background: #0d1117; color: #c9d1d9; }
1015    .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
1016    header { text-align: center; margin-bottom: 2rem; }
1017    nav { background: #161b22; padding: 1rem; border-radius: 8px; margin-bottom: 2rem; border: 1px solid #30363d; }
1018    nav ul { list-style: none; padding: 0; }
1019    nav li { margin: 0.5rem 0; }
1020    nav a { text-decoration: none; color: #58a6ff; }
1021    h1, h2, h3 { color: #f0f6fc; }
1022    code { background: #161b22; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1023    pre { background: #161b22; padding: 1rem; border-radius: 6px; overflow-x: auto; border: 1px solid #30363d; }
1024</style>
1025"#.to_string()
1026    }
1027
1028    fn get_material_styles(&self) -> String {
1029        r#"
1030<style>
1031    body { font-family: 'Roboto', sans-serif; margin: 0; background: #fafafa; }
1032    .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
1033    header { text-align: center; margin-bottom: 2rem; background: #2196f3; color: white; padding: 2rem; border-radius: 4px; }
1034    nav { background: white; padding: 1rem; border-radius: 4px; margin-bottom: 2rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
1035    nav ul { list-style: none; padding: 0; }
1036    nav li { margin: 0.5rem 0; }
1037    nav a { text-decoration: none; color: #2196f3; }
1038    h1, h2, h3 { color: #212121; }
1039    code { background: #e8eaf6; padding: 2px 4px; border-radius: 3px; font-family: monospace; }
1040    pre { background: #e8eaf6; padding: 1rem; border-radius: 4px; overflow-x: auto; }
1041</style>
1042"#.to_string()
1043    }
1044
1045    fn get_bootstrap_styles(&self) -> String {
1046        r#"
1047<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
1048<style>
1049    .container { margin-top: 2rem; }
1050    nav { background: #f8f9fa; padding: 1rem; border-radius: 0.375rem; margin-bottom: 2rem; }
1051    code { color: #e83e8c; }
1052</style>
1053"#.to_string()
1054    }
1055
1056    fn get_minimal_styles(&self) -> String {
1057        r#"
1058<style>
1059    body { font-family: Georgia, serif; margin: 0; background: white; line-height: 1.6; }
1060    .container { max-width: 800px; margin: 0 auto; padding: 20px; }
1061    header { margin-bottom: 2rem; }
1062    nav { margin-bottom: 2rem; }
1063    nav ul { list-style: none; padding: 0; }
1064    nav a { text-decoration: none; color: #000; border-bottom: 1px dotted; }
1065    h1, h2, h3 { font-weight: normal; }
1066    code { font-family: 'Courier New', monospace; }
1067</style>
1068"#
1069        .to_string()
1070    }
1071
1072    fn get_html_scripts(&self) -> String {
1073        r##"
1074<script>
1075    // Add interactive features
1076    document.addEventListener('DOMContentLoaded', function() {
1077        // Smooth scrolling for navigation links
1078        const links = document.querySelectorAll('nav a[href^="#"]');
1079        links.forEach(link => {
1080            link.addEventListener('click', function(e) {
1081                e.preventDefault();
1082                const target = document.querySelector(this.getAttribute('href'));
1083                if (target) {
1084                    target.scrollIntoView({ behavior: 'smooth' });
1085                }
1086            });
1087        });
1088    });
1089</script>
1090"##
1091        .to_string()
1092    }
1093}
1094
1095/// Create a documentation generator with default settings
1096#[wasm_bindgen]
1097pub fn create_default_doc_generator() -> AutoDocGenerator {
1098    AutoDocGenerator::new(DocConfig::new())
1099}
1100
1101/// Create a documentation generator for HTML output
1102#[wasm_bindgen]
1103pub fn create_html_doc_generator() -> AutoDocGenerator {
1104    let mut config = DocConfig::new();
1105    config.set_format(DocFormat::HTML);
1106    AutoDocGenerator::new(config)
1107}
1108
1109/// Create a documentation generator for Markdown output
1110#[wasm_bindgen]
1111pub fn create_markdown_doc_generator() -> AutoDocGenerator {
1112    let mut config = DocConfig::new();
1113    config.set_format(DocFormat::Markdown);
1114    AutoDocGenerator::new(config)
1115}
1116
1117/// Version and build information for the trustformers-wasm crate
1118#[wasm_bindgen]
1119#[derive(Debug, Clone, Serialize, Deserialize)]
1120pub struct VersionInfo {
1121    /// Crate version
1122    version: String,
1123    /// Git commit hash (if available)
1124    git_hash: String,
1125    /// Build target
1126    target: String,
1127    /// Build profile (debug/release)
1128    profile: String,
1129    /// Build timestamp
1130    build_time: String,
1131    /// Enabled features
1132    features: Vec<String>,
1133}
1134
1135#[wasm_bindgen]
1136impl VersionInfo {
1137    #[wasm_bindgen(getter)]
1138    pub fn version(&self) -> String {
1139        self.version.clone()
1140    }
1141
1142    #[wasm_bindgen(getter)]
1143    pub fn git_hash(&self) -> String {
1144        self.git_hash.clone()
1145    }
1146
1147    #[wasm_bindgen(getter)]
1148    pub fn target(&self) -> String {
1149        self.target.clone()
1150    }
1151
1152    #[wasm_bindgen(getter)]
1153    pub fn profile(&self) -> String {
1154        self.profile.clone()
1155    }
1156
1157    #[wasm_bindgen(getter)]
1158    pub fn build_time(&self) -> String {
1159        self.build_time.clone()
1160    }
1161
1162    #[wasm_bindgen(getter)]
1163    pub fn features(&self) -> Array {
1164        self.features.iter().map(|f| JsValue::from(f.as_str())).collect()
1165    }
1166}
1167
1168/// Get version and build information for the trustformers-wasm crate
1169#[wasm_bindgen]
1170pub fn get_version_info() -> VersionInfo {
1171    VersionInfo {
1172        version: env!("CARGO_PKG_VERSION").to_string(),
1173        git_hash: option_env!("GIT_HASH").unwrap_or("unknown").to_string(),
1174        target: std::env::consts::ARCH.to_string() + "-" + std::env::consts::OS,
1175        profile: if cfg!(debug_assertions) { "debug".to_string() } else { "release".to_string() },
1176        build_time: option_env!("BUILD_TIME").unwrap_or("unknown").to_string(),
1177        features: get_enabled_features(),
1178    }
1179}
1180
1181/// Get list of enabled Cargo features
1182#[allow(clippy::vec_init_then_push)] // Needed for cfg-gated feature collection
1183fn get_enabled_features() -> Vec<String> {
1184    let mut features = Vec::new();
1185
1186    #[cfg(feature = "console_panic")]
1187    features.push("console_panic".to_string());
1188
1189    #[cfg(feature = "webgpu")]
1190    features.push("webgpu".to_string());
1191
1192    #[cfg(feature = "web-workers")]
1193    features.push("web-workers".to_string());
1194
1195    #[cfg(feature = "shared-memory")]
1196    features.push("shared-memory".to_string());
1197
1198    #[cfg(feature = "kernel-fusion")]
1199    features.push("kernel-fusion".to_string());
1200
1201    #[cfg(feature = "async-executor")]
1202    features.push("async-executor".to_string());
1203
1204    #[cfg(feature = "indexeddb")]
1205    features.push("indexeddb".to_string());
1206
1207    #[cfg(feature = "memory64")]
1208    features.push("memory64".to_string());
1209
1210    #[cfg(feature = "streaming-loader")]
1211    features.push("streaming-loader".to_string());
1212
1213    #[cfg(feature = "model-splitting")]
1214    features.push("model-splitting".to_string());
1215
1216    #[cfg(feature = "react-components")]
1217    features.push("react-components".to_string());
1218
1219    #[cfg(feature = "vue-components")]
1220    features.push("vue-components".to_string());
1221
1222    #[cfg(feature = "angular-components")]
1223    features.push("angular-components".to_string());
1224
1225    #[cfg(feature = "web-components")]
1226    features.push("web-components".to_string());
1227
1228    #[cfg(feature = "playground")]
1229    features.push("playground".to_string());
1230
1231    #[cfg(feature = "streaming-generation")]
1232    features.push("streaming-generation".to_string());
1233
1234    #[cfg(feature = "mobile-optimization")]
1235    features.push("mobile-optimization".to_string());
1236
1237    #[cfg(feature = "dlmalloc-alloc")]
1238    features.push("dlmalloc-alloc".to_string());
1239
1240    features
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245    use super::*;
1246
1247    #[test]
1248    fn test_doc_config() {
1249        let mut config = DocConfig::new();
1250        assert_eq!(config.format, DocFormat::HTML);
1251        assert_eq!(config.theme, DocTheme::Default);
1252
1253        config.set_format(DocFormat::Markdown);
1254        assert_eq!(config.format, DocFormat::Markdown);
1255    }
1256
1257    #[test]
1258    fn test_typescript_parsing() {
1259        let mut generator = AutoDocGenerator::new(DocConfig::new());
1260
1261        let typescript = r#"
1262export interface TestInterface {
1263  name: string;
1264  optional?: number;
1265}
1266        "#;
1267
1268        let result = generator.parse_typescript(typescript);
1269        assert!(result.is_ok());
1270        assert_eq!(generator.ts_nodes.len(), 1);
1271        assert_eq!(generator.ts_nodes[0].name, "TestInterface");
1272    }
1273
1274    #[test]
1275    fn test_documentation_generation() {
1276        let generator = AutoDocGenerator::new(DocConfig::new());
1277        let doc = generator.generate_documentation();
1278        assert!(doc.is_ok());
1279    }
1280}