1use std::collections::{HashMap, HashSet};
7
8use super::{capitalize, estimate_tokens, VarEntry, VarResolver};
9
10pub struct VarExpander {
12 resolver: VarResolver,
13 expansion_cache: HashMap<String, String>,
14}
15
16impl VarExpander {
17 pub fn new(resolver: VarResolver) -> Self {
19 Self {
20 resolver,
21 expansion_cache: HashMap::new(),
22 }
23 }
24
25 pub fn expand_var(
27 &mut self,
28 name: &str,
29 max_depth: usize,
30 visited: &mut HashSet<String>,
31 ) -> Option<String> {
32 if let Some(cached) = self.expansion_cache.get(name) {
34 return Some(cached.clone());
35 }
36
37 if visited.contains(name) {
39 return Some(format!("[CYCLE:${}]", name));
40 }
41
42 let var = self.resolver.get(name)?;
43 visited.insert(name.to_string());
44
45 let mut value = var.value.clone();
46
47 if max_depth > 0 {
49 let refs = self.resolver.find_references(&value);
50 for r in refs.iter().rev() {
51 if let Some(expanded) = self.expand_var(&r.name, max_depth - 1, visited) {
52 value = format!("{}{}{}", &value[..r.start], expanded, &value[r.end..]);
53 }
54 }
55 }
56
57 self.expansion_cache.insert(name.to_string(), value.clone());
58 Some(value)
59 }
60
61 pub fn expand_text(&mut self, text: &str, mode: ExpansionMode) -> ExpansionResult {
63 let refs = self.resolver.find_references(text);
64 let tokens_before = estimate_tokens(text);
65 let mut expanded = text.to_string();
66 let mut vars_expanded = Vec::new();
67 let mut vars_unresolved = Vec::new();
68 let mut chains = Vec::new();
69
70 for r in refs.iter().rev() {
72 if let Some(var) = self.resolver.get(&r.name).cloned() {
73 vars_expanded.push(r.name.clone());
74
75 let chain = self.get_inheritance_chain(&r.name);
77 chains.push(chain);
78
79 let replacement = self.format_var(&r.name, &var, r.modifier.as_deref(), mode);
81 expanded = format!(
82 "{}{}{}",
83 &expanded[..r.start],
84 replacement,
85 &expanded[r.end..]
86 );
87 } else {
88 vars_unresolved.push(r.name.clone());
89 }
90 }
91
92 ExpansionResult {
93 original: text.to_string(),
94 expanded,
95 vars_expanded,
96 vars_unresolved,
97 tokens_before,
98 tokens_after: estimate_tokens(text),
99 inheritance_chains: chains,
100 }
101 }
102
103 pub fn get_inheritance_chain(&self, name: &str) -> InheritanceChain {
105 let mut chain = vec![name.to_string()];
106 let mut visited = HashSet::new();
107 visited.insert(name.to_string());
108
109 let has_cycle = self.build_chain(name, &mut chain, &mut visited);
110 let depth = chain.len() - 1;
111
112 InheritanceChain {
113 root: name.to_string(),
114 chain,
115 depth,
116 has_cycle,
117 }
118 }
119
120 fn build_chain(
123 &self,
124 name: &str,
125 chain: &mut Vec<String>,
126 visited: &mut HashSet<String>,
127 ) -> bool {
128 if let Some(var) = self.resolver.get(name) {
129 for ref_name in &var.refs {
130 if visited.contains(ref_name) {
131 return true;
133 }
134 visited.insert(ref_name.clone());
135 chain.push(ref_name.clone());
136 if self.build_chain(ref_name, chain, visited) {
137 return true;
138 }
139 }
140 }
141 false
142 }
143
144 fn format_var(
145 &mut self,
146 name: &str,
147 var: &VarEntry,
148 modifier: Option<&str>,
149 mode: ExpansionMode,
150 ) -> String {
151 if let Some(m) = modifier {
153 return match m {
154 "description" | "summary" => var.description.clone().unwrap_or_default(),
155 "value" => var.value.clone(),
156 "type" => var.var_type.to_string(),
157 _ => var.value.clone(),
158 };
159 }
160
161 match mode {
163 ExpansionMode::None => format!("${}", name),
164 ExpansionMode::Summary => var.description.clone().unwrap_or_else(|| name.to_string()),
165 ExpansionMode::Inline => {
166 let mut visited = HashSet::new();
167 self.expand_var(name, 3, &mut visited)
168 .unwrap_or_else(|| var.value.clone())
169 }
170 ExpansionMode::Annotated => {
171 format!("**${}** → {}", name, self.humanize_value(&var.value))
172 }
173 ExpansionMode::Block => self.format_block(name, var),
174 ExpansionMode::Interactive => {
175 format!(
176 r#"<acp-var name="{}" description="{}">{}</acp-var>"#,
177 name,
178 var.description.as_deref().unwrap_or(name),
179 var.description.as_deref().unwrap_or(name)
180 )
181 }
182 }
183 }
184
185 fn format_block(&self, name: &str, var: &VarEntry) -> String {
186 let mut lines = Vec::new();
187 lines.push(format!(
188 "> **{}**: {}",
189 name,
190 var.description.as_deref().unwrap_or("")
191 ));
192 lines.push(">".to_string());
193 lines.push(format!("> - **type**: {}", var.var_type));
194 lines.push(format!("> - **value**: {}", var.value));
195 lines.join("\n")
196 }
197
198 fn humanize_value(&self, value: &str) -> String {
199 value
200 .split('|')
201 .filter_map(|part| {
202 let (key, val) = part.split_once(':')?;
203
204 Some(format!("{}: {}", capitalize(key), val))
205 })
206 .collect::<Vec<_>>()
207 .join(" | ")
208 }
209
210 pub fn clear_cache(&mut self) {
212 self.expansion_cache.clear();
213 }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum ExpansionMode {
219 None,
221 Summary,
223 Inline,
225 Annotated,
227 Block,
229 Interactive,
231}
232
233#[derive(Debug, Clone)]
235pub struct ExpansionResult {
236 pub original: String,
237 pub expanded: String,
238 pub vars_expanded: Vec<String>,
239 pub vars_unresolved: Vec<String>,
240 pub tokens_before: usize,
241 pub tokens_after: usize,
242 pub inheritance_chains: Vec<InheritanceChain>,
243}
244
245#[derive(Debug, Clone)]
247pub struct InheritanceChain {
248 pub root: String,
249 pub chain: Vec<String>,
250 pub depth: usize,
251 pub has_cycle: bool,
252}