import 'dart:io'; import 'package:clide_app/kernel/src/theme/palette.dart'; import 'package:clide_app/kernel/src/theme/semantic.dart'; import 'package:flutter/services.dart'; import 'package:yaml/yaml.dart'; class ThemeDefinition { const ThemeDefinition({ required this.name, required this.displayName, required this.dark, required this.palette, this.semanticOverride, this.surfaceOverride, this.extensionOverride, }); final String name; final String displayName; final bool dark; final Palette palette; final SemanticRoles? semanticOverride; final Map? surfaceOverride; final Map? extensionOverride; } class ThemeLoader { const ThemeLoader(); ThemeDefinition fromYamlString(String text, {String? fallbackName}) { final doc = loadYaml(text); if (doc is! Map) { throw FormatException('Theme root is not a map'); } final name = (doc['name'] as String?) ?? fallbackName; if (name == null || name.isEmpty) { throw const FormatException('Theme missing `name`'); } final displayName = (doc['display_name'] as String?) ?? name; final dark = (doc['dark'] as bool?) ?? true; final paletteYaml = doc['palette']; if (paletteYaml is! Map) { throw const FormatException('Theme missing `palette`'); } final palette = _parsePalette(paletteYaml); final semantic = doc['semantic']; final surface = doc['surface']; final extension = doc['extension']; return ThemeDefinition( name: name, displayName: displayName, dark: dark, palette: palette, semanticOverride: semantic is Map ? _parseSemantic(semantic, palette) : null, surfaceOverride: surface is Map ? _parseRefMap(surface) : null, extensionOverride: extension is Map ? _parseRefMap(extension) : null, ); } Future fromAsset( AssetBundle bundle, String assetPath) async { final txt = await bundle.loadString(assetPath); final fallback = assetPath.split('/').last.replaceAll('.yaml', ''); return fromYamlString(txt, fallbackName: fallback); } Future fromFile(File f) async { final txt = await f.readAsString(); final fallback = f.uri.pathSegments.last.replaceAll('.yaml', ''); return fromYamlString(txt, fallbackName: fallback); } } Palette _parsePalette(Map src) { final colors = {}; src.forEach((k, v) { if (v is! String) return; final c = Palette.parseHex(v); if (c != null) colors['$k'] = c; }); return Palette(colors); } SemanticRoles _parseSemantic(Map src, Palette palette) { final roles = {}; src.forEach((k, v) { if (v is! String) return; final resolved = v.startsWith('#') ? Palette.parseHex(v) : palette.lookup(v); if (resolved != null) roles['$k'] = resolved; }); return SemanticRoles(roles); } Map _parseRefMap(Map src) { final out = {}; src.forEach((k, v) { if (v is String) out['$k'] = v; }); return out; }