Skip to main content

fission_design_system_codegen/
lib.rs

1use anyhow::{anyhow, bail, Context, Result};
2use serde_json::Value;
3use std::collections::{BTreeMap, BTreeSet};
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
8pub struct Config {
9    pub dsp_path: PathBuf,
10    pub out_file: String,
11    pub type_name: String,
12    pub crate_path: String,
13}
14
15impl Config {
16    pub fn new(dsp_path: impl Into<PathBuf>) -> Self {
17        Self {
18            dsp_path: dsp_path.into(),
19            out_file: "app_design_system.rs".into(),
20            type_name: "AppDesignSystem".into(),
21            crate_path: "fission_theme".into(),
22        }
23    }
24}
25
26pub fn generate(config: Config) -> Result<PathBuf> {
27    let out_dir = std::env::var_os("OUT_DIR").ok_or_else(|| anyhow!("OUT_DIR is not set"))?;
28    let out_path = PathBuf::from(out_dir).join(&config.out_file);
29    let package = Package::load(&config.dsp_path)?;
30    println!("cargo:rerun-if-changed={}", package.dsp_path.display());
31    println!("cargo:rerun-if-changed={}", package.tokens_path.display());
32    let code = package.generate(&config)?;
33    fs::write(&out_path, code)
34        .with_context(|| format!("failed to write {}", out_path.display()))?;
35    Ok(out_path)
36}
37
38#[derive(Debug, Clone)]
39struct Package {
40    dsp_path: PathBuf,
41    tokens_path: PathBuf,
42    dsp: Value,
43    tokens: TokenStore,
44}
45
46impl Package {
47    fn load(dsp_path: &Path) -> Result<Self> {
48        let dsp_path = dsp_path.to_path_buf();
49        let dsp_text = fs::read_to_string(&dsp_path)
50            .with_context(|| format!("failed to read DSP manifest {}", dsp_path.display()))?;
51        let dsp: Value = serde_json::from_str(&dsp_text)
52            .with_context(|| format!("invalid JSON in {}", dsp_path.display()))?;
53        let dsp_dir = dsp_path
54            .parent()
55            .ok_or_else(|| anyhow!("DSP path has no parent: {}", dsp_path.display()))?;
56        let token_ref = dsp
57            .pointer("/tokens/$ref")
58            .and_then(Value::as_str)
59            .unwrap_or("tokens.json");
60        let tokens_path = dsp_dir.join(token_ref);
61        let tokens_text = fs::read_to_string(&tokens_path)
62            .with_context(|| format!("failed to read token file {}", tokens_path.display()))?;
63        let raw_tokens: Value = serde_json::from_str(&tokens_text)
64            .with_context(|| format!("invalid JSON in {}", tokens_path.display()))?;
65        let tokens = TokenStore::from_value(&raw_tokens)?;
66        Ok(Self {
67            dsp_path,
68            tokens_path,
69            dsp,
70            tokens,
71        })
72    }
73
74    fn generate(&self, config: &Config) -> Result<String> {
75        let krate = &config.crate_path;
76        let type_name = &config.type_name;
77        let info = self.info(krate);
78        let light = self.theme_expr(krate, Mode::Light)?;
79        let dark = self.theme_expr(krate, Mode::Dark)?;
80        let design_tokens = self.design_tokens_expr(krate)?;
81        let components = self.components_expr(krate)?;
82        let patterns = self.patterns_expr(krate)?;
83        let assets = self.assets_expr(krate)?;
84
85        Ok(format!(
86            r#"// @generated by fission-design-system-codegen. Do not edit by hand.
87#[allow(clippy::all)]
88#[allow(dead_code)]
89pub struct {type_name};
90
91static DESIGN_INFO: ::std::sync::OnceLock<{krate}::DesignSystemInfo> = ::std::sync::OnceLock::new();
92static DESIGN_TOKENS: ::std::sync::OnceLock<{krate}::DesignTokenSet> = ::std::sync::OnceLock::new();
93static DESIGN_COMPONENTS: ::std::sync::OnceLock<Vec<{krate}::DesignComponentSpec>> = ::std::sync::OnceLock::new();
94static DESIGN_PATTERNS: ::std::sync::OnceLock<Vec<{krate}::DesignPatternSpec>> = ::std::sync::OnceLock::new();
95static DESIGN_ASSETS: ::std::sync::OnceLock<{krate}::DesignAssetManifest> = ::std::sync::OnceLock::new();
96static LIGHT_THEME: ::std::sync::OnceLock<{krate}::Theme> = ::std::sync::OnceLock::new();
97static DARK_THEME: ::std::sync::OnceLock<{krate}::Theme> = ::std::sync::OnceLock::new();
98
99impl {krate}::DesignSystem for {type_name} {{
100    fn info() -> &'static {krate}::DesignSystemInfo {{
101        DESIGN_INFO.get_or_init(|| {info})
102    }}
103
104    fn tokens() -> &'static {krate}::DesignTokenSet {{
105        DESIGN_TOKENS.get_or_init(|| {design_tokens})
106    }}
107
108    fn components() -> &'static [{krate}::DesignComponentSpec] {{
109        DESIGN_COMPONENTS.get_or_init(|| {components}).as_slice()
110    }}
111
112    fn patterns() -> &'static [{krate}::DesignPatternSpec] {{
113        DESIGN_PATTERNS.get_or_init(|| {patterns}).as_slice()
114    }}
115
116    fn assets() -> &'static {krate}::DesignAssetManifest {{
117        DESIGN_ASSETS.get_or_init(|| {assets})
118    }}
119
120    fn theme_ref(mode: {krate}::DesignMode) -> &'static {krate}::Theme {{
121        match mode {{
122            {krate}::DesignMode::Light => LIGHT_THEME.get_or_init(|| {light}),
123            {krate}::DesignMode::Dark => DARK_THEME.get_or_init(|| {dark}),
124        }}
125    }}
126}}
127"#
128        ))
129    }
130
131    fn info(&self, krate: &str) -> String {
132        let package = self.dsp.get("$package").unwrap_or(&Value::Null);
133        let name = package
134            .get("name")
135            .and_then(Value::as_str)
136            .or_else(|| self.dsp.pointer("/brand/name").and_then(Value::as_str))
137            .unwrap_or("design-system");
138        let version = package
139            .get("version")
140            .and_then(Value::as_str)
141            .unwrap_or("0.0.0");
142        let description = package
143            .get("description")
144            .and_then(Value::as_str)
145            .unwrap_or("");
146        format!(
147            "{krate}::DesignSystemInfo {{ name: {}, version: {}, description: {}, source: {} }}",
148            rust_string(name),
149            rust_string(version),
150            rust_string(description),
151            rust_string(&self.dsp_path.display().to_string()),
152        )
153    }
154
155    fn theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
156        let mode_name = mode.as_str();
157        let colors = self.color_tokens_expr(krate, mode)?;
158        let spacing = self.spacing_tokens_expr(krate)?;
159        let typography = self.typography_tokens_expr(krate)?;
160        let radii = self.radius_tokens_expr(krate)?;
161        let elevations = self.elevation_tokens_expr(krate)?;
162        let motion = self.motion_tokens_expr(krate)?;
163        let data_visualization = self.data_visualization_tokens_expr(krate, mode)?;
164        let components = self.component_theme_expr(krate, mode)?;
165        Ok(format!(
166            r#"{krate}::Theme {{
167                tokens: {krate}::Tokens {{
168                    colors: {colors},
169                    spacing: {spacing},
170                    typography: {typography},
171                    radii: {radii},
172                    elevations: {elevations},
173                    motion: {motion},
174                    data_visualization: {data_visualization},
175                }},
176                components: {components},
177                design_system: {krate}::ResolvedDesignSystem {{
178                    mode: {krate}::DesignMode::{mode_name},
179                    info: <{type_placeholder} as {krate}::DesignSystem>::info().clone(),
180                    tokens: <{type_placeholder} as {krate}::DesignSystem>::tokens().clone(),
181                    components: <{type_placeholder} as {krate}::DesignSystem>::components().to_vec(),
182                    patterns: <{type_placeholder} as {krate}::DesignSystem>::patterns().to_vec(),
183                    assets: <{type_placeholder} as {krate}::DesignSystem>::assets().clone(),
184                }},
185            }}"#,
186            type_placeholder = "Self"
187        ))
188    }
189
190    fn color_tokens_expr(&self, krate: &str, mode: Mode) -> Result<String> {
191        let prefix = match mode {
192            Mode::Light => "color.light",
193            Mode::Dark => "color.dark",
194        };
195        let fallback_on_error = match mode {
196            Mode::Light => "#FFFFFF",
197            Mode::Dark => "#020617",
198        };
199        let c =
200            |name: &str| -> Result<String> { self.color_expr(krate, &format!("{prefix}.{name}")) };
201        let c_or = |name: &str, fallback: &str| -> Result<String> {
202            self.color_expr_optional(krate, &format!("{prefix}.{name}"), fallback)
203        };
204        Ok(format!(
205            r#"{krate}::ColorTokens {{
206                primary: {primary},
207                on_primary: {on_primary},
208                primary_hover: {primary_hover},
209                primary_subtle: {primary_subtle},
210                secondary: {secondary},
211                on_secondary: {on_secondary},
212                surface: {surface},
213                on_surface: {on_surface},
214                surface_raised: {surface_raised},
215                surface_sunken: {surface_sunken},
216                background: {background},
217                on_background: {on_background},
218                error: {error},
219                on_error: {on_error},
220                success: {success},
221                warning: {warning},
222                info: {info},
223                border: {border},
224                border_strong: {border_strong},
225                divider: {divider},
226                text_primary: {text_primary},
227                text_secondary: {text_secondary},
228                text_muted: {text_muted},
229                text_link: {text_link},
230                heading: {heading},
231                focus_ring: {focus_ring},
232            }}"#,
233            primary = c("primary")?,
234            on_primary = c("on_primary")?,
235            primary_hover = c_or(
236                "primary_hover",
237                &self.resolve_token_string(&format!("{prefix}.primary"))?
238            )?,
239            primary_subtle = c_or(
240                "primary_subtle",
241                &self.resolve_token_string(&format!("{prefix}.surface"))?
242            )?,
243            secondary = c("secondary")?,
244            on_secondary = c("on_secondary")?,
245            surface = c("surface")?,
246            on_surface = c_or(
247                "on_surface",
248                &self.resolve_token_string(&format!("{prefix}.text_primary"))?
249            )?,
250            surface_raised = c_or(
251                "surface_raised",
252                &self.resolve_token_string(&format!("{prefix}.surface"))?
253            )?,
254            surface_sunken = c_or(
255                "surface_sunken",
256                &self.resolve_token_string(&format!("{prefix}.background"))?
257            )?,
258            background = c("background")?,
259            on_background = c_or(
260                "on_background",
261                &self.resolve_token_string(&format!("{prefix}.text_primary"))?
262            )?,
263            error = c("error")?,
264            on_error = self.color_literal_expr(krate, fallback_on_error)?,
265            success = c_or("success", "#10B981")?,
266            warning = c_or("warning", "#F59E0B")?,
267            info = c_or("info", "#0EA5E9")?,
268            border = c("border")?,
269            border_strong = c_or(
270                "border_strong",
271                &self.resolve_token_string(&format!("{prefix}.border"))?
272            )?,
273            divider = c_or(
274                "divider",
275                &self.resolve_token_string(&format!("{prefix}.border"))?
276            )?,
277            text_primary = c("text_primary")?,
278            text_secondary = c("text_secondary")?,
279            text_muted = c_or(
280                "text_muted",
281                &self.resolve_token_string(&format!("{prefix}.text_secondary"))?
282            )?,
283            text_link = c_or(
284                "text_link",
285                &self.resolve_token_string(&format!("{prefix}.primary"))?
286            )?,
287            heading = c_or(
288                "heading",
289                &self.resolve_token_string(&format!("{prefix}.text_primary"))?
290            )?,
291            focus_ring = c_or(
292                "focus_ring",
293                &self.resolve_token_string(&format!("{prefix}.primary"))?
294            )?,
295        ))
296    }
297
298    fn spacing_tokens_expr(&self, krate: &str) -> Result<String> {
299        Ok(format!(
300            "{krate}::SpacingTokens {{ none: {}, xs: {}, s: {}, m: {}, l: {}, xl: {}, xxl: {}, xxxl: {}, xxxxl: {} }}",
301            f32_lit(self.dimension("spacing.none")?),
302            f32_lit(self.dimension("spacing.xs")?),
303            f32_lit(self.dimension("spacing.s")?),
304            f32_lit(self.dimension("spacing.m")?),
305            f32_lit(self.dimension("spacing.l")?),
306            f32_lit(self.dimension("spacing.xl")?),
307            f32_lit(self.dimension_optional("spacing.2xl", 48.0)?),
308            f32_lit(self.dimension_optional("spacing.3xl", 64.0)?),
309            f32_lit(self.dimension_optional("spacing.4xl", 96.0)?),
310        ))
311    }
312
313    fn typography_tokens_expr(&self, krate: &str) -> Result<String> {
314        Ok(format!(
315            r#"{krate}::TypographyTokens {{
316                font_family_sans: {},
317                font_family_serif: {},
318                font_family_mono: {},
319                font_weight_regular: {},
320                font_weight_medium: {},
321                font_weight_semibold: {},
322                font_weight_bold: {},
323                font_size_xs: {},
324                font_size_sm: {},
325                font_size_base: {},
326                label_large_size: {},
327                body_medium_size: {},
328                body_large_size: {},
329                font_size_lg: {},
330                font_size_xl: {},
331                heading_size: {},
332                heading2_size: {},
333                heading1_size: {},
334                display_sm_size: {},
335                display_md_size: {},
336                line_height_display: {},
337                line_height_heading: {},
338                line_height_snug: {},
339                line_height_normal: {},
340                line_height_relaxed: {},
341                letter_spacing_tight: {},
342                letter_spacing_normal: {},
343                letter_spacing_label: {},
344                letter_spacing_kicker: {},
345            }}"#,
346            rust_string(&self.string_token_optional("typography.font_family.sans", "Inter")?),
347            rust_string(&self.string_token_optional("typography.font_family.serif", "Georgia")?),
348            rust_string(&self.string_token_optional("typography.font_family.mono", "monospace")?),
349            self.number_optional("typography.font_weight.regular", 400.0)? as u16,
350            self.number_optional("typography.font_weight.medium", 500.0)? as u16,
351            self.number_optional("typography.font_weight.semibold", 600.0)? as u16,
352            self.number_optional("typography.font_weight.bold", 700.0)? as u16,
353            f32_lit(self.dimension_optional("typography.font_size.xs", 12.0)?),
354            f32_lit(self.dimension_optional("typography.font_size.sm", 13.0)?),
355            f32_lit(self.dimension_optional("typography.font_size.base", 14.0)?),
356            f32_lit(self.dimension("typography.font_size.label_large")?),
357            f32_lit(self.dimension("typography.font_size.body")?),
358            f32_lit(self.dimension("typography.font_size.body_large")?),
359            f32_lit(self.dimension_optional("typography.font_size.lg", 20.0)?),
360            f32_lit(self.dimension_optional("typography.font_size.xl", 24.0)?),
361            f32_lit(self.dimension("typography.font_size.h3")?),
362            f32_lit(self.dimension_optional("typography.font_size.h2", 36.0)?),
363            f32_lit(self.dimension_optional("typography.font_size.h1", 48.0)?),
364            f32_lit(self.dimension_optional("typography.font_size.display_sm", 60.0)?),
365            f32_lit(self.dimension_optional("typography.font_size.display_md", 72.0)?),
366            f32_lit(self.number_optional("typography.line_height.display", 0.98)?),
367            f32_lit(self.number_optional("typography.line_height.heading", 1.05)?),
368            f32_lit(self.number_optional("typography.line_height.snug", 1.4)?),
369            f32_lit(self.number_optional("typography.line_height.normal", 1.6)?),
370            f32_lit(self.number_optional("typography.line_height.relaxed", 1.68)?),
371            f32_lit(self.dimension_optional("typography.letter_spacing.tight", -0.01)?),
372            f32_lit(self.dimension_optional("typography.letter_spacing.normal", 0.0)?),
373            f32_lit(self.dimension_optional("typography.letter_spacing.label", 0.1)?),
374            f32_lit(self.dimension_optional("typography.letter_spacing.kicker", 0.14)?),
375        ))
376    }
377
378    fn radius_tokens_expr(&self, krate: &str) -> Result<String> {
379        Ok(format!(
380            "{krate}::RadiusTokens {{ none: {}, small: {}, medium: {}, large: {}, xl: {}, xxl: {}, full: {} }}",
381            f32_lit(self.dimension_optional("radius.none", 0.0)?),
382            f32_lit(self.dimension("radius.small")?),
383            f32_lit(self.dimension("radius.medium")?),
384            f32_lit(self.dimension("radius.large")?),
385            f32_lit(self.dimension_optional("radius.xl", 16.0)?),
386            f32_lit(self.dimension_optional("radius.2xl", 24.0)?),
387            f32_lit(self.dimension("radius.full")?),
388        ))
389    }
390
391    fn elevation_tokens_expr(&self, krate: &str) -> Result<String> {
392        Ok(format!(
393            "{krate}::ElevationTokens {{ level0: {}, level1: {}, level2: {}, level3: {}, level4: {}, level5: {}, focus: {} }}",
394            self.shadow_option_expr(krate, "elevation.level0")?,
395            self.shadow_option_expr(krate, "elevation.level1")?,
396            self.shadow_option_expr(krate, "elevation.level2")?,
397            self.shadow_option_expr(krate, "elevation.level3")?,
398            self.shadow_option_expr(krate, "elevation.level4")?,
399            self.shadow_option_expr(krate, "elevation.level5")?,
400            self.shadow_option_expr(krate, "elevation.focus")?,
401        ))
402    }
403
404    fn motion_tokens_expr(&self, krate: &str) -> Result<String> {
405        Ok(format!(
406            "{krate}::MotionTokens {{ duration_instant_ms: {}, duration_micro_ms: {}, duration_fast_ms: {}, duration_normal_ms: {}, duration_slow_ms: {}, duration_deliberate_ms: {}, easing_linear: {}, easing_standard: {}, easing_in: {}, easing_out: {}, easing_ease: {} }}",
407            self.duration_ms_optional("motion.duration.instant", 0)?,
408            self.duration_ms_optional("motion.duration.micro", 120)?,
409            self.duration_ms_optional("motion.duration.fast", 160)?,
410            self.duration_ms_optional("motion.duration.normal", 200)?,
411            self.duration_ms_optional("motion.duration.slow", 300)?,
412            self.duration_ms_optional("motion.duration.deliberate", 480)?,
413            self.easing_expr(krate, "motion.easing.linear")?,
414            self.easing_expr(krate, "motion.easing.standard")?,
415            self.easing_expr(krate, "motion.easing.in")?,
416            self.easing_expr(krate, "motion.easing.out")?,
417            self.easing_expr(krate, "motion.easing.ease")?,
418        ))
419    }
420
421    fn data_visualization_tokens_expr(&self, krate: &str, mode: Mode) -> Result<String> {
422        let palette = self.palette_expr(krate, mode)?;
423        Ok(format!(
424            "{krate}::DataVisualizationTokens {{ palette: vec![{palette}] }}"
425        ))
426    }
427
428    fn component_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
429        let button = self.button_theme_expr(krate, mode)?;
430        let text_input = self.text_input_theme_expr(krate, mode)?;
431        let badge = self.badge_theme_expr(krate, mode)?;
432        let tabs = self.tabs_theme_expr(krate, mode)?;
433        let modal = self.modal_theme_expr(krate, mode)?;
434        let progress = self.progress_theme_expr(krate, mode)?;
435        let tooltip = self.tooltip_theme_expr(krate, mode)?;
436        let card = self.card_theme_expr(krate, mode)?;
437        let feature_icon = self.feature_icon_theme_expr(krate, mode)?;
438        let colors_prefix = match mode {
439            Mode::Light => "color.light",
440            Mode::Dark => "color.dark",
441        };
442        Ok(format!(
443            r#"{krate}::ComponentTheme {{
444                button: {button},
445                text_input: {text_input},
446                calendar: {krate}::CalendarTheme {{ bg_color: {surface}, border_color: {border}, radius: {radius_medium}, selected_bg: {primary}, selected_text: {on_primary}, today_outline: {secondary} }},
447                pagination: {krate}::PaginationTheme {{ spacing: {spacing_s}, active_bg: {primary}, active_text: {on_primary} }},
448                timeline: {krate}::TimelineTheme {{ dot_size: 12.0, line_width: 2.0, dot_color: {primary}, line_color: {border} }},
449                segmented_control: {krate}::SegmentedControlTheme {{ bg_color: {surface}, border_color: {border}, radius: {radius_full}, active_bg: {primary}, active_text: {on_primary} }},
450                alert: {krate}::AlertTheme {{ info_bg: {info_bg}, warning_bg: {warning_bg}, error_bg: {error_bg}, success_bg: {success_bg}, radius: {radius_medium} }},
451                badge: {badge},
452                tabs: {tabs},
453                modal: {modal},
454                tree_view: {krate}::TreeViewTheme {{ indent: 16.0, selected_bg: {primary}.with_alpha(52), hover_bg: {surface} }},
455                progress: {progress},
456                tooltip: {tooltip},
457                card: {card},
458                feature_icon: {feature_icon},
459            }}"#,
460            surface = self.color_expr(krate, &format!("{colors_prefix}.surface"))?,
461            border = self.color_expr(krate, &format!("{colors_prefix}.border"))?,
462            primary = self.color_expr(krate, &format!("{colors_prefix}.primary"))?,
463            on_primary = self.color_expr(krate, &format!("{colors_prefix}.on_primary"))?,
464            secondary = self.color_expr(krate, &format!("{colors_prefix}.secondary"))?,
465            info_bg = self.color_literal_expr(krate, "#E6F2FF")?,
466            warning_bg = self.color_literal_expr(krate, "#FFF4E5")?,
467            error_bg = format!(
468                "{}.with_alpha(30)",
469                self.color_expr(krate, &format!("{colors_prefix}.error"))?
470            ),
471            success_bg = self.color_literal_expr(krate, "#EDF7ED")?,
472            radius_medium = f32_lit(self.dimension("radius.medium")?),
473            radius_full = f32_lit(self.dimension("radius.full")?),
474            spacing_s = f32_lit(self.dimension("spacing.s")?),
475        ))
476    }
477
478    fn button_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
479        let colors_prefix = match mode {
480            Mode::Light => "color.light",
481            Mode::Dark => "color.dark",
482        };
483        let height = self.dsp_dimension_optional(
484            "/components/button/sizes/md/height",
485            self.dimension_optional("component.button.height", 42.0)?,
486        )?;
487        let padding_h = self.dsp_dimension_optional(
488            "/components/button/sizes/md/padding_x",
489            self.dimension_optional("component.button.padding_horizontal", 16.0)?,
490        )?;
491        let padding_v = self.dimension_optional("component.button.padding_vertical", 8.0)?;
492        let radius = self.dsp_dimension_optional(
493            "/components/button/radius",
494            self.dimension_optional("component.button.radius", 9999.0)?,
495        )?;
496        let text_size = self.dsp_dimension_optional(
497            "/components/button/sizes/md/font_size",
498            self.dimension_optional("component.button.text_size", 15.0)?,
499        )?;
500        Ok(format!(
501            r#"{krate}::ButtonTheme {{
502                height: {height},
503                padding_horizontal: {padding_h},
504                padding_vertical: {padding_v},
505                radius: {radius},
506                text_size: {text_size},
507                elevation_rest: {elevation_rest},
508                elevation_hover: {elevation_hover},
509                elevation_pressed: {elevation_pressed},
510                focus_stroke: Some({krate}::Stroke {{ fill: {krate}::Fill::Solid({focus}), width: 2.0, dash_array: None, line_cap: {krate}::LineCap::Round, line_join: {krate}::LineJoin::Round }}),
511                icon_size: {icon_size},
512                font_weight: {font_weight},
513                line_height: {line_height},
514                transition: {transition},
515                sizes: vec![{sizes}],
516                hierarchies: vec![{hierarchies}],
517            }}"#,
518            height = f32_lit(height),
519            padding_h = f32_lit(padding_h),
520            padding_v = f32_lit(padding_v),
521            radius = f32_lit(radius),
522            text_size = f32_lit(text_size),
523            elevation_rest = self.shadow_option_expr(krate, "component.button.elevation_rest")?,
524            elevation_hover = self.shadow_option_expr(krate, "component.button.elevation_hover")?,
525            elevation_pressed =
526                self.shadow_option_expr(krate, "component.button.elevation_pressed")?,
527            focus = self.color_expr_optional(
528                krate,
529                &format!("{colors_prefix}.focus_ring"),
530                &self.resolve_token_string(&format!("{colors_prefix}.primary"))?
531            )?,
532            icon_size = f32_lit(self.style_dimension_optional(
533                mode,
534                self.dsp.pointer("/components/button/icon_size"),
535                20.0,
536            )?),
537            font_weight = self.style_u16_optional(
538                mode,
539                self.dsp.pointer("/components/button/font_weight"),
540                600,
541            )?,
542            line_height = f32_lit(
543                self.dsp_dimension_optional("/components/button/sizes/md/line_height", 20.0,)?
544            ),
545            transition = self.motion_option_expr(
546                krate,
547                mode,
548                self.dsp.pointer("/components/button/transition"),
549            )?,
550            sizes = self.size_styles_expr(krate, mode, "/components/button/sizes")?,
551            hierarchies = self.enum_state_styles_expr(
552                krate,
553                mode,
554                "/components/button/hierarchies",
555                button_hierarchy_variant,
556            )?,
557        ))
558    }
559
560    fn text_input_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
561        let colors_prefix = match mode {
562            Mode::Light => "color.light",
563            Mode::Dark => "color.dark",
564        };
565        let height = self.dsp_dimension_optional(
566            "/components/input/sizes/md/height",
567            self.dimension_optional("component.text_input.height", 40.0)?,
568        )?;
569        let padding_h = self.dsp_dimension_optional(
570            "/components/input/sizes/md/padding_x",
571            self.dimension_optional("component.text_input.padding_h", 16.0)?,
572        )?;
573        let radius = self.dsp_dimension_optional(
574            "/components/input/radius",
575            self.dimension_optional("component.text_input.radius", 4.0)?,
576        )?;
577        let font_size = self.dsp_dimension_optional(
578            "/components/input/font_size",
579            self.dimension_optional("component.text_input.font_size", 17.0)?,
580        )?;
581        Ok(format!(
582            r#"{krate}::TextInputTheme {{
583                height: {height},
584                padding_h: {padding_h},
585                radius: {radius},
586                font_size: {font_size},
587                border_color: {border},
588                border_width: {border_width},
589                focus_color: {focus},
590                text_color: {text},
591                placeholder_color: {placeholder},
592                line_height: {line_height},
593                font_weight: {font_weight},
594                sizes: vec![{sizes}],
595                states: {states},
596                placeholder_style: {placeholder_style},
597                label_style: {label_style},
598                helper_style: {helper_style},
599            }}"#,
600            height = f32_lit(height),
601            padding_h = f32_lit(padding_h),
602            radius = f32_lit(radius),
603            font_size = f32_lit(font_size),
604            border = self.color_expr(krate, &format!("{colors_prefix}.border"))?,
605            border_width =
606                f32_lit(self.dimension_optional("component.text_input.border_width", 1.0)?),
607            focus = self.color_expr_optional(
608                krate,
609                &format!("{colors_prefix}.focus_ring"),
610                &self.resolve_token_string(&format!("{colors_prefix}.primary"))?
611            )?,
612            text = self.color_expr(krate, &format!("{colors_prefix}.text_primary"))?,
613            placeholder = self.color_expr_optional(
614                krate,
615                &format!("{colors_prefix}.text_muted"),
616                &self.resolve_token_string(&format!("{colors_prefix}.text_secondary"))?
617            )?,
618            line_height =
619                f32_lit(self.dsp_dimension_optional("/components/input/line_height", 24.0)?),
620            font_weight = self.style_u16_optional(
621                mode,
622                self.dsp.pointer("/components/input/font_weight"),
623                400,
624            )?,
625            sizes = self.size_styles_expr(krate, mode, "/components/input/sizes")?,
626            states =
627                self.state_styles_expr(krate, mode, self.dsp.pointer("/components/input/states"))?,
628            placeholder_style = self.style_expr(
629                krate,
630                mode,
631                self.dsp.pointer("/components/input/states/placeholder")
632            )?,
633            label_style =
634                self.style_expr(krate, mode, self.dsp.pointer("/components/input/label"))?,
635            helper_style = self.style_expr(
636                krate,
637                mode,
638                self.dsp.pointer("/components/input/helper_text")
639            )?,
640        ))
641    }
642
643    fn badge_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
644        let radius = self.style_dimension_optional(
645            mode,
646            self.dsp.pointer("/components/badge/radius"),
647            self.dimension_optional("component.badge.radius", self.dimension("radius.full")?)?,
648        )?;
649        let font_size = self.dsp_dimension_optional(
650            "/components/badge/sizes/md/font_size",
651            self.dimension_optional("component.badge.font_size", 10.0)?,
652        )?;
653        Ok(format!(
654            r#"{krate}::BadgeTheme {{
655                radius: {radius},
656                font_size: {font_size},
657                font_weight: {font_weight},
658                sizes: vec![{sizes}],
659                tones: vec![{tones}],
660            }}"#,
661            radius = f32_lit(radius),
662            font_size = f32_lit(font_size),
663            font_weight = self.style_u16_optional(
664                mode,
665                self.dsp.pointer("/components/badge/font_weight"),
666                500
667            )?,
668            sizes = self.size_styles_expr(krate, mode, "/components/badge/sizes")?,
669            tones =
670                self.enum_styles_expr(krate, mode, "/components/badge/colors", badge_tone_variant)?,
671        ))
672    }
673
674    fn tabs_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
675        let colors_prefix = match mode {
676            Mode::Light => "color.light",
677            Mode::Dark => "color.dark",
678        };
679        let active = self.color_expr(krate, &format!("{colors_prefix}.primary"))?;
680        let inactive = self.color_expr(krate, &format!("{colors_prefix}.text_secondary"))?;
681        let background = self.color_expr(krate, &format!("{colors_prefix}.background"))?;
682        let divider = self.color_expr_optional(
683            krate,
684            &format!("{colors_prefix}.divider"),
685            &self.resolve_token_string(&format!("{colors_prefix}.border"))?,
686        )?;
687        Ok(format!(
688            r#"{krate}::TabsTheme {{
689                active_color: {active},
690                inactive_color: {inactive},
691                indicator_height: {indicator_height},
692                background: {background},
693                divider_color: {divider}.with_alpha(120),
694                sizes: vec![{sizes}],
695                states: {states},
696                track_style: {track},
697            }}"#,
698            indicator_height = f32_lit(self.dsp_dimension_optional(
699                "/components/tabs/sizes/md/indicator_height",
700                self.dimension_optional("component.tabs.indicator_height", 3.0)?
701            )?),
702            sizes = self.size_styles_expr(krate, mode, "/components/tabs/sizes")?,
703            states =
704                self.state_styles_expr(krate, mode, self.dsp.pointer("/components/tabs/states"))?,
705            track = self.style_expr(krate, mode, self.dsp.pointer("/components/tabs/track"))?,
706        ))
707    }
708
709    fn modal_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
710        let bg = self
711            .style_fill_color_expr(
712                krate,
713                mode,
714                self.dsp.pointer("/components/modal/background"),
715            )?
716            .unwrap_or(self.color_expr(krate, &format!("color.{}.surface", mode.color_name()))?);
717        let radius = self.style_dimension_optional(
718            mode,
719            self.dsp.pointer("/components/modal/radius"),
720            self.dimension_optional("component.modal.radius", self.dimension("radius.large")?)?,
721        )?;
722        let max_width = self.style_dimension_optional(
723            mode,
724            self.dsp.pointer("/components/modal/max_width"),
725            self.dimension_optional("component.modal.max_width", 600.0)?,
726        )?;
727        let shadow = self.shadow_option_expr(krate, "component.modal.shadow")?;
728        let container_style = self.style_expr_for_component(
729            krate,
730            mode,
731            "/components/modal",
732            &["background", "radius", "max_width", "box_shadow"],
733        )?;
734        let scrim_style =
735            self.style_expr(krate, mode, self.dsp.pointer("/components/modal/scrim"))?;
736        let scrim_blur = self
737            .dsp
738            .pointer("/components/modal/scrim/backdrop_filter")
739            .and_then(Value::as_str)
740            .and_then(|value| {
741                value
742                    .strip_prefix("blur(")
743                    .and_then(|v| v.strip_suffix(')'))
744            })
745            .and_then(|value| parse_dimension(value).ok())
746            .unwrap_or(4.0);
747        Ok(format!(
748            r#"{krate}::ModalTheme {{
749                bg_color: {bg},
750                radius: {radius},
751                shadow: {shadow},
752                max_width: {max_width},
753                container_style: {container_style},
754                scrim_style: {scrim_style},
755                scrim_blur: {scrim_blur},
756            }}"#,
757            radius = f32_lit(radius),
758            max_width = f32_lit(max_width),
759            scrim_blur = f32_lit(scrim_blur),
760        ))
761    }
762
763    fn progress_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
764        let height = self.style_dimension_optional(
765            mode,
766            self.dsp.pointer("/components/progress_bar/height"),
767            self.dimension_optional("component.progress.height", 8.0)?,
768        )?;
769        let radius = self.style_dimension_optional(
770            mode,
771            self.dsp.pointer("/components/progress_bar/radius"),
772            self.dimension("radius.full")?,
773        )?;
774        let track_style = self.style_expr(
775            krate,
776            mode,
777            self.dsp.pointer("/components/progress_bar/track"),
778        )?;
779        let fill_style = self.style_expr(
780            krate,
781            mode,
782            self.dsp.pointer("/components/progress_bar/fill"),
783        )?;
784        let track_color = self
785            .style_fill_color_expr(
786                krate,
787                mode,
788                self.dsp
789                    .pointer("/components/progress_bar/track/background"),
790            )?
791            .unwrap_or_else(|| {
792                self.color_expr(krate, &format!("color.{}.border", mode.color_name()))
793                    .unwrap()
794            });
795        let fill_color = self
796            .style_fill_color_expr(
797                krate,
798                mode,
799                self.dsp.pointer("/components/progress_bar/fill/background"),
800            )?
801            .unwrap_or_else(|| {
802                self.color_expr(krate, &format!("color.{}.primary", mode.color_name()))
803                    .unwrap()
804            });
805        Ok(format!(
806            r#"{krate}::ProgressTheme {{
807                height: {height},
808                track_color: {track_color},
809                bar_color: {fill_color},
810                radius: {radius},
811                track_style: {track_style},
812                fill_style: {fill_style},
813            }}"#,
814            height = f32_lit(height),
815            radius = f32_lit(radius),
816        ))
817    }
818
819    fn tooltip_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
820        let style = self.style_expr_for_component(
821            krate,
822            mode,
823            "/components/tooltip",
824            &[
825                "background",
826                "color",
827                "radius",
828                "font_size",
829                "padding",
830                "max_width",
831                "box_shadow",
832            ],
833        )?;
834        let bg = self
835            .style_fill_color_expr(
836                krate,
837                mode,
838                self.dsp.pointer("/components/tooltip/background"),
839            )?
840            .unwrap_or(self.color_literal_expr(krate, "#323232")?);
841        let text = self
842            .style_fill_color_expr(krate, mode, self.dsp.pointer("/components/tooltip/color"))?
843            .unwrap_or(self.color_literal_expr(krate, "#FFFFFF")?);
844        let radius = self.style_dimension_optional(
845            mode,
846            self.dsp.pointer("/components/tooltip/radius"),
847            self.dimension_optional("component.tooltip.radius", self.dimension("radius.small")?)?,
848        )?;
849        let font_size = self.style_dimension_optional(
850            mode,
851            self.dsp.pointer("/components/tooltip/font_size"),
852            self.dimension_optional("component.tooltip.font_size", 12.0)?,
853        )?;
854        let (padding_x, padding_y) = self
855            .dsp
856            .pointer("/components/tooltip/padding")
857            .and_then(Value::as_str)
858            .and_then(|raw| self.resolve_refs_in_string_for_mode(raw, mode).ok())
859            .and_then(|raw| parse_padding(&raw).ok())
860            .map(|p| (p[0], p[2]))
861            .unwrap_or((10.0, 8.0));
862        let max_width = self.style_dimension_optional(
863            mode,
864            self.dsp.pointer("/components/tooltip/max_width"),
865            240.0,
866        )?;
867        Ok(format!(
868            r#"{krate}::TooltipTheme {{
869                bg_color: {bg},
870                text_color: {text},
871                radius: {radius},
872                font_size: {font_size},
873                padding_x: {padding_x},
874                padding_y: {padding_y},
875                max_width: {max_width},
876                style: {style},
877            }}"#,
878            radius = f32_lit(radius),
879            font_size = f32_lit(font_size),
880            padding_x = f32_lit(padding_x),
881            padding_y = f32_lit(padding_y),
882            max_width = f32_lit(max_width),
883        ))
884    }
885
886    fn card_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
887        let padding = self.style_dimension_optional(
888            mode,
889            self.dsp.pointer("/components/card/padding"),
890            self.dimension_optional("component.card.padding", self.dimension("spacing.l")?)?,
891        )?;
892        let radius = self.style_dimension_optional(
893            mode,
894            self.dsp.pointer("/components/card/radius"),
895            self.dimension_optional("component.card.radius", self.dimension("radius.large")?)?,
896        )?;
897        let patterns = self.enum_styles_expr(
898            krate,
899            mode,
900            "/components/card/patterns",
901            card_pattern_variant,
902        )?;
903        let hover = self.style_expr(
904            krate,
905            mode,
906            self.dsp.pointer("/components/card/interaction/hover"),
907        )?;
908        Ok(format!(
909            r#"{krate}::CardTheme {{
910                padding: {padding},
911                radius: {radius},
912                default_pattern: {krate}::CardPattern::Raised,
913                patterns: vec![{patterns}],
914                hover_style: {hover},
915            }}"#,
916            padding = f32_lit(padding),
917            radius = f32_lit(radius),
918        ))
919    }
920
921    fn feature_icon_theme_expr(&self, krate: &str, mode: Mode) -> Result<String> {
922        Ok(format!(
923            r#"{krate}::FeatureIconTheme {{
924                sizes: vec![{sizes}],
925                tones: vec![{tones}],
926                shadow: {shadow},
927            }}"#,
928            sizes = self.size_styles_expr(krate, mode, "/components/feature_icon/sizes")?,
929            tones = self.enum_styles_expr(
930                krate,
931                mode,
932                "/components/feature_icon/tones",
933                feature_icon_tone_variant
934            )?,
935            shadow = self.shadow_option_from_value_expr(
936                krate,
937                mode,
938                self.dsp.pointer("/components/feature_icon/box_shadow"),
939            )?,
940        ))
941    }
942
943    fn palette_expr(&self, krate: &str, mode: Mode) -> Result<String> {
944        let mut colors = Vec::new();
945        if let Some(items) = self
946            .dsp
947            .pointer("/data_visualization/palette")
948            .or_else(|| self.dsp.pointer("/charts/palette"))
949            .and_then(Value::as_array)
950        {
951            for item in items {
952                if let Some(color) = self.style_fill_color_expr(krate, mode, Some(item))? {
953                    colors.push(color);
954                }
955            }
956        }
957        if colors.is_empty() {
958            let token_paths = [
959                "color.teal.700",
960                "color.brand.blue.600",
961                "color.semantic.warning",
962                "color.semantic.error",
963                "color.semantic.success",
964                "color.semantic.info",
965                "color.brand.orange.600",
966                "color.teal.500",
967            ];
968            for path in token_paths {
969                if self.tokens.contains(path) {
970                    colors.push(self.color_expr(krate, path)?);
971                }
972            }
973        }
974        if colors.is_empty() {
975            colors = vec![
976                self.color_literal_expr(krate, "#14B8A6")?,
977                self.color_literal_expr(krate, "#4DA6E0")?,
978                self.color_literal_expr(krate, "#F59E0B")?,
979                self.color_literal_expr(krate, "#F43F5E")?,
980            ];
981        }
982        Ok(colors.join(","))
983    }
984
985    fn size_styles_expr(&self, krate: &str, mode: Mode, pointer: &str) -> Result<String> {
986        let Some(obj) = self.dsp.pointer(pointer).and_then(Value::as_object) else {
987            return Ok(String::new());
988        };
989        let mut items = Vec::new();
990        for (name, value) in obj {
991            if let Some(variant) = component_size_variant(krate, name) {
992                items.push(format!(
993                    "({variant}, {})",
994                    self.style_expr(krate, mode, Some(value))?
995                ));
996            }
997        }
998        Ok(items.join(","))
999    }
1000
1001    fn enum_styles_expr(
1002        &self,
1003        krate: &str,
1004        mode: Mode,
1005        pointer: &str,
1006        variant: fn(&str, &str) -> Option<String>,
1007    ) -> Result<String> {
1008        let Some(obj) = self.dsp.pointer(pointer).and_then(Value::as_object) else {
1009            return Ok(String::new());
1010        };
1011        let mut items = Vec::new();
1012        for (name, value) in obj {
1013            if let Some(variant_expr) = variant(krate, name) {
1014                items.push(format!(
1015                    "({variant_expr}, {})",
1016                    self.style_expr(krate, mode, Some(value))?
1017                ));
1018            }
1019        }
1020        Ok(items.join(","))
1021    }
1022
1023    fn enum_state_styles_expr(
1024        &self,
1025        krate: &str,
1026        mode: Mode,
1027        pointer: &str,
1028        variant: fn(&str, &str) -> Option<String>,
1029    ) -> Result<String> {
1030        let Some(obj) = self.dsp.pointer(pointer).and_then(Value::as_object) else {
1031            return Ok(String::new());
1032        };
1033        let mut items = Vec::new();
1034        for (name, value) in obj {
1035            if let Some(variant_expr) = variant(krate, name) {
1036                items.push(format!(
1037                    "({variant_expr}, {})",
1038                    self.state_styles_expr(krate, mode, Some(value))?
1039                ));
1040            }
1041        }
1042        Ok(items.join(","))
1043    }
1044
1045    fn state_styles_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1046        let state = |name: &str| -> Result<String> {
1047            let expr = value
1048                .and_then(|v| v.get(name))
1049                .map(|v| self.style_expr(krate, mode, Some(v)))
1050                .transpose()?;
1051            Ok(expr
1052                .map(|expr| format!("Some({expr})"))
1053                .unwrap_or_else(|| "None".into()))
1054        };
1055        let default = value
1056            .and_then(|v| v.get("default"))
1057            .map(|v| self.style_expr(krate, mode, Some(v)))
1058            .transpose()?
1059            .unwrap_or_else(|| format!("{krate}::ResolvedComponentStyle::default()"));
1060        Ok(format!(
1061            r#"{krate}::ComponentStateStyles {{
1062                default: {default},
1063                hover: {hover},
1064                active: {active},
1065                focus: {focus},
1066                disabled: {disabled},
1067                error: {error},
1068                selected: {selected},
1069            }}"#,
1070            hover = state("hover")?,
1071            active = state("active")?,
1072            focus = state("focus")?,
1073            disabled = state("disabled")?,
1074            error = state("error")?,
1075            selected = state("selected")?,
1076        ))
1077    }
1078
1079    fn style_expr_for_component(
1080        &self,
1081        krate: &str,
1082        mode: Mode,
1083        pointer: &str,
1084        keys: &[&str],
1085    ) -> Result<String> {
1086        let Some(source) = self.dsp.pointer(pointer).and_then(Value::as_object) else {
1087            return self.style_expr(krate, mode, None);
1088        };
1089        let mut obj = serde_json::Map::new();
1090        for key in keys {
1091            if let Some(value) = source.get(*key) {
1092                obj.insert((*key).to_string(), value.clone());
1093            }
1094        }
1095        self.style_expr(krate, mode, Some(&Value::Object(obj)))
1096    }
1097
1098    fn style_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1099        let Some(value) = value else {
1100            return Ok(format!("{krate}::ResolvedComponentStyle::default()"));
1101        };
1102        let background = self.fill_option_expr(krate, mode, field(value, "background"))?;
1103        let text_color = self
1104            .style_fill_color_expr(krate, mode, field(value, "color"))?
1105            .map(|expr| format!("Some({expr})"))
1106            .unwrap_or_else(|| "None".into());
1107        let border = self.border_option_expr(
1108            krate,
1109            mode,
1110            field(value, "border").or_else(|| field(value, "border_bottom")),
1111        )?;
1112        let shadows = self.shadow_layers_expr(
1113            krate,
1114            mode,
1115            field(value, "box_shadow").or_else(|| field(value, "shadow")),
1116        )?;
1117        let radius = self.style_dimension_option_expr(mode, field(value, "radius"))?;
1118        let height = self.style_dimension_option_expr(mode, field(value, "height"))?;
1119        let width = self.style_dimension_option_expr(mode, field(value, "width"))?;
1120        let size = self.style_dimension_option_expr(mode, field(value, "size"))?;
1121        let width = if width == "None" { size.clone() } else { width };
1122        let height = if height == "None" { size } else { height };
1123        let padding_x = self.style_dimension_option_expr(mode, field(value, "padding_x"))?;
1124        let padding_y = self.style_dimension_option_expr(mode, field(value, "padding_y"))?;
1125        let padding = self.padding_option_expr(mode, field(value, "padding"))?;
1126        let gap = self.style_dimension_option_expr(mode, field(value, "gap"))?;
1127        let font_size = self.style_dimension_option_expr(mode, field(value, "font_size"))?;
1128        let font_weight = self.style_u16_option_expr(mode, field(value, "font_weight"))?;
1129        let line_height = self.style_dimension_option_expr(mode, field(value, "line_height"))?;
1130        let letter_spacing =
1131            self.style_dimension_option_expr(mode, field(value, "letter_spacing"))?;
1132        let icon_size = self.style_dimension_option_expr(mode, field(value, "icon_size"))?;
1133        let max_width = self.style_dimension_option_expr(mode, field(value, "max_width"))?;
1134        let transition = self.motion_option_expr(krate, mode, field(value, "transition"))?;
1135        Ok(format!(
1136            r#"{krate}::ResolvedComponentStyle {{
1137                background: {background},
1138                text_color: {text_color},
1139                border: {border},
1140                radius: {radius},
1141                height: {height},
1142                width: {width},
1143                padding_x: {padding_x},
1144                padding_y: {padding_y},
1145                padding: {padding},
1146                gap: {gap},
1147                font_size: {font_size},
1148                font_weight: {font_weight},
1149                line_height: {line_height},
1150                letter_spacing: {letter_spacing},
1151                icon_size: {icon_size},
1152                max_width: {max_width},
1153                shadows: {shadows},
1154                transition: {transition},
1155            }}"#
1156        ))
1157    }
1158
1159    fn fill_option_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1160        let Some(fill) = self.fill_expr(krate, mode, value)? else {
1161            return Ok("None".into());
1162        };
1163        Ok(format!("Some({fill})"))
1164    }
1165
1166    fn fill_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<Option<String>> {
1167        let Some(raw) = self.resolved_value_string(mode, value)? else {
1168            return Ok(None);
1169        };
1170        let raw = raw.trim();
1171        if raw.eq_ignore_ascii_case("none") || raw.eq_ignore_ascii_case("auto") {
1172            return Ok(None);
1173        }
1174        if raw.eq_ignore_ascii_case("transparent") {
1175            return Ok(Some(format!(
1176                "{krate}::Fill::Solid({})",
1177                color_expr(krate, 0, 0, 0, 0)
1178            )));
1179        }
1180        if raw.starts_with("linear-gradient(") {
1181            let stops = gradient_stops(raw)
1182                .into_iter()
1183                .enumerate()
1184                .map(|(idx, color)| {
1185                    let position = if idx == 0 { 0.0 } else { 1.0 };
1186                    Ok(format!(
1187                        "({}, {})",
1188                        f32_lit(position),
1189                        self.color_literal_expr(krate, color)?
1190                    ))
1191                })
1192                .collect::<Result<Vec<_>>>()?;
1193            return Ok(Some(format!(
1194                "{krate}::Fill::LinearGradient {{ start: (0.0, 0.0), end: (1.0, 1.0), stops: vec![{}] }}",
1195                stops.join(",")
1196            )));
1197        }
1198        if raw.starts_with("radial-gradient(") {
1199            let colors = gradient_stops(raw);
1200            let mut stops = Vec::new();
1201            for (idx, color) in colors.iter().enumerate() {
1202                let position = if colors.len() <= 1 {
1203                    0.0
1204                } else {
1205                    idx as f32 / (colors.len() - 1) as f32
1206                };
1207                stops.push(format!(
1208                    "({}, {})",
1209                    f32_lit(position),
1210                    self.color_literal_expr(krate, color)?
1211                ));
1212            }
1213            return Ok(Some(format!(
1214                "{krate}::Fill::RadialGradient {{ center: (0.5, 0.5), radius: 1.0, stops: vec![{}] }}",
1215                stops.join(",")
1216            )));
1217        }
1218        if parse_color(raw).is_ok() {
1219            return Ok(Some(format!(
1220                "{krate}::Fill::Solid({})",
1221                self.color_literal_expr(krate, raw)?
1222            )));
1223        }
1224        Ok(None)
1225    }
1226
1227    fn style_fill_color_expr(
1228        &self,
1229        krate: &str,
1230        mode: Mode,
1231        value: Option<&Value>,
1232    ) -> Result<Option<String>> {
1233        let Some(raw) = self.resolved_value_string(mode, value)? else {
1234            return Ok(None);
1235        };
1236        let raw = raw.trim();
1237        if raw.eq_ignore_ascii_case("transparent") {
1238            return Ok(Some(color_expr(krate, 0, 0, 0, 0)));
1239        }
1240        if parse_color(raw).is_ok() {
1241            return Ok(Some(self.color_literal_expr(krate, raw)?));
1242        }
1243        Ok(None)
1244    }
1245
1246    fn border_option_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1247        let Some(raw) = self.resolved_value_string(mode, value)? else {
1248            return Ok("None".into());
1249        };
1250        let raw = raw.trim();
1251        if raw.eq_ignore_ascii_case("none") || raw.eq_ignore_ascii_case("transparent") {
1252            return Ok("None".into());
1253        }
1254        let Some((width, color)) = parse_border(raw) else {
1255            return Ok("None".into());
1256        };
1257        Ok(format!(
1258            "Some({krate}::ComponentBorder {{ fill: {krate}::Fill::Solid({}), width: {} }})",
1259            self.color_literal_expr(krate, color)?,
1260            f32_lit(width)
1261        ))
1262    }
1263
1264    fn shadow_layers_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1265        let Some(raw) = self.resolved_value_string(mode, value)? else {
1266            return Ok("Vec::new()".into());
1267        };
1268        let layers = parse_shadow_layers(&raw);
1269        let exprs = layers
1270            .iter()
1271            .map(|layer| shadow_layer_expr(krate, layer))
1272            .collect::<Vec<_>>();
1273        Ok(format!("vec![{}]", exprs.join(",")))
1274    }
1275
1276    fn shadow_option_from_value_expr(
1277        &self,
1278        krate: &str,
1279        mode: Mode,
1280        value: Option<&Value>,
1281    ) -> Result<String> {
1282        let Some(raw) = self.resolved_value_string(mode, value)? else {
1283            return Ok("None".into());
1284        };
1285        let layers = parse_shadow_layers(&raw);
1286        if let Some(layer) = layers.iter().find(|layer| !layer.inset) {
1287            Ok(format!("Some({})", box_shadow_expr(krate, layer)))
1288        } else {
1289            Ok("None".into())
1290        }
1291    }
1292
1293    fn style_dimension_optional(
1294        &self,
1295        mode: Mode,
1296        value: Option<&Value>,
1297        fallback: f32,
1298    ) -> Result<f32> {
1299        let Some(raw) = self.resolved_value_string(mode, value)? else {
1300            return Ok(fallback);
1301        };
1302        parse_dimension(&raw)
1303            .or_else(|_| raw.parse::<f32>())
1304            .or(Ok(fallback))
1305    }
1306
1307    fn style_dimension_option_expr(&self, mode: Mode, value: Option<&Value>) -> Result<String> {
1308        let Some(raw) = self.resolved_value_string(mode, value)? else {
1309            return Ok("None".into());
1310        };
1311        if raw == "auto" {
1312            return Ok("None".into());
1313        }
1314        let Ok(dimension) = parse_dimension(&raw).or_else(|_| raw.parse::<f32>()) else {
1315            return Ok("None".into());
1316        };
1317        Ok(format!("Some({})", f32_lit(dimension)))
1318    }
1319
1320    fn style_u16_optional(&self, mode: Mode, value: Option<&Value>, fallback: u16) -> Result<u16> {
1321        let Some(raw) = self.resolved_value_string(mode, value)? else {
1322            return Ok(fallback);
1323        };
1324        Ok(raw.parse::<u16>().unwrap_or(fallback))
1325    }
1326
1327    fn style_u16_option_expr(&self, mode: Mode, value: Option<&Value>) -> Result<String> {
1328        let Some(raw) = self.resolved_value_string(mode, value)? else {
1329            return Ok("None".into());
1330        };
1331        Ok(raw
1332            .parse::<u16>()
1333            .map(|value| format!("Some({value})"))
1334            .unwrap_or_else(|_| "None".into()))
1335    }
1336
1337    fn padding_option_expr(&self, mode: Mode, value: Option<&Value>) -> Result<String> {
1338        let Some(raw) = self.resolved_value_string(mode, value)? else {
1339            return Ok("None".into());
1340        };
1341        let Ok(padding) = parse_padding(&raw) else {
1342            return Ok("None".into());
1343        };
1344        Ok(format!(
1345            "Some([{}, {}, {}, {}])",
1346            f32_lit(padding[0]),
1347            f32_lit(padding[1]),
1348            f32_lit(padding[2]),
1349            f32_lit(padding[3])
1350        ))
1351    }
1352
1353    fn motion_option_expr(&self, krate: &str, mode: Mode, value: Option<&Value>) -> Result<String> {
1354        let Some(raw) = self.resolved_value_string(mode, value)? else {
1355            return Ok("None".into());
1356        };
1357        let duration = raw
1358            .split_whitespace()
1359            .find_map(|part| parse_duration_ms(part).ok())
1360            .unwrap_or(self.duration_ms_optional("motion.duration.fast", 160)?);
1361        let easing = if raw.contains("cubic-bezier") {
1362            let start = raw.find("cubic-bezier").unwrap_or(0);
1363            let value = raw[start..].split_whitespace().next().unwrap_or("linear");
1364            easing_expr(krate, value.trim_end_matches(','))
1365        } else if raw.contains("ease") {
1366            format!("{krate}::EasingCurve::Ease")
1367        } else {
1368            self.easing_expr(krate, "motion.easing.standard")?
1369        };
1370        Ok(format!(
1371            "Some({krate}::ComponentMotion {{ duration_ms: {duration}, easing: {easing} }})"
1372        ))
1373    }
1374
1375    fn resolved_value_string(&self, mode: Mode, value: Option<&Value>) -> Result<Option<String>> {
1376        let Some(value) = value else {
1377            return Ok(None);
1378        };
1379        match value {
1380            Value::String(raw) => Ok(Some(self.resolve_refs_in_string_for_mode(raw, mode)?)),
1381            Value::Number(number) => Ok(Some(number.to_string())),
1382            _ => Ok(None),
1383        }
1384    }
1385
1386    fn design_tokens_expr(&self, krate: &str) -> Result<String> {
1387        let mut items = Vec::new();
1388        for path in self.tokens.paths() {
1389            let token = self.tokens.get_raw(path).unwrap();
1390            let kind = token.kind.clone().unwrap_or_else(|| "custom".into());
1391            let resolved = self.resolve_token_string(path)?;
1392            let value_expr = self.design_value_expr(krate, &kind, &resolved)?;
1393            items.push(format!(
1394                "{krate}::DesignToken {{ path: {}, kind: {}, value: {value_expr} }}",
1395                rust_string(path),
1396                rust_string(&kind),
1397            ));
1398        }
1399        Ok(format!(
1400            "{krate}::DesignTokenSet {{ tokens: vec![{}] }}",
1401            items.join(",")
1402        ))
1403    }
1404
1405    fn components_expr(&self, krate: &str) -> Result<String> {
1406        let Some(obj) = self.dsp.get("components").and_then(Value::as_object) else {
1407            return Ok("Vec::new()".into());
1408        };
1409        let mut components = Vec::new();
1410        for (name, value) in obj {
1411            components.push(self.component_spec_expr(krate, name, value)?);
1412        }
1413        Ok(format!("vec![{}]", components.join(",")))
1414    }
1415
1416    fn component_spec_expr(&self, krate: &str, name: &str, value: &Value) -> Result<String> {
1417        let description = value
1418            .get("$description")
1419            .and_then(Value::as_str)
1420            .unwrap_or("");
1421        let anatomy = value
1422            .get("anatomy")
1423            .and_then(Value::as_array)
1424            .map(|arr| {
1425                arr.iter()
1426                    .filter_map(Value::as_str)
1427                    .map(rust_string)
1428                    .collect::<Vec<_>>()
1429            })
1430            .unwrap_or_default();
1431        let props = self.object_properties_expr(krate, value)?;
1432        Ok(format!(
1433            "{krate}::DesignComponentSpec {{ name: {}, description: {}, anatomy: vec![{}], properties: {props} }}",
1434            rust_string(name), rust_string(description), anatomy.join(",")
1435        ))
1436    }
1437
1438    fn patterns_expr(&self, krate: &str) -> Result<String> {
1439        let Some(obj) = self.dsp.get("patterns").and_then(Value::as_object) else {
1440            return Ok("Vec::new()".into());
1441        };
1442        let mut patterns = Vec::new();
1443        for (name, value) in obj {
1444            let description = value
1445                .get("$description")
1446                .and_then(Value::as_str)
1447                .unwrap_or("");
1448            let props = self.object_properties_expr(krate, value)?;
1449            patterns.push(format!(
1450                "{krate}::DesignPatternSpec {{ name: {}, description: {}, properties: {props} }}",
1451                rust_string(name),
1452                rust_string(description)
1453            ));
1454        }
1455        Ok(format!("vec![{}]", patterns.join(",")))
1456    }
1457
1458    fn assets_expr(&self, krate: &str) -> Result<String> {
1459        let assets = self.dsp.get("assets").unwrap_or(&Value::Null);
1460        let mut logos = Vec::new();
1461        if let Some(arr) = assets.get("logos").and_then(Value::as_array) {
1462            for item in arr {
1463                logos.push(asset_expr(krate, item));
1464            }
1465        }
1466        let mut fonts = Vec::new();
1467        if let Some(arr) = assets.get("fonts").and_then(Value::as_array) {
1468            for item in arr {
1469                fonts.push(asset_expr(krate, item));
1470            }
1471        }
1472        Ok(format!(
1473            "{krate}::DesignAssetManifest {{ logos: vec![{}], fonts: vec![{}] }}",
1474            logos.join(","),
1475            fonts.join(",")
1476        ))
1477    }
1478
1479    fn object_properties_expr(&self, krate: &str, value: &Value) -> Result<String> {
1480        let Some(obj) = value.as_object() else {
1481            return Ok("Vec::new()".into());
1482        };
1483        let mut props = Vec::new();
1484        for (key, value) in obj {
1485            if key.starts_with('$') || key == "anatomy" || key == "tab_anatomy" {
1486                continue;
1487            }
1488            let value_expr = self.any_design_value_expr(krate, value)?;
1489            props.push(format!(
1490                "{krate}::DesignProperty {{ name: {}, value: {value_expr} }}",
1491                rust_string(key)
1492            ));
1493        }
1494        Ok(format!("vec![{}]", props.join(",")))
1495    }
1496
1497    fn any_design_value_expr(&self, krate: &str, value: &Value) -> Result<String> {
1498        match value {
1499            Value::String(s) => {
1500                let resolved = self.resolve_refs_in_string(s)?;
1501                self.design_value_expr(krate, "custom", &resolved)
1502            }
1503            Value::Number(n) => Ok(format!(
1504                "{krate}::DesignValue::Number({})",
1505                f32_lit(n.as_f64().unwrap_or_default() as f32)
1506            )),
1507            Value::Bool(b) => Ok(format!("{krate}::DesignValue::Bool({b})")),
1508            Value::Array(arr) => {
1509                let values = arr
1510                    .iter()
1511                    .map(|v| self.any_design_value_expr(krate, v))
1512                    .collect::<Result<Vec<_>>>()?;
1513                Ok(format!(
1514                    "{krate}::DesignValue::List(vec![{}])",
1515                    values.join(",")
1516                ))
1517            }
1518            Value::Object(obj) => {
1519                if let Some(v) = obj.get("value").or_else(|| obj.get("$value")) {
1520                    return self.any_design_value_expr(krate, v);
1521                }
1522                let mut props = Vec::new();
1523                for (key, child) in obj {
1524                    if key.starts_with('$') {
1525                        continue;
1526                    }
1527                    let value_expr = self.any_design_value_expr(krate, child)?;
1528                    props.push(format!(
1529                        "{krate}::DesignProperty {{ name: {}, value: {value_expr} }}",
1530                        rust_string(key)
1531                    ));
1532                }
1533                Ok(format!(
1534                    "{krate}::DesignValue::Object(vec![{}])",
1535                    props.join(",")
1536                ))
1537            }
1538            Value::Null => Ok(format!("{krate}::DesignValue::None")),
1539        }
1540    }
1541
1542    fn design_value_expr(&self, krate: &str, kind: &str, value: &str) -> Result<String> {
1543        if kind == "color" || value.trim_start().starts_with('#') || value.starts_with("rgb") {
1544            if let Ok((r, g, b, a)) = parse_color(value) {
1545                return Ok(format!(
1546                    "{krate}::DesignValue::Color({})",
1547                    color_expr(krate, r, g, b, a)
1548                ));
1549            }
1550        }
1551        if kind == "dimension" || kind == "size" || value.trim_end().ends_with("px") {
1552            if let Ok(px) = parse_dimension(value) {
1553                return Ok(format!("{krate}::DesignValue::Dimension({})", f32_lit(px)));
1554            }
1555        }
1556        if kind == "duration" || value.trim_end().ends_with("ms") {
1557            if let Ok(ms) = parse_duration_ms(value) {
1558                return Ok(format!("{krate}::DesignValue::DurationMs({ms})"));
1559            }
1560        }
1561        if kind == "shadow" || value.contains("rgba(") || value == "none" {
1562            let layers = parse_shadow_layers(value);
1563            let exprs = layers
1564                .iter()
1565                .map(|layer| shadow_layer_expr(krate, layer))
1566                .collect::<Vec<_>>();
1567            return Ok(format!(
1568                "{krate}::DesignValue::Shadow(vec![{}])",
1569                exprs.join(",")
1570            ));
1571        }
1572        if kind == "cubicBezier"
1573            || value.starts_with("cubic-bezier")
1574            || matches!(value, "linear" | "ease")
1575        {
1576            return Ok(format!(
1577                "{krate}::DesignValue::Easing({})",
1578                easing_expr(krate, value)
1579            ));
1580        }
1581        if let Ok(num) = value.parse::<f32>() {
1582            return Ok(format!("{krate}::DesignValue::Number({})", f32_lit(num)));
1583        }
1584        Ok(format!(
1585            "{krate}::DesignValue::Text({})",
1586            rust_string(value)
1587        ))
1588    }
1589
1590    fn color_expr(&self, krate: &str, path: &str) -> Result<String> {
1591        let value = self.resolve_token_string(path)?;
1592        self.color_literal_expr(krate, &value)
1593    }
1594
1595    fn color_expr_optional(&self, krate: &str, path: &str, fallback: &str) -> Result<String> {
1596        if self.tokens.contains(path) {
1597            self.color_expr(krate, path)
1598        } else {
1599            self.color_literal_expr(krate, fallback)
1600        }
1601    }
1602
1603    fn color_literal_expr(&self, krate: &str, value: &str) -> Result<String> {
1604        let (r, g, b, a) = parse_color(value).with_context(|| format!("invalid color {value}"))?;
1605        Ok(color_expr(krate, r, g, b, a))
1606    }
1607
1608    fn dimension(&self, path: &str) -> Result<f32> {
1609        let value = self.resolve_token_string(path)?;
1610        parse_dimension(&value).with_context(|| format!("invalid dimension token {path} = {value}"))
1611    }
1612
1613    fn dimension_optional(&self, path: &str, fallback: f32) -> Result<f32> {
1614        if self.tokens.contains(path) {
1615            self.dimension(path)
1616        } else {
1617            Ok(fallback)
1618        }
1619    }
1620
1621    fn number_optional(&self, path: &str, fallback: f32) -> Result<f32> {
1622        if !self.tokens.contains(path) {
1623            return Ok(fallback);
1624        }
1625        let value = self.resolve_token_string(path)?;
1626        value
1627            .parse::<f32>()
1628            .or_else(|_| parse_dimension(&value))
1629            .with_context(|| format!("invalid number token {path} = {value}"))
1630    }
1631
1632    fn duration_ms_optional(&self, path: &str, fallback: u64) -> Result<u64> {
1633        if !self.tokens.contains(path) {
1634            return Ok(fallback);
1635        }
1636        let value = self.resolve_token_string(path)?;
1637        parse_duration_ms(&value)
1638            .with_context(|| format!("invalid duration token {path} = {value}"))
1639    }
1640
1641    fn string_token_optional(&self, path: &str, fallback: &str) -> Result<String> {
1642        if !self.tokens.contains(path) {
1643            return Ok(fallback.to_string());
1644        }
1645        self.resolve_token_string(path)
1646    }
1647
1648    fn shadow_option_expr(&self, krate: &str, path: &str) -> Result<String> {
1649        let value = if self.tokens.contains(path) {
1650            self.resolve_token_string(path)?
1651        } else {
1652            return Ok("None".into());
1653        };
1654        let layers = parse_shadow_layers(&value);
1655        if let Some(layer) = layers.iter().find(|layer| !layer.inset) {
1656            Ok(format!("Some({})", box_shadow_expr(krate, layer)))
1657        } else {
1658            Ok("None".into())
1659        }
1660    }
1661
1662    fn easing_expr(&self, krate: &str, path: &str) -> Result<String> {
1663        let value = if self.tokens.contains(path) {
1664            self.resolve_token_string(path)?
1665        } else {
1666            "linear".into()
1667        };
1668        Ok(easing_expr(krate, &value))
1669    }
1670
1671    fn dsp_dimension_optional(&self, pointer: &str, fallback: f32) -> Result<f32> {
1672        let Some(value) = self.dsp.pointer(pointer) else {
1673            return Ok(fallback);
1674        };
1675        let raw = match value {
1676            Value::String(s) => self.resolve_refs_in_string(s)?,
1677            Value::Number(n) => n.to_string(),
1678            _ => return Ok(fallback),
1679        };
1680        parse_dimension(&raw)
1681            .or_else(|_| raw.parse::<f32>())
1682            .or(Ok(fallback))
1683    }
1684
1685    fn resolve_token_string(&self, path: &str) -> Result<String> {
1686        self.tokens.resolve(path)
1687    }
1688
1689    fn resolve_refs_in_string(&self, raw: &str) -> Result<String> {
1690        let mut out = String::new();
1691        let mut rest = raw;
1692        while let Some(start) = rest.find('{') {
1693            let (before, after_start) = rest.split_at(start);
1694            out.push_str(before);
1695            let after_start = &after_start[1..];
1696            let Some(end) = after_start.find('}') else {
1697                bail!("unclosed token reference in {raw}");
1698            };
1699            let token = &after_start[..end];
1700            if self.tokens.contains(token) {
1701                out.push_str(&self.resolve_token_string(token)?);
1702            } else {
1703                out.push('{');
1704                out.push_str(token);
1705                out.push('}');
1706            }
1707            rest = &after_start[end + 1..];
1708        }
1709        out.push_str(rest);
1710        Ok(out)
1711    }
1712
1713    fn resolve_refs_in_string_for_mode(&self, raw: &str, mode: Mode) -> Result<String> {
1714        let mut out = String::new();
1715        let mut rest = raw;
1716        while let Some(start) = rest.find('{') {
1717            let (before, after_start) = rest.split_at(start);
1718            out.push_str(before);
1719            let after_start = &after_start[1..];
1720            let Some(end) = after_start.find('}') else {
1721                bail!("unclosed token reference in {raw}");
1722            };
1723            let mut token = after_start[..end].to_string();
1724            match mode {
1725                Mode::Light => {}
1726                Mode::Dark => {
1727                    if let Some(suffix) = token.strip_prefix("color.light.") {
1728                        let dark = format!("color.dark.{suffix}");
1729                        if self.tokens.contains(&dark) {
1730                            token = dark;
1731                        }
1732                    }
1733                }
1734            }
1735            if self.tokens.contains(&token) {
1736                out.push_str(&self.resolve_token_string(&token)?);
1737            } else {
1738                out.push('{');
1739                out.push_str(&token);
1740                out.push('}');
1741            }
1742            rest = &after_start[end + 1..];
1743        }
1744        out.push_str(rest);
1745        Ok(out)
1746    }
1747}
1748
1749#[derive(Debug, Clone, Copy)]
1750enum Mode {
1751    Light,
1752    Dark,
1753}
1754impl Mode {
1755    fn as_str(self) -> &'static str {
1756        match self {
1757            Self::Light => "Light",
1758            Self::Dark => "Dark",
1759        }
1760    }
1761
1762    fn color_name(self) -> &'static str {
1763        match self {
1764            Self::Light => "light",
1765            Self::Dark => "dark",
1766        }
1767    }
1768}
1769
1770#[derive(Debug, Clone)]
1771struct Token {
1772    value: String,
1773    kind: Option<String>,
1774}
1775
1776#[derive(Debug, Clone)]
1777struct TokenStore {
1778    tokens: BTreeMap<String, Token>,
1779}
1780
1781impl TokenStore {
1782    fn from_value(value: &Value) -> Result<Self> {
1783        let mut tokens = BTreeMap::new();
1784        flatten_tokens(value, String::new(), &mut tokens)?;
1785        Ok(Self { tokens })
1786    }
1787
1788    fn contains(&self, path: &str) -> bool {
1789        self.tokens.contains_key(path)
1790    }
1791    fn paths(&self) -> impl Iterator<Item = &str> {
1792        self.tokens.keys().map(String::as_str)
1793    }
1794    fn get_raw(&self, path: &str) -> Option<&Token> {
1795        self.tokens.get(path)
1796    }
1797
1798    fn resolve(&self, path: &str) -> Result<String> {
1799        self.resolve_inner(path, &mut BTreeSet::new())
1800    }
1801
1802    fn resolve_inner(&self, path: &str, seen: &mut BTreeSet<String>) -> Result<String> {
1803        if !seen.insert(path.to_string()) {
1804            bail!("cyclic token reference involving {path}");
1805        }
1806        let token = self
1807            .tokens
1808            .get(path)
1809            .ok_or_else(|| anyhow!("unknown token reference {{{path}}}"))?;
1810        let value = token.value.trim();
1811        if let Some(inner) = value.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
1812            let resolved = self.resolve_inner(inner, seen)?;
1813            seen.remove(path);
1814            return Ok(resolved);
1815        }
1816        let mut out = String::new();
1817        let mut rest = value;
1818        while let Some(start) = rest.find('{') {
1819            let (before, after_start) = rest.split_at(start);
1820            out.push_str(before);
1821            let after_start = &after_start[1..];
1822            let Some(end) = after_start.find('}') else {
1823                bail!("unclosed token reference in {value}");
1824            };
1825            let inner = &after_start[..end];
1826            out.push_str(&self.resolve_inner(inner, seen)?);
1827            rest = &after_start[end + 1..];
1828        }
1829        out.push_str(rest);
1830        seen.remove(path);
1831        Ok(out)
1832    }
1833}
1834
1835fn flatten_tokens(value: &Value, prefix: String, out: &mut BTreeMap<String, Token>) -> Result<()> {
1836    let Some(obj) = value.as_object() else {
1837        return Ok(());
1838    };
1839    for (key, child) in obj {
1840        if key.starts_with('$') {
1841            continue;
1842        }
1843        let path = if prefix.is_empty() {
1844            key.clone()
1845        } else {
1846            format!("{prefix}.{key}")
1847        };
1848        if let Some(child_obj) = child.as_object() {
1849            let value_field = child_obj.get("value").or_else(|| child_obj.get("$value"));
1850            if let Some(raw_value) = value_field {
1851                let token_value = match raw_value {
1852                    Value::String(s) => s.clone(),
1853                    Value::Number(n) => n.to_string(),
1854                    Value::Bool(b) => b.to_string(),
1855                    other => other.to_string(),
1856                };
1857                let kind = child_obj
1858                    .get("type")
1859                    .or_else(|| child_obj.get("$type"))
1860                    .and_then(Value::as_str)
1861                    .map(ToOwned::to_owned);
1862                out.insert(
1863                    path,
1864                    Token {
1865                        value: token_value,
1866                        kind,
1867                    },
1868                );
1869            } else {
1870                flatten_tokens(child, path, out)?;
1871            }
1872        }
1873    }
1874    Ok(())
1875}
1876
1877#[derive(Debug, Clone)]
1878struct ShadowLayer {
1879    color: (u8, u8, u8, u8),
1880    offset_x: f32,
1881    offset_y: f32,
1882    blur_radius: f32,
1883    spread_radius: f32,
1884    inset: bool,
1885}
1886
1887fn parse_shadow_layers(value: &str) -> Vec<ShadowLayer> {
1888    if value.trim() == "none" {
1889        return Vec::new();
1890    }
1891    split_css_layers(value)
1892        .into_iter()
1893        .filter_map(|layer| parse_shadow_layer(layer.trim()).ok())
1894        .collect()
1895}
1896
1897fn parse_shadow_layer(layer: &str) -> Result<ShadowLayer> {
1898    let inset = layer.contains("inset");
1899    let color_start = layer
1900        .find("rgba(")
1901        .or_else(|| layer.find("rgb("))
1902        .ok_or_else(|| anyhow!("shadow has no rgb/rgba color: {layer}"))?;
1903    let color_end = layer[color_start..]
1904        .find(')')
1905        .ok_or_else(|| anyhow!("unterminated rgb/rgba in shadow: {layer}"))?
1906        + color_start;
1907    let color_raw = &layer[color_start..=color_end];
1908    let color = parse_rgb_color(color_raw)?;
1909    let nums = layer[..color_start]
1910        .replace("inset", "")
1911        .split_whitespace()
1912        .filter_map(|part| parse_dimension(part).ok())
1913        .collect::<Vec<_>>();
1914    Ok(ShadowLayer {
1915        color,
1916        offset_x: *nums.get(0).unwrap_or(&0.0),
1917        offset_y: *nums.get(1).unwrap_or(&0.0),
1918        blur_radius: *nums.get(2).unwrap_or(&0.0),
1919        spread_radius: *nums.get(3).unwrap_or(&0.0),
1920        inset,
1921    })
1922}
1923
1924fn split_css_layers(value: &str) -> Vec<&str> {
1925    let mut layers = Vec::new();
1926    let mut depth = 0usize;
1927    let mut start = 0usize;
1928    for (idx, ch) in value.char_indices() {
1929        match ch {
1930            '(' => depth += 1,
1931            ')' => depth = depth.saturating_sub(1),
1932            ',' if depth == 0 => {
1933                layers.push(&value[start..idx]);
1934                start = idx + 1;
1935            }
1936            _ => {}
1937        }
1938    }
1939    layers.push(&value[start..]);
1940    layers
1941}
1942
1943fn parse_hex_color(value: &str) -> Result<(u8, u8, u8, u8)> {
1944    let hex = value
1945        .trim()
1946        .strip_prefix('#')
1947        .ok_or_else(|| anyhow!("not a hex color: {value}"))?;
1948    match hex.len() {
1949        3 => {
1950            let r = u8::from_str_radix(&hex[0..1].repeat(2), 16)?;
1951            let g = u8::from_str_radix(&hex[1..2].repeat(2), 16)?;
1952            let b = u8::from_str_radix(&hex[2..3].repeat(2), 16)?;
1953            Ok((r, g, b, 255))
1954        }
1955        6 | 8 => {
1956            let r = u8::from_str_radix(&hex[0..2], 16)?;
1957            let g = u8::from_str_radix(&hex[2..4], 16)?;
1958            let b = u8::from_str_radix(&hex[4..6], 16)?;
1959            let a = if hex.len() == 8 {
1960                u8::from_str_radix(&hex[6..8], 16)?
1961            } else {
1962                255
1963            };
1964            Ok((r, g, b, a))
1965        }
1966        _ => bail!("invalid hex color length: {value}"),
1967    }
1968}
1969
1970fn parse_color(value: &str) -> Result<(u8, u8, u8, u8)> {
1971    let value = value.trim();
1972    if value.eq_ignore_ascii_case("transparent") {
1973        return Ok((0, 0, 0, 0));
1974    }
1975    if value.starts_with('#') {
1976        return parse_hex_color(value);
1977    }
1978    if value.starts_with("rgb(") || value.starts_with("rgba(") {
1979        return parse_rgb_color(value);
1980    }
1981    bail!("unsupported color value: {value}")
1982}
1983
1984fn parse_rgb_color(value: &str) -> Result<(u8, u8, u8, u8)> {
1985    let inner = value
1986        .trim()
1987        .trim_start_matches("rgba(")
1988        .trim_start_matches("rgb(")
1989        .trim_end_matches(')');
1990    let parts = inner.split(',').map(str::trim).collect::<Vec<_>>();
1991    let r = parts
1992        .get(0)
1993        .ok_or_else(|| anyhow!("missing red in {value}"))?
1994        .parse::<u8>()?;
1995    let g = parts
1996        .get(1)
1997        .ok_or_else(|| anyhow!("missing green in {value}"))?
1998        .parse::<u8>()?;
1999    let b = parts
2000        .get(2)
2001        .ok_or_else(|| anyhow!("missing blue in {value}"))?
2002        .parse::<u8>()?;
2003    let a = if let Some(alpha) = parts.get(3) {
2004        let alpha = alpha.parse::<f32>()?.clamp(0.0, 1.0);
2005        (alpha * 255.0).round() as u8
2006    } else {
2007        255
2008    };
2009    Ok((r, g, b, a))
2010}
2011
2012fn parse_border(value: &str) -> Option<(f32, &str)> {
2013    let mut width = None;
2014    let mut color_start = None;
2015    for part in value.split_whitespace() {
2016        if width.is_none() {
2017            if let Ok(px) = parse_dimension(part) {
2018                width = Some(px);
2019                continue;
2020            }
2021        }
2022        if part.starts_with('#') || part.starts_with("rgb") || part == "transparent" {
2023            color_start = value.find(part);
2024            break;
2025        }
2026    }
2027    match (width, color_start) {
2028        (Some(width), Some(start)) => Some((width, value[start..].trim())),
2029        _ => None,
2030    }
2031}
2032
2033fn parse_padding(value: &str) -> Result<[f32; 4]> {
2034    let parts = value
2035        .split_whitespace()
2036        .map(parse_dimension)
2037        .collect::<Result<Vec<_>>>()?;
2038    let (top, right, bottom, left) = match parts.as_slice() {
2039        [all] => (*all, *all, *all, *all),
2040        [vertical, horizontal] => (*vertical, *horizontal, *vertical, *horizontal),
2041        [top, horizontal, bottom] => (*top, *horizontal, *bottom, *horizontal),
2042        [top, right, bottom, left, ..] => (*top, *right, *bottom, *left),
2043        _ => (0.0, 0.0, 0.0, 0.0),
2044    };
2045    Ok([left, right, top, bottom])
2046}
2047
2048fn gradient_stops(value: &str) -> Vec<&str> {
2049    let inner = value
2050        .split_once('(')
2051        .and_then(|(_, rest)| rest.strip_suffix(')'))
2052        .unwrap_or(value);
2053    split_css_layers(inner)
2054        .into_iter()
2055        .map(str::trim)
2056        .filter(|part| part.starts_with('#') || part.starts_with("rgb") || *part == "transparent")
2057        .map(|part| part.split_whitespace().next().unwrap_or(part))
2058        .collect()
2059}
2060
2061fn parse_dimension(value: &str) -> Result<f32> {
2062    let trimmed = value.trim().trim_matches('"');
2063    if let Some(px) = trimmed.strip_suffix("px") {
2064        return Ok(px.trim().parse()?);
2065    }
2066    if let Some(em) = trimmed.strip_suffix("em") {
2067        return Ok(em.trim().parse()?);
2068    }
2069    Ok(trimmed.parse()?)
2070}
2071
2072fn field<'a>(value: &'a Value, name: &str) -> Option<&'a Value> {
2073    value.get(name)
2074}
2075
2076fn component_size_variant(krate: &str, name: &str) -> Option<String> {
2077    let variant = match name {
2078        "sm" => "Sm",
2079        "md" => "Md",
2080        "lg" => "Lg",
2081        "xl" => "Xl",
2082        _ => return None,
2083    };
2084    Some(format!("{krate}::ComponentSize::{variant}"))
2085}
2086
2087fn button_hierarchy_variant(krate: &str, name: &str) -> Option<String> {
2088    let variant = match name {
2089        "primary" => "Primary",
2090        "secondary_color" => "SecondaryColor",
2091        "secondary_gray" => "SecondaryGray",
2092        "tertiary_color" => "TertiaryColor",
2093        "tertiary_gray" => "TertiaryGray",
2094        "link_color" => "LinkColor",
2095        "link_gray" => "LinkGray",
2096        "destructive" => "Destructive",
2097        _ => return None,
2098    };
2099    Some(format!("{krate}::ButtonHierarchy::{variant}"))
2100}
2101
2102fn badge_tone_variant(krate: &str, name: &str) -> Option<String> {
2103    let variant = match name {
2104        "brand" => "Brand",
2105        "gray" => "Gray",
2106        "success" => "Success",
2107        "warning" => "Warning",
2108        "error" => "Error",
2109        "blue" => "Blue",
2110        "orange" => "Orange",
2111        _ => return None,
2112    };
2113    Some(format!("{krate}::BadgeTone::{variant}"))
2114}
2115
2116fn card_pattern_variant(krate: &str, name: &str) -> Option<String> {
2117    let variant = match name {
2118        "plain" => "Plain",
2119        "raised" => "Raised",
2120        "tinted" => "Tinted",
2121        "elevated" => "Elevated",
2122        _ => return None,
2123    };
2124    Some(format!("{krate}::CardPattern::{variant}"))
2125}
2126
2127fn feature_icon_tone_variant(krate: &str, name: &str) -> Option<String> {
2128    let variant = match name {
2129        "brand" => "Brand",
2130        "gray" => "Gray",
2131        "blue" => "Blue",
2132        "orange" => "Orange",
2133        _ => return None,
2134    };
2135    Some(format!("{krate}::FeatureIconTone::{variant}"))
2136}
2137
2138fn parse_duration_ms(value: &str) -> Result<u64> {
2139    let trimmed = value.trim();
2140    if let Some(ms) = trimmed.strip_suffix("ms") {
2141        return Ok(ms.trim().parse()?);
2142    }
2143    if let Some(s) = trimmed.strip_suffix('s') {
2144        return Ok((s.trim().parse::<f32>()? * 1000.0).round() as u64);
2145    }
2146    Ok(trimmed.parse()?)
2147}
2148
2149fn color_expr(krate: &str, r: u8, g: u8, b: u8, a: u8) -> String {
2150    format!("{krate}::Color {{ r: {r}, g: {g}, b: {b}, a: {a} }}")
2151}
2152
2153fn box_shadow_expr(krate: &str, layer: &ShadowLayer) -> String {
2154    format!(
2155        "{krate}::BoxShadow {{ color: {}, offset: ({}, {}), blur_radius: {} }}",
2156        color_expr(
2157            krate,
2158            layer.color.0,
2159            layer.color.1,
2160            layer.color.2,
2161            layer.color.3
2162        ),
2163        f32_lit(layer.offset_x),
2164        f32_lit(layer.offset_y),
2165        f32_lit(layer.blur_radius)
2166    )
2167}
2168
2169fn shadow_layer_expr(krate: &str, layer: &ShadowLayer) -> String {
2170    format!(
2171        "{krate}::ShadowLayer {{ color: {}, offset: ({}, {}), blur_radius: {}, spread_radius: {}, inset: {} }}",
2172        color_expr(krate, layer.color.0, layer.color.1, layer.color.2, layer.color.3),
2173        f32_lit(layer.offset_x),
2174        f32_lit(layer.offset_y),
2175        f32_lit(layer.blur_radius),
2176        f32_lit(layer.spread_radius),
2177        layer.inset
2178    )
2179}
2180
2181fn easing_expr(krate: &str, value: &str) -> String {
2182    let value = value.trim();
2183    if let Some(inner) = value
2184        .strip_prefix("cubic-bezier(")
2185        .and_then(|s| s.strip_suffix(')'))
2186    {
2187        let nums = inner
2188            .split(',')
2189            .filter_map(|n| n.trim().parse::<f32>().ok())
2190            .collect::<Vec<_>>();
2191        if nums.len() == 4 {
2192            return format!(
2193                "{krate}::EasingCurve::CubicBezier({}, {}, {}, {})",
2194                f32_lit(nums[0]),
2195                f32_lit(nums[1]),
2196                f32_lit(nums[2]),
2197                f32_lit(nums[3])
2198            );
2199        }
2200    }
2201    match value {
2202        "linear" => format!("{krate}::EasingCurve::Linear"),
2203        "ease" => format!("{krate}::EasingCurve::Ease"),
2204        _ => format!("{krate}::EasingCurve::Named({})", rust_string(value)),
2205    }
2206}
2207
2208fn asset_expr(krate: &str, item: &Value) -> String {
2209    let id = item
2210        .get("id")
2211        .and_then(Value::as_str)
2212        .or_else(|| item.get("family").and_then(Value::as_str))
2213        .unwrap_or("");
2214    let path = item.get("path").and_then(Value::as_str).unwrap_or("");
2215    let format = item.get("format").and_then(Value::as_str).unwrap_or("");
2216    format!(
2217        "{krate}::DesignAsset {{ id: {}, path: {}, format: {} }}",
2218        rust_string(id),
2219        rust_string(path),
2220        rust_string(format)
2221    )
2222}
2223
2224fn rust_string(value: &str) -> String {
2225    format!("{:?}.to_string()", value)
2226}
2227
2228fn f32_lit(value: f32) -> String {
2229    if value.is_finite() && value.fract() == 0.0 {
2230        format!("{value:.1}")
2231    } else {
2232        let mut out = value.to_string();
2233        if !out.contains('.') && !out.contains('e') && !out.contains('E') {
2234            out.push_str(".0");
2235        }
2236        out
2237    }
2238}