#
tokens: 45698/50000 7/64 files (page 3/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 4. Use http://codebase.md/mhmzdev/figma-flutter-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   └── config.json
├── .github
│   └── workflows
│       └── release.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── docs
│   ├── cursor_rules_example.md
│   ├── figma-flutter-mcp.md
│   ├── figma-framework-mcp.md
│   ├── getting-started.md
│   └── images
│       ├── button.png
│       ├── figma-flutter-mcp.png
│       ├── screen.png
│       ├── svg.gif
│       ├── svgs_clean.gif
│       ├── text-style-frame.png
│       └── theme-frame.png
├── LICENSE.md
├── package.json
├── README.ja.md
├── README.ko.md
├── README.md
├── README.zh-cn.md
├── README.zh-tw.md
├── src
│   ├── cli.ts
│   ├── config.ts
│   ├── extractors
│   │   ├── colors
│   │   │   ├── core.ts
│   │   │   ├── extractor.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   ├── components
│   │   │   ├── core.ts
│   │   │   ├── deduplicated-extractor.ts
│   │   │   ├── extractor.ts
│   │   │   ├── index.ts
│   │   │   ├── types.ts
│   │   │   └── variant-analyzer.ts
│   │   ├── flutter
│   │   │   ├── global-vars.ts
│   │   │   ├── index.ts
│   │   │   ├── style-library.ts
│   │   │   └── style-merger.ts
│   │   ├── screens
│   │   │   ├── core.ts
│   │   │   ├── extractor.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   └── typography
│   │       ├── core.ts
│   │       ├── extractor.ts
│   │       ├── index.ts
│   │       └── types.ts
│   ├── figma-config.ts
│   ├── server.ts
│   ├── services
│   │   └── figma.ts
│   ├── tools
│   │   ├── flutter
│   │   │   ├── assets
│   │   │   │   ├── asset-manager.ts
│   │   │   │   ├── assets.ts
│   │   │   │   └── svg-assets.ts
│   │   │   ├── components
│   │   │   │   ├── component-tool.ts
│   │   │   │   ├── deduplicated-helpers.ts
│   │   │   │   └── helpers.ts
│   │   │   ├── index.ts
│   │   │   ├── screens
│   │   │   │   ├── helpers.ts
│   │   │   │   └── screen-tool.ts
│   │   │   ├── semantic-detection.ts
│   │   │   ├── theme
│   │   │   │   ├── colors
│   │   │   │   │   ├── theme-generator.ts
│   │   │   │   │   └── theme-tool.ts
│   │   │   │   └── typography
│   │   │   │       ├── typography-generator.ts
│   │   │   │       └── typography-tool.ts
│   │   │   └── visual-context.ts
│   │   └── index.ts
│   ├── types
│   │   ├── errors.ts
│   │   ├── figma.ts
│   │   └── flutter.ts
│   └── utils
│       ├── figma-url-parser.ts
│       ├── helpers.ts
│       ├── logger.ts
│       ├── retry.ts
│       └── validation.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/src/extractors/flutter/style-library.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/extractors/flutter/style-library.mts
  2 | 
  3 | import { Logger } from '../../utils/logger.js';
  4 | 
  5 | export interface FlutterStyleDefinition {
  6 |   id: string;
  7 |   category: 'decoration' | 'text' | 'layout' | 'padding';
  8 |   properties: Record<string, any>;
  9 |   flutterCode: string;
 10 |   hash: string;
 11 |   semanticHash: string;
 12 |   usageCount: number;
 13 |   parentId?: string;
 14 |   childIds: string[];
 15 |   variance?: number; // How different from parent (0-1)
 16 | }
 17 | 
 18 | export interface StyleRelationship {
 19 |   parentId?: string;
 20 |   childIds: string[];
 21 |   variance: number; // How different from parent (0-1)
 22 | }
 23 | 
 24 | export interface OptimizationReport {
 25 |   totalStyles: number;
 26 |   duplicatesRemoved: number;
 27 |   variantsCreated: number;
 28 |   hierarchyDepth: number;
 29 |   memoryReduction: string;
 30 | }
 31 | 
 32 | export class FlutterStyleLibrary {
 33 |   private static instance: FlutterStyleLibrary;
 34 |   private styles = new Map<string, FlutterStyleDefinition>();
 35 |   private hashToId = new Map<string, string>();
 36 |   private semanticHashToId = new Map<string, string>();
 37 |   private autoOptimizeEnabled = true;
 38 |   private optimizationThreshold = 20; // Auto-optimize after every N styles
 39 |   private lastOptimizationCount = 0;
 40 |   
 41 |   static getInstance(): FlutterStyleLibrary {
 42 |     if (!this.instance) {
 43 |       this.instance = new FlutterStyleLibrary();
 44 |     }
 45 |     return this.instance;
 46 |   }
 47 |   
 48 |   addStyle(category: string, properties: any, context?: string): string {
 49 |     const hash = this.generateHash(properties);
 50 |     const semanticHash = this.generateSemanticHash(properties);
 51 |     
 52 |     Logger.info(`🎨 Adding ${category} style with properties:`, JSON.stringify(properties, null, 2));
 53 |     Logger.info(`📝 Generated hashes - Exact: ${hash.substring(0, 20)}..., Semantic: ${semanticHash.substring(0, 20)}...`);
 54 |     
 55 |     // Check for exact matches first
 56 |     if (this.hashToId.has(hash)) {
 57 |       const existingId = this.hashToId.get(hash)!;
 58 |       const style = this.styles.get(existingId)!;
 59 |       style.usageCount++;
 60 |       Logger.info(`✅ Exact match found! Reusing style ${existingId} (usage: ${style.usageCount})`);
 61 |       return existingId;
 62 |     }
 63 |     
 64 |     // Check for semantic equivalents
 65 |     if (this.semanticHashToId.has(semanticHash)) {
 66 |       const existingId = this.semanticHashToId.get(semanticHash)!;
 67 |       const style = this.styles.get(existingId)!;
 68 |       style.usageCount++;
 69 |       Logger.info(`🔍 Semantic match found! Reusing style ${existingId} (usage: ${style.usageCount})`);
 70 |       return existingId;
 71 |     }
 72 |     
 73 |     // Check if this should be a variant of existing style
 74 |     const parentStyle = this.findPotentialParent(properties);
 75 |     
 76 |     if (parentStyle) {
 77 |       const variance = this.calculateVariance(properties, parentStyle.properties);
 78 |       Logger.info(`🌳 Parent style found: ${parentStyle.id} (variance: ${(variance * 100).toFixed(1)}%)`);
 79 |     }
 80 |     
 81 |     const generatedId = this.generateId();
 82 |     const styleId = `${category}${generatedId.charAt(0).toUpperCase()}${generatedId.slice(1)}`;
 83 |     const definition: FlutterStyleDefinition = {
 84 |       id: styleId,
 85 |       category: category as any,
 86 |       properties,
 87 |       flutterCode: this.generateFlutterCode(category, properties),
 88 |       hash,
 89 |       semanticHash,
 90 |       usageCount: 1,
 91 |       parentId: parentStyle?.id,
 92 |       childIds: [],
 93 |       variance: parentStyle ? this.calculateVariance(properties, parentStyle.properties) : undefined
 94 |     };
 95 |     
 96 |     // Update parent-child relationships
 97 |     if (parentStyle) {
 98 |       parentStyle.childIds.push(styleId);
 99 |       Logger.info(`🔗 Updated parent ${parentStyle.id} with child ${styleId}`);
100 |     }
101 |     
102 |     Logger.info(`✨ Created new style: ${styleId} (total styles: ${this.styles.size + 1})`);
103 |     
104 |     this.styles.set(styleId, definition);
105 |     this.hashToId.set(hash, styleId);
106 |     this.semanticHashToId.set(semanticHash, styleId);
107 |     
108 |     // Auto-optimize if threshold is reached
109 |     this.checkAutoOptimization();
110 |     
111 |     return styleId;
112 |   }
113 |   
114 |   getStyle(id: string): FlutterStyleDefinition | undefined {
115 |     return this.styles.get(id);
116 |   }
117 |   
118 |   getAllStyles(): FlutterStyleDefinition[] {
119 |     return Array.from(this.styles.values());
120 |   }
121 |   
122 |   findSimilarStyles(properties: any, threshold: number = 0.8): string[] {
123 |     const similarStyles: string[] = [];
124 |     
125 |     for (const [id, style] of this.styles) {
126 |       const similarity = this.calculateSimilarity(properties, style.properties);
127 |       if (similarity >= threshold && similarity < 1.0) {
128 |         similarStyles.push(id);
129 |       }
130 |     }
131 |     
132 |     return similarStyles;
133 |   }
134 |   
135 |   getStyleHierarchy(): Record<string, StyleRelationship> {
136 |     const hierarchy: Record<string, StyleRelationship> = {};
137 |     
138 |     for (const [id, style] of this.styles) {
139 |       hierarchy[id] = {
140 |         parentId: style.parentId,
141 |         childIds: style.childIds,
142 |         variance: style.variance || 0
143 |       };
144 |     }
145 |     
146 |     return hierarchy;
147 |   }
148 |   
149 |   optimizeLibrary(): OptimizationReport {
150 |     const beforeCount = this.styles.size;
151 |     let duplicatesRemoved = 0;
152 |     let variantsCreated = 0;
153 |     let hierarchyDepth = 0;
154 |     
155 |     // Find and merge exact duplicates (shouldn't happen with current logic, but safety check)
156 |     const hashGroups = new Map<string, string[]>();
157 |     for (const [id, style] of this.styles) {
158 |       const group = hashGroups.get(style.hash) || [];
159 |       group.push(id);
160 |       hashGroups.set(style.hash, group);
161 |     }
162 |     
163 |     // Remove duplicates (keep first, redirect others)
164 |     for (const [hash, ids] of hashGroups) {
165 |       if (ids.length > 1) {
166 |         const keepId = ids[0];
167 |         const keepStyle = this.styles.get(keepId)!;
168 |         
169 |         for (let i = 1; i < ids.length; i++) {
170 |           const removeId = ids[i];
171 |           const removeStyle = this.styles.get(removeId)!;
172 |           
173 |           // Merge usage counts
174 |           keepStyle.usageCount += removeStyle.usageCount;
175 |           
176 |           // Remove duplicate
177 |           this.styles.delete(removeId);
178 |           this.hashToId.delete(removeStyle.hash);
179 |           this.semanticHashToId.delete(removeStyle.semanticHash);
180 |           duplicatesRemoved++;
181 |         }
182 |       }
183 |     }
184 |     
185 |     // Calculate hierarchy depth
186 |     for (const style of this.styles.values()) {
187 |       if (style.childIds.length > 0) {
188 |         variantsCreated += style.childIds.length;
189 |       }
190 |       
191 |       // Calculate depth from this node
192 |       let depth = 0;
193 |       let currentStyle = style;
194 |       while (currentStyle.parentId) {
195 |         depth++;
196 |         currentStyle = this.styles.get(currentStyle.parentId)!;
197 |         if (!currentStyle) break; // Safety check
198 |       }
199 |       hierarchyDepth = Math.max(hierarchyDepth, depth);
200 |     }
201 |     
202 |     const afterCount = this.styles.size;
203 |     const memoryReduction = beforeCount > 0 
204 |       ? `${((beforeCount - afterCount) / beforeCount * 100).toFixed(1)}%`
205 |       : '0%';
206 |     
207 |     return {
208 |       totalStyles: afterCount,
209 |       duplicatesRemoved,
210 |       variantsCreated,
211 |       hierarchyDepth,
212 |       memoryReduction
213 |     };
214 |   }
215 |   
216 |   reset(): void {
217 |     this.styles.clear();
218 |     this.hashToId.clear();
219 |     this.semanticHashToId.clear();
220 |     this.lastOptimizationCount = 0;
221 |   }
222 |   
223 |   setAutoOptimization(enabled: boolean, threshold: number = 20): void {
224 |     this.autoOptimizeEnabled = enabled;
225 |     this.optimizationThreshold = threshold;
226 |     Logger.info(`⚙️  Auto-optimization ${enabled ? 'enabled' : 'disabled'} (threshold: ${threshold})`);
227 |   }
228 |   
229 |   private checkAutoOptimization(): void {
230 |     if (!this.autoOptimizeEnabled) return;
231 |     
232 |     const currentCount = this.styles.size;
233 |     const stylesSinceLastOptimization = currentCount - this.lastOptimizationCount;
234 |     
235 |     if (stylesSinceLastOptimization >= this.optimizationThreshold) {
236 |       Logger.info(`🚀 Auto-optimization triggered! (${stylesSinceLastOptimization} new styles since last optimization)`);
237 |       this.runAutoOptimization();
238 |       this.lastOptimizationCount = currentCount;
239 |     }
240 |   }
241 |   
242 |   private runAutoOptimization(): OptimizationReport {
243 |     Logger.info(`⚡ Running auto-optimization...`);
244 |     const report = this.optimizeLibrary();
245 |     Logger.info(`✅ Auto-optimization complete:`, {
246 |       totalStyles: report.totalStyles,
247 |       duplicatesRemoved: report.duplicatesRemoved,
248 |       variantsCreated: report.variantsCreated,
249 |       hierarchyDepth: report.hierarchyDepth,
250 |       memoryReduction: report.memoryReduction
251 |     });
252 |     return report;
253 |   }
254 |   
255 |   private generateHash(properties: any): string {
256 |     // Use a more robust hash generation that preserves nested object properties
257 |     return JSON.stringify(properties, (key, value) => {
258 |       if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
259 |         // Sort object keys for consistent hashing
260 |         const sortedObj: any = {};
261 |         Object.keys(value).sort().forEach(k => {
262 |           sortedObj[k] = value[k];
263 |         });
264 |         return sortedObj;
265 |       }
266 |       return value;
267 |     });
268 |   }
269 |   
270 |   private generateSemanticHash(properties: any): string {
271 |     // Normalize property values before hashing
272 |     const normalized = this.normalizeProperties(properties);
273 |     Logger.info(`🔄 Normalized properties:`, JSON.stringify(normalized, null, 2));
274 |     
275 |     // Create semantic fingerprint that catches equivalent styles
276 |     const semanticKey = this.createSemanticKey(normalized);
277 |     Logger.info(`🔑 Semantic key:`, JSON.stringify(semanticKey, null, 2));
278 |     
279 |     return this.hashObject(semanticKey);
280 |   }
281 |   
282 |   private normalizeProperties(properties: any): any {
283 |     const normalized = JSON.parse(JSON.stringify(properties)); // Deep copy
284 |     
285 |     // Normalize color representations
286 |     if (normalized.fills) {
287 |       normalized.fills = normalized.fills.map((fill: any) => {
288 |         if (fill.hex) {
289 |           // Normalize hex colors (e.g., #000000 -> #000000, #000 -> #000000)
290 |           let hex = fill.hex.toLowerCase();
291 |           if (hex === '#000') hex = '#000000';
292 |           if (hex === '#fff') hex = '#ffffff';
293 |           
294 |           const normalizedFill = { ...fill, hex };
295 |           if (hex === '#000000') normalizedFill.normalized = 'black';
296 |           if (hex === '#ffffff') normalizedFill.normalized = 'white';
297 |           
298 |           return normalizedFill;
299 |         }
300 |         return fill;
301 |       });
302 |     }
303 |     
304 |     // Normalize padding representations
305 |     if (normalized.padding) {
306 |       const p = normalized.padding;
307 |       // EdgeInsets.all(8) === EdgeInsets.fromLTRB(8,8,8,8)
308 |       if (p.top === p.right && p.right === p.bottom && p.bottom === p.left) {
309 |         normalized.padding = { uniform: p.top, isUniform: true };
310 |       }
311 |     }
312 |     
313 |     // Normalize border radius
314 |     if (normalized.cornerRadius && typeof normalized.cornerRadius === 'object') {
315 |       const r = normalized.cornerRadius;
316 |       if (r.topLeft === r.topRight && r.topRight === r.bottomLeft && r.bottomLeft === r.bottomRight) {
317 |         normalized.cornerRadius = r.topLeft;
318 |       }
319 |     }
320 |     
321 |     return normalized;
322 |   }
323 |   
324 |   private createSemanticKey(properties: any): any {
325 |     // Create a semantic representation that focuses on visual impact
326 |     const key: any = {};
327 |     
328 |     // Group similar properties - be more specific to avoid false matches
329 |     if (properties.fills && properties.fills.length > 0) {
330 |       // Use the actual hex value to distinguish different colors
331 |       key.color = properties.fills[0].hex?.toLowerCase();
332 |     }
333 |     
334 |     if (properties.cornerRadius !== undefined) {
335 |       key.borderRadius = typeof properties.cornerRadius === 'number' 
336 |         ? properties.cornerRadius 
337 |         : JSON.stringify(properties.cornerRadius);
338 |     }
339 |     
340 |     if (properties.padding) {
341 |       key.padding = properties.padding.isUniform 
342 |         ? properties.padding.uniform 
343 |         : JSON.stringify(properties.padding);
344 |     }
345 |     
346 |     if (properties.effects?.dropShadows?.length > 0) {
347 |       key.hasShadow = true;
348 |       key.shadowIntensity = properties.effects.dropShadows.length;
349 |       // Include shadow details for more specificity
350 |       key.shadowDetails = properties.effects.dropShadows.map((s: any) => ({
351 |         color: s.hex,
352 |         blur: s.radius,
353 |         offset: s.offset
354 |       }));
355 |     }
356 |     
357 |     return key;
358 |   }
359 |   
360 |   private hashObject(obj: any): string {
361 |     return JSON.stringify(obj, Object.keys(obj).sort());
362 |   }
363 |   
364 |   private findPotentialParent(properties: any, threshold: number = 0.8): FlutterStyleDefinition | undefined {
365 |     const allStyles = Array.from(this.styles.values());
366 |     
367 |     for (const style of allStyles) {
368 |       const similarity = this.calculateSimilarity(properties, style.properties);
369 |       if (similarity >= threshold && similarity < 1.0) {
370 |         return style;
371 |       }
372 |     }
373 |     
374 |     return undefined;
375 |   }
376 |   
377 |   private calculateSimilarity(props1: any, props2: any): number {
378 |     const keys1 = new Set(Object.keys(props1));
379 |     const keys2 = new Set(Object.keys(props2));
380 |     const allKeys = new Set([...keys1, ...keys2]);
381 |     
382 |     let matches = 0;
383 |     let total = allKeys.size;
384 |     
385 |     for (const key of allKeys) {
386 |       if (keys1.has(key) && keys2.has(key)) {
387 |         // Both have the key, check if values are similar
388 |         if (this.areValuesSimilar(props1[key], props2[key])) {
389 |           matches++;
390 |         }
391 |       }
392 |       // If only one has the key, it's a difference (no match)
393 |     }
394 |     
395 |     return total > 0 ? matches / total : 0;
396 |   }
397 |   
398 |   private areValuesSimilar(val1: any, val2: any): boolean {
399 |     if (val1 === val2) return true;
400 |     
401 |     // Handle arrays (like fills)
402 |     if (Array.isArray(val1) && Array.isArray(val2)) {
403 |       if (val1.length !== val2.length) return false;
404 |       return val1.every((item, index) => this.areValuesSimilar(item, val2[index]));
405 |     }
406 |     
407 |     // Handle objects
408 |     if (typeof val1 === 'object' && typeof val2 === 'object' && val1 !== null && val2 !== null) {
409 |       const keys1 = Object.keys(val1);
410 |       const keys2 = Object.keys(val2);
411 |       if (keys1.length !== keys2.length) return false;
412 |       return keys1.every(key => this.areValuesSimilar(val1[key], val2[key]));
413 |     }
414 |     
415 |     // Handle numbers with tolerance
416 |     if (typeof val1 === 'number' && typeof val2 === 'number') {
417 |       return Math.abs(val1 - val2) < 0.01;
418 |     }
419 |     
420 |     return false;
421 |   }
422 |   
423 |   private calculateVariance(childProps: any, parentProps: any): number {
424 |     const similarity = this.calculateSimilarity(childProps, parentProps);
425 |     return 1 - similarity; // Variance is inverse of similarity
426 |   }
427 |   
428 |   private generateId(): string {
429 |     return Date.now().toString(36) + Math.random().toString(36).substr(2, 4);
430 |   }
431 |   
432 |   private generateFlutterCode(category: string, properties: any): string {
433 |     switch (category) {
434 |       case 'decoration':
435 |         return FlutterCodeGenerator.generateDecoration(properties);
436 |       case 'text':
437 |         return FlutterCodeGenerator.generateTextStyle(properties);
438 |       case 'padding':
439 |         return FlutterCodeGenerator.generatePadding(properties);
440 |       case 'layout':
441 |         // Layout code generation can be added later
442 |         return `// ${category} implementation`;
443 |       default:
444 |         return `// ${category} implementation`;
445 |     }
446 |   }
447 | }
448 | 
449 | export class FlutterCodeGenerator {
450 |   static generateDecoration(properties: any): string {
451 |     let code = 'BoxDecoration(\n';
452 |     
453 |     if (properties.fills?.length > 0) {
454 |       const fill = properties.fills[0];
455 |       if (fill.hex) {
456 |         code += `  color: Color(0xFF${fill.hex.substring(1)}),\n`;
457 |       }
458 |     }
459 |     
460 |     if (properties.cornerRadius !== undefined) {
461 |       if (typeof properties.cornerRadius === 'number') {
462 |         code += `  borderRadius: BorderRadius.circular(${properties.cornerRadius}),\n`;
463 |       } else {
464 |         const r = properties.cornerRadius;
465 |         code += `  borderRadius: BorderRadius.only(\n`;
466 |         code += `    topLeft: Radius.circular(${r.topLeft}),\n`;
467 |         code += `    topRight: Radius.circular(${r.topRight}),\n`;
468 |         code += `    bottomLeft: Radius.circular(${r.bottomLeft}),\n`;
469 |         code += `    bottomRight: Radius.circular(${r.bottomRight}),\n`;
470 |         code += `  ),\n`;
471 |       }
472 |     }
473 |     
474 |     if (properties.effects?.dropShadows?.length > 0) {
475 |       code += `  boxShadow: [\n`;
476 |       properties.effects.dropShadows.forEach((shadow: any) => {
477 |         code += `    BoxShadow(\n`;
478 |         code += `      color: Color(0xFF${shadow.hex.substring(1)}).withOpacity(${shadow.opacity}),\n`;
479 |         code += `      offset: Offset(${shadow.offset.x}, ${shadow.offset.y}),\n`;
480 |         code += `      blurRadius: ${shadow.radius},\n`;
481 |         if (shadow.spread) {
482 |           code += `      spreadRadius: ${shadow.spread},\n`;
483 |         }
484 |         code += `    ),\n`;
485 |       });
486 |       code += `  ],\n`;
487 |     }
488 |     
489 |     code += ')';
490 |     return code;
491 |   }
492 |   
493 |   static generatePadding(properties: any): string {
494 |     const p = properties.padding;
495 |     if (!p) return 'EdgeInsets.zero';
496 |     
497 |     if (p.isUniform) {
498 |       return `EdgeInsets.all(${p.top})`;
499 |     }
500 |     return `EdgeInsets.fromLTRB(${p.left}, ${p.top}, ${p.right}, ${p.bottom})`;
501 |   }
502 |   
503 |   static generateTextStyle(properties: any): string {
504 |     const parts: string[] = [];
505 |     
506 |     if (properties.fontFamily) {
507 |       parts.push(`fontFamily: '${properties.fontFamily}'`);
508 |     }
509 |     if (properties.fontSize) {
510 |       parts.push(`fontSize: ${properties.fontSize}`);
511 |     }
512 |     if (properties.fontWeight && properties.fontWeight !== 400) {
513 |       const weight = properties.fontWeight >= 700 ? 'FontWeight.bold' : 
514 |                     properties.fontWeight >= 600 ? 'FontWeight.w600' :
515 |                     properties.fontWeight >= 500 ? 'FontWeight.w500' :
516 |                     'FontWeight.normal';
517 |       parts.push(`fontWeight: ${weight}`);
518 |     }
519 |     
520 |     return `TextStyle(${parts.join(', ')})`;
521 |   }
522 | }
523 | 
```

--------------------------------------------------------------------------------
/src/tools/flutter/screens/helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/tools/flutter/screens/helpers.mts
  2 | 
  3 | import type {ScreenAnalysis, ScreenSection, NavigationElement, ScreenAssetInfo} from "../../../extractors/screens/types.js";
  4 | import {generateScreenVisualContext} from "../visual-context.js";
  5 | 
  6 | /**
  7 |  * Generate comprehensive screen analysis report
  8 |  */
  9 | export function generateScreenAnalysisReport(
 10 |     analysis: ScreenAnalysis,
 11 |     parsedInput?: any
 12 | ): string {
 13 |     let output = `Screen Analysis Report\n\n`;
 14 | 
 15 |     // Screen metadata
 16 |     output += `Screen: ${analysis.metadata.name}\n`;
 17 |     output += `Type: ${analysis.metadata.type}\n`;
 18 |     output += `Node ID: ${analysis.metadata.nodeId}\n`;
 19 |     output += `Device Type: ${analysis.metadata.deviceType}\n`;
 20 |     output += `Orientation: ${analysis.metadata.orientation}\n`;
 21 |     output += `Dimensions: ${Math.round(analysis.metadata.dimensions.width)}×${Math.round(analysis.metadata.dimensions.height)}px\n`;
 22 |     if (parsedInput) {
 23 |         output += `Source: ${parsedInput.source === 'url' ? 'Figma URL' : 'Direct input'}\n`;
 24 |     }
 25 |     output += `\n`;
 26 | 
 27 |     // Screen layout information
 28 |     output += `Screen Layout:\n`;
 29 |     output += `- Layout Type: ${analysis.layout.type}\n`;
 30 |     if (analysis.layout.scrollable) {
 31 |         output += `- Scrollable: Yes\n`;
 32 |     }
 33 |     if (analysis.layout.hasHeader) {
 34 |         output += `- Has Header: Yes\n`;
 35 |     }
 36 |     if (analysis.layout.hasFooter) {
 37 |         output += `- Has Footer: Yes\n`;
 38 |     }
 39 |     if (analysis.layout.hasNavigation) {
 40 |         output += `- Has Navigation: Yes\n`;
 41 |     }
 42 |     if (analysis.layout.contentArea) {
 43 |         const area = analysis.layout.contentArea;
 44 |         output += `- Content Area: ${Math.round(area.width)}×${Math.round(area.height)}px at (${Math.round(area.x)}, ${Math.round(area.y)})\n`;
 45 |     }
 46 |     output += `\n`;
 47 | 
 48 |     // Screen sections
 49 |     if (analysis.sections.length > 0) {
 50 |         output += `Screen Sections (${analysis.sections.length} identified):\n`;
 51 |         analysis.sections.forEach((section, index) => {
 52 |             output += `${index + 1}. ${section.name} (${section.type.toUpperCase()})\n`;
 53 |             output += `   Priority: ${section.importance}/10\n`;
 54 |             
 55 |             if (section.layout.dimensions) {
 56 |                 const dims = section.layout.dimensions;
 57 |                 output += `   Size: ${Math.round(dims.width)}×${Math.round(dims.height)}px\n`;
 58 |             }
 59 |             
 60 |             if (section.children.length > 0) {
 61 |                 output += `   Contains: ${section.children.length} elements\n`;
 62 |             }
 63 |             
 64 |             if (section.components.length > 0) {
 65 |                 output += `   Components: ${section.components.length} nested component(s)\n`;
 66 |             }
 67 |         });
 68 |         output += `\n`;
 69 |     }
 70 | 
 71 |     // Navigation information
 72 |     if (analysis.navigation.navigationElements.length > 0) {
 73 |         output += `Navigation Elements:\n`;
 74 |         
 75 |         if (analysis.navigation.hasTabBar) output += `- Has Tab Bar\n`;
 76 |         if (analysis.navigation.hasAppBar) output += `- Has App Bar\n`;
 77 |         if (analysis.navigation.hasDrawer) output += `- Has Drawer\n`;
 78 |         if (analysis.navigation.hasBottomSheet) output += `- Has Bottom Sheet\n`;
 79 |         
 80 |         output += `\nNavigation Items (${analysis.navigation.navigationElements.length}):\n`;
 81 |         analysis.navigation.navigationElements.forEach((nav, index) => {
 82 |             const activeMark = nav.isActive ? ' [ACTIVE]' : '';
 83 |             const iconMark = nav.icon ? ' 🎯' : '';
 84 |             output += `${index + 1}. ${nav.name} (${nav.type.toUpperCase()})${activeMark}${iconMark}\n`;
 85 |             if (nav.text) {
 86 |                 output += `   Text: "${nav.text}"\n`;
 87 |             }
 88 |         });
 89 |         output += `\n`;
 90 |     }
 91 | 
 92 |     // Assets information
 93 |     if (analysis.assets.length > 0) {
 94 |         output += `Screen Assets (${analysis.assets.length} found):\n`;
 95 |         
 96 |         const assetsByType = groupAssetsByType(analysis.assets);
 97 |         Object.entries(assetsByType).forEach(([type, assets]) => {
 98 |             output += `${type.toUpperCase()} (${assets.length}):\n`;
 99 |             assets.forEach(asset => {
100 |                 output += `- ${asset.name} (${asset.size}, ${asset.usage})\n`;
101 |             });
102 |         });
103 |         output += `\n`;
104 |     }
105 | 
106 |     // Nested components for separate analysis
107 |     if (analysis.components.length > 0) {
108 |         output += `Nested Components Found (${analysis.components.length}):\n`;
109 |         output += `These components should be analyzed separately:\n`;
110 |         analysis.components.forEach((comp, index) => {
111 |             output += `${index + 1}. ${comp.name}\n`;
112 |             output += `   Node ID: ${comp.nodeId}\n`;
113 |             output += `   Type: ${comp.instanceType || 'COMPONENT'}\n`;
114 |             if (comp.componentKey) {
115 |                 output += `   Component Key: ${comp.componentKey}\n`;
116 |             }
117 |         });
118 |         output += `\n`;
119 |     }
120 | 
121 |     // Skipped nodes report
122 |     if (analysis.skippedNodes && analysis.skippedNodes.length > 0) {
123 |         output += `Analysis Limitations:\n`;
124 |         
125 |         const deviceUISkipped = analysis.skippedNodes.filter(node => node.reason === 'device_ui_element');
126 |         const limitSkipped = analysis.skippedNodes.filter(node => node.reason === 'max_sections');
127 |         
128 |         if (deviceUISkipped.length > 0) {
129 |             output += `${deviceUISkipped.length} device UI elements were automatically filtered out:\n`;
130 |             deviceUISkipped.forEach((skipped, index) => {
131 |                 output += `${index + 1}. ${skipped.name} (${skipped.type}) - device UI placeholder\n`;
132 |             });
133 |             output += `\n`;
134 |         }
135 |         
136 |         if (limitSkipped.length > 0) {
137 |             output += `${limitSkipped.length} sections were skipped due to limits:\n`;
138 |             limitSkipped.forEach((skipped, index) => {
139 |                 output += `${index + 1}. ${skipped.name} (${skipped.type}) - ${skipped.reason}\n`;
140 |             });
141 |             output += `\nTo analyze all sections, increase the maxSections parameter.\n`;
142 |         }
143 |         
144 |         output += `\n`;
145 |     }
146 | 
147 |     // Visual context for AI implementation
148 |     if (parsedInput?.source === 'url') {
149 |         // Reconstruct the Figma URL from the parsed input
150 |         const figmaUrl = `https://www.figma.com/design/${parsedInput.fileId}/?node-id=${parsedInput.nodeId}`;
151 |         output += generateScreenVisualContext(analysis, figmaUrl, parsedInput.nodeId);
152 |         output += `\n`;
153 |     }
154 | 
155 |     // Flutter implementation guidance
156 |     output += generateFlutterScreenGuidance(analysis);
157 | 
158 |     return output;
159 | }
160 | 
161 | /**
162 |  * Generate screen structure inspection report
163 |  */
164 | export function generateScreenStructureReport(node: any, showAllSections: boolean): string {
165 |     let output = `Screen Structure Inspection\n\n`;
166 | 
167 |     output += `Screen: ${node.name}\n`;
168 |     output += `Type: ${node.type}\n`;
169 |     output += `Node ID: ${node.id}\n`;
170 |     output += `Sections: ${node.children?.length || 0}\n`;
171 | 
172 |     if (node.absoluteBoundingBox) {
173 |         const bbox = node.absoluteBoundingBox;
174 |         output += `Dimensions: ${Math.round(bbox.width)}×${Math.round(bbox.height)}px\n`;
175 |         
176 |         // Device type detection
177 |         const deviceType = bbox.width > bbox.height ? 'Landscape' : 'Portrait';
178 |         const screenSize = Math.max(bbox.width, bbox.height) > 1200 ? 'Desktop' : 
179 |                           Math.max(bbox.width, bbox.height) > 800 ? 'Tablet' : 'Mobile';
180 |         output += `Device: ${screenSize} ${deviceType}\n`;
181 |     }
182 | 
183 |     output += `\n`;
184 | 
185 |     if (!node.children || node.children.length === 0) {
186 |         output += `This screen has no sections.\n`;
187 |         return output;
188 |     }
189 | 
190 |     output += `Screen Structure:\n`;
191 | 
192 |     const sectionsToShow = showAllSections ? node.children : node.children.slice(0, 20);
193 |     const hasMore = node.children.length > sectionsToShow.length;
194 | 
195 |     sectionsToShow.forEach((section: any, index: number) => {
196 |         const isComponent = section.type === 'COMPONENT' || section.type === 'INSTANCE';
197 |         const componentMark = isComponent ? ' [COMPONENT]' : '';
198 |         const hiddenMark = section.visible === false ? ' [HIDDEN]' : '';
199 |         
200 |         // Detect section type
201 |         const sectionType = detectSectionTypeFromName(section.name);
202 |         const typeMark = sectionType !== 'content' ? ` [${sectionType.toUpperCase()}]` : '';
203 | 
204 |         output += `${index + 1}. ${section.name} (${section.type})${componentMark}${typeMark}${hiddenMark}\n`;
205 | 
206 |         if (section.absoluteBoundingBox) {
207 |             const bbox = section.absoluteBoundingBox;
208 |             output += `   Size: ${Math.round(bbox.width)}×${Math.round(bbox.height)}px\n`;
209 |             output += `   Position: (${Math.round(bbox.x)}, ${Math.round(bbox.y)})\n`;
210 |         }
211 | 
212 |         if (section.children && section.children.length > 0) {
213 |             output += `   Contains: ${section.children.length} child elements\n`;
214 |             
215 |             // Show component count
216 |             const componentCount = section.children.filter((child: any) => 
217 |                 child.type === 'COMPONENT' || child.type === 'INSTANCE'
218 |             ).length;
219 |             if (componentCount > 0) {
220 |                 output += `   Components: ${componentCount} nested component(s)\n`;
221 |             }
222 |         }
223 | 
224 |         // Show basic styling info
225 |         if (section.fills && section.fills.length > 0) {
226 |             const fill = section.fills[0];
227 |             if (fill.color) {
228 |                 const hex = rgbaToHex(fill.color);
229 |                 output += `   Background: ${hex}\n`;
230 |             }
231 |         }
232 |     });
233 | 
234 |     if (hasMore) {
235 |         output += `\n... and ${node.children.length - sectionsToShow.length} more sections.\n`;
236 |         output += `Use showAllSections: true to see all sections.\n`;
237 |     }
238 | 
239 |     // Analysis recommendations
240 |     output += `\nAnalysis Recommendations:\n`;
241 |     
242 |     const componentSections = node.children.filter((section: any) =>
243 |         section.type === 'COMPONENT' || section.type === 'INSTANCE'
244 |     );
245 |     if (componentSections.length > 0) {
246 |         output += `- Found ${componentSections.length} component sections for separate analysis\n`;
247 |     }
248 | 
249 |     const largeSections = node.children.filter((section: any) => {
250 |         const bbox = section.absoluteBoundingBox;
251 |         return bbox && (bbox.width * bbox.height) > 20000;
252 |     });
253 |     if (largeSections.length > 5) {
254 |         output += `- Screen has ${largeSections.length} large sections - consider increasing maxSections\n`;
255 |     }
256 | 
257 |     // Detect navigation elements
258 |     const navSections = node.children.filter((section: any) => {
259 |         const name = section.name.toLowerCase();
260 |         return name.includes('nav') || name.includes('tab') || name.includes('menu') || 
261 |                name.includes('header') || name.includes('footer');
262 |     });
263 |     if (navSections.length > 0) {
264 |         output += `- Found ${navSections.length} navigation-related sections\n`;
265 |     }
266 | 
267 |     return output;
268 | }
269 | 
270 | /**
271 |  * Generate Flutter screen implementation guidance
272 |  */
273 | export function generateFlutterScreenGuidance(analysis: ScreenAnalysis): string {
274 |     let guidance = `Flutter Screen Implementation Guidance:\n\n`;
275 | 
276 |     // Widget composition best practices
277 |     guidance += `🏗️  Widget Composition Best Practices:\n`;
278 |     guidance += `- Start by building the complete screen widget tree in a single build() method\n`;
279 |     guidance += `- Keep composing widgets inline until you reach ~250 lines of code\n`;
280 |     guidance += `- Only then break down into private StatelessWidget classes for sections\n`;
281 |     guidance += `- Use private widgets (prefix with _) for internal screen component breakdown\n`;
282 |     guidance += `- Avoid functional widgets - always use StatelessWidget classes\n\n`;
283 |     
284 |     guidance += `📱 Device UI Filtering:\n`;
285 |     guidance += `- Status bars, battery icons, wifi indicators are automatically filtered out\n`;
286 |     guidance += `- Home indicators, notches, and device bezels are ignored during analysis\n`;
287 |     guidance += `- Only actual app design content is analyzed for Flutter implementation\n`;
288 |     guidance += `- Use SafeArea widget in Flutter to handle device-specific insets\n\n`;
289 | 
290 |     // Main scaffold structure
291 |     guidance += `Main Screen Structure:\n`;
292 |     guidance += `Scaffold(\n`;
293 |     
294 |     // App bar
295 |     if (analysis.navigation.hasAppBar) {
296 |         guidance += `  appBar: AppBar(\n`;
297 |         guidance += `    title: Text('${analysis.metadata.name}'),\n`;
298 |         guidance += `    // Add app bar actions and styling\n`;
299 |         guidance += `  ),\n`;
300 |     }
301 |     
302 |     // Drawer
303 |     if (analysis.navigation.hasDrawer) {
304 |         guidance += `  drawer: Drawer(\n`;
305 |         guidance += `    // Add drawer content\n`;
306 |         guidance += `  ),\n`;
307 |     }
308 |     
309 |     // Body structure
310 |     guidance += `  body: `;
311 |     
312 |     if (analysis.layout.scrollable) {
313 |         guidance += `SingleChildScrollView(\n`;
314 |         guidance += `    child: Column(\n`;
315 |         guidance += `      children: [\n`;
316 |     } else {
317 |         guidance += `Column(\n`;
318 |         guidance += `    children: [\n`;
319 |     }
320 |     
321 |     // Add sections
322 |     analysis.sections.forEach((section, index) => {
323 |         const widgetName = toPascalCase(section.name);
324 |         guidance += `        ${widgetName}(), // ${section.type} section\n`;
325 |     });
326 |     
327 |     guidance += `      ],\n`;
328 |     guidance += `    ),\n`;
329 |     
330 |     if (analysis.layout.scrollable) {
331 |         guidance += `  ),\n`;
332 |     }
333 |     
334 |     // Bottom navigation
335 |     if (analysis.navigation.hasTabBar) {
336 |         guidance += `  bottomNavigationBar: BottomNavigationBar(\n`;
337 |         guidance += `    items: [\n`;
338 |         
339 |         const tabItems = analysis.navigation.navigationElements.filter(nav => nav.type === 'tab');
340 |         tabItems.slice(0, 5).forEach(tab => {
341 |             guidance += `      BottomNavigationBarItem(\n`;
342 |             guidance += `        icon: Icon(Icons.${tab.icon ? 'placeholder' : 'home'}),\n`;
343 |             guidance += `        label: '${tab.text || tab.name}',\n`;
344 |             guidance += `      ),\n`;
345 |         });
346 |         
347 |         guidance += `    ],\n`;
348 |         guidance += `  ),\n`;
349 |     }
350 |     
351 |     guidance += `)\n\n`;
352 | 
353 |     // Section widgets guidance
354 |     if (analysis.sections.length > 0) {
355 |         guidance += `Section Widgets:\n`;
356 |         analysis.sections.forEach((section, index) => {
357 |             const widgetName = toPascalCase(section.name);
358 |             guidance += `${index + 1}. ${widgetName}() - ${section.type} section\n`;
359 |             guidance += `   Elements: ${section.children.length} child elements\n`;
360 |             if (section.components.length > 0) {
361 |                 guidance += `   Components: ${section.components.length} nested components\n`;
362 |             }
363 |         });
364 |         guidance += `\n`;
365 |     }
366 | 
367 |     // Navigation guidance
368 |     if (analysis.navigation.navigationElements.length > 0) {
369 |         guidance += `Navigation Implementation:\n`;
370 |         
371 |         const buttons = analysis.navigation.navigationElements.filter(nav => nav.type === 'button');
372 |         const tabs = analysis.navigation.navigationElements.filter(nav => nav.type === 'tab');
373 |         const links = analysis.navigation.navigationElements.filter(nav => nav.type === 'link');
374 |         
375 |         if (buttons.length > 0) {
376 |             guidance += `Buttons (${buttons.length}):\n`;
377 |             buttons.forEach(button => {
378 |                 guidance += `- ElevatedButton(onPressed: () {}, child: Text('${button.text || button.name}'))\n`;
379 |             });
380 |         }
381 |         
382 |         if (tabs.length > 0) {
383 |             guidance += `Tab Navigation (${tabs.length}):\n`;
384 |             guidance += `- Use TabBar with ${tabs.length} tabs\n`;
385 |             guidance += `- Consider TabBarView for content switching\n`;
386 |         }
387 |         
388 |         if (links.length > 0) {
389 |             guidance += `Links (${links.length}):\n`;
390 |             links.forEach(link => {
391 |                 guidance += `- TextButton(onPressed: () {}, child: Text('${link.text || link.name}'))\n`;
392 |             });
393 |         }
394 |         
395 |         guidance += `\n`;
396 |     }
397 | 
398 |     // Asset guidance
399 |     if (analysis.assets.length > 0) {
400 |         guidance += `Assets Implementation:\n`;
401 |         
402 |         const images = analysis.assets.filter(asset => asset.type === 'image');
403 |         const icons = analysis.assets.filter(asset => asset.type === 'icon');
404 |         const illustrations = analysis.assets.filter(asset => asset.type === 'illustration');
405 |         
406 |         if (images.length > 0) {
407 |             guidance += `Images (${images.length}): Use Image.asset() or Image.network()\n`;
408 |         }
409 |         
410 |         if (icons.length > 0) {
411 |             guidance += `Icons (${icons.length}): Use Icon() widget with appropriate IconData\n`;
412 |         }
413 |         
414 |         if (illustrations.length > 0) {
415 |             guidance += `Illustrations (${illustrations.length}): Use SvgPicture or Image.asset()\n`;
416 |         }
417 |         
418 |         guidance += `\n`;
419 |     }
420 | 
421 |     // Responsive design guidance
422 |     guidance += `Responsive Design:\n`;
423 |     guidance += `- Device Type: ${analysis.metadata.deviceType}\n`;
424 |     guidance += `- Orientation: ${analysis.metadata.orientation}\n`;
425 |     
426 |     if (analysis.metadata.deviceType === 'mobile') {
427 |         guidance += `- Optimize for mobile: Use SingleChildScrollView, consider bottom navigation\n`;
428 |     } else if (analysis.metadata.deviceType === 'tablet') {
429 |         guidance += `- Tablet layout: Consider using NavigationRail or side navigation\n`;
430 |     } else if (analysis.metadata.deviceType === 'desktop') {
431 |         guidance += `- Desktop layout: Use NavigationRail, consider multi-column layouts\n`;
432 |     }
433 | 
434 |     return guidance;
435 | }
436 | 
437 | // Helper functions
438 | function groupAssetsByType(assets: ScreenAssetInfo[]): Record<string, ScreenAssetInfo[]> {
439 |     return assets.reduce((acc, asset) => {
440 |         if (!acc[asset.type]) {
441 |             acc[asset.type] = [];
442 |         }
443 |         acc[asset.type].push(asset);
444 |         return acc;
445 |     }, {} as Record<string, ScreenAssetInfo[]>);
446 | }
447 | 
448 | function detectSectionTypeFromName(name: string): string {
449 |     const lowerName = name.toLowerCase();
450 |     
451 |     if (lowerName.includes('header') || lowerName.includes('app bar')) return 'header';
452 |     if (lowerName.includes('footer') || lowerName.includes('bottom')) return 'footer';
453 |     if (lowerName.includes('nav') || lowerName.includes('menu')) return 'navigation';
454 |     if (lowerName.includes('modal') || lowerName.includes('dialog')) return 'modal';
455 |     if (lowerName.includes('sidebar')) return 'sidebar';
456 |     
457 |     return 'content';
458 | }
459 | 
460 | function toPascalCase(str: string): string {
461 |     return str
462 |         .replace(/[^a-zA-Z0-9]/g, ' ')
463 |         .replace(/\w+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
464 |         .replace(/\s/g, '');
465 | }
466 | 
467 | function rgbaToHex(color: {r: number; g: number; b: number; a?: number}): string {
468 |     const r = Math.round(color.r * 255);
469 |     const g = Math.round(color.g * 255);
470 |     const b = Math.round(color.b * 255);
471 | 
472 |     return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
473 | }
474 | 
```

--------------------------------------------------------------------------------
/src/tools/flutter/visual-context.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/tools/flutter/visual-context.ts
  2 | 
  3 | import type { ComponentAnalysis } from '../../extractors/components/types.js';
  4 | import type { ScreenAnalysis } from '../../extractors/screens/types.js';
  5 | 
  6 | /**
  7 |  * Generate visual context for component analysis
  8 |  */
  9 | export function generateComponentVisualContext(
 10 |     analysis: ComponentAnalysis, 
 11 |     figmaUrl?: string,
 12 |     nodeId?: string
 13 | ): string {
 14 |     let context = `📐 Visual Context for AI Implementation:\n`;
 15 |     context += `${'='.repeat(50)}\n\n`;
 16 | 
 17 |     // Design reference
 18 |     if (figmaUrl) {
 19 |         context += `🎨 Design Reference:\n`;
 20 |         context += `   • Figma URL: ${figmaUrl}\n`;
 21 |         if (nodeId) {
 22 |             context += `   • Node ID: ${nodeId}\n`;
 23 |         }
 24 |         context += `   • Component: ${analysis.metadata.name}\n`;
 25 |         context += `   • Type: ${analysis.metadata.type}\n\n`;
 26 |     }
 27 | 
 28 |     // ASCII layout representation
 29 |     context += `📏 Layout Structure:\n`;
 30 |     context += generateComponentAsciiLayout(analysis);
 31 |     context += `\n`;
 32 | 
 33 |     // Spatial relationships
 34 |     context += `📍 Spatial Relationships:\n`;
 35 |     context += generateComponentSpatialDescription(analysis);
 36 |     context += `\n`;
 37 | 
 38 |     // Visual patterns
 39 |     context += `🎯 Visual Design Patterns:\n`;
 40 |     context += generateComponentPatternDescription(analysis);
 41 |     context += `\n`;
 42 | 
 43 |     // Implementation guidance
 44 |     context += `💡 Implementation Guidance:\n`;
 45 |     context += generateComponentImplementationHints(analysis);
 46 |     
 47 |     // Semantic detection information
 48 |     context += `\n🧠 Enhanced Semantic Detection:\n`;
 49 |     context += `   • Multi-factor analysis with confidence scoring\n`;
 50 |     context += `   • Context-aware classification using position and parent information\n`;
 51 |     context += `   • Design pattern recognition for improved accuracy\n`;
 52 |     context += `   • Fallback to legacy detection for low-confidence classifications\n`;
 53 |     context += `   • Reduced false positives through evidence-based classification\n`;
 54 | 
 55 |     return context;
 56 | }
 57 | 
 58 | /**
 59 |  * Generate visual context for screen analysis
 60 |  */
 61 | export function generateScreenVisualContext(
 62 |     analysis: ScreenAnalysis,
 63 |     figmaUrl?: string,
 64 |     nodeId?: string
 65 | ): string {
 66 |     let context = `📱 Screen Visual Context for AI Implementation:\n`;
 67 |     context += `${'='.repeat(55)}\n\n`;
 68 | 
 69 |     // Design reference
 70 |     if (figmaUrl) {
 71 |         context += `🎨 Design Reference:\n`;
 72 |         context += `   • Figma URL: ${figmaUrl}\n`;
 73 |         if (nodeId) {
 74 |             context += `   • Node ID: ${nodeId}\n`;
 75 |         }
 76 |         context += `   • Screen: ${analysis.metadata.name}\n`;
 77 |         context += `   • Device: ${analysis.metadata.deviceType} (${analysis.metadata.orientation})\n\n`;
 78 |     }
 79 | 
 80 |     // Screen layout with ASCII representation
 81 |     context += `📏 Screen Layout Structure:\n`;
 82 |     context += generateScreenAsciiLayout(analysis);
 83 |     context += `\n`;
 84 | 
 85 |     // Visual hierarchy
 86 |     context += `📊 Visual Hierarchy (top to bottom):\n`;
 87 |     context += generateScreenHierarchy(analysis);
 88 |     context += `\n`;
 89 | 
 90 |     // Spatial relationships
 91 |     context += `📍 Component Spatial Relationships:\n`;
 92 |     context += generateScreenSpatialDescription(analysis);
 93 |     context += `\n`;
 94 | 
 95 |     // Design patterns
 96 |     context += `🎯 Visual Design Patterns Detected:\n`;
 97 |     context += generateScreenPatternDescription(analysis);
 98 |     context += `\n`;
 99 | 
100 |     // Implementation guidance
101 |     context += `💡 Flutter Implementation Strategy:\n`;
102 |     context += generateScreenImplementationHints(analysis);
103 |     
104 |     // Semantic detection information
105 |     context += `\n🧠 Enhanced Semantic Detection:\n`;
106 |     context += `   • Advanced section type detection with confidence scoring\n`;
107 |     context += `   • Multi-factor analysis for text element classification\n`;
108 |     context += `   • Context-aware position and styling analysis\n`;
109 |     context += `   • Improved navigation and interactive element detection\n`;
110 |     context += `   • Reduced misclassification through evidence-based decisions\n`;
111 | 
112 |     if (figmaUrl) {
113 |         context += `\n🔗 Reference for Verification:\n`;
114 |         context += `   View the original design at: ${figmaUrl}\n`;
115 |         context += `   Use this to verify your implementation matches the intended visual design.\n`;
116 |     }
117 | 
118 |     return context;
119 | }
120 | 
121 | /**
122 |  * Generate ASCII layout for component
123 |  */
124 | function generateComponentAsciiLayout(analysis: ComponentAnalysis): string {
125 |     const width = Math.min(Math.max(Math.round(analysis.layout.dimensions.width / 20), 10), 50);
126 |     const height = Math.min(Math.max(Math.round(analysis.layout.dimensions.height / 20), 3), 10);
127 |     
128 |     let ascii = `┌${'─'.repeat(width)}┐\n`;
129 |     
130 |     // Add component name in the middle
131 |     const nameLines = Math.floor(height / 2);
132 |     for (let i = 0; i < height; i++) {
133 |         if (i === nameLines) {
134 |             const name = analysis.metadata.name.substring(0, width - 2);
135 |             const padding = Math.max(0, Math.floor((width - name.length) / 2));
136 |             ascii += `│${' '.repeat(padding)}${name}${' '.repeat(width - padding - name.length)}│\n`;
137 |         } else {
138 |             ascii += `│${' '.repeat(width)}│\n`;
139 |         }
140 |     }
141 |     
142 |     ascii += `└${'─'.repeat(width)}┘\n`;
143 |     ascii += `Dimensions: ${Math.round(analysis.layout.dimensions.width)}×${Math.round(analysis.layout.dimensions.height)}px\n`;
144 |     
145 |     // Add layout type indicator
146 |     if (analysis.layout.type === 'auto-layout') {
147 |         const direction = analysis.layout.direction === 'horizontal' ? '↔' : '↕';
148 |         ascii += `Layout: Auto-layout ${direction} (${analysis.layout.direction})\n`;
149 |         if (analysis.layout.spacing) {
150 |             ascii += `Spacing: ${analysis.layout.spacing}px\n`;
151 |         }
152 |     }
153 | 
154 |     return ascii;
155 | }
156 | 
157 | /**
158 |  * Generate ASCII layout for screen
159 |  */
160 | function generateScreenAsciiLayout(analysis: ScreenAnalysis): string {
161 |     const screenWidth = Math.min(Math.max(Math.round(analysis.metadata.dimensions.width / 30), 15), 40);
162 |     const screenHeight = Math.min(Math.max(Math.round(analysis.metadata.dimensions.height / 40), 8), 20);
163 |     
164 |     let ascii = `📱 Screen Layout Map:\n`;
165 |     ascii += `┌${'─'.repeat(screenWidth)}┐\n`;
166 |     
167 |     // Categorize sections by position
168 |     const headerSections = analysis.sections.filter(s => s.type === 'header');
169 |     const contentSections = analysis.sections.filter(s => s.type === 'content');
170 |     const footerSections = analysis.sections.filter(s => s.type === 'footer');
171 |     const navSections = analysis.sections.filter(s => s.type === 'navigation');
172 |     
173 |     let currentLine = 0;
174 |     
175 |     // Header area
176 |     if (headerSections.length > 0) {
177 |         const headerLines = Math.ceil(screenHeight * 0.15);
178 |         for (let i = 0; i < headerLines && currentLine < screenHeight; i++) {
179 |             if (i === Math.floor(headerLines / 2)) {
180 |                 const text = 'HEADER';
181 |                 const padding = Math.max(0, Math.floor((screenWidth - text.length) / 2));
182 |                 ascii += `│${' '.repeat(padding)}${text}${' '.repeat(screenWidth - padding - text.length)}│\n`;
183 |             } else {
184 |                 ascii += `│${' '.repeat(screenWidth)}│\n`;
185 |             }
186 |             currentLine++;
187 |         }
188 |     }
189 |     
190 |     // Content area
191 |     const contentLines = screenHeight - currentLine - (footerSections.length > 0 ? Math.ceil(screenHeight * 0.15) : 0);
192 |     for (let i = 0; i < contentLines && currentLine < screenHeight; i++) {
193 |         if (i === Math.floor(contentLines / 2)) {
194 |             const text = 'CONTENT';
195 |             const padding = Math.max(0, Math.floor((screenWidth - text.length) / 2));
196 |             ascii += `│${' '.repeat(padding)}${text}${' '.repeat(screenWidth - padding - text.length)}│\n`;
197 |         } else {
198 |             ascii += `│${' '.repeat(screenWidth)}│\n`;
199 |         }
200 |         currentLine++;
201 |     }
202 |     
203 |     // Footer area
204 |     if (footerSections.length > 0) {
205 |         const remainingLines = screenHeight - currentLine;
206 |         for (let i = 0; i < remainingLines; i++) {
207 |             if (i === Math.floor(remainingLines / 2)) {
208 |                 const text = navSections.length > 0 ? 'NAVIGATION' : 'FOOTER';
209 |                 const padding = Math.max(0, Math.floor((screenWidth - text.length) / 2));
210 |                 ascii += `│${' '.repeat(padding)}${text}${' '.repeat(screenWidth - padding - text.length)}│\n`;
211 |             } else {
212 |                 ascii += `│${' '.repeat(screenWidth)}│\n`;
213 |             }
214 |         }
215 |     }
216 |     
217 |     ascii += `└${'─'.repeat(screenWidth)}┘\n`;
218 |     ascii += `Screen: ${Math.round(analysis.metadata.dimensions.width)}×${Math.round(analysis.metadata.dimensions.height)}px (${analysis.metadata.deviceType})\n`;
219 | 
220 |     return ascii;
221 | }
222 | 
223 | /**
224 |  * Generate component spatial description
225 |  */
226 | function generateComponentSpatialDescription(analysis: ComponentAnalysis): string {
227 |     let description = '';
228 |     
229 |     // Layout analysis
230 |     if (analysis.layout.type === 'auto-layout') {
231 |         description += `   • Layout flow: ${analysis.layout.direction} auto-layout\n`;
232 |         if (analysis.layout.spacing) {
233 |             description += `   • Element spacing: ${analysis.layout.spacing}px consistent\n`;
234 |         }
235 |         if (analysis.layout.alignItems) {
236 |             description += `   • Cross-axis alignment: ${analysis.layout.alignItems}\n`;
237 |         }
238 |         if (analysis.layout.justifyContent) {
239 |             description += `   • Main-axis alignment: ${analysis.layout.justifyContent}\n`;
240 |         }
241 |     } else {
242 |         description += `   • Layout flow: absolute positioning\n`;
243 |     }
244 | 
245 |     // Padding analysis
246 |     if (analysis.layout.padding) {
247 |         const p = analysis.layout.padding;
248 |         if (p.isUniform) {
249 |             description += `   • Internal padding: ${p.top}px uniform\n`;
250 |         } else {
251 |             description += `   • Internal padding: ${p.top}px ${p.right}px ${p.bottom}px ${p.left}px (TRBL)\n`;
252 |         }
253 |     }
254 | 
255 |     // Children positioning
256 |     if (analysis.children.length > 0) {
257 |         description += `   • Contains ${analysis.children.length} child elements\n`;
258 |         const highImportanceChildren = analysis.children.filter(c => c.visualImportance >= 7);
259 |         if (highImportanceChildren.length > 0) {
260 |             description += `   • ${highImportanceChildren.length} high-priority elements (visual weight ≥7)\n`;
261 |         }
262 |     }
263 | 
264 |     return description;
265 | }
266 | 
267 | /**
268 |  * Generate screen spatial description
269 |  */
270 | function generateScreenSpatialDescription(analysis: ScreenAnalysis): string {
271 |     let description = '';
272 |     
273 |     // Screen zones
274 |     const screenHeight = analysis.metadata.dimensions.height;
275 |     description += `   • Header zone: Y < ${Math.round(screenHeight * 0.15)}px (top 15%)\n`;
276 |     description += `   • Content zone: Y ${Math.round(screenHeight * 0.15)}px - ${Math.round(screenHeight * 0.85)}px\n`;
277 |     description += `   • Footer zone: Y > ${Math.round(screenHeight * 0.85)}px (bottom 15%)\n`;
278 | 
279 |     // Content area
280 |     if (analysis.layout.contentArea) {
281 |         const area = analysis.layout.contentArea;
282 |         description += `   • Primary content area: ${Math.round(area.width)}×${Math.round(area.height)}px\n`;
283 |         description += `   • Content position: (${Math.round(area.x)}, ${Math.round(area.y)})\n`;
284 |     }
285 | 
286 |     // Section distribution
287 |     if (analysis.sections.length > 0) {
288 |         const sectionsByType = analysis.sections.reduce((acc, section) => {
289 |             acc[section.type] = (acc[section.type] || 0) + 1;
290 |             return acc;
291 |         }, {} as Record<string, number>);
292 |         
293 |         description += `   • Section distribution: `;
294 |         Object.entries(sectionsByType).forEach(([type, count], index) => {
295 |             description += `${count} ${type}${index < Object.keys(sectionsByType).length - 1 ? ', ' : ''}`;
296 |         });
297 |         description += `\n`;
298 |     }
299 | 
300 |     return description;
301 | }
302 | 
303 | /**
304 |  * Generate screen hierarchy
305 |  */
306 | function generateScreenHierarchy(analysis: ScreenAnalysis): string {
307 |     let hierarchy = '';
308 |     
309 |     // Sort sections by importance and position
310 |     const sortedSections = [...analysis.sections].sort((a, b) => {
311 |         // First by type priority (header > content > footer)
312 |         const typePriority = { header: 3, navigation: 2, content: 1, footer: 0, sidebar: 1, modal: 2, other: 0 };
313 |         const aPriority = typePriority[a.type] || 0;
314 |         const bPriority = typePriority[b.type] || 0;
315 |         if (aPriority !== bPriority) return bPriority - aPriority;
316 |         
317 |         // Then by importance score
318 |         return b.importance - a.importance;
319 |     });
320 | 
321 |     sortedSections.forEach((section, index) => {
322 |         const position = section.layout.dimensions ? 
323 |             `${Math.round(section.layout.dimensions.width)}×${Math.round(section.layout.dimensions.height)}px` : 
324 |             'auto';
325 |         hierarchy += `   ${index + 1}. ${section.name} (${section.type.toUpperCase()}) - ${position}\n`;
326 |         hierarchy += `      Priority: ${section.importance}/10`;
327 |         if (section.children.length > 0) {
328 |             hierarchy += `, Contains: ${section.children.length} elements`;
329 |         }
330 |         if (section.components.length > 0) {
331 |             hierarchy += `, Components: ${section.components.length}`;
332 |         }
333 |         hierarchy += `\n`;
334 |     });
335 | 
336 |     return hierarchy;
337 | }
338 | 
339 | /**
340 |  * Generate component pattern description
341 |  */
342 | function generateComponentPatternDescription(analysis: ComponentAnalysis): string {
343 |     let patterns = '';
344 |     
345 |     // Layout pattern
346 |     patterns += `   • Layout type: ${analysis.layout.type}\n`;
347 |     
348 |     // Spacing pattern
349 |     if (analysis.layout.spacing !== undefined) {
350 |         patterns += `   • Spacing system: ${analysis.layout.spacing}px consistent\n`;
351 |     }
352 |     
353 |     // Visual styling patterns
354 |     if (analysis.styling.fills && analysis.styling.fills.length > 0) {
355 |         const primaryColor = analysis.styling.fills[0].hex;
356 |         patterns += `   • Color pattern: Primary ${primaryColor}\n`;
357 |     }
358 |     
359 |     if (analysis.styling.cornerRadius !== undefined) {
360 |         const radius = typeof analysis.styling.cornerRadius === 'number' 
361 |             ? analysis.styling.cornerRadius 
362 |             : `${analysis.styling.cornerRadius.topLeft}px mixed`;
363 |         patterns += `   • Border radius: ${radius}px consistent\n`;
364 |     }
365 |     
366 |     // Component grouping
367 |     if (analysis.children.length > 0) {
368 |         const componentChildren = analysis.children.filter(c => c.isNestedComponent).length;
369 |         if (componentChildren > 0) {
370 |             patterns += `   • Component composition: ${componentChildren}/${analysis.children.length} nested components\n`;
371 |         }
372 |     }
373 | 
374 |     // Visual weight
375 |     const textElements = analysis.children.filter(c => c.type === 'TEXT').length;
376 |     const visualElements = analysis.children.length - textElements;
377 |     patterns += `   • Content balance: ${textElements} text, ${visualElements} visual elements\n`;
378 | 
379 |     return patterns;
380 | }
381 | 
382 | /**
383 |  * Generate screen pattern description
384 |  */
385 | function generateScreenPatternDescription(analysis: ScreenAnalysis): string {
386 |     let patterns = '';
387 |     
388 |     // Overall layout pattern
389 |     patterns += `   • Layout type: ${analysis.layout.type}\n`;
390 |     if (analysis.layout.scrollable) {
391 |         patterns += `   • Scroll behavior: vertical scrolling enabled\n`;
392 |     }
393 |     
394 |     // Section patterns
395 |     const sectionTypes = analysis.sections.map(s => s.type);
396 |     const hasStandardLayout = sectionTypes.includes('header') && sectionTypes.includes('content');
397 |     patterns += `   • Screen structure: ${hasStandardLayout ? 'standard' : 'custom'} layout pattern\n`;
398 |     
399 |     // Navigation patterns
400 |     if (analysis.navigation.hasTabBar) patterns += `   • Navigation: bottom tab bar\n`;
401 |     if (analysis.navigation.hasAppBar) patterns += `   • Navigation: top app bar\n`;
402 |     if (analysis.navigation.hasDrawer) patterns += `   • Navigation: side drawer\n`;
403 |     
404 |     // Visual weight distribution
405 |     const headerSections = analysis.sections.filter(s => s.type === 'header').length;
406 |     const contentSections = analysis.sections.filter(s => s.type === 'content').length;
407 |     const footerSections = analysis.sections.filter(s => s.type === 'footer' || s.type === 'navigation').length;
408 |     
409 |     if (headerSections > contentSections) {
410 |         patterns += `   • Visual weight: header-heavy design\n`;
411 |     } else if (footerSections > contentSections) {
412 |         patterns += `   • Visual weight: bottom-heavy design\n`;
413 |     } else {
414 |         patterns += `   • Visual weight: content-focused design\n`;
415 |     }
416 | 
417 |     return patterns;
418 | }
419 | 
420 | /**
421 |  * Generate component implementation hints
422 |  */
423 | function generateComponentImplementationHints(analysis: ComponentAnalysis): string {
424 |     let hints = '';
425 |     
426 |     // Main container suggestion
427 |     if (analysis.layout.type === 'auto-layout') {
428 |         const widget = analysis.layout.direction === 'horizontal' ? 'Row' : 'Column';
429 |         hints += `   • Main container: Use ${widget}() for ${analysis.layout.direction} layout\n`;
430 |         if (analysis.layout.spacing) {
431 |             hints += `   • Spacing: Add SizedBox gaps of ${analysis.layout.spacing}px\n`;
432 |         }
433 |     } else {
434 |         hints += `   • Main container: Use Stack() or Container() for absolute positioning\n`;
435 |     }
436 |     
437 |     // Styling approach
438 |     if (analysis.styling.fills || analysis.styling.strokes || analysis.styling.cornerRadius !== undefined) {
439 |         hints += `   • Styling: Implement BoxDecoration for visual styling\n`;
440 |     }
441 |     
442 |     // Text handling
443 |     const textChildren = analysis.children.filter(c => c.type === 'TEXT');
444 |     if (textChildren.length > 0) {
445 |         hints += `   • Text elements: ${textChildren.length} Text() widgets with custom styling\n`;
446 |     }
447 |     
448 |     // Component composition
449 |     const nestedComponents = analysis.children.filter(c => c.isNestedComponent);
450 |     if (nestedComponents.length > 0) {
451 |         hints += `   • Component structure: Break down ${nestedComponents.length} nested components\n`;
452 |     }
453 |     
454 |     // Responsive considerations
455 |     if (analysis.layout.dimensions.width > 400) {
456 |         hints += `   • Responsive: Consider MediaQuery for larger screens\n`;
457 |     }
458 | 
459 |     return hints;
460 | }
461 | 
462 | /**
463 |  * Generate screen implementation hints
464 |  */
465 | function generateScreenImplementationHints(analysis: ScreenAnalysis): string {
466 |     let hints = '';
467 |     
468 |     // Main scaffold structure
469 |     hints += `   • Main structure: Scaffold with systematic layout\n`;
470 |     
471 |     // App bar recommendation
472 |     if (analysis.navigation.hasAppBar) {
473 |         hints += `   • App bar: AppBar widget for top navigation\n`;
474 |     }
475 |     
476 |     // Body structure
477 |     if (analysis.layout.scrollable) {
478 |         hints += `   • Body: SingleChildScrollView with Column layout\n`;
479 |     } else {
480 |         hints += `   • Body: Column layout for fixed content\n`;
481 |     }
482 |     
483 |     // Navigation recommendation
484 |     if (analysis.navigation.hasTabBar) {
485 |         hints += `   • Bottom navigation: BottomNavigationBar for tab switching\n`;
486 |     }
487 |     if (analysis.navigation.hasDrawer) {
488 |         hints += `   • Side navigation: Drawer widget for menu access\n`;
489 |     }
490 |     
491 |     // Section breakdown
492 |     if (analysis.sections.length > 3) {
493 |         hints += `   • Widget organization: Break into ${analysis.sections.length} section widgets\n`;
494 |     }
495 |     
496 |     // Device considerations
497 |     if (analysis.metadata.deviceType === 'mobile') {
498 |         hints += `   • Mobile optimization: Use SafeArea and responsive sizing\n`;
499 |     } else if (analysis.metadata.deviceType === 'tablet') {
500 |         hints += `   • Tablet layout: Consider NavigationRail for wider screens\n`;
501 |     }
502 |     
503 |     // Asset handling
504 |     if (analysis.assets.length > 0) {
505 |         hints += `   • Assets: ${analysis.assets.length} image/icon assets to implement\n`;
506 |     }
507 | 
508 |     return hints;
509 | }
510 | 
```

--------------------------------------------------------------------------------
/src/tools/flutter/components/helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type {ComponentVariant} from "../../../extractors/components/types.js";
  2 | import type {ComponentAnalysis} from "../../../extractors/components/types.js";
  3 | import {generateFlutterTextWidget} from "../../../extractors/components/extractor.js";
  4 | import {generateComponentVisualContext} from "../visual-context.js";
  5 | 
  6 | /**
  7 |  * Generate variant selection prompt when there are more than 3 variants
  8 |  */
  9 | export function generateVariantSelectionPrompt(
 10 |     componentName: string,
 11 |     selectionInfo: any,
 12 |     variants: ComponentVariant[]
 13 | ): string {
 14 |     let output = `Component Set "${componentName}" has ${selectionInfo.totalCount} variants.\n\n`;
 15 |     output += `Since there are more than 3 variants, please specify which ones to analyze.\n\n`;
 16 | 
 17 |     output += `Available variants:\n`;
 18 |     variants.forEach((variant, index) => {
 19 |         const defaultMark = variant.isDefault ? ' (default)' : '';
 20 |         output += `${index + 1}. ${variant.name}${defaultMark}\n`;
 21 |     });
 22 | 
 23 |     output += `\nVariant properties:\n`;
 24 |     Object.entries(selectionInfo.variantProperties).forEach(([prop, values]: [string, any]) => {
 25 |         output += `- ${prop}: ${Array.from(values).join(', ')}\n`;
 26 |     });
 27 | 
 28 |     if (selectionInfo.defaultVariant) {
 29 |         output += `\nDefault variant: ${selectionInfo.defaultVariant.name}\n`;
 30 |     }
 31 | 
 32 |     output += `\nTo analyze specific variants, run the tool again with:\n`;
 33 |     output += `variantSelection: ["variant name 1", "variant name 2"]\n\n`;
 34 |     output += `Or to analyze all variants (may be token-intensive):\n`;
 35 |     output += `variantSelection: ${JSON.stringify(variants.slice(0, 3).map(v => v.name))}\n`;
 36 | 
 37 |     return output;
 38 | }
 39 | 
 40 | /**
 41 |  * Generate comprehensive component analysis report
 42 |  */
 43 | export function generateComponentAnalysisReport(
 44 |     analysis: ComponentAnalysis,
 45 |     variantAnalysis?: ComponentVariant[],
 46 |     selectedVariants?: ComponentVariant[],
 47 |     parsedInput?: any
 48 | ): string {
 49 |     let output = `Component Analysis Report\n\n`;
 50 | 
 51 |     // Component metadata
 52 |     output += `Component: ${analysis.metadata.name}\n`;
 53 |     output += `Type: ${analysis.metadata.type}\n`;
 54 |     output += `Node ID: ${analysis.metadata.nodeId}\n`;
 55 |     if (parsedInput) {
 56 |         output += `Source: ${parsedInput.source === 'url' ? 'Figma URL' : 'Direct input'}\n`;
 57 |     }
 58 |     output += `\n`;
 59 | 
 60 |     // Variant information
 61 |     if (variantAnalysis && variantAnalysis.length > 0) {
 62 |         output += `Variants Analysis:\n`;
 63 |         if (selectedVariants && selectedVariants.length > 0) {
 64 |             output += `Analyzed variants (${selectedVariants.length} of ${variantAnalysis.length}):\n`;
 65 |             selectedVariants.forEach(variant => {
 66 |                 const defaultMark = variant.isDefault ? ' (default)' : '';
 67 |                 output += `- ${variant.name}${defaultMark}\n`;
 68 |             });
 69 |         } else {
 70 |             output += `Total variants: ${variantAnalysis.length}\n`;
 71 |         }
 72 |         output += `\n`;
 73 |     }
 74 | 
 75 |     // Layout information
 76 |     output += `Layout Structure:\n`;
 77 |     output += `- Type: ${analysis.layout.type}\n`;
 78 |     output += `- Dimensions: ${Math.round(analysis.layout.dimensions.width)}×${Math.round(analysis.layout.dimensions.height)}px\n`;
 79 | 
 80 |     if (analysis.layout.direction) {
 81 |         output += `- Direction: ${analysis.layout.direction}\n`;
 82 |     }
 83 |     if (analysis.layout.spacing !== undefined) {
 84 |         output += `- Spacing: ${analysis.layout.spacing}px\n`;
 85 |     }
 86 |     if (analysis.layout.padding) {
 87 |         const p = analysis.layout.padding;
 88 |         if (p.isUniform) {
 89 |             output += `- Padding: ${p.top}px (uniform)\n`;
 90 |         } else {
 91 |             output += `- Padding: ${p.top}px ${p.right}px ${p.bottom}px ${p.left}px\n`;
 92 |         }
 93 |     }
 94 |     if (analysis.layout.alignItems) {
 95 |         output += `- Align Items: ${analysis.layout.alignItems}\n`;
 96 |     }
 97 |     if (analysis.layout.justifyContent) {
 98 |         output += `- Justify Content: ${analysis.layout.justifyContent}\n`;
 99 |     }
100 |     output += `\n`;
101 | 
102 |     // Styling information
103 |     output += `Visual Styling:\n`;
104 |     if (analysis.styling.fills && analysis.styling.fills.length > 0) {
105 |         const fill = analysis.styling.fills[0];
106 |         output += `- Background: ${fill.hex || fill.type}`;
107 |         if (fill.opacity && fill.opacity !== 1) {
108 |             output += ` (${Math.round(fill.opacity * 100)}% opacity)`;
109 |         }
110 |         output += `\n`;
111 |     }
112 |     if (analysis.styling.strokes && analysis.styling.strokes.length > 0) {
113 |         const stroke = analysis.styling.strokes[0];
114 |         output += `- Border: ${stroke.weight}px solid ${stroke.hex}\n`;
115 |     }
116 |     if (analysis.styling.cornerRadius !== undefined) {
117 |         if (typeof analysis.styling.cornerRadius === 'number') {
118 |             output += `- Corner radius: ${analysis.styling.cornerRadius}px\n`;
119 |         } else {
120 |             const r = analysis.styling.cornerRadius;
121 |             output += `- Corner radius: ${r.topLeft}px ${r.topRight}px ${r.bottomRight}px ${r.bottomLeft}px\n`;
122 |         }
123 |     }
124 |     if (analysis.styling.opacity && analysis.styling.opacity !== 1) {
125 |         output += `- Opacity: ${Math.round(analysis.styling.opacity * 100)}%\n`;
126 |     }
127 | 
128 |     // Effects (shadows, blurs)
129 |     if (analysis.styling.effects) {
130 |         const effects = analysis.styling.effects;
131 |         if (effects.dropShadows.length > 0) {
132 |             effects.dropShadows.forEach((shadow, index) => {
133 |                 output += `- Drop shadow ${index + 1}: ${shadow.hex} offset(${shadow.offset.x}, ${shadow.offset.y}) blur ${shadow.radius}px`;
134 |                 if (shadow.spread) {
135 |                     output += ` spread ${shadow.spread}px`;
136 |                 }
137 |                 output += `\n`;
138 |             });
139 |         }
140 |         if (effects.innerShadows.length > 0) {
141 |             output += `- Inner shadows: ${effects.innerShadows.length} effect(s)\n`;
142 |         }
143 |         if (effects.blurs.length > 0) {
144 |             output += `- Blur effects: ${effects.blurs.length} effect(s)\n`;
145 |         }
146 |     }
147 |     output += `\n`;
148 | 
149 |     // Children information
150 |     if (analysis.children.length > 0) {
151 |         output += `Child Elements (${analysis.children.length} analyzed):\n`;
152 |         analysis.children.forEach((child, index) => {
153 |             const componentMark = child.isNestedComponent ? ' [COMPONENT]' : '';
154 |             const importanceMark = ` (priority: ${child.visualImportance}/10)`;
155 |             output += `${index + 1}. ${child.name} (${child.type})${componentMark}${importanceMark}\n`;
156 | 
157 |             if (child.basicInfo?.layout?.dimensions) {
158 |                 const dims = child.basicInfo.layout.dimensions;
159 |                 output += `   Size: ${Math.round(dims.width)}×${Math.round(dims.height)}px\n`;
160 |             }
161 | 
162 |             if (child.basicInfo?.styling?.fills && child.basicInfo.styling.fills.length > 0) {
163 |                 output += `   Background: ${child.basicInfo.styling.fills[0].hex}\n`;
164 |             }
165 | 
166 |             if (child.basicInfo?.text) {
167 |                 const textInfo = child.basicInfo.text;
168 |                 const placeholderMark = textInfo.isPlaceholder ? ' [PLACEHOLDER]' : '';
169 |                 const semanticMark = textInfo.semanticType && textInfo.semanticType !== 'other' ? ` [${textInfo.semanticType.toUpperCase()}]` : '';
170 | 
171 |                 output += `   Text Content: "${textInfo.content}"${placeholderMark}${semanticMark}\n`;
172 | 
173 |                 if (textInfo.fontFamily || textInfo.fontSize || textInfo.fontWeight) {
174 |                     const fontParts = [];
175 |                     if (textInfo.fontFamily) fontParts.push(textInfo.fontFamily);
176 |                     if (textInfo.fontSize) fontParts.push(`${textInfo.fontSize}px`);
177 |                     if (textInfo.fontWeight && textInfo.fontWeight !== 400) fontParts.push(`weight: ${textInfo.fontWeight}`);
178 |                     output += `   Typography: ${fontParts.join(' ')}\n`;
179 |                 }
180 | 
181 |                 if (textInfo.textCase && textInfo.textCase !== 'mixed') {
182 |                     output += `   Text Case: ${textInfo.textCase}\n`;
183 |                 }
184 |             }
185 |         });
186 |         output += `\n`;
187 |     }
188 | 
189 |     // Nested components for separate analysis
190 |     if (analysis.nestedComponents.length > 0) {
191 |         output += `Nested Components Found (${analysis.nestedComponents.length}):\n`;
192 |         output += `These components should be analyzed separately to maintain reusability:\n`;
193 |         analysis.nestedComponents.forEach((comp, index) => {
194 |             output += `${index + 1}. ${comp.name}\n`;
195 |             output += `   Node ID: ${comp.nodeId}\n`;
196 |             output += `   Type: ${comp.instanceType || 'COMPONENT'}\n`;
197 |             if (comp.componentKey) {
198 |                 output += `   Component Key: ${comp.componentKey}\n`;
199 |             }
200 |         });
201 |         output += `\n`;
202 |     }
203 | 
204 |     // Skipped nodes report
205 |     if (analysis.skippedNodes && analysis.skippedNodes.length > 0) {
206 |         output += `Analysis Limitations:\n`;
207 |         output += `${analysis.skippedNodes.length} nodes were skipped due to the maxChildNodes limit:\n`;
208 |         analysis.skippedNodes.forEach((skipped, index) => {
209 |             output += `${index + 1}. ${skipped.name} (${skipped.type})\n`;
210 |         });
211 |         output += `\nTo analyze all nodes, increase the maxChildNodes parameter.\n\n`;
212 |     }
213 | 
214 |     // Visual context for AI implementation
215 |     if (parsedInput?.source === 'url') {
216 |         // Reconstruct the Figma URL from the parsed input
217 |         const figmaUrl = `https://www.figma.com/design/${parsedInput.fileId}/?node-id=${parsedInput.nodeId}`;
218 |         output += generateComponentVisualContext(analysis, figmaUrl, parsedInput.nodeId);
219 |         output += `\n`;
220 |     }
221 | 
222 |     // Flutter implementation guidance
223 |     output += generateFlutterGuidance(analysis);
224 | 
225 |     return output;
226 | }
227 | 
228 | /**
229 |  * Generate Flutter implementation guidance
230 |  */
231 | export function generateFlutterGuidance(analysis: ComponentAnalysis): string {
232 |     let guidance = `Flutter Implementation Guidance:\n\n`;
233 | 
234 |     // Widget composition best practices
235 |     guidance += `🏗️  Widget Composition Best Practices:\n`;
236 |     guidance += `- Start by building the complete widget tree in a single build() method\n`;
237 |     guidance += `- Keep composing widgets inline until you reach ~200 lines of code\n`;
238 |     guidance += `- Only then extract reusable parts into private StatelessWidget classes\n`;
239 |     guidance += `- Use private widgets (prefix with _) for internal component breakdown\n`;
240 |     guidance += `- Avoid functional widgets - always use StatelessWidget classes\n\n`;
241 | 
242 |     // Main container guidance
243 |     guidance += `Main Widget Structure:\n`;
244 |     if (analysis.layout.type === 'auto-layout') {
245 |         const containerWidget = analysis.layout.direction === 'horizontal' ? 'Row' : 'Column';
246 |         guidance += `- Use ${containerWidget}() as the main layout widget\n`;
247 | 
248 |         if (analysis.layout.spacing && analysis.layout.spacing > 0) {
249 |             const spacingWidget = analysis.layout.direction === 'horizontal' ? 'width' : 'height';
250 |             guidance += `- Add spacing with SizedBox(${spacingWidget}: ${analysis.layout.spacing})\n`;
251 |         }
252 | 
253 |         if (analysis.layout.alignItems) {
254 |             guidance += `- CrossAxisAlignment: ${mapFigmaToFlutterAlignment(analysis.layout.alignItems)}\n`;
255 |         }
256 |         if (analysis.layout.justifyContent) {
257 |             guidance += `- MainAxisAlignment: ${mapFigmaToFlutterAlignment(analysis.layout.justifyContent)}\n`;
258 |         }
259 |     } else {
260 |         guidance += `- Use Container() or Stack() for layout\n`;
261 |     }
262 | 
263 |     // Styling guidance
264 |     if (hasVisualStyling(analysis.styling)) {
265 |         guidance += `\nContainer Decoration:\n`;
266 |         guidance += `Container(\n`;
267 |         guidance += `  decoration: BoxDecoration(\n`;
268 | 
269 |         if (analysis.styling.fills && analysis.styling.fills.length > 0) {
270 |             const fill = analysis.styling.fills[0];
271 |             if (fill.hex) {
272 |                 guidance += `    color: Color(0xFF${fill.hex.substring(1)}),\n`;
273 |             }
274 |         }
275 | 
276 |         if (analysis.styling.strokes && analysis.styling.strokes.length > 0) {
277 |             const stroke = analysis.styling.strokes[0];
278 |             guidance += `    border: Border.all(\n`;
279 |             guidance += `      color: Color(0xFF${stroke.hex.substring(1)}),\n`;
280 |             guidance += `      width: ${stroke.weight},\n`;
281 |             guidance += `    ),\n`;
282 |         }
283 | 
284 |         if (analysis.styling.cornerRadius !== undefined) {
285 |             if (typeof analysis.styling.cornerRadius === 'number') {
286 |                 guidance += `    borderRadius: BorderRadius.circular(${analysis.styling.cornerRadius}),\n`;
287 |             } else {
288 |                 const r = analysis.styling.cornerRadius;
289 |                 guidance += `    borderRadius: BorderRadius.only(\n`;
290 |                 guidance += `      topLeft: Radius.circular(${r.topLeft}),\n`;
291 |                 guidance += `      topRight: Radius.circular(${r.topRight}),\n`;
292 |                 guidance += `      bottomLeft: Radius.circular(${r.bottomLeft}),\n`;
293 |                 guidance += `      bottomRight: Radius.circular(${r.bottomRight}),\n`;
294 |                 guidance += `    ),\n`;
295 |             }
296 |         }
297 | 
298 |         if (analysis.styling.effects?.dropShadows.length) {
299 |             guidance += `    boxShadow: [\n`;
300 |             analysis.styling.effects.dropShadows.forEach(shadow => {
301 |                 guidance += `      BoxShadow(\n`;
302 |                 guidance += `        color: Color(0xFF${shadow.hex.substring(1)}).withOpacity(${shadow.opacity.toFixed(2)}),\n`;
303 |                 guidance += `        offset: Offset(${shadow.offset.x}, ${shadow.offset.y}),\n`;
304 |                 guidance += `        blurRadius: ${shadow.radius},\n`;
305 |                 if (shadow.spread) {
306 |                     guidance += `        spreadRadius: ${shadow.spread},\n`;
307 |                 }
308 |                 guidance += `      ),\n`;
309 |             });
310 |             guidance += `    ],\n`;
311 |         }
312 | 
313 |         guidance += `  ),\n`;
314 | 
315 |         if (analysis.layout.padding) {
316 |             const p = analysis.layout.padding;
317 |             if (p.isUniform) {
318 |                 guidance += `  padding: EdgeInsets.all(${p.top}),\n`;
319 |             } else {
320 |                 guidance += `  padding: EdgeInsets.fromLTRB(${p.left}, ${p.top}, ${p.right}, ${p.bottom}),\n`;
321 |             }
322 |         }
323 | 
324 |         guidance += `  child: /* Your content here */\n`;
325 |         guidance += `)\n\n`;
326 |     }
327 | 
328 |     // Component organization guidance
329 |     if (analysis.nestedComponents.length > 0) {
330 |         guidance += `Component Architecture:\n`;
331 |         guidance += `Create separate widget classes for reusability:\n`;
332 |         analysis.nestedComponents.forEach((comp, index) => {
333 |             const widgetName = toPascalCase(comp.name);
334 |             guidance += `${index + 1}. ${widgetName}() - Node ID: ${comp.nodeId}\n`;
335 |         });
336 |         guidance += `\nAnalyze each nested component separately using the analyze_figma_component tool.\n\n`;
337 |     }
338 | 
339 |     // Text widget guidance with enhanced Flutter suggestions
340 |     const textChildren = analysis.children.filter(child => child.type === 'TEXT');
341 |     if (textChildren.length > 0) {
342 |         guidance += `Text Elements & Flutter Widgets:\n`;
343 |         textChildren.forEach((textChild, index) => {
344 |             const textInfo = textChild.basicInfo?.text;
345 |             if (textInfo) {
346 |                 // Import the generateFlutterTextWidget function result
347 |                 const widgetSuggestion = generateFlutterTextWidget(textInfo);
348 |                 const placeholderNote = textInfo.isPlaceholder ? ' // Placeholder text - replace with actual content' : '';
349 |                 const semanticNote = textInfo.semanticType && textInfo.semanticType !== 'other' ? ` // Detected as ${textInfo.semanticType}` : '';
350 | 
351 |                 guidance += `${index + 1}. "${textInfo.content}"${placeholderNote}${semanticNote}\n`;
352 |                 guidance += `   Flutter Widget:\n`;
353 | 
354 |                 // Indent the widget suggestion
355 |                 const indentedWidget = widgetSuggestion.split('\n').map(line => `   ${line}`).join('\n');
356 |                 guidance += `${indentedWidget}\n\n`;
357 |             } else {
358 |                 guidance += `${index + 1}. Text('${textChild.name}') // No text info available\n\n`;
359 |             }
360 |         });
361 |     }
362 | 
363 |     return guidance;
364 | }
365 | 
366 | /**
367 |  * Generate structure inspection report
368 |  */
369 | export function generateStructureInspectionReport(node: any, showAllChildren: boolean): string {
370 |     let output = `Component Structure Inspection\n\n`;
371 | 
372 |     output += `Component: ${node.name}\n`;
373 |     output += `Type: ${node.type}\n`;
374 |     output += `Node ID: ${node.id}\n`;
375 |     output += `Children: ${node.children?.length || 0}\n`;
376 | 
377 |     if (node.absoluteBoundingBox) {
378 |         const bbox = node.absoluteBoundingBox;
379 |         output += `Dimensions: ${Math.round(bbox.width)}×${Math.round(bbox.height)}px\n`;
380 |     }
381 | 
382 |     output += `\n`;
383 | 
384 |     if (!node.children || node.children.length === 0) {
385 |         output += `This component has no children.\n`;
386 |         return output;
387 |     }
388 | 
389 |     output += `Child Structure:\n`;
390 | 
391 |     const childrenToShow = showAllChildren ? node.children : node.children.slice(0, 15);
392 |     const hasMore = node.children.length > childrenToShow.length;
393 | 
394 |     childrenToShow.forEach((child: any, index: number) => {
395 |         const isComponent = child.type === 'COMPONENT' || child.type === 'INSTANCE';
396 |         const componentMark = isComponent ? ' [COMPONENT]' : '';
397 |         const hiddenMark = child.visible === false ? ' [HIDDEN]' : '';
398 | 
399 |         output += `${index + 1}. ${child.name} (${child.type})${componentMark}${hiddenMark}\n`;
400 | 
401 |         if (child.absoluteBoundingBox) {
402 |             const bbox = child.absoluteBoundingBox;
403 |             output += `   Size: ${Math.round(bbox.width)}×${Math.round(bbox.height)}px\n`;
404 |         }
405 | 
406 |         if (child.children && child.children.length > 0) {
407 |             output += `   Contains: ${child.children.length} child nodes\n`;
408 |         }
409 | 
410 |         // Show basic styling info
411 |         if (child.fills && child.fills.length > 0) {
412 |             const fill = child.fills[0];
413 |             if (fill.color) {
414 |                 const hex = rgbaToHex(fill.color);
415 |                 output += `   Background: ${hex}\n`;
416 |             }
417 |         }
418 |     });
419 | 
420 |     if (hasMore) {
421 |         output += `\n... and ${node.children.length - childrenToShow.length} more children.\n`;
422 |         output += `Use showAllChildren: true to see all children.\n`;
423 |     }
424 | 
425 |     // Analysis recommendations
426 |     output += `\nAnalysis Recommendations:\n`;
427 |     const componentChildren = node.children.filter((child: any) =>
428 |         child.type === 'COMPONENT' || child.type === 'INSTANCE'
429 |     );
430 | 
431 |     if (componentChildren.length > 0) {
432 |         output += `- Found ${componentChildren.length} nested components for separate analysis\n`;
433 |     }
434 | 
435 |     const largeChildren = node.children.filter((child: any) => {
436 |         const bbox = child.absoluteBoundingBox;
437 |         return bbox && (bbox.width * bbox.height) > 5000;
438 |     });
439 | 
440 |     if (largeChildren.length > 3) {
441 |         output += `- Component has ${largeChildren.length} large children - consider increasing maxChildNodes\n`;
442 |     }
443 | 
444 |     const textChildren = node.children.filter((child: any) => child.type === 'TEXT');
445 |     if (textChildren.length > 0) {
446 |         output += `- Found ${textChildren.length} text nodes for content extraction\n`;
447 |     }
448 | 
449 |     return output;
450 | }
451 | 
452 | // Helper functions
453 | export function mapFigmaToFlutterAlignment(alignment: string): string {
454 |     const alignmentMap: Record<string, string> = {
455 |         'MIN': 'CrossAxisAlignment.start',
456 |         'CENTER': 'CrossAxisAlignment.center',
457 |         'MAX': 'CrossAxisAlignment.end',
458 |         'SPACE_BETWEEN': 'MainAxisAlignment.spaceBetween',
459 |         'SPACE_AROUND': 'MainAxisAlignment.spaceAround',
460 |         'SPACE_EVENLY': 'MainAxisAlignment.spaceEvenly'
461 |     };
462 | 
463 |     return alignmentMap[alignment] || 'CrossAxisAlignment.center';
464 | }
465 | 
466 | export function hasVisualStyling(styling: any): boolean {
467 |     return !!(styling.fills?.length || styling.strokes?.length ||
468 |         styling.cornerRadius !== undefined || styling.effects?.dropShadows?.length);
469 | }
470 | 
471 | export function toPascalCase(str: string): string {
472 |     return str
473 |         .replace(/[^a-zA-Z0-9]/g, ' ')
474 |         .replace(/\w+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
475 |         .replace(/\s/g, '');
476 | }
477 | 
478 | export function rgbaToHex(color: {r: number; g: number; b: number; a?: number}): string {
479 |     const r = Math.round(color.r * 255);
480 |     const g = Math.round(color.g * 255);
481 |     const b = Math.round(color.b * 255);
482 | 
483 |     return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
484 | }
```

--------------------------------------------------------------------------------
/src/tools/flutter/semantic-detection.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/tools/flutter/semantic-detection.ts
  2 | 
  3 | /**
  4 |  * Advanced semantic detection with multi-factor analysis and confidence scoring
  5 |  * Based on heuristics improvement recommendations
  6 |  */
  7 | 
  8 | export interface SemanticContext {
  9 |     parentType?: string;
 10 |     siblingTypes: string[];
 11 |     screenPosition: 'header' | 'content' | 'footer' | 'unknown';
 12 |     componentType: 'form' | 'navigation' | 'content' | 'card' | 'button-group' | 'unknown';
 13 |     layoutDirection?: 'horizontal' | 'vertical';
 14 |     isInteractive?: boolean;
 15 |     hasActionableNeighbors?: boolean;
 16 | }
 17 | 
 18 | export interface SemanticClassification {
 19 |     type: 'heading' | 'body' | 'label' | 'button' | 'link' | 'caption' | 'error' | 'success' | 'warning' | 'other';
 20 |     confidence: number; // 0-1
 21 |     alternatives: Array<{type: string; confidence: number}>;
 22 |     reasoning: string[];
 23 | }
 24 | 
 25 | export interface PositionContext {
 26 |     isTopLevel: boolean;
 27 |     isBottomLevel: boolean;
 28 |     isIsolated: boolean;
 29 |     visualWeight: number;
 30 |     relativeSize: number;
 31 |     hasNearbyInteractive: boolean;
 32 | }
 33 | 
 34 | export interface TextAnalysis {
 35 |     isActionable: boolean;
 36 |     isDescriptive: boolean;
 37 |     isNavigational: boolean;
 38 |     isStatusMessage: boolean;
 39 |     length: number;
 40 |     structure: 'imperative' | 'declarative' | 'question' | 'fragment' | 'mixed';
 41 |     hasActionWords: boolean;
 42 |     hasImperativeForm: boolean;
 43 | }
 44 | 
 45 | export interface DesignPattern {
 46 |     name: string;
 47 |     confidence: number;
 48 |     indicators: string[];
 49 | }
 50 | 
 51 | /**
 52 |  * Enhanced semantic type detection with multi-factor analysis
 53 |  */
 54 | export function detectSemanticTypeAdvanced(
 55 |     content: string,
 56 |     nodeName: string,
 57 |     context: SemanticContext,
 58 |     nodeProperties?: any
 59 | ): SemanticClassification {
 60 |     const factors = {
 61 |         textContent: analyzeTextContent(content),
 62 |         nodeName: analyzeNodeName(nodeName),
 63 |         position: nodeProperties ? analyzePosition(nodeProperties, context) : null,
 64 |         styling: nodeProperties ? analyzeStyling(nodeProperties) : null,
 65 |         patterns: detectDesignPatterns(content, nodeName, nodeProperties, context)
 66 |     };
 67 | 
 68 |     return weightedClassification(factors, context);
 69 | }
 70 | 
 71 | /**
 72 |  * Analyze text content for semantic clues
 73 |  */
 74 | function analyzeTextContent(content: string): TextAnalysis {
 75 |     const lowerContent = content.toLowerCase().trim();
 76 |     
 77 |     // Check for action words
 78 |     const actionWords = [
 79 |         'click', 'tap', 'press', 'submit', 'send', 'save', 'cancel', 'continue',
 80 |         'next', 'back', 'close', 'start', 'begin', 'try', 'get', 'download',
 81 |         'upload', 'buy', 'purchase', 'add', 'remove', 'edit', 'delete', 'create'
 82 |     ];
 83 |     
 84 |     const hasActionWords = actionWords.some(word => lowerContent.includes(word));
 85 |     
 86 |     // Check for imperative form (simplified detection)
 87 |     const hasImperativeForm = /^(let's|please|go|try|get|make|do|use|see|find|learn|discover)/.test(lowerContent) ||
 88 |                              /^[a-z]+ (now|here|more|started)$/i.test(lowerContent);
 89 |     
 90 |     // Check for navigational keywords
 91 |     const navKeywords = ['home', 'back', 'next', 'previous', 'menu', 'settings', 'profile', 'about', 'contact', 'help'];
 92 |     const isNavigational = navKeywords.some(word => lowerContent.includes(word));
 93 |     
 94 |     // Check for status messages
 95 |     const statusKeywords = ['error', 'success', 'warning', 'complete', 'failed', 'loading', 'pending'];
 96 |     const isStatusMessage = statusKeywords.some(word => lowerContent.includes(word));
 97 |     
 98 |     // Determine structure
 99 |     let structure: TextAnalysis['structure'] = 'mixed';
100 |     if (content.endsWith('?')) structure = 'question';
101 |     else if (hasImperativeForm || hasActionWords) structure = 'imperative';
102 |     else if (content.length > 50 && content.includes('.')) structure = 'declarative';
103 |     else if (content.length < 30 && !content.includes('.')) structure = 'fragment';
104 |     
105 |     return {
106 |         isActionable: hasActionWords || hasImperativeForm,
107 |         isDescriptive: content.length > 50 && !hasActionWords,
108 |         isNavigational,
109 |         isStatusMessage,
110 |         length: content.length,
111 |         structure,
112 |         hasActionWords,
113 |         hasImperativeForm
114 |     };
115 | }
116 | 
117 | /**
118 |  * Analyze node name for semantic clues
119 |  */
120 | function analyzeNodeName(nodeName: string): {type: string; confidence: number} {
121 |     const lowerName = nodeName.toLowerCase();
122 |     
123 |     // High confidence name patterns
124 |     if (lowerName.includes('button') || lowerName.includes('btn')) {
125 |         return {type: 'button', confidence: 0.9};
126 |     }
127 |     if (lowerName.includes('heading') || lowerName.includes('title') || /h[1-6]/.test(lowerName)) {
128 |         return {type: 'heading', confidence: 0.9};
129 |     }
130 |     if (lowerName.includes('label') || lowerName.includes('field')) {
131 |         return {type: 'label', confidence: 0.8};
132 |     }
133 |     if (lowerName.includes('link') || lowerName.includes('nav')) {
134 |         return {type: 'link', confidence: 0.8};
135 |     }
136 |     if (lowerName.includes('caption') || lowerName.includes('subtitle')) {
137 |         return {type: 'caption', confidence: 0.8};
138 |     }
139 |     if (lowerName.includes('error') || lowerName.includes('warning') || lowerName.includes('success')) {
140 |         return {type: lowerName.includes('error') ? 'error' : lowerName.includes('warning') ? 'warning' : 'success', confidence: 0.9};
141 |     }
142 |     
143 |     return {type: 'unknown', confidence: 0.0};
144 | }
145 | 
146 | /**
147 |  * Analyze position context
148 |  */
149 | function analyzePosition(nodeProperties: any, context: SemanticContext): PositionContext {
150 |     const bounds = nodeProperties.absoluteBoundingBox;
151 |     const parentBounds = nodeProperties.parent?.absoluteBoundingBox;
152 |     
153 |     if (!bounds || !parentBounds) {
154 |         return {
155 |             isTopLevel: false,
156 |             isBottomLevel: false,
157 |             isIsolated: false,
158 |             visualWeight: 1,
159 |             relativeSize: 1,
160 |             hasNearbyInteractive: false
161 |         };
162 |     }
163 |     
164 |     const relativeY = (bounds.y - parentBounds.y) / parentBounds.height;
165 |     const relativeSize = (bounds.width * bounds.height) / (parentBounds.width * parentBounds.height);
166 |     
167 |     return {
168 |         isTopLevel: relativeY < 0.2,
169 |         isBottomLevel: relativeY > 0.8,
170 |         isIsolated: calculateIsolation(bounds, nodeProperties.siblings || []),
171 |         visualWeight: calculateVisualWeight(nodeProperties),
172 |         relativeSize,
173 |         hasNearbyInteractive: context.hasActionableNeighbors || false
174 |     };
175 | }
176 | 
177 | /**
178 |  * Analyze styling patterns
179 |  */
180 | function analyzeStyling(nodeProperties: any): {buttonLike: number; textLike: number; statusLike: number} {
181 |     const styling = nodeProperties.styling || {};
182 |     
183 |     let buttonLike = 0;
184 |     let textLike = 0;
185 |     let statusLike = 0;
186 |     
187 |     // Button-like characteristics
188 |     if (styling.fills?.length > 0) buttonLike += 0.3;
189 |     if (styling.strokes?.length > 0) buttonLike += 0.2;
190 |     if (styling.cornerRadius !== undefined && styling.cornerRadius > 4) buttonLike += 0.3;
191 |     if (nodeProperties.layout?.padding) buttonLike += 0.2;
192 |     
193 |     // Text-like characteristics
194 |     if (!styling.fills || styling.fills.length === 0) textLike += 0.3;
195 |     if (!styling.strokes || styling.strokes.length === 0) textLike += 0.2;
196 |     if (!styling.cornerRadius || styling.cornerRadius === 0) textLike += 0.3;
197 |     
198 |     // Status-like characteristics (color-based)
199 |     if (styling.fills?.length > 0) {
200 |         const primaryColor = styling.fills[0].hex?.toLowerCase();
201 |         if (primaryColor?.includes('red') || primaryColor?.includes('ff')) statusLike += 0.4;
202 |         if (primaryColor?.includes('green') || primaryColor?.includes('0f')) statusLike += 0.4;
203 |         if (primaryColor?.includes('orange') || primaryColor?.includes('yellow')) statusLike += 0.4;
204 |     }
205 |     
206 |     return {buttonLike, textLike, statusLike};
207 | }
208 | 
209 | /**
210 |  * Detect design patterns
211 |  */
212 | function detectDesignPatterns(
213 |     content: string,
214 |     nodeName: string,
215 |     nodeProperties: any,
216 |     context: SemanticContext
217 | ): DesignPattern[] {
218 |     const patterns: DesignPattern[] = [];
219 |     
220 |     // Button pattern
221 |     const buttonPattern = detectButtonPattern(content, nodeName, nodeProperties, context);
222 |     if (buttonPattern.confidence > 0.5) patterns.push(buttonPattern);
223 |     
224 |     // Heading pattern
225 |     const headingPattern = detectHeadingPattern(content, nodeName, nodeProperties, context);
226 |     if (headingPattern.confidence > 0.5) patterns.push(headingPattern);
227 |     
228 |     // Form label pattern
229 |     const labelPattern = detectLabelPattern(content, nodeName, nodeProperties, context);
230 |     if (labelPattern.confidence > 0.5) patterns.push(labelPattern);
231 |     
232 |     // Navigation pattern
233 |     const navPattern = detectNavigationPattern(content, nodeName, nodeProperties, context);
234 |     if (navPattern.confidence > 0.5) patterns.push(navPattern);
235 |     
236 |     return patterns;
237 | }
238 | 
239 | /**
240 |  * Detect button pattern
241 |  */
242 | function detectButtonPattern(content: string, nodeName: string, nodeProperties: any, context: SemanticContext): DesignPattern {
243 |     const indicators: string[] = [];
244 |     let confidence = 0;
245 |     
246 |     // Content analysis
247 |     const textAnalysis = analyzeTextContent(content);
248 |     if (textAnalysis.isActionable) {
249 |         confidence += 0.4;
250 |         indicators.push('actionable text');
251 |     }
252 |     
253 |     // Visual characteristics
254 |     const styling = analyzeStyling(nodeProperties);
255 |     if (styling.buttonLike > 0.6) {
256 |         confidence += 0.3;
257 |         indicators.push('button-like styling');
258 |     }
259 |     
260 |     // Context analysis
261 |     if (context.componentType === 'form' && textAnalysis.hasActionWords) {
262 |         confidence += 0.2;
263 |         indicators.push('form context with action words');
264 |     }
265 |     
266 |     // Name analysis
267 |     if (nodeName.toLowerCase().includes('button')) {
268 |         confidence += 0.1;
269 |         indicators.push('button in name');
270 |     }
271 |     
272 |     return {
273 |         name: 'button',
274 |         confidence: Math.min(confidence, 1),
275 |         indicators
276 |     };
277 | }
278 | 
279 | /**
280 |  * Detect heading pattern
281 |  */
282 | function detectHeadingPattern(content: string, nodeName: string, nodeProperties: any, context: SemanticContext): DesignPattern {
283 |     const indicators: string[] = [];
284 |     let confidence = 0;
285 |     
286 |     // Size and typography
287 |     const fontSize = nodeProperties.text?.fontSize || 0;
288 |     const fontWeight = nodeProperties.text?.fontWeight || 400;
289 |     
290 |     if (fontSize > 18) {
291 |         confidence += 0.3;
292 |         indicators.push('large font size');
293 |     }
294 |     if (fontWeight >= 600) {
295 |         confidence += 0.2;
296 |         indicators.push('bold font weight');
297 |     }
298 |     
299 |     // Content structure
300 |     if (content.length < 80 && !content.endsWith('.')) {
301 |         confidence += 0.2;
302 |         indicators.push('short non-sentence text');
303 |     }
304 |     
305 |     // Position context
306 |     const position = analyzePosition(nodeProperties, context);
307 |     if (position.isTopLevel) {
308 |         confidence += 0.2;
309 |         indicators.push('top-level position');
310 |     }
311 |     
312 |     // Context analysis
313 |     if (context.screenPosition === 'header') {
314 |         confidence += 0.1;
315 |         indicators.push('header context');
316 |     }
317 |     
318 |     return {
319 |         name: 'heading',
320 |         confidence: Math.min(confidence, 1),
321 |         indicators
322 |     };
323 | }
324 | 
325 | /**
326 |  * Detect label pattern
327 |  */
328 | function detectLabelPattern(content: string, nodeName: string, nodeProperties: any, context: SemanticContext): DesignPattern {
329 |     const indicators: string[] = [];
330 |     let confidence = 0;
331 |     
332 |     // Content characteristics
333 |     if (content.length < 40 && content.endsWith(':')) {
334 |         confidence += 0.4;
335 |         indicators.push('short text ending with colon');
336 |     }
337 |     
338 |     // Context analysis
339 |     if (context.componentType === 'form') {
340 |         confidence += 0.3;
341 |         indicators.push('form context');
342 |     }
343 |     
344 |     // Name analysis
345 |     if (nodeName.toLowerCase().includes('label')) {
346 |         confidence += 0.3;
347 |         indicators.push('label in name');
348 |     }
349 |     
350 |     return {
351 |         name: 'label',
352 |         confidence: Math.min(confidence, 1),
353 |         indicators
354 |     };
355 | }
356 | 
357 | /**
358 |  * Detect navigation pattern
359 |  */
360 | function detectNavigationPattern(content: string, nodeName: string, nodeProperties: any, context: SemanticContext): DesignPattern {
361 |     const indicators: string[] = [];
362 |     let confidence = 0;
363 |     
364 |     // Content analysis
365 |     const textAnalysis = analyzeTextContent(content);
366 |     if (textAnalysis.isNavigational) {
367 |         confidence += 0.4;
368 |         indicators.push('navigational content');
369 |     }
370 |     
371 |     // Context analysis
372 |     if (context.componentType === 'navigation' || context.screenPosition === 'header') {
373 |         confidence += 0.3;
374 |         indicators.push('navigation context');
375 |     }
376 |     
377 |     // Layout analysis
378 |     if (context.hasActionableNeighbors) {
379 |         confidence += 0.2;
380 |         indicators.push('near other actionable elements');
381 |     }
382 |     
383 |     return {
384 |         name: 'navigation',
385 |         confidence: Math.min(confidence, 1),
386 |         indicators
387 |     };
388 | }
389 | 
390 | /**
391 |  * Weighted classification based on all factors
392 |  */
393 | function weightedClassification(factors: any, context: SemanticContext): SemanticClassification {
394 |     const candidates: Array<{type: string; confidence: number; reasoning: string[]}> = [];
395 |     
396 |     // Pattern-based classification (highest priority)
397 |     if (factors.patterns) {
398 |         factors.patterns.forEach((pattern: DesignPattern) => {
399 |             if (pattern.confidence > 0.6) {
400 |                 candidates.push({
401 |                     type: pattern.name,
402 |                     confidence: pattern.confidence,
403 |                     reasoning: [`Strong ${pattern.name} pattern: ${pattern.indicators.join(', ')}`]
404 |                 });
405 |             }
406 |         });
407 |     }
408 |     
409 |     // Text content analysis
410 |     if (factors.textContent) {
411 |         const textAnalysis = factors.textContent as TextAnalysis;
412 |         
413 |         if (textAnalysis.isActionable && textAnalysis.length < 50) {
414 |             candidates.push({
415 |                 type: 'button',
416 |                 confidence: 0.7,
417 |                 reasoning: ['Actionable short text content']
418 |             });
419 |         }
420 |         
421 |         if (textAnalysis.isNavigational) {
422 |             candidates.push({
423 |                 type: 'link',
424 |                 confidence: 0.6,
425 |                 reasoning: ['Navigational text content']
426 |             });
427 |         }
428 |         
429 |         if (textAnalysis.isStatusMessage) {
430 |             const statusType = determineStatusType(factors.textContent, factors.styling);
431 |             candidates.push({
432 |                 type: statusType,
433 |                 confidence: 0.8,
434 |                 reasoning: ['Status message detected']
435 |             });
436 |         }
437 |         
438 |         if (textAnalysis.length > 80 || textAnalysis.structure === 'declarative') {
439 |             candidates.push({
440 |                 type: 'body',
441 |                 confidence: 0.6,
442 |                 reasoning: ['Long descriptive text']
443 |             });
444 |         }
445 |     }
446 |     
447 |     // Name-based classification
448 |     if (factors.nodeName && factors.nodeName.confidence > 0.7) {
449 |         candidates.push({
450 |             type: factors.nodeName.type,
451 |             confidence: factors.nodeName.confidence,
452 |             reasoning: ['Node name indicates type']
453 |         });
454 |     }
455 |     
456 |     // Apply confidence threshold - only return high confidence classifications
457 |     const highConfidenceCandidates = candidates.filter(c => c.confidence >= 0.6);
458 |     
459 |     if (highConfidenceCandidates.length === 0) {
460 |         return {
461 |             type: 'other',
462 |             confidence: 0.0,
463 |             alternatives: candidates.map(c => ({type: c.type, confidence: c.confidence})),
464 |             reasoning: ['Low confidence in all classifications - deferring to AI interpretation']
465 |         };
466 |     }
467 |     
468 |     // Sort by confidence and return the best match
469 |     highConfidenceCandidates.sort((a, b) => b.confidence - a.confidence);
470 |     const bestMatch = highConfidenceCandidates[0];
471 |     
472 |     return {
473 |         type: bestMatch.type as any,
474 |         confidence: bestMatch.confidence,
475 |         alternatives: highConfidenceCandidates.slice(1).map(c => ({type: c.type, confidence: c.confidence})),
476 |         reasoning: bestMatch.reasoning
477 |     };
478 | }
479 | 
480 | /**
481 |  * Helper functions
482 |  */
483 | function calculateIsolation(bounds: any, siblings: any[]): boolean {
484 |     if (!siblings || siblings.length === 0) return true;
485 |     
486 |     const minDistance = 50; // pixels
487 |     return !siblings.some((sibling: any) => {
488 |         if (!sibling.absoluteBoundingBox) return false;
489 |         
490 |         const distance = Math.sqrt(
491 |             Math.pow(bounds.x - sibling.absoluteBoundingBox.x, 2) +
492 |             Math.pow(bounds.y - sibling.absoluteBoundingBox.y, 2)
493 |         );
494 |         
495 |         return distance < minDistance;
496 |     });
497 | }
498 | 
499 | function calculateVisualWeight(nodeProperties: any): number {
500 |     let weight = 1;
501 |     
502 |     // Size contribution
503 |     const area = (nodeProperties.absoluteBoundingBox?.width || 0) * (nodeProperties.absoluteBoundingBox?.height || 0);
504 |     if (area > 10000) weight += 2;
505 |     else if (area > 5000) weight += 1;
506 |     
507 |     // Styling contribution
508 |     if (nodeProperties.styling?.fills?.length > 0) weight += 1;
509 |     if (nodeProperties.styling?.strokes?.length > 0) weight += 0.5;
510 |     if (nodeProperties.text?.fontWeight >= 600) weight += 1;
511 |     
512 |     return weight;
513 | }
514 | 
515 | function determineStatusType(textAnalysis: TextAnalysis, styling: any): string {
516 |     const content = textAnalysis;
517 |     // This is a simplified version - in practice you'd analyze the content more thoroughly
518 |     if (styling?.statusLike > 0.5) {
519 |         // Could analyze color to determine if error, warning, or success
520 |         return 'error'; // simplified
521 |     }
522 |     return 'other';
523 | }
524 | 
525 | /**
526 |  * Enhanced section type detection with confidence scoring
527 |  */
528 | export function detectSectionTypeAdvanced(
529 |     node: any,
530 |     parent?: any,
531 |     siblings?: any[]
532 | ): {type: 'header' | 'navigation' | 'content' | 'footer' | 'sidebar' | 'modal' | 'other'; confidence: number; reasoning: string[]} {
533 |     const factors = {
534 |         nodeName: analyzeNodeName(node.name),
535 |         position: parent ? analyzePosition(node, generateSemanticContext(node, parent, siblings)) : null,
536 |         styling: analyzeStyling(node),
537 |         siblings: siblings ? analyzeSiblingContext(siblings) : null
538 |     };
539 | 
540 |     return classifySection(factors);
541 | }
542 | 
543 | /**
544 |  * Classify section based on multiple factors
545 |  */
546 | function classifySection(factors: any): {type: 'header' | 'navigation' | 'content' | 'footer' | 'sidebar' | 'modal' | 'other'; confidence: number; reasoning: string[]} {
547 |     const candidates: Array<{type: 'header' | 'navigation' | 'content' | 'footer' | 'sidebar' | 'modal' | 'other'; confidence: number; reasoning: string[]}> = [];
548 |     
549 |     // Name-based classification
550 |     if (factors.nodeName.confidence > 0.7) {
551 |         const nameType = factors.nodeName.type;
552 |         const sectionType = (['header', 'navigation', 'content', 'footer', 'sidebar', 'modal'].includes(nameType)) 
553 |             ? nameType as 'header' | 'navigation' | 'content' | 'footer' | 'sidebar' | 'modal'
554 |             : 'other' as const;
555 |         
556 |         candidates.push({
557 |             type: sectionType,
558 |             confidence: factors.nodeName.confidence,
559 |             reasoning: ['Node name indicates section type']
560 |         });
561 |     }
562 |     
563 |     // Position-based classification
564 |     if (factors.position) {
565 |         if (factors.position.isTopLevel) {
566 |             candidates.push({
567 |                 type: 'header',
568 |                 confidence: 0.7,
569 |                 reasoning: ['Positioned in top area of screen']
570 |             });
571 |         } else if (factors.position.isBottomLevel) {
572 |             candidates.push({
573 |                 type: 'footer',
574 |                 confidence: 0.7,
575 |                 reasoning: ['Positioned in bottom area of screen']
576 |             });
577 |         }
578 |     }
579 |     
580 |     // Styling-based classification
581 |     if (factors.styling.buttonLike > 0.6 && factors.siblings?.hasMultipleButtons) {
582 |         candidates.push({
583 |             type: 'navigation',
584 |             confidence: 0.6,
585 |             reasoning: ['Multiple button-like elements suggest navigation']
586 |         });
587 |     }
588 |     
589 |     // Apply confidence threshold
590 |     const highConfidenceCandidates = candidates.filter(c => c.confidence >= 0.6);
591 |     
592 |     if (highConfidenceCandidates.length === 0) {
593 |         return {
594 |             type: 'content',
595 |             confidence: 0.5,
596 |             reasoning: ['Default classification - insufficient confidence in alternatives']
597 |         };
598 |     }
599 |     
600 |     // Return best match
601 |     highConfidenceCandidates.sort((a, b) => b.confidence - a.confidence);
602 |     const bestMatch = highConfidenceCandidates[0];
603 |     
604 |     return {
605 |         type: bestMatch.type,
606 |         confidence: bestMatch.confidence,
607 |         reasoning: bestMatch.reasoning
608 |     };
609 | }
610 | 
611 | /**
612 |  * Analyze sibling context for section detection
613 |  */
614 | function analyzeSiblingContext(siblings: any[]): {hasMultipleButtons: boolean; hasNavigation: boolean} {
615 |     const buttonLikeSiblings = siblings.filter(sibling => {
616 |         const name = sibling.name?.toLowerCase() || '';
617 |         return name.includes('button') || name.includes('btn') || name.includes('tab');
618 |     });
619 |     
620 |     const navigationSiblings = siblings.filter(sibling => {
621 |         const name = sibling.name?.toLowerCase() || '';
622 |         return name.includes('nav') || name.includes('menu') || name.includes('link');
623 |     });
624 |     
625 |     return {
626 |         hasMultipleButtons: buttonLikeSiblings.length >= 2,
627 |         hasNavigation: navigationSiblings.length > 0
628 |     };
629 | }
630 | 
631 | /**
632 |  * Generate semantic context from component hierarchy
633 |  */
634 | export function generateSemanticContext(
635 |     node: any,
636 |     parent?: any,
637 |     siblings?: any[]
638 | ): SemanticContext {
639 |     const parentName = parent?.name?.toLowerCase() || '';
640 |     const siblingTypes = siblings?.map(s => s.type) || [];
641 |     
642 |     // Determine screen position
643 |     let screenPosition: SemanticContext['screenPosition'] = 'unknown';
644 |     if (parent?.absoluteBoundingBox) {
645 |         const relativeY = (node.absoluteBoundingBox?.y || 0) - parent.absoluteBoundingBox.y;
646 |         const parentHeight = parent.absoluteBoundingBox.height;
647 |         
648 |         if (relativeY < parentHeight * 0.15) screenPosition = 'header';
649 |         else if (relativeY > parentHeight * 0.85) screenPosition = 'footer';
650 |         else screenPosition = 'content';
651 |     }
652 |     
653 |     // Determine component type
654 |     let componentType: SemanticContext['componentType'] = 'unknown';
655 |     if (parentName.includes('form') || parentName.includes('input')) componentType = 'form';
656 |     else if (parentName.includes('nav') || parentName.includes('menu')) componentType = 'navigation';
657 |     else if (parentName.includes('card') || parentName.includes('item')) componentType = 'card';
658 |     else if (parentName.includes('button') && siblings?.length) componentType = 'button-group';
659 |     else componentType = 'content';
660 |     
661 |     // Detect actionable neighbors
662 |     const hasActionableNeighbors = siblings?.some(sibling => {
663 |         const name = sibling.name?.toLowerCase() || '';
664 |         return name.includes('button') || name.includes('link') || name.includes('nav');
665 |     }) || false;
666 |     
667 |     return {
668 |         parentType: parent?.type,
669 |         siblingTypes,
670 |         screenPosition,
671 |         componentType,
672 |         layoutDirection: parent?.layoutMode === 'HORIZONTAL' ? 'horizontal' : 'vertical',
673 |         hasActionableNeighbors
674 |     };
675 | }
676 | 
```

--------------------------------------------------------------------------------
/src/extractors/screens/extractor.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/extractors/screens/extractor.mts
  2 | 
  3 | import type {FigmaNode} from '../../types/figma.js';
  4 | import type {
  5 |     ScreenAnalysis,
  6 |     ScreenMetadata,
  7 |     ScreenLayoutInfo,
  8 |     ScreenSection,
  9 |     NavigationInfo,
 10 |     NavigationElement,
 11 |     ScreenAssetInfo,
 12 |     SkippedNodeInfo,
 13 |     ScreenExtractionOptions
 14 | } from './types.js';
 15 | import type {ComponentChild, NestedComponentInfo} from '../components/types.js';
 16 | import {
 17 |     extractLayoutInfo,
 18 |     extractStylingInfo,
 19 |     createComponentChild,
 20 |     createNestedComponentInfo,
 21 |     calculateVisualImportance,
 22 |     isComponentNode
 23 | } from '../components/extractor.js';
 24 | import { detectSectionTypeAdvanced } from '../../tools/flutter/semantic-detection.js';
 25 | 
 26 | /**
 27 |  * Extract screen metadata
 28 |  */
 29 | export function extractScreenMetadata(node: FigmaNode): ScreenMetadata {
 30 |     const dimensions = {
 31 |         width: node.absoluteBoundingBox?.width || 0,
 32 |         height: node.absoluteBoundingBox?.height || 0
 33 |     };
 34 | 
 35 |     const deviceType = detectDeviceType(dimensions);
 36 |     const orientation = detectOrientation(dimensions);
 37 | 
 38 |     return {
 39 |         name: node.name,
 40 |         type: node.type as 'FRAME' | 'PAGE' | 'COMPONENT',
 41 |         nodeId: node.id,
 42 |         deviceType,
 43 |         orientation,
 44 |         dimensions
 45 |     };
 46 | }
 47 | 
 48 | /**
 49 |  * Extract screen layout information
 50 |  */
 51 | export function extractScreenLayoutInfo(node: FigmaNode): ScreenLayoutInfo {
 52 |     const baseLayout = extractLayoutInfo(node);
 53 |     
 54 |     return {
 55 |         ...baseLayout,
 56 |         scrollable: detectScrollable(node),
 57 |         hasHeader: detectHeader(node),
 58 |         hasFooter: detectFooter(node),
 59 |         hasNavigation: detectNavigation(node),
 60 |         contentArea: calculateContentArea(node)
 61 |     };
 62 | }
 63 | 
 64 | /**
 65 |  * Analyze screen sections (header, content, footer, etc.)
 66 |  */
 67 | export function analyzeScreenSections(
 68 |     node: FigmaNode,
 69 |     options: Required<ScreenExtractionOptions>
 70 | ): {
 71 |     sections: ScreenSection[];
 72 |     components: NestedComponentInfo[];
 73 |     skippedNodes: SkippedNodeInfo[];
 74 | } {
 75 |     const sections: ScreenSection[] = [];
 76 |     const components: NestedComponentInfo[] = [];
 77 |     const skippedNodes: SkippedNodeInfo[] = [];
 78 | 
 79 |     if (!node.children || node.children.length === 0) {
 80 |         return {sections, components, skippedNodes};
 81 |     }
 82 | 
 83 |     // Filter visible nodes unless includeHiddenNodes is true
 84 |     let visibleChildren = node.children;
 85 |     if (!options.includeHiddenNodes) {
 86 |         visibleChildren = node.children.filter(child => child.visible !== false);
 87 |     }
 88 | 
 89 |     // Filter out device UI elements (status bars, notches, home indicators, etc.)
 90 |     const filteredDeviceUI = visibleChildren.filter(child => isDeviceUIElement(child, node));
 91 |     visibleChildren = visibleChildren.filter(child => !isDeviceUIElement(child, node));
 92 |     
 93 |     // Add filtered device UI elements to skipped nodes for reporting
 94 |     filteredDeviceUI.forEach(deviceUINode => {
 95 |         skippedNodes.push({
 96 |             nodeId: deviceUINode.id,
 97 |             name: deviceUINode.name,
 98 |             type: deviceUINode.type,
 99 |             reason: 'device_ui_element'
100 |         });
101 |     });
102 | 
103 |     // Analyze each child as potential section
104 |     const sectionsWithImportance = visibleChildren.map(child => {
105 |         const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
106 |         return {
107 |             node: child,
108 |             importance: calculateSectionImportance(child),
109 |             sectionType: detectSectionType(child, node, siblings)
110 |         };
111 |     });
112 | 
113 |     // Sort by importance
114 |     sectionsWithImportance.sort((a, b) => b.importance - a.importance);
115 | 
116 |     // Process up to maxSections
117 |     const processedCount = Math.min(sectionsWithImportance.length, options.maxSections);
118 | 
119 |     for (let i = 0; i < sectionsWithImportance.length; i++) {
120 |         const {node: child, importance, sectionType} = sectionsWithImportance[i];
121 | 
122 |         if (i < processedCount) {
123 |             const section = createScreenSection(child, sectionType, importance, options);
124 |             sections.push(section);
125 | 
126 |             // Collect nested components from this section
127 |             section.components.forEach(comp => {
128 |                 if (!components.find(c => c.nodeId === comp.nodeId)) {
129 |                     components.push(comp);
130 |                 }
131 |             });
132 |         } else {
133 |             skippedNodes.push({
134 |                 nodeId: child.id,
135 |                 name: child.name,
136 |                 type: child.type,
137 |                 reason: 'max_sections'
138 |             });
139 |         }
140 |     }
141 | 
142 |     return {sections, components, skippedNodes};
143 | }
144 | 
145 | /**
146 |  * Extract navigation information
147 |  */
148 | export function extractNavigationInfo(node: FigmaNode): NavigationInfo {
149 |     const navigationElements: NavigationElement[] = [];
150 |     
151 |     // Traverse to find navigation elements
152 |     traverseForNavigation(node, navigationElements);
153 | 
154 |     return {
155 |         hasTabBar: detectTabBar(node),
156 |         hasAppBar: detectAppBar(node),
157 |         hasDrawer: detectDrawer(node),
158 |         hasBottomSheet: detectBottomSheet(node),
159 |         navigationElements
160 |     };
161 | }
162 | 
163 | /**
164 |  * Extract screen assets information
165 |  */
166 | export function extractScreenAssets(node: FigmaNode): ScreenAssetInfo[] {
167 |     const assets: ScreenAssetInfo[] = [];
168 |     
169 |     traverseForAssets(node, assets);
170 |     
171 |     return assets;
172 | }
173 | 
174 | /**
175 |  * Create screen section
176 |  */
177 | function createScreenSection(
178 |     node: FigmaNode,
179 |     sectionType: ScreenSection['type'],
180 |     importance: number,
181 |     options: Required<ScreenExtractionOptions>
182 | ): ScreenSection {
183 |     const children: ComponentChild[] = [];
184 |     const components: NestedComponentInfo[] = [];
185 | 
186 |     // Analyze section children
187 |     if (node.children) {
188 |         const visibleChildren = options.includeHiddenNodes 
189 |             ? node.children 
190 |             : node.children.filter(child => child.visible !== false);
191 | 
192 |         visibleChildren.forEach(child => {
193 |             const childImportance = calculateVisualImportance(child);
194 |             const isComponent = isComponentNode(child);
195 | 
196 |             if (isComponent) {
197 |                 components.push(createNestedComponentInfo(child));
198 |             }
199 | 
200 |             // Pass parent and siblings for better semantic detection
201 |             const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
202 |             children.push(createComponentChild(child, childImportance, isComponent, {
203 |                 maxChildNodes: 20, // Higher limit for screens
204 |                 maxDepth: options.maxDepth,
205 |                 includeHiddenNodes: options.includeHiddenNodes,
206 |                 prioritizeComponents: true,
207 |                 extractTextContent: true
208 |             }, node, siblings));
209 |         });
210 |     }
211 | 
212 |     return {
213 |         id: `section_${node.id}`,
214 |         name: node.name,
215 |         type: sectionType,
216 |         nodeId: node.id,
217 |         layout: extractLayoutInfo(node),
218 |         styling: extractStylingInfo(node),
219 |         children,
220 |         components,
221 |         importance
222 |     };
223 | }
224 | 
225 | /**
226 |  * Calculate section importance for prioritization
227 |  */
228 | function calculateSectionImportance(node: FigmaNode): number {
229 |     let score = 0;
230 | 
231 |     // Size importance (0-3 points)
232 |     const area = (node.absoluteBoundingBox?.width || 0) * (node.absoluteBoundingBox?.height || 0);
233 |     if (area > 50000) score += 3;
234 |     else if (area > 20000) score += 2;
235 |     else if (area > 5000) score += 1;
236 | 
237 |     // Position importance (0-2 points) - top elements are more important
238 |     const y = node.absoluteBoundingBox?.y || 0;
239 |     if (y < 100) score += 2; // Header area
240 |     else if (y < 200) score += 1;
241 | 
242 |     // Type importance (0-3 points)
243 |     if (node.type === 'COMPONENT' || node.type === 'INSTANCE') score += 3;
244 |     else if (node.type === 'FRAME') score += 2;
245 | 
246 |     // Name-based importance (0-2 points)
247 |     const name = node.name.toLowerCase();
248 |     if (name.includes('header') || name.includes('nav') || name.includes('footer')) score += 2;
249 |     else if (name.includes('content') || name.includes('main') || name.includes('body')) score += 1;
250 | 
251 |     return Math.min(score, 10);
252 | }
253 | 
254 | /**
255 |  * Detect section type based on node properties
256 |  * Enhanced with multi-factor analysis and confidence scoring
257 |  */
258 | function detectSectionType(node: FigmaNode, parent?: FigmaNode, siblings?: FigmaNode[]): ScreenSection['type'] {
259 |     // Try advanced detection first
260 |     try {
261 |         const classification = detectSectionTypeAdvanced(node, parent, siblings);
262 |         
263 |         // Use advanced classification if confidence is high enough
264 |         if (classification.confidence >= 0.6) {
265 |             return classification.type as ScreenSection['type'];
266 |         }
267 |         
268 |         // Log reasoning for debugging (in development)
269 |         if (process.env.NODE_ENV === 'development') {
270 |             console.debug(`Low confidence (${classification.confidence}) for section "${node.name}": ${classification.reasoning.join(', ')}`);
271 |         }
272 |     } catch (error) {
273 |         // Fall back to legacy detection if advanced detection fails
274 |         console.warn('Advanced section detection failed, using legacy method:', error);
275 |     }
276 | 
277 |     // Legacy detection as fallback
278 |     return detectSectionTypeLegacy(node);
279 | }
280 | 
281 | /**
282 |  * Legacy section type detection (fallback)
283 |  */
284 | function detectSectionTypeLegacy(node: FigmaNode): ScreenSection['type'] {
285 |     const name = node.name.toLowerCase();
286 |     const bounds = node.absoluteBoundingBox;
287 | 
288 |     // Name-based detection
289 |     if (name.includes('header') || name.includes('app bar') || name.includes('top bar')) return 'header';
290 |     if (name.includes('footer') || name.includes('bottom') || name.includes('tab bar')) return 'footer';
291 |     if (name.includes('nav') || name.includes('menu') || name.includes('sidebar')) return 'navigation';
292 |     if (name.includes('modal') || name.includes('dialog') || name.includes('popup')) return 'modal';
293 |     if (name.includes('content') || name.includes('main') || name.includes('body')) return 'content';
294 | 
295 |     // Position-based detection
296 |     if (bounds) {
297 |         const screenHeight = bounds.y + bounds.height;
298 |         
299 |         // Top 15% of screen likely header
300 |         if (bounds.y < screenHeight * 0.15) return 'header';
301 |         
302 |         // Bottom 15% of screen likely footer/navigation
303 |         if (bounds.y > screenHeight * 0.85) return 'footer';
304 |         
305 |         // Side areas might be navigation
306 |         if (bounds.width < 100 && bounds.height > 200) return 'sidebar';
307 |     }
308 | 
309 |     return 'content';
310 | }
311 | 
312 | /**
313 |  * Detect device type based on dimensions
314 |  */
315 | function detectDeviceType(dimensions: {width: number; height: number}): ScreenMetadata['deviceType'] {
316 |     const {width, height} = dimensions;
317 |     const maxDimension = Math.max(width, height);
318 |     const minDimension = Math.min(width, height);
319 | 
320 |     // Mobile devices (typical ranges)
321 |     if (maxDimension <= 900 && minDimension <= 500) return 'mobile';
322 |     
323 |     // Tablet devices
324 |     if (maxDimension <= 1400 && minDimension <= 1000) return 'tablet';
325 |     
326 |     // Desktop
327 |     if (maxDimension > 1400) return 'desktop';
328 | 
329 |     return 'unknown';
330 | }
331 | 
332 | /**
333 |  * Detect orientation
334 |  */
335 | function detectOrientation(dimensions: {width: number; height: number}): ScreenMetadata['orientation'] {
336 |     return dimensions.width > dimensions.height ? 'landscape' : 'portrait';
337 | }
338 | 
339 | /**
340 |  * Detect if screen is scrollable
341 |  */
342 | function detectScrollable(node: FigmaNode): boolean {
343 |     // Check for scroll properties or overflow
344 |     const nodeAny = node as any;
345 |     return !!(nodeAny.overflowDirection || nodeAny.scrollBehavior);
346 | }
347 | 
348 | /**
349 |  * Detect header presence
350 |  */
351 | function detectHeader(node: FigmaNode): boolean {
352 |     if (!node.children) return false;
353 |     
354 |     return node.children.some(child => {
355 |         const name = child.name.toLowerCase();
356 |         const bounds = child.absoluteBoundingBox;
357 |         
358 |         return (name.includes('header') || name.includes('app bar') || name.includes('top bar')) ||
359 |                (bounds && bounds.y < 150); // Top area
360 |     });
361 | }
362 | 
363 | /**
364 |  * Detect footer presence
365 |  */
366 | function detectFooter(node: FigmaNode): boolean {
367 |     if (!node.children) return false;
368 |     
369 |     const screenHeight = node.absoluteBoundingBox?.height || 0;
370 |     
371 |     return node.children.some(child => {
372 |         const name = child.name.toLowerCase();
373 |         const bounds = child.absoluteBoundingBox;
374 |         
375 |         return (name.includes('footer') || name.includes('bottom') || name.includes('tab bar')) ||
376 |                (bounds && bounds.y > screenHeight * 0.8); // Bottom area
377 |     });
378 | }
379 | 
380 | /**
381 |  * Detect navigation presence
382 |  */
383 | function detectNavigation(node: FigmaNode): boolean {
384 |     if (!node.children) return false;
385 |     
386 |     return node.children.some(child => {
387 |         const name = child.name.toLowerCase();
388 |         return name.includes('nav') || name.includes('menu') || name.includes('tab') || name.includes('drawer');
389 |     });
390 | }
391 | 
392 | /**
393 |  * Calculate content area
394 |  */
395 | function calculateContentArea(node: FigmaNode): ScreenLayoutInfo['contentArea'] {
396 |     const bounds = node.absoluteBoundingBox;
397 |     if (!bounds) return undefined;
398 | 
399 |     // Simple heuristic: assume content is the main area minus header/footer
400 |     return {
401 |         x: bounds.x,
402 |         y: bounds.y + 100, // Assume 100px header
403 |         width: bounds.width,
404 |         height: bounds.height - 200 // Assume 100px header + 100px footer
405 |     };
406 | }
407 | 
408 | /**
409 |  * Detect tab bar
410 |  */
411 | function detectTabBar(node: FigmaNode): boolean {
412 |     return traverseAndCheck(node, child => {
413 |         const name = child.name.toLowerCase();
414 |         return !!(name.includes('tab bar') || name.includes('bottom nav') || 
415 |                (name.includes('tab') && child.children && child.children.length > 1));
416 |     });
417 | }
418 | 
419 | /**
420 |  * Detect app bar
421 |  */
422 | function detectAppBar(node: FigmaNode): boolean {
423 |     return traverseAndCheck(node, child => {
424 |         const name = child.name.toLowerCase();
425 |         return !!(name.includes('app bar') || name.includes('header') || name.includes('top bar'));
426 |     });
427 | }
428 | 
429 | /**
430 |  * Detect drawer
431 |  */
432 | function detectDrawer(node: FigmaNode): boolean {
433 |     return traverseAndCheck(node, child => {
434 |         const name = child.name.toLowerCase();
435 |         return !!(name.includes('drawer') || name.includes('sidebar') || name.includes('menu'));
436 |     });
437 | }
438 | 
439 | /**
440 |  * Detect bottom sheet
441 |  */
442 | function detectBottomSheet(node: FigmaNode): boolean {
443 |     return traverseAndCheck(node, child => {
444 |         const name = child.name.toLowerCase();
445 |         return !!(name.includes('bottom sheet') || name.includes('modal') || name.includes('popup'));
446 |     });
447 | }
448 | 
449 | /**
450 |  * Traverse and check condition
451 |  */
452 | function traverseAndCheck(node: FigmaNode, condition: (node: FigmaNode) => boolean): boolean {
453 |     if (condition(node)) return true;
454 |     
455 |     if (node.children) {
456 |         return node.children.some(child => traverseAndCheck(child, condition));
457 |     }
458 |     
459 |     return false;
460 | }
461 | 
462 | /**
463 |  * Traverse for navigation elements
464 |  */
465 | function traverseForNavigation(node: FigmaNode, results: NavigationElement[], depth: number = 0): void {
466 |     if (depth > 3) return;
467 | 
468 |     const name = node.name.toLowerCase();
469 |     
470 |     // Check if this node is a navigation element
471 |     if (isNavigationElement(node)) {
472 |         results.push({
473 |             nodeId: node.id,
474 |             name: node.name,
475 |             type: detectNavigationElementType(node),
476 |             text: extractNavigationText(node),
477 |             icon: hasIcon(node),
478 |             isActive: detectActiveState(node)
479 |         });
480 |     }
481 | 
482 |     // Traverse children
483 |     if (node.children) {
484 |         node.children.forEach(child => {
485 |             traverseForNavigation(child, results, depth + 1);
486 |         });
487 |     }
488 | }
489 | 
490 | /**
491 |  * Check if node is navigation element
492 |  */
493 | function isNavigationElement(node: FigmaNode): boolean {
494 |     const name = node.name.toLowerCase();
495 |     
496 |     return name.includes('tab') || name.includes('button') || name.includes('link') ||
497 |            name.includes('menu') || name.includes('nav') || 
498 |            (node.type === 'INSTANCE' && name.includes('item'));
499 | }
500 | 
501 | /**
502 |  * Detect navigation element type
503 |  */
504 | function detectNavigationElementType(node: FigmaNode): NavigationElement['type'] {
505 |     const name = node.name.toLowerCase();
506 |     
507 |     if (name.includes('tab')) return 'tab';
508 |     if (name.includes('button')) return 'button';
509 |     if (name.includes('link')) return 'link';
510 |     if (name.includes('icon')) return 'icon';
511 |     if (name.includes('menu')) return 'menu';
512 |     
513 |     return 'other';
514 | }
515 | 
516 | /**
517 |  * Extract navigation text
518 |  */
519 | function extractNavigationText(node: FigmaNode): string | undefined {
520 |     // Look for text children
521 |     if (node.children) {
522 |         for (const child of node.children) {
523 |             if (child.type === 'TEXT' && child.name) {
524 |                 return child.name;
525 |             }
526 |         }
527 |     }
528 |     
529 |     // Fallback to node name if it looks like text
530 |     const name = node.name;
531 |     if (name && !name.toLowerCase().includes('component') && !name.toLowerCase().includes('instance')) {
532 |         return name;
533 |     }
534 |     
535 |     return undefined;
536 | }
537 | 
538 | /**
539 |  * Check if node has icon
540 |  */
541 | function hasIcon(node: FigmaNode): boolean {
542 |     if (!node.children) return false;
543 |     
544 |     return node.children.some(child => {
545 |         const name = child.name.toLowerCase();
546 |         return name.includes('icon') || child.type === 'VECTOR';
547 |     });
548 | }
549 | 
550 | /**
551 |  * Detect active state
552 |  */
553 | function detectActiveState(node: FigmaNode): boolean {
554 |     const name = node.name.toLowerCase();
555 |     return name.includes('active') || name.includes('selected') || name.includes('current');
556 | }
557 | 
558 | /**
559 |  * Check if a node represents device UI elements that should be ignored
560 |  */
561 | function isDeviceUIElement(child: FigmaNode, parent: FigmaNode): boolean {
562 |     const name = child.name.toLowerCase();
563 |     const bounds = child.absoluteBoundingBox;
564 |     const parentBounds = parent.absoluteBoundingBox;
565 |     
566 |     if (!bounds || !parentBounds) return false;
567 |     
568 |     // Name-based detection for common device UI elements
569 |     const deviceUIKeywords = [
570 |         'status bar', 'statusbar', 'status_bar',
571 |         'battery', 'signal', 'wifi', 'cellular', 'carrier',
572 |         'notch', 'dynamic island', 'safe area',
573 |         'home indicator', 'home_indicator', 'home bar',
574 |         'navigation bar', 'system bar', 'system_bar',
575 |         'chin', 'bezel', 'nav bar',
576 |         'clock', 'time indicator',
577 |         'signal strength', 'battery indicator',
578 |         'screen recording', 'screen_recording'
579 |     ];
580 |     
581 |     // Check if name contains device UI keywords
582 |     if (deviceUIKeywords.some(keyword => name.includes(keyword))) {
583 |         return true;
584 |     }
585 |     
586 |     // Position and size-based detection
587 |     const screenWidth = parentBounds.width;
588 |     const screenHeight = parentBounds.height;
589 |     const elementWidth = bounds.width;
590 |     const elementHeight = bounds.height;
591 |     const elementY = bounds.y - parentBounds.y; // Relative position
592 |     const elementX = bounds.x - parentBounds.x;
593 |     
594 |     // Status bar detection (top of screen)
595 |     if (elementY <= 50 && // Very close to top
596 |         elementWidth >= screenWidth * 0.8 && // Nearly full width
597 |         elementHeight <= 50) { // Thin height
598 |         
599 |         // Additional checks for status bar content
600 |         if (hasStatusBarContent(child)) {
601 |             return true;
602 |         }
603 |     }
604 |     
605 |     // Home indicator detection (bottom of screen)
606 |     if (elementY >= screenHeight - 50 && // Very close to bottom
607 |         elementWidth <= screenWidth * 0.4 && // Narrow width (typical home indicator)
608 |         elementHeight <= 20 && // Very thin
609 |         elementX >= screenWidth * 0.3 && // Centered horizontally
610 |         elementX <= screenWidth * 0.7) {
611 |         return true;
612 |     }
613 |     
614 |     // Notch/Dynamic Island detection (top center, small area)
615 |     if (elementY <= 30 && // Very top
616 |         elementWidth <= screenWidth * 0.3 && // Small width
617 |         elementHeight <= 30 && // Small height
618 |         elementX >= screenWidth * 0.35 && // Centered area
619 |         elementX <= screenWidth * 0.65) {
620 |         return true;
621 |     }
622 |     
623 |     // Side bezels or navigation areas
624 |     if ((elementX <= 20 || elementX >= screenWidth - 20) && // At edges
625 |         elementWidth <= 40 && // Thin
626 |         elementHeight >= screenHeight * 0.3) { // Tall
627 |         return true;
628 |     }
629 |     
630 |     // Small elements in corners (likely UI indicators)
631 |     const isInCorner = (elementX <= 50 || elementX >= screenWidth - 50) &&
632 |                       (elementY <= 50 || elementY >= screenHeight - 50);
633 |     if (isInCorner && elementWidth <= 80 && elementHeight <= 80) {
634 |         return true;
635 |     }
636 |     
637 |     return false;
638 | }
639 | 
640 | /**
641 |  * Check if a node contains typical status bar content
642 |  */
643 | function hasStatusBarContent(node: FigmaNode): boolean {
644 |     if (!node.children) return false;
645 |     
646 |     const statusBarElements = node.children.some(child => {
647 |         const name = child.name.toLowerCase();
648 |         return name.includes('battery') || 
649 |                name.includes('signal') || 
650 |                name.includes('wifi') || 
651 |                name.includes('time') || 
652 |                name.includes('clock') ||
653 |                name.includes('carrier') ||
654 |                name.includes('cellular') ||
655 |                (child.type === 'TEXT' && /^\d{1,2}:\d{2}/.test(child.name)); // Time format
656 |     });
657 |     
658 |     return statusBarElements;
659 | }
660 | 
661 | /**
662 |  * Traverse for assets
663 |  */
664 | function traverseForAssets(node: FigmaNode, results: ScreenAssetInfo[], depth: number = 0): void {
665 |     if (depth > 4) return;
666 | 
667 |     // Check if this node is an asset
668 |     if (isAssetNode(node)) {
669 |         results.push({
670 |             nodeId: node.id,
671 |             name: node.name,
672 |             type: detectAssetType(node),
673 |             size: detectAssetSize(node),
674 |             usage: detectAssetUsage(node)
675 |         });
676 |     }
677 | 
678 |     // Traverse children
679 |     if (node.children) {
680 |         node.children.forEach(child => {
681 |             traverseForAssets(child, results, depth + 1);
682 |         });
683 |     }
684 | }
685 | 
686 | /**
687 |  * Check if node is an asset
688 |  */
689 | function isAssetNode(node: FigmaNode): boolean {
690 |     // Check for image fills
691 |     if (node.fills && node.fills.some((fill: any) => fill.type === 'IMAGE')) return true;
692 |     
693 |     // Check for vectors that are likely assets
694 |     if (node.type === 'VECTOR') {
695 |         const name = node.name.toLowerCase();
696 |         return name.includes('image') || name.includes('illustration') || 
697 |                name.includes('icon') || name.includes('logo');
698 |     }
699 |     
700 |     return false;
701 | }
702 | 
703 | /**
704 |  * Detect asset type
705 |  */
706 | function detectAssetType(node: FigmaNode): ScreenAssetInfo['type'] {
707 |     const name = node.name.toLowerCase();
708 |     
709 |     if (name.includes('icon')) return 'icon';
710 |     if (name.includes('illustration') || name.includes('graphic')) return 'illustration';
711 |     if (name.includes('background') || name.includes('bg')) return 'background';
712 |     
713 |     return 'image';
714 | }
715 | 
716 | /**
717 |  * Detect asset size
718 |  */
719 | function detectAssetSize(node: FigmaNode): ScreenAssetInfo['size'] {
720 |     const bounds = node.absoluteBoundingBox;
721 |     if (!bounds) return 'medium';
722 |     
723 |     const area = bounds.width * bounds.height;
724 |     
725 |     if (area < 2500) return 'small'; // < 50x50
726 |     if (area > 40000) return 'large'; // > 200x200
727 |     
728 |     return 'medium';
729 | }
730 | 
731 | /**
732 |  * Detect asset usage
733 |  */
734 | function detectAssetUsage(node: FigmaNode): ScreenAssetInfo['usage'] {
735 |     const name = node.name.toLowerCase();
736 |     
737 |     if (name.includes('logo') || name.includes('brand')) return 'branding';
738 |     if (name.includes('nav') || name.includes('menu') || name.includes('tab')) return 'navigation';
739 |     if (name.includes('background') || name.includes('decoration')) return 'decorative';
740 |     
741 |     return 'content';
742 | }
743 | 
```

--------------------------------------------------------------------------------
/src/extractors/components/extractor.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // src/extractors/components/extractor.mts
  2 | 
  3 | import type {FigmaNode, FigmaColor, FigmaEffect} from '../../types/figma.js';
  4 | import type {
  5 |     ComponentMetadata,
  6 |     LayoutInfo,
  7 |     StylingInfo,
  8 |     ComponentChild,
  9 |     NestedComponentInfo,
 10 |     SkippedNodeInfo,
 11 |     CategorizedEffects,
 12 |     ColorInfo,
 13 |     StrokeInfo,
 14 |     CornerRadii,
 15 |     PaddingInfo,
 16 |     TextInfo,
 17 |     ComponentExtractionOptions
 18 | } from './types.js';
 19 | import { detectSemanticTypeAdvanced, generateSemanticContext } from '../../tools/flutter/semantic-detection.js';
 20 | 
 21 | /**
 22 |  * Extract component metadata
 23 |  */
 24 | export function extractMetadata(node: FigmaNode, userDefinedAsComponent: boolean): ComponentMetadata {
 25 |     const metadata: ComponentMetadata = {
 26 |         name: node.name,
 27 |         type: node.type as 'COMPONENT' | 'COMPONENT_SET' | 'FRAME',
 28 |         nodeId: node.id,
 29 |         isUserDefinedComponent: userDefinedAsComponent
 30 |     };
 31 | 
 32 |     // Add component-specific metadata
 33 |     if (node.type === 'COMPONENT' || node.type === 'COMPONENT_SET') {
 34 |         metadata.componentKey = (node as any).componentKey;
 35 |     }
 36 | 
 37 |     if (node.type === 'COMPONENT_SET') {
 38 |         metadata.variantCount = node.children?.length || 0;
 39 |     }
 40 | 
 41 |     return metadata;
 42 | }
 43 | 
 44 | /**
 45 |  * Extract layout information
 46 |  */
 47 | export function extractLayoutInfo(node: FigmaNode): LayoutInfo {
 48 |     const layout: LayoutInfo = {
 49 |         type: determineLayoutType(node),
 50 |         dimensions: {
 51 |             width: node.absoluteBoundingBox?.width || 0,
 52 |             height: node.absoluteBoundingBox?.height || 0
 53 |         }
 54 |     };
 55 | 
 56 |     // Auto-layout specific properties
 57 |     if (node.layoutMode) {
 58 |         layout.direction = node.layoutMode === 'HORIZONTAL' ? 'horizontal' : 'vertical';
 59 |         layout.spacing = node.itemSpacing || 0;
 60 | 
 61 |         // Extract padding
 62 |         if (hasPadding(node)) {
 63 |             layout.padding = extractPadding(node);
 64 |         }
 65 | 
 66 |         // Alignment properties
 67 |         layout.alignItems = (node as any).primaryAxisAlignItems;
 68 |         layout.justifyContent = (node as any).counterAxisAlignItems;
 69 |     }
 70 | 
 71 |     // Constraints
 72 |     if (node.constraints) {
 73 |         layout.constraints = node.constraints;
 74 |     }
 75 | 
 76 |     return layout;
 77 | }
 78 | 
 79 | /**
 80 |  * Extract styling information
 81 |  */
 82 | export function extractStylingInfo(node: FigmaNode): StylingInfo {
 83 |     const styling: StylingInfo = {};
 84 | 
 85 |     // Fills (background colors/gradients)
 86 |     if (node.fills && node.fills.length > 0) {
 87 |         styling.fills = node.fills.map(convertFillToColorInfo);
 88 |     }
 89 | 
 90 |     // Strokes (borders)
 91 |     if (node.strokes && node.strokes.length > 0) {
 92 |         styling.strokes = node.strokes.map(convertStrokeInfo);
 93 |     }
 94 | 
 95 |     // Effects (shadows, blurs)
 96 |     if (node.effects && node.effects.length > 0) {
 97 |         styling.effects = categorizeEffects(node.effects);
 98 |     }
 99 | 
100 |     // Corner radius
101 |     const cornerRadius = extractCornerRadius(node);
102 |     if (cornerRadius) {
103 |         styling.cornerRadius = cornerRadius;
104 |     }
105 | 
106 |     // Opacity
107 |     if ((node as any).opacity !== undefined && (node as any).opacity !== 1) {
108 |         styling.opacity = (node as any).opacity;
109 |     }
110 | 
111 |     return styling;
112 | }
113 | 
114 | /**
115 |  * Analyze child nodes with prioritization and limits
116 |  */
117 | export function analyzeChildren(
118 |     node: FigmaNode,
119 |     options: Required<ComponentExtractionOptions>
120 | ): {
121 |     children: ComponentChild[];
122 |     nestedComponents: NestedComponentInfo[];
123 |     skippedNodes: SkippedNodeInfo[];
124 | } {
125 |     const children: ComponentChild[] = [];
126 |     const nestedComponents: NestedComponentInfo[] = [];
127 |     const skippedNodes: SkippedNodeInfo[] = [];
128 | 
129 |     if (!node.children || node.children.length === 0) {
130 |         return {children, nestedComponents, skippedNodes};
131 |     }
132 | 
133 |     // Filter visible nodes unless includeHiddenNodes is true
134 |     let visibleChildren = node.children;
135 |     if (!options.includeHiddenNodes) {
136 |         visibleChildren = node.children.filter(child => child.visible !== false);
137 |     }
138 | 
139 |     // Calculate visual importance for all children
140 |     const childrenWithImportance = visibleChildren.map(child => ({
141 |         node: child,
142 |         importance: calculateVisualImportance(child),
143 |         isComponent: isComponentNode(child)
144 |     }));
145 | 
146 |     // Sort by importance (components first if prioritized, then by visual importance)
147 |     childrenWithImportance.sort((a, b) => {
148 |         if (options.prioritizeComponents) {
149 |             if (a.isComponent && !b.isComponent) return -1;
150 |             if (!a.isComponent && b.isComponent) return 1;
151 |         }
152 |         return b.importance - a.importance;
153 |     });
154 | 
155 |     // Process up to maxChildNodes
156 |     const processedCount = Math.min(childrenWithImportance.length, options.maxChildNodes);
157 | 
158 |     for (let i = 0; i < childrenWithImportance.length; i++) {
159 |         const {node: child, importance, isComponent} = childrenWithImportance[i];
160 | 
161 |         if (i < processedCount) {
162 |             // Check if this is a nested component
163 |             if (isComponent) {
164 |                 nestedComponents.push(createNestedComponentInfo(child));
165 |             }
166 | 
167 |             // Pass parent and siblings for better semantic detection
168 |             const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
169 |             children.push(createComponentChild(child, importance, isComponent, options, node, siblings));
170 |         } else {
171 |             // Track skipped nodes
172 |             skippedNodes.push({
173 |                 nodeId: child.id,
174 |                 name: child.name,
175 |                 type: child.type,
176 |                 reason: 'max_nodes'
177 |             });
178 |         }
179 |     }
180 | 
181 |     return {children, nestedComponents, skippedNodes};
182 | }
183 | 
184 | /**
185 |  * Create nested component information
186 |  */
187 | export function createNestedComponentInfo(node: FigmaNode): NestedComponentInfo {
188 |     return {
189 |         nodeId: node.id,
190 |         name: node.name,
191 |         componentKey: (node as any).componentKey,
192 |         masterComponent: (node as any).masterComponent?.key,
193 |         isComponentInstance: node.type === 'INSTANCE',
194 |         needsSeparateAnalysis: true,
195 |         instanceType: node.type === 'INSTANCE' ? 'COMPONENT' : node.type as 'COMPONENT' | 'COMPONENT_SET'
196 |     };
197 | }
198 | 
199 | /**
200 |  * Create component child information
201 |  */
202 | export function createComponentChild(
203 |     node: FigmaNode,
204 |     importance: number,
205 |     isNestedComponent: boolean,
206 |     options: Required<ComponentExtractionOptions>,
207 |     parent?: FigmaNode,
208 |     siblings?: FigmaNode[]
209 | ): ComponentChild {
210 |     const child: ComponentChild = {
211 |         nodeId: node.id,
212 |         name: node.name,
213 |         type: node.type,
214 |         isNestedComponent,
215 |         visualImportance: importance
216 |     };
217 | 
218 |     // Extract basic info for non-component children
219 |     if (!isNestedComponent) {
220 |         child.basicInfo = {
221 |             layout: extractBasicLayout(node),
222 |             styling: extractBasicStyling(node)
223 |         };
224 | 
225 |         // Extract text info for text nodes
226 |         if (node.type === 'TEXT' && options.extractTextContent) {
227 |             child.basicInfo.text = extractTextInfo(node, parent, siblings);
228 |         }
229 |     }
230 | 
231 |     return child;
232 | }
233 | 
234 | /**
235 |  * Calculate visual importance score (1-10)
236 |  */
237 | export function calculateVisualImportance(node: FigmaNode): number {
238 |     let score = 0;
239 | 
240 |     // Size importance (0-4 points)
241 |     const area = (node.absoluteBoundingBox?.width || 0) * (node.absoluteBoundingBox?.height || 0);
242 |     if (area > 10000) score += 4;
243 |     else if (area > 5000) score += 3;
244 |     else if (area > 1000) score += 2;
245 |     else if (area > 100) score += 1;
246 | 
247 |     // Type importance (0-3 points)
248 |     if (node.type === 'COMPONENT' || node.type === 'INSTANCE') score += 3;
249 |     else if (node.type === 'FRAME') score += 2;
250 |     else if (node.type === 'TEXT') score += 2;
251 |     else if (node.type === 'VECTOR') score += 1;
252 | 
253 |     // Styling importance (0-2 points)
254 |     if (node.fills && node.fills.length > 0) score += 1;
255 |     if (node.effects && node.effects.length > 0) score += 1;
256 | 
257 |     // Has children importance (0-1 point)
258 |     if (node.children && node.children.length > 0) score += 1;
259 | 
260 |     return Math.min(score, 10);
261 | }
262 | 
263 | /**
264 |  * Check if node is a component
265 |  */
266 | export function isComponentNode(node: FigmaNode): boolean {
267 |     return node.type === 'COMPONENT' || node.type === 'INSTANCE' || node.type === 'COMPONENT_SET';
268 | }
269 | 
270 | /**
271 |  * Determine layout type from node properties
272 |  */
273 | export function determineLayoutType(node: FigmaNode): 'auto-layout' | 'absolute' | 'frame' {
274 |     if (node.layoutMode) {
275 |         return 'auto-layout';
276 |     }
277 |     if (node.type === 'FRAME' || node.type === 'COMPONENT') {
278 |         return 'frame';
279 |     }
280 |     return 'absolute';
281 | }
282 | 
283 | /**
284 |  * Check if node has padding
285 |  */
286 | export function hasPadding(node: FigmaNode): boolean {
287 |     return !!(node.paddingTop || node.paddingRight || node.paddingBottom || node.paddingLeft);
288 | }
289 | 
290 | /**
291 |  * Extract padding information
292 |  */
293 | export function extractPadding(node: FigmaNode): PaddingInfo {
294 |     const top = node.paddingTop || 0;
295 |     const right = node.paddingRight || 0;
296 |     const bottom = node.paddingBottom || 0;
297 |     const left = node.paddingLeft || 0;
298 | 
299 |     return {
300 |         top,
301 |         right,
302 |         bottom,
303 |         left,
304 |         isUniform: top === right && right === bottom && bottom === left
305 |     };
306 | }
307 | 
308 | /**
309 |  * Convert fill to color info
310 |  */
311 | export function convertFillToColorInfo(fill: any): ColorInfo {
312 |     const colorInfo: ColorInfo = {
313 |         type: fill.type,
314 |         opacity: fill.opacity
315 |     };
316 | 
317 |     if (fill.color) {
318 |         colorInfo.color = fill.color;
319 |         colorInfo.hex = rgbaToHex(fill.color);
320 |     }
321 | 
322 |     if (fill.gradientStops) {
323 |         colorInfo.gradientStops = fill.gradientStops;
324 |     }
325 | 
326 |     return colorInfo;
327 | }
328 | 
329 | /**
330 |  * Convert stroke to stroke info
331 |  */
332 | export function convertStrokeInfo(stroke: any): StrokeInfo {
333 |     return {
334 |         type: stroke.type,
335 |         color: stroke.color,
336 |         hex: rgbaToHex(stroke.color),
337 |         weight: (stroke as any).strokeWeight || 1,
338 |         align: (stroke as any).strokeAlign
339 |     };
340 | }
341 | 
342 | /**
343 |  * Categorize effects for Flutter mapping
344 |  */
345 | export function categorizeEffects(effects: FigmaEffect[]): CategorizedEffects {
346 |     const categorized: CategorizedEffects = {
347 |         dropShadows: [],
348 |         innerShadows: [],
349 |         blurs: []
350 |     };
351 | 
352 |     effects.forEach(effect => {
353 |         if (effect.type === 'DROP_SHADOW' && effect.visible !== false) {
354 |             categorized.dropShadows.push({
355 |                 color: effect.color!,
356 |                 hex: rgbaToHex(effect.color!),
357 |                 offset: effect.offset || {x: 0, y: 0},
358 |                 radius: effect.radius,
359 |                 spread: effect.spread,
360 |                 opacity: effect.color?.a || 1
361 |             });
362 |         } else if (effect.type === 'INNER_SHADOW' && effect.visible !== false) {
363 |             categorized.innerShadows.push({
364 |                 color: effect.color!,
365 |                 hex: rgbaToHex(effect.color!),
366 |                 offset: effect.offset || {x: 0, y: 0},
367 |                 radius: effect.radius,
368 |                 spread: effect.spread,
369 |                 opacity: effect.color?.a || 1
370 |             });
371 |         } else if ((effect.type === 'LAYER_BLUR' || effect.type === 'BACKGROUND_BLUR') && effect.visible !== false) {
372 |             categorized.blurs.push({
373 |                 type: effect.type,
374 |                 radius: effect.radius
375 |             });
376 |         }
377 |     });
378 | 
379 |     return categorized;
380 | }
381 | 
382 | /**
383 |  * Extract corner radius
384 |  */
385 | export function extractCornerRadius(node: FigmaNode): number | CornerRadii | undefined {
386 |     const nodeAny = node as any;
387 | 
388 |     if (nodeAny.cornerRadius !== undefined) {
389 |         return nodeAny.cornerRadius;
390 |     }
391 | 
392 |     // Check for individual corner radii
393 |     if (nodeAny.rectangleCornerRadii && Array.isArray(nodeAny.rectangleCornerRadii)) {
394 |         const [topLeft, topRight, bottomRight, bottomLeft] = nodeAny.rectangleCornerRadii;
395 |         const isUniform = topLeft === topRight && topRight === bottomRight && bottomRight === bottomLeft;
396 | 
397 |         if (isUniform) {
398 |             return topLeft;
399 |         }
400 | 
401 |         return {
402 |             topLeft,
403 |             topRight,
404 |             bottomLeft,
405 |             bottomRight,
406 |             isUniform: false
407 |         };
408 |     }
409 | 
410 |     return undefined;
411 | }
412 | 
413 | /**
414 |  * Extract basic layout info for non-component children
415 |  */
416 | export function extractBasicLayout(node: FigmaNode): Partial<LayoutInfo> {
417 |     return {
418 |         type: determineLayoutType(node),
419 |         dimensions: {
420 |             width: node.absoluteBoundingBox?.width || 0,
421 |             height: node.absoluteBoundingBox?.height || 0
422 |         }
423 |     };
424 | }
425 | 
426 | /**
427 |  * Extract basic styling info for non-component children
428 |  */
429 | export function extractBasicStyling(node: FigmaNode): Partial<StylingInfo> {
430 |     const styling: Partial<StylingInfo> = {};
431 | 
432 |     if (node.fills && node.fills.length > 0) {
433 |         styling.fills = node.fills.slice(0, 1).map(convertFillToColorInfo); // Limit to primary fill
434 |     }
435 | 
436 |     const cornerRadius = extractCornerRadius(node);
437 |     if (cornerRadius) {
438 |         styling.cornerRadius = cornerRadius;
439 |     }
440 | 
441 |     return styling;
442 | }
443 | 
444 | /**
445 |  * Extract enhanced text information
446 |  */
447 | export function extractTextInfo(node: FigmaNode, parent?: FigmaNode, siblings?: FigmaNode[]): TextInfo | undefined {
448 |     if (node.type !== 'TEXT') return undefined;
449 | 
450 |     const textContent = getActualTextContent(node);
451 |     const isPlaceholder = isPlaceholderText(textContent);
452 | 
453 |     return {
454 |         content: textContent,
455 |         isPlaceholder,
456 |         fontFamily: node.style?.fontFamily,
457 |         fontSize: node.style?.fontSize,
458 |         fontWeight: node.style?.fontWeight,
459 |         textAlign: node.style?.textAlignHorizontal,
460 |         textCase: detectTextCase(textContent),
461 |         semanticType: detectSemanticType(textContent, node.name, node, parent, siblings),
462 |         placeholder: isPlaceholder
463 |     };
464 | }
465 | 
466 | /**
467 |  * Get actual text content from various sources
468 |  */
469 | function getActualTextContent(node: FigmaNode): string {
470 |     // 1. Primary source: characters property (official Figma API text content)
471 |     if (node.characters && node.characters.trim().length > 0) {
472 |         return node.characters.trim();
473 |     }
474 | 
475 |     // 2. Check fills for text content (sometimes stored in fill metadata)
476 |     if (node.fills) {
477 |         for (const fill of node.fills) {
478 |             if ((fill as any).textData || (fill as any).content) {
479 |                 const textContent = (fill as any).textData || (fill as any).content;
480 |                 if (textContent && textContent.trim().length > 0) {
481 |                     return textContent.trim();
482 |                 }
483 |             }
484 |         }
485 |     }
486 | 
487 |     // 3. Check for text in component properties (for component instances)
488 |     if (node.type === 'INSTANCE' && (node as any).componentProperties) {
489 |         const textProps = extractTextFromComponentProperties((node as any).componentProperties);
490 |         if (textProps && textProps.trim().length > 0) {
491 |             return textProps.trim();
492 |         }
493 |     }
494 | 
495 |     // 4. Analyze node name for meaningful content
496 |     const nodeName = node.name;
497 | 
498 |     // If node name looks like actual content (not generic), use it
499 |     if (isLikelyActualContent(nodeName)) {
500 |         return nodeName;
501 |     }
502 | 
503 |     // 5. Fallback to node name with placeholder flag
504 |     return nodeName;
505 | }
506 | 
507 | /**
508 |  * Check if node name looks like actual content vs generic label
509 |  */
510 | function isLikelyActualContent(name: string): boolean {
511 |     const genericPatterns = [
512 |         /^text$/i,
513 |         /^label$/i,
514 |         /^heading$/i,
515 |         /^title$/i,
516 |         /^body\s*\d*$/i,
517 |         /^text\s*\d+$/i,
518 |         /^heading\s*\d+$/i,
519 |         /^h\d+$/i,
520 |         /^lorem\s+ipsum/i,
521 |         /^sample\s+text/i,
522 |         /^placeholder/i,
523 |         /^example\s+text/i,
524 |         /^demo\s+text/i,
525 |         /^text\s*layer/i,
526 |         /^component\s*\d+/i
527 |     ];
528 | 
529 |     // If it matches generic patterns, it's probably not actual content
530 |     if (genericPatterns.some(pattern => pattern.test(name))) {
531 |         return false;
532 |     }
533 | 
534 |     // If it's very short and common UI text, it might be actual content
535 |     const shortUIText = ['ok', 'yes', 'no', 'save', 'cancel', 'close', 'menu', 'home', 'back', 'next', 'login', 'signup'];
536 |     if (name.length <= 8 && shortUIText.includes(name.toLowerCase())) {
537 |         return true;
538 |     }
539 | 
540 |     // If it contains real words and is reasonably long, likely actual content
541 |     if (name.length > 3 && name.length < 100) {
542 |         // Check if it has word-like structure
543 |         const hasWords = /\b[a-zA-Z]{2,}\b/.test(name);
544 |         const hasSpaces = name.includes(' ');
545 | 
546 |         if (hasWords && (hasSpaces || name.length > 8)) {
547 |             return true;
548 |         }
549 |     }
550 | 
551 |     return false;
552 | }
553 | 
554 | /**
555 |  * Check if text content is placeholder/dummy text
556 |  */
557 | function isPlaceholderText(content: string): boolean {
558 |     if (!content || content.trim().length === 0) {
559 |         return true;
560 |     }
561 | 
562 |     const trimmedContent = content.trim().toLowerCase();
563 | 
564 |     // Common placeholder patterns
565 |     const placeholderPatterns = [
566 |         /lorem\s+ipsum/i,
567 |         /dolor\s+sit\s+amet/i,
568 |         /consectetur\s+adipiscing/i,
569 |         /the\s+quick\s+brown\s+fox/i,
570 |         /sample\s+text/i,
571 |         /placeholder/i,
572 |         /example\s+text/i,
573 |         /demo\s+text/i,
574 |         /test\s+content/i,
575 |         /dummy\s+text/i,
576 |         /text\s+goes\s+here/i,
577 |         /your\s+text\s+here/i,
578 |         /add\s+text\s+here/i,
579 |         /enter\s+text/i,
580 |         /\[.*\]/,  // Text in brackets like [Your text here]
581 |         /^heading\s*\d*$/i,
582 |         /^title\s*\d*$/i,
583 |         /^body\s*\d*$/i,
584 |         /^text\s*\d*$/i,
585 |         /^label\s*\d*$/i,
586 |         /^h[1-6]$/i,
587 |         /^paragraph$/i,
588 |         /^caption$/i,
589 |         /^subtitle$/i,
590 |         /^overline$/i
591 |     ];
592 | 
593 |     // Check against placeholder patterns
594 |     if (placeholderPatterns.some(pattern => pattern.test(content))) {
595 |         return true;
596 |     }
597 | 
598 |     // Check for generic single words that are likely placeholders
599 |     const genericWords = [
600 |         'text', 'label', 'title', 'heading', 'body', 'content', 
601 |         'description', 'subtitle', 'caption', 'paragraph', 'copy'
602 |     ];
603 | 
604 |     if (genericWords.includes(trimmedContent)) {
605 |         return true;
606 |     }
607 | 
608 |     // Check for repeated characters (like "AAAA" or "xxxx")
609 |     if (content.length > 2 && /^(.)\1+$/.test(content.trim())) {
610 |         return true;
611 |     }
612 | 
613 |     // Check for Lorem Ipsum variations
614 |     if (/lorem|ipsum|dolor|sit|amet|consectetur|adipiscing|elit/i.test(content)) {
615 |         return true;
616 |     }
617 | 
618 |     return false;
619 | }
620 | 
621 | /**
622 |  * Extract text from component properties
623 |  */
624 | function extractTextFromComponentProperties(properties: any): string | null {
625 |     if (!properties || typeof properties !== 'object') {
626 |         return null;
627 |     }
628 | 
629 |     // Look for common text property names
630 |     const textPropertyNames = ['text', 'label', 'title', 'content', 'value', 'caption'];
631 | 
632 |     for (const propName of textPropertyNames) {
633 |         if (properties[propName] && typeof properties[propName] === 'string') {
634 |             return properties[propName];
635 |         }
636 |     }
637 | 
638 |     // Look for any string property that might contain text
639 |     for (const [key, value] of Object.entries(properties)) {
640 |         if (typeof value === 'string' && value.length > 0 && !isPlaceholderText(value)) {
641 |             return value;
642 |         }
643 |     }
644 | 
645 |     return null;
646 | }
647 | 
648 | /**
649 |  * Detect text case pattern
650 |  */
651 | function detectTextCase(content: string): 'uppercase' | 'lowercase' | 'capitalize' | 'sentence' | 'mixed' {
652 |     if (content.length === 0) return 'mixed';
653 | 
654 |     const isAllUpper = content === content.toUpperCase() && content !== content.toLowerCase();
655 |     const isAllLower = content === content.toLowerCase() && content !== content.toUpperCase();
656 | 
657 |     if (isAllUpper) return 'uppercase';
658 |     if (isAllLower) return 'lowercase';
659 | 
660 |     // Check if it's title case (first letter of each word capitalized)
661 |     const words = content.split(/\s+/);
662 |     const isTitleCase = words.every(word => {
663 |         return word.length === 0 || word[0] === word[0].toUpperCase();
664 |     });
665 | 
666 |     if (isTitleCase) return 'capitalize';
667 | 
668 |     // Check if it's sentence case (first letter capitalized, rest normal)
669 |     if (content[0] === content[0].toUpperCase()) {
670 |         return 'sentence';
671 |     }
672 | 
673 |     return 'mixed';
674 | }
675 | 
676 | /**
677 |  * Detect semantic type of text based on content and context
678 |  * Enhanced with multi-factor analysis and confidence scoring
679 |  */
680 | function detectSemanticType(
681 |     content: string, 
682 |     nodeName: string, 
683 |     node?: any, 
684 |     parent?: any, 
685 |     siblings?: any[]
686 | ): 'heading' | 'body' | 'label' | 'button' | 'link' | 'caption' | 'error' | 'success' | 'warning' | 'other' {
687 |     // Skip detection for placeholder text
688 |     if (isPlaceholderText(content)) {
689 |         return 'other';
690 |     }
691 | 
692 |     // Use advanced semantic detection if node properties are available
693 |     if (node) {
694 |         try {
695 |             const context = generateSemanticContext(node, parent, siblings);
696 |             const classification = detectSemanticTypeAdvanced(content, nodeName, context, node);
697 |             
698 |             // Only use advanced classification if confidence is high enough
699 |             if (classification.confidence >= 0.6) {
700 |                 return classification.type;
701 |             }
702 |             
703 |             // Log reasoning for debugging (in development)
704 |             if (process.env.NODE_ENV === 'development') {
705 |                 console.debug(`Low confidence (${classification.confidence}) for "${content}": ${classification.reasoning.join(', ')}`);
706 |             }
707 |         } catch (error) {
708 |             // Fall back to legacy detection if advanced detection fails
709 |             console.warn('Advanced semantic detection failed, using legacy method:', error);
710 |         }
711 |     }
712 | 
713 |     // Legacy detection as fallback
714 |     return detectSemanticTypeLegacy(content, nodeName);
715 | }
716 | 
717 | /**
718 |  * Legacy semantic type detection (fallback)
719 |  */
720 | function detectSemanticTypeLegacy(content: string, nodeName: string): 'heading' | 'body' | 'label' | 'button' | 'link' | 'caption' | 'error' | 'success' | 'warning' | 'other' {
721 |     const lowerContent = content.toLowerCase().trim();
722 |     const lowerNodeName = nodeName.toLowerCase();
723 | 
724 |     // Button text patterns - exact matches for common button labels
725 |     const buttonPatterns = [
726 |         /^(click|tap|press|submit|send|save|cancel|ok|yes|no|continue|next|back|close|done|finish|start|begin)$/i,
727 |         /^(login|log in|sign in|signup|sign up|register|logout|log out|sign out)$/i,
728 |         /^(buy|purchase|add|remove|delete|edit|update|create|new|get started|learn more|try now)$/i,
729 |         /^(download|upload|share|copy|paste|cut|undo|redo|refresh|reload|search|filter|sort|apply|reset|clear)$/i,
730 |         /^(accept|decline|agree|disagree|confirm|verify|validate|approve|reject)$/i
731 |     ];
732 | 
733 |     if (buttonPatterns.some(pattern => pattern.test(content)) || lowerNodeName.includes('button')) {
734 |         return 'button';
735 |     }
736 | 
737 |     // Error/status text patterns - more comprehensive detection
738 |     const errorPatterns = /error|invalid|required|missing|failed|wrong|incorrect|forbidden|unauthorized|not found|unavailable|expired|timeout/i;
739 |     const successPatterns = /success|completed|done|saved|updated|created|uploaded|downloaded|sent|delivered|confirmed|verified|approved/i;
740 |     const warningPatterns = /warning|caution|note|important|attention|notice|alert|reminder|tip|info|information/i;
741 | 
742 |     if (errorPatterns.test(content)) {
743 |         return 'error';
744 |     }
745 | 
746 |     if (successPatterns.test(content)) {
747 |         return 'success';
748 |     }
749 | 
750 |     if (warningPatterns.test(content)) {
751 |         return 'warning';
752 |     }
753 | 
754 |     // Link patterns - navigation and informational links
755 |     const linkPatterns = [
756 |         /^(learn more|read more|see more|view all|show all|details|click here|view details)$/i,
757 |         /^(about|contact|help|support|faq|terms|privacy|policy|documentation|docs|guide|tutorial)$/i,
758 |         /^(home|dashboard|profile|settings|preferences|account|billing|notifications|security)$/i
759 |     ];
760 | 
761 |     if (linkPatterns.some(pattern => pattern.test(content)) || lowerNodeName.includes('link')) {
762 |         return 'link';
763 |     }
764 | 
765 |     // Heading patterns - based on structure and context
766 |     if (content.length < 80 && !content.endsWith('.') && !content.includes('\n')) {
767 |         if (lowerNodeName.includes('heading') || lowerNodeName.includes('title') || /h[1-6]/.test(lowerNodeName)) {
768 |             return 'heading';
769 |         }
770 |         // Check if it looks like a title (short, starts with capital, no sentence punctuation)
771 |         if (content.length < 50 && /^[A-Z]/.test(content) && !/[.!?]$/.test(content)) {
772 |             return 'heading';
773 |         }
774 |     }
775 | 
776 |     // Label patterns - form labels and descriptive text
777 |     if (content.length < 40 && (content.endsWith(':') || lowerNodeName.includes('label') || lowerNodeName.includes('field'))) {
778 |         return 'label';
779 |     }
780 | 
781 |     // Caption patterns - short descriptive text
782 |     if (content.length < 120 && (
783 |         lowerNodeName.includes('caption') || 
784 |         lowerNodeName.includes('subtitle') || 
785 |         lowerNodeName.includes('description') ||
786 |         lowerNodeName.includes('meta')
787 |     )) {
788 |         return 'caption';
789 |     }
790 | 
791 |     // Body text - longer content, paragraphs
792 |     if (content.length > 80 || content.includes('\n') || content.includes('. ')) {
793 |         return 'body';
794 |     }
795 | 
796 |     return 'other';
797 | }
798 | 
799 | /**
800 |  * Generate Flutter widget suggestion based on semantic type and text info
801 |  */
802 | export function generateFlutterTextWidget(textInfo: TextInfo): string {
803 |     // Escape single quotes in content for Dart strings
804 |     const escapedContent = textInfo.content.replace(/'/g, "\\'");
805 |     
806 |     if (textInfo.isPlaceholder) {
807 |         return `Text('${escapedContent}') // TODO: Replace with actual content`;
808 |     }
809 | 
810 |     // Generate style properties based on text info
811 |     const styleProps: string[] = [];
812 |     if (textInfo.fontFamily) {
813 |         styleProps.push(`fontFamily: '${textInfo.fontFamily}'`);
814 |     }
815 |     if (textInfo.fontSize) {
816 |         styleProps.push(`fontSize: ${textInfo.fontSize}`);
817 |     }
818 |     if (textInfo.fontWeight && textInfo.fontWeight !== 400) {
819 |         const fontWeight = textInfo.fontWeight >= 700 ? 'FontWeight.bold' : 
820 |                           textInfo.fontWeight >= 600 ? 'FontWeight.w600' :
821 |                           textInfo.fontWeight >= 500 ? 'FontWeight.w500' :
822 |                           textInfo.fontWeight <= 300 ? 'FontWeight.w300' : 'FontWeight.normal';
823 |         styleProps.push(`fontWeight: ${fontWeight}`);
824 |     }
825 | 
826 |     const customStyle = styleProps.length > 0 ? `TextStyle(${styleProps.join(', ')})` : null;
827 | 
828 |     switch (textInfo.semanticType) {
829 |         case 'button':
830 |             return `ElevatedButton(\n  onPressed: () {\n    // TODO: Implement button action\n  },\n  child: Text('${escapedContent}'),\n)`;
831 | 
832 |         case 'link':
833 |             return `TextButton(\n  onPressed: () {\n    // TODO: Implement navigation\n  },\n  child: Text('${escapedContent}'),\n)`;
834 | 
835 |         case 'heading':
836 |             const headingStyle = customStyle || 'Theme.of(context).textTheme.headlineMedium';
837 |             return `Text(\n  '${escapedContent}',\n  style: ${headingStyle},\n)`;
838 | 
839 |         case 'body':
840 |             const bodyStyle = customStyle || 'Theme.of(context).textTheme.bodyMedium';
841 |             return `Text(\n  '${escapedContent}',\n  style: ${bodyStyle},\n)`;
842 | 
843 |         case 'caption':
844 |             const captionStyle = customStyle || 'Theme.of(context).textTheme.bodySmall';
845 |             return `Text(\n  '${escapedContent}',\n  style: ${captionStyle},\n)`;
846 | 
847 |         case 'label':
848 |             const labelStyle = customStyle || 'Theme.of(context).textTheme.labelMedium';
849 |             return `Text(\n  '${escapedContent}',\n  style: ${labelStyle},\n)`;
850 | 
851 |         case 'error':
852 |             const errorStyle = customStyle ? 
853 |                 `${customStyle.slice(0, -1)}, color: Theme.of(context).colorScheme.error)` :
854 |                 'TextStyle(color: Theme.of(context).colorScheme.error)';
855 |             return `Text(\n  '${escapedContent}',\n  style: ${errorStyle},\n)`;
856 | 
857 |         case 'success':
858 |             const successStyle = customStyle ?
859 |                 `${customStyle.slice(0, -1)}, color: Colors.green)` :
860 |                 'TextStyle(color: Colors.green)';
861 |             return `Text(\n  '${escapedContent}',\n  style: ${successStyle},\n)`;
862 | 
863 |         case 'warning':
864 |             const warningStyle = customStyle ?
865 |                 `${customStyle.slice(0, -1)}, color: Colors.orange)` :
866 |                 'TextStyle(color: Colors.orange)';
867 |             return `Text(\n  '${escapedContent}',\n  style: ${warningStyle},\n)`;
868 | 
869 |         default:
870 |             return customStyle ? 
871 |                 `Text(\n  '${escapedContent}',\n  style: ${customStyle},\n)` :
872 |                 `Text('${escapedContent}')`;
873 |     }
874 | }
875 | 
876 | /**
877 |  * Get all text content from a component tree
878 |  */
879 | export function extractAllTextContent(node: FigmaNode): Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}> {
880 |     const textNodes: Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}> = [];
881 | 
882 |     traverseForText(node, textNodes);
883 | 
884 |     return textNodes;
885 | }
886 | 
887 | /**
888 |  * Recursively traverse node tree to find all text nodes
889 |  */
890 | function traverseForText(
891 |     node: FigmaNode,
892 |     results: Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}>,
893 |     depth: number = 0
894 | ): void {
895 |     if (depth > 5) return; // Prevent infinite recursion
896 | 
897 |     if (node.type === 'TEXT') {
898 |         const textInfo = extractTextInfo(node);
899 |         if (textInfo) {
900 |             results.push({
901 |                 nodeId: node.id,
902 |                 textInfo,
903 |                 widgetSuggestion: generateFlutterTextWidget(textInfo)
904 |             });
905 |         }
906 |     }
907 | 
908 |     if (node.children) {
909 |         node.children.forEach(child => {
910 |             traverseForText(child, results, depth + 1);
911 |         });
912 |     }
913 | }
914 | 
915 | /**
916 |  * Convert RGBA color to hex string
917 |  */
918 | export function rgbaToHex(color: FigmaColor): string {
919 |     const r = Math.round(color.r * 255);
920 |     const g = Math.round(color.g * 255);
921 |     const b = Math.round(color.b * 255);
922 | 
923 |     return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
924 | }
```
Page 3/4FirstPrevNextLast