#
tokens: 19847/50000 3/64 files (page 3/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 3. Use http://codebase.md/mhmzdev/figma-flutter-mcp?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/screens/extractor.ts:
--------------------------------------------------------------------------------

```typescript
// src/extractors/screens/extractor.mts

import type {FigmaNode} from '../../types/figma.js';
import type {
    ScreenAnalysis,
    ScreenMetadata,
    ScreenLayoutInfo,
    ScreenSection,
    NavigationInfo,
    NavigationElement,
    ScreenAssetInfo,
    SkippedNodeInfo,
    ScreenExtractionOptions
} from './types.js';
import type {ComponentChild, NestedComponentInfo} from '../components/types.js';
import {
    extractLayoutInfo,
    extractStylingInfo,
    createComponentChild,
    createNestedComponentInfo,
    calculateVisualImportance,
    isComponentNode
} from '../components/extractor.js';
import { detectSectionTypeAdvanced } from '../../tools/flutter/semantic-detection.js';

/**
 * Extract screen metadata
 */
export function extractScreenMetadata(node: FigmaNode): ScreenMetadata {
    const dimensions = {
        width: node.absoluteBoundingBox?.width || 0,
        height: node.absoluteBoundingBox?.height || 0
    };

    const deviceType = detectDeviceType(dimensions);
    const orientation = detectOrientation(dimensions);

    return {
        name: node.name,
        type: node.type as 'FRAME' | 'PAGE' | 'COMPONENT',
        nodeId: node.id,
        deviceType,
        orientation,
        dimensions
    };
}

/**
 * Extract screen layout information
 */
export function extractScreenLayoutInfo(node: FigmaNode): ScreenLayoutInfo {
    const baseLayout = extractLayoutInfo(node);
    
    return {
        ...baseLayout,
        scrollable: detectScrollable(node),
        hasHeader: detectHeader(node),
        hasFooter: detectFooter(node),
        hasNavigation: detectNavigation(node),
        contentArea: calculateContentArea(node)
    };
}

/**
 * Analyze screen sections (header, content, footer, etc.)
 */
export function analyzeScreenSections(
    node: FigmaNode,
    options: Required<ScreenExtractionOptions>
): {
    sections: ScreenSection[];
    components: NestedComponentInfo[];
    skippedNodes: SkippedNodeInfo[];
} {
    const sections: ScreenSection[] = [];
    const components: NestedComponentInfo[] = [];
    const skippedNodes: SkippedNodeInfo[] = [];

    if (!node.children || node.children.length === 0) {
        return {sections, components, skippedNodes};
    }

    // Filter visible nodes unless includeHiddenNodes is true
    let visibleChildren = node.children;
    if (!options.includeHiddenNodes) {
        visibleChildren = node.children.filter(child => child.visible !== false);
    }

    // Filter out device UI elements (status bars, notches, home indicators, etc.)
    const filteredDeviceUI = visibleChildren.filter(child => isDeviceUIElement(child, node));
    visibleChildren = visibleChildren.filter(child => !isDeviceUIElement(child, node));
    
    // Add filtered device UI elements to skipped nodes for reporting
    filteredDeviceUI.forEach(deviceUINode => {
        skippedNodes.push({
            nodeId: deviceUINode.id,
            name: deviceUINode.name,
            type: deviceUINode.type,
            reason: 'device_ui_element'
        });
    });

    // Analyze each child as potential section
    const sectionsWithImportance = visibleChildren.map(child => {
        const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
        return {
            node: child,
            importance: calculateSectionImportance(child),
            sectionType: detectSectionType(child, node, siblings)
        };
    });

    // Sort by importance
    sectionsWithImportance.sort((a, b) => b.importance - a.importance);

    // Process up to maxSections
    const processedCount = Math.min(sectionsWithImportance.length, options.maxSections);

    for (let i = 0; i < sectionsWithImportance.length; i++) {
        const {node: child, importance, sectionType} = sectionsWithImportance[i];

        if (i < processedCount) {
            const section = createScreenSection(child, sectionType, importance, options);
            sections.push(section);

            // Collect nested components from this section
            section.components.forEach(comp => {
                if (!components.find(c => c.nodeId === comp.nodeId)) {
                    components.push(comp);
                }
            });
        } else {
            skippedNodes.push({
                nodeId: child.id,
                name: child.name,
                type: child.type,
                reason: 'max_sections'
            });
        }
    }

    return {sections, components, skippedNodes};
}

/**
 * Extract navigation information
 */
export function extractNavigationInfo(node: FigmaNode): NavigationInfo {
    const navigationElements: NavigationElement[] = [];
    
    // Traverse to find navigation elements
    traverseForNavigation(node, navigationElements);

    return {
        hasTabBar: detectTabBar(node),
        hasAppBar: detectAppBar(node),
        hasDrawer: detectDrawer(node),
        hasBottomSheet: detectBottomSheet(node),
        navigationElements
    };
}

/**
 * Extract screen assets information
 */
export function extractScreenAssets(node: FigmaNode): ScreenAssetInfo[] {
    const assets: ScreenAssetInfo[] = [];
    
    traverseForAssets(node, assets);
    
    return assets;
}

/**
 * Create screen section
 */
function createScreenSection(
    node: FigmaNode,
    sectionType: ScreenSection['type'],
    importance: number,
    options: Required<ScreenExtractionOptions>
): ScreenSection {
    const children: ComponentChild[] = [];
    const components: NestedComponentInfo[] = [];

    // Analyze section children
    if (node.children) {
        const visibleChildren = options.includeHiddenNodes 
            ? node.children 
            : node.children.filter(child => child.visible !== false);

        visibleChildren.forEach(child => {
            const childImportance = calculateVisualImportance(child);
            const isComponent = isComponentNode(child);

            if (isComponent) {
                components.push(createNestedComponentInfo(child));
            }

            // Pass parent and siblings for better semantic detection
            const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
            children.push(createComponentChild(child, childImportance, isComponent, {
                maxChildNodes: 20, // Higher limit for screens
                maxDepth: options.maxDepth,
                includeHiddenNodes: options.includeHiddenNodes,
                prioritizeComponents: true,
                extractTextContent: true
            }, node, siblings));
        });
    }

    return {
        id: `section_${node.id}`,
        name: node.name,
        type: sectionType,
        nodeId: node.id,
        layout: extractLayoutInfo(node),
        styling: extractStylingInfo(node),
        children,
        components,
        importance
    };
}

/**
 * Calculate section importance for prioritization
 */
function calculateSectionImportance(node: FigmaNode): number {
    let score = 0;

    // Size importance (0-3 points)
    const area = (node.absoluteBoundingBox?.width || 0) * (node.absoluteBoundingBox?.height || 0);
    if (area > 50000) score += 3;
    else if (area > 20000) score += 2;
    else if (area > 5000) score += 1;

    // Position importance (0-2 points) - top elements are more important
    const y = node.absoluteBoundingBox?.y || 0;
    if (y < 100) score += 2; // Header area
    else if (y < 200) score += 1;

    // Type importance (0-3 points)
    if (node.type === 'COMPONENT' || node.type === 'INSTANCE') score += 3;
    else if (node.type === 'FRAME') score += 2;

    // Name-based importance (0-2 points)
    const name = node.name.toLowerCase();
    if (name.includes('header') || name.includes('nav') || name.includes('footer')) score += 2;
    else if (name.includes('content') || name.includes('main') || name.includes('body')) score += 1;

    return Math.min(score, 10);
}

/**
 * Detect section type based on node properties
 * Enhanced with multi-factor analysis and confidence scoring
 */
function detectSectionType(node: FigmaNode, parent?: FigmaNode, siblings?: FigmaNode[]): ScreenSection['type'] {
    // Try advanced detection first
    try {
        const classification = detectSectionTypeAdvanced(node, parent, siblings);
        
        // Use advanced classification if confidence is high enough
        if (classification.confidence >= 0.6) {
            return classification.type as ScreenSection['type'];
        }
        
        // Log reasoning for debugging (in development)
        if (process.env.NODE_ENV === 'development') {
            console.debug(`Low confidence (${classification.confidence}) for section "${node.name}": ${classification.reasoning.join(', ')}`);
        }
    } catch (error) {
        // Fall back to legacy detection if advanced detection fails
        console.warn('Advanced section detection failed, using legacy method:', error);
    }

    // Legacy detection as fallback
    return detectSectionTypeLegacy(node);
}

/**
 * Legacy section type detection (fallback)
 */
function detectSectionTypeLegacy(node: FigmaNode): ScreenSection['type'] {
    const name = node.name.toLowerCase();
    const bounds = node.absoluteBoundingBox;

    // Name-based detection
    if (name.includes('header') || name.includes('app bar') || name.includes('top bar')) return 'header';
    if (name.includes('footer') || name.includes('bottom') || name.includes('tab bar')) return 'footer';
    if (name.includes('nav') || name.includes('menu') || name.includes('sidebar')) return 'navigation';
    if (name.includes('modal') || name.includes('dialog') || name.includes('popup')) return 'modal';
    if (name.includes('content') || name.includes('main') || name.includes('body')) return 'content';

    // Position-based detection
    if (bounds) {
        const screenHeight = bounds.y + bounds.height;
        
        // Top 15% of screen likely header
        if (bounds.y < screenHeight * 0.15) return 'header';
        
        // Bottom 15% of screen likely footer/navigation
        if (bounds.y > screenHeight * 0.85) return 'footer';
        
        // Side areas might be navigation
        if (bounds.width < 100 && bounds.height > 200) return 'sidebar';
    }

    return 'content';
}

/**
 * Detect device type based on dimensions
 */
function detectDeviceType(dimensions: {width: number; height: number}): ScreenMetadata['deviceType'] {
    const {width, height} = dimensions;
    const maxDimension = Math.max(width, height);
    const minDimension = Math.min(width, height);

    // Mobile devices (typical ranges)
    if (maxDimension <= 900 && minDimension <= 500) return 'mobile';
    
    // Tablet devices
    if (maxDimension <= 1400 && minDimension <= 1000) return 'tablet';
    
    // Desktop
    if (maxDimension > 1400) return 'desktop';

    return 'unknown';
}

/**
 * Detect orientation
 */
function detectOrientation(dimensions: {width: number; height: number}): ScreenMetadata['orientation'] {
    return dimensions.width > dimensions.height ? 'landscape' : 'portrait';
}

/**
 * Detect if screen is scrollable
 */
function detectScrollable(node: FigmaNode): boolean {
    // Check for scroll properties or overflow
    const nodeAny = node as any;
    return !!(nodeAny.overflowDirection || nodeAny.scrollBehavior);
}

/**
 * Detect header presence
 */
function detectHeader(node: FigmaNode): boolean {
    if (!node.children) return false;
    
    return node.children.some(child => {
        const name = child.name.toLowerCase();
        const bounds = child.absoluteBoundingBox;
        
        return (name.includes('header') || name.includes('app bar') || name.includes('top bar')) ||
               (bounds && bounds.y < 150); // Top area
    });
}

/**
 * Detect footer presence
 */
function detectFooter(node: FigmaNode): boolean {
    if (!node.children) return false;
    
    const screenHeight = node.absoluteBoundingBox?.height || 0;
    
    return node.children.some(child => {
        const name = child.name.toLowerCase();
        const bounds = child.absoluteBoundingBox;
        
        return (name.includes('footer') || name.includes('bottom') || name.includes('tab bar')) ||
               (bounds && bounds.y > screenHeight * 0.8); // Bottom area
    });
}

/**
 * Detect navigation presence
 */
function detectNavigation(node: FigmaNode): boolean {
    if (!node.children) return false;
    
    return node.children.some(child => {
        const name = child.name.toLowerCase();
        return name.includes('nav') || name.includes('menu') || name.includes('tab') || name.includes('drawer');
    });
}

/**
 * Calculate content area
 */
function calculateContentArea(node: FigmaNode): ScreenLayoutInfo['contentArea'] {
    const bounds = node.absoluteBoundingBox;
    if (!bounds) return undefined;

    // Simple heuristic: assume content is the main area minus header/footer
    return {
        x: bounds.x,
        y: bounds.y + 100, // Assume 100px header
        width: bounds.width,
        height: bounds.height - 200 // Assume 100px header + 100px footer
    };
}

/**
 * Detect tab bar
 */
function detectTabBar(node: FigmaNode): boolean {
    return traverseAndCheck(node, child => {
        const name = child.name.toLowerCase();
        return !!(name.includes('tab bar') || name.includes('bottom nav') || 
               (name.includes('tab') && child.children && child.children.length > 1));
    });
}

/**
 * Detect app bar
 */
function detectAppBar(node: FigmaNode): boolean {
    return traverseAndCheck(node, child => {
        const name = child.name.toLowerCase();
        return !!(name.includes('app bar') || name.includes('header') || name.includes('top bar'));
    });
}

/**
 * Detect drawer
 */
function detectDrawer(node: FigmaNode): boolean {
    return traverseAndCheck(node, child => {
        const name = child.name.toLowerCase();
        return !!(name.includes('drawer') || name.includes('sidebar') || name.includes('menu'));
    });
}

/**
 * Detect bottom sheet
 */
function detectBottomSheet(node: FigmaNode): boolean {
    return traverseAndCheck(node, child => {
        const name = child.name.toLowerCase();
        return !!(name.includes('bottom sheet') || name.includes('modal') || name.includes('popup'));
    });
}

/**
 * Traverse and check condition
 */
function traverseAndCheck(node: FigmaNode, condition: (node: FigmaNode) => boolean): boolean {
    if (condition(node)) return true;
    
    if (node.children) {
        return node.children.some(child => traverseAndCheck(child, condition));
    }
    
    return false;
}

/**
 * Traverse for navigation elements
 */
function traverseForNavigation(node: FigmaNode, results: NavigationElement[], depth: number = 0): void {
    if (depth > 3) return;

    const name = node.name.toLowerCase();
    
    // Check if this node is a navigation element
    if (isNavigationElement(node)) {
        results.push({
            nodeId: node.id,
            name: node.name,
            type: detectNavigationElementType(node),
            text: extractNavigationText(node),
            icon: hasIcon(node),
            isActive: detectActiveState(node)
        });
    }

    // Traverse children
    if (node.children) {
        node.children.forEach(child => {
            traverseForNavigation(child, results, depth + 1);
        });
    }
}

/**
 * Check if node is navigation element
 */
function isNavigationElement(node: FigmaNode): boolean {
    const name = node.name.toLowerCase();
    
    return name.includes('tab') || name.includes('button') || name.includes('link') ||
           name.includes('menu') || name.includes('nav') || 
           (node.type === 'INSTANCE' && name.includes('item'));
}

/**
 * Detect navigation element type
 */
function detectNavigationElementType(node: FigmaNode): NavigationElement['type'] {
    const name = node.name.toLowerCase();
    
    if (name.includes('tab')) return 'tab';
    if (name.includes('button')) return 'button';
    if (name.includes('link')) return 'link';
    if (name.includes('icon')) return 'icon';
    if (name.includes('menu')) return 'menu';
    
    return 'other';
}

/**
 * Extract navigation text
 */
function extractNavigationText(node: FigmaNode): string | undefined {
    // Look for text children
    if (node.children) {
        for (const child of node.children) {
            if (child.type === 'TEXT' && child.name) {
                return child.name;
            }
        }
    }
    
    // Fallback to node name if it looks like text
    const name = node.name;
    if (name && !name.toLowerCase().includes('component') && !name.toLowerCase().includes('instance')) {
        return name;
    }
    
    return undefined;
}

/**
 * Check if node has icon
 */
function hasIcon(node: FigmaNode): boolean {
    if (!node.children) return false;
    
    return node.children.some(child => {
        const name = child.name.toLowerCase();
        return name.includes('icon') || child.type === 'VECTOR';
    });
}

/**
 * Detect active state
 */
function detectActiveState(node: FigmaNode): boolean {
    const name = node.name.toLowerCase();
    return name.includes('active') || name.includes('selected') || name.includes('current');
}

/**
 * Check if a node represents device UI elements that should be ignored
 */
function isDeviceUIElement(child: FigmaNode, parent: FigmaNode): boolean {
    const name = child.name.toLowerCase();
    const bounds = child.absoluteBoundingBox;
    const parentBounds = parent.absoluteBoundingBox;
    
    if (!bounds || !parentBounds) return false;
    
    // Name-based detection for common device UI elements
    const deviceUIKeywords = [
        'status bar', 'statusbar', 'status_bar',
        'battery', 'signal', 'wifi', 'cellular', 'carrier',
        'notch', 'dynamic island', 'safe area',
        'home indicator', 'home_indicator', 'home bar',
        'navigation bar', 'system bar', 'system_bar',
        'chin', 'bezel', 'nav bar',
        'clock', 'time indicator',
        'signal strength', 'battery indicator',
        'screen recording', 'screen_recording'
    ];
    
    // Check if name contains device UI keywords
    if (deviceUIKeywords.some(keyword => name.includes(keyword))) {
        return true;
    }
    
    // Position and size-based detection
    const screenWidth = parentBounds.width;
    const screenHeight = parentBounds.height;
    const elementWidth = bounds.width;
    const elementHeight = bounds.height;
    const elementY = bounds.y - parentBounds.y; // Relative position
    const elementX = bounds.x - parentBounds.x;
    
    // Status bar detection (top of screen)
    if (elementY <= 50 && // Very close to top
        elementWidth >= screenWidth * 0.8 && // Nearly full width
        elementHeight <= 50) { // Thin height
        
        // Additional checks for status bar content
        if (hasStatusBarContent(child)) {
            return true;
        }
    }
    
    // Home indicator detection (bottom of screen)
    if (elementY >= screenHeight - 50 && // Very close to bottom
        elementWidth <= screenWidth * 0.4 && // Narrow width (typical home indicator)
        elementHeight <= 20 && // Very thin
        elementX >= screenWidth * 0.3 && // Centered horizontally
        elementX <= screenWidth * 0.7) {
        return true;
    }
    
    // Notch/Dynamic Island detection (top center, small area)
    if (elementY <= 30 && // Very top
        elementWidth <= screenWidth * 0.3 && // Small width
        elementHeight <= 30 && // Small height
        elementX >= screenWidth * 0.35 && // Centered area
        elementX <= screenWidth * 0.65) {
        return true;
    }
    
    // Side bezels or navigation areas
    if ((elementX <= 20 || elementX >= screenWidth - 20) && // At edges
        elementWidth <= 40 && // Thin
        elementHeight >= screenHeight * 0.3) { // Tall
        return true;
    }
    
    // Small elements in corners (likely UI indicators)
    const isInCorner = (elementX <= 50 || elementX >= screenWidth - 50) &&
                      (elementY <= 50 || elementY >= screenHeight - 50);
    if (isInCorner && elementWidth <= 80 && elementHeight <= 80) {
        return true;
    }
    
    return false;
}

/**
 * Check if a node contains typical status bar content
 */
function hasStatusBarContent(node: FigmaNode): boolean {
    if (!node.children) return false;
    
    const statusBarElements = node.children.some(child => {
        const name = child.name.toLowerCase();
        return name.includes('battery') || 
               name.includes('signal') || 
               name.includes('wifi') || 
               name.includes('time') || 
               name.includes('clock') ||
               name.includes('carrier') ||
               name.includes('cellular') ||
               (child.type === 'TEXT' && /^\d{1,2}:\d{2}/.test(child.name)); // Time format
    });
    
    return statusBarElements;
}

/**
 * Traverse for assets
 */
function traverseForAssets(node: FigmaNode, results: ScreenAssetInfo[], depth: number = 0): void {
    if (depth > 4) return;

    // Check if this node is an asset
    if (isAssetNode(node)) {
        results.push({
            nodeId: node.id,
            name: node.name,
            type: detectAssetType(node),
            size: detectAssetSize(node),
            usage: detectAssetUsage(node)
        });
    }

    // Traverse children
    if (node.children) {
        node.children.forEach(child => {
            traverseForAssets(child, results, depth + 1);
        });
    }
}

/**
 * Check if node is an asset
 */
function isAssetNode(node: FigmaNode): boolean {
    // Check for image fills
    if (node.fills && node.fills.some((fill: any) => fill.type === 'IMAGE')) return true;
    
    // Check for vectors that are likely assets
    if (node.type === 'VECTOR') {
        const name = node.name.toLowerCase();
        return name.includes('image') || name.includes('illustration') || 
               name.includes('icon') || name.includes('logo');
    }
    
    return false;
}

/**
 * Detect asset type
 */
function detectAssetType(node: FigmaNode): ScreenAssetInfo['type'] {
    const name = node.name.toLowerCase();
    
    if (name.includes('icon')) return 'icon';
    if (name.includes('illustration') || name.includes('graphic')) return 'illustration';
    if (name.includes('background') || name.includes('bg')) return 'background';
    
    return 'image';
}

/**
 * Detect asset size
 */
function detectAssetSize(node: FigmaNode): ScreenAssetInfo['size'] {
    const bounds = node.absoluteBoundingBox;
    if (!bounds) return 'medium';
    
    const area = bounds.width * bounds.height;
    
    if (area < 2500) return 'small'; // < 50x50
    if (area > 40000) return 'large'; // > 200x200
    
    return 'medium';
}

/**
 * Detect asset usage
 */
function detectAssetUsage(node: FigmaNode): ScreenAssetInfo['usage'] {
    const name = node.name.toLowerCase();
    
    if (name.includes('logo') || name.includes('brand')) return 'branding';
    if (name.includes('nav') || name.includes('menu') || name.includes('tab')) return 'navigation';
    if (name.includes('background') || name.includes('decoration')) return 'decorative';
    
    return 'content';
}

```

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

```typescript
// src/extractors/components/extractor.mts

import type {FigmaNode, FigmaColor, FigmaEffect} from '../../types/figma.js';
import type {
    ComponentMetadata,
    LayoutInfo,
    StylingInfo,
    ComponentChild,
    NestedComponentInfo,
    SkippedNodeInfo,
    CategorizedEffects,
    ColorInfo,
    StrokeInfo,
    CornerRadii,
    PaddingInfo,
    TextInfo,
    ComponentExtractionOptions
} from './types.js';
import { detectSemanticTypeAdvanced, generateSemanticContext } from '../../tools/flutter/semantic-detection.js';

/**
 * Extract component metadata
 */
export function extractMetadata(node: FigmaNode, userDefinedAsComponent: boolean): ComponentMetadata {
    const metadata: ComponentMetadata = {
        name: node.name,
        type: node.type as 'COMPONENT' | 'COMPONENT_SET' | 'FRAME',
        nodeId: node.id,
        isUserDefinedComponent: userDefinedAsComponent
    };

    // Add component-specific metadata
    if (node.type === 'COMPONENT' || node.type === 'COMPONENT_SET') {
        metadata.componentKey = (node as any).componentKey;
    }

    if (node.type === 'COMPONENT_SET') {
        metadata.variantCount = node.children?.length || 0;
    }

    return metadata;
}

/**
 * Extract layout information
 */
export function extractLayoutInfo(node: FigmaNode): LayoutInfo {
    const layout: LayoutInfo = {
        type: determineLayoutType(node),
        dimensions: {
            width: node.absoluteBoundingBox?.width || 0,
            height: node.absoluteBoundingBox?.height || 0
        }
    };

    // Auto-layout specific properties
    if (node.layoutMode) {
        layout.direction = node.layoutMode === 'HORIZONTAL' ? 'horizontal' : 'vertical';
        layout.spacing = node.itemSpacing || 0;

        // Extract padding
        if (hasPadding(node)) {
            layout.padding = extractPadding(node);
        }

        // Alignment properties
        layout.alignItems = (node as any).primaryAxisAlignItems;
        layout.justifyContent = (node as any).counterAxisAlignItems;
    }

    // Constraints
    if (node.constraints) {
        layout.constraints = node.constraints;
    }

    return layout;
}

/**
 * Extract styling information
 */
export function extractStylingInfo(node: FigmaNode): StylingInfo {
    const styling: StylingInfo = {};

    // Fills (background colors/gradients)
    if (node.fills && node.fills.length > 0) {
        styling.fills = node.fills.map(convertFillToColorInfo);
    }

    // Strokes (borders)
    if (node.strokes && node.strokes.length > 0) {
        styling.strokes = node.strokes.map(convertStrokeInfo);
    }

    // Effects (shadows, blurs)
    if (node.effects && node.effects.length > 0) {
        styling.effects = categorizeEffects(node.effects);
    }

    // Corner radius
    const cornerRadius = extractCornerRadius(node);
    if (cornerRadius) {
        styling.cornerRadius = cornerRadius;
    }

    // Opacity
    if ((node as any).opacity !== undefined && (node as any).opacity !== 1) {
        styling.opacity = (node as any).opacity;
    }

    return styling;
}

/**
 * Analyze child nodes with prioritization and limits
 */
export function analyzeChildren(
    node: FigmaNode,
    options: Required<ComponentExtractionOptions>
): {
    children: ComponentChild[];
    nestedComponents: NestedComponentInfo[];
    skippedNodes: SkippedNodeInfo[];
} {
    const children: ComponentChild[] = [];
    const nestedComponents: NestedComponentInfo[] = [];
    const skippedNodes: SkippedNodeInfo[] = [];

    if (!node.children || node.children.length === 0) {
        return {children, nestedComponents, skippedNodes};
    }

    // Filter visible nodes unless includeHiddenNodes is true
    let visibleChildren = node.children;
    if (!options.includeHiddenNodes) {
        visibleChildren = node.children.filter(child => child.visible !== false);
    }

    // Calculate visual importance for all children
    const childrenWithImportance = visibleChildren.map(child => ({
        node: child,
        importance: calculateVisualImportance(child),
        isComponent: isComponentNode(child)
    }));

    // Sort by importance (components first if prioritized, then by visual importance)
    childrenWithImportance.sort((a, b) => {
        if (options.prioritizeComponents) {
            if (a.isComponent && !b.isComponent) return -1;
            if (!a.isComponent && b.isComponent) return 1;
        }
        return b.importance - a.importance;
    });

    // Process up to maxChildNodes
    const processedCount = Math.min(childrenWithImportance.length, options.maxChildNodes);

    for (let i = 0; i < childrenWithImportance.length; i++) {
        const {node: child, importance, isComponent} = childrenWithImportance[i];

        if (i < processedCount) {
            // Check if this is a nested component
            if (isComponent) {
                nestedComponents.push(createNestedComponentInfo(child));
            }

            // Pass parent and siblings for better semantic detection
            const siblings = visibleChildren.filter(sibling => sibling.id !== child.id);
            children.push(createComponentChild(child, importance, isComponent, options, node, siblings));
        } else {
            // Track skipped nodes
            skippedNodes.push({
                nodeId: child.id,
                name: child.name,
                type: child.type,
                reason: 'max_nodes'
            });
        }
    }

    return {children, nestedComponents, skippedNodes};
}

/**
 * Create nested component information
 */
export function createNestedComponentInfo(node: FigmaNode): NestedComponentInfo {
    return {
        nodeId: node.id,
        name: node.name,
        componentKey: (node as any).componentKey,
        masterComponent: (node as any).masterComponent?.key,
        isComponentInstance: node.type === 'INSTANCE',
        needsSeparateAnalysis: true,
        instanceType: node.type === 'INSTANCE' ? 'COMPONENT' : node.type as 'COMPONENT' | 'COMPONENT_SET'
    };
}

/**
 * Create component child information
 */
export function createComponentChild(
    node: FigmaNode,
    importance: number,
    isNestedComponent: boolean,
    options: Required<ComponentExtractionOptions>,
    parent?: FigmaNode,
    siblings?: FigmaNode[]
): ComponentChild {
    const child: ComponentChild = {
        nodeId: node.id,
        name: node.name,
        type: node.type,
        isNestedComponent,
        visualImportance: importance
    };

    // Extract basic info for non-component children
    if (!isNestedComponent) {
        child.basicInfo = {
            layout: extractBasicLayout(node),
            styling: extractBasicStyling(node)
        };

        // Extract text info for text nodes
        if (node.type === 'TEXT' && options.extractTextContent) {
            child.basicInfo.text = extractTextInfo(node, parent, siblings);
        }
    }

    return child;
}

/**
 * Calculate visual importance score (1-10)
 */
export function calculateVisualImportance(node: FigmaNode): number {
    let score = 0;

    // Size importance (0-4 points)
    const area = (node.absoluteBoundingBox?.width || 0) * (node.absoluteBoundingBox?.height || 0);
    if (area > 10000) score += 4;
    else if (area > 5000) score += 3;
    else if (area > 1000) score += 2;
    else if (area > 100) score += 1;

    // Type importance (0-3 points)
    if (node.type === 'COMPONENT' || node.type === 'INSTANCE') score += 3;
    else if (node.type === 'FRAME') score += 2;
    else if (node.type === 'TEXT') score += 2;
    else if (node.type === 'VECTOR') score += 1;

    // Styling importance (0-2 points)
    if (node.fills && node.fills.length > 0) score += 1;
    if (node.effects && node.effects.length > 0) score += 1;

    // Has children importance (0-1 point)
    if (node.children && node.children.length > 0) score += 1;

    return Math.min(score, 10);
}

/**
 * Check if node is a component
 */
export function isComponentNode(node: FigmaNode): boolean {
    return node.type === 'COMPONENT' || node.type === 'INSTANCE' || node.type === 'COMPONENT_SET';
}

/**
 * Determine layout type from node properties
 */
export function determineLayoutType(node: FigmaNode): 'auto-layout' | 'absolute' | 'frame' {
    if (node.layoutMode) {
        return 'auto-layout';
    }
    if (node.type === 'FRAME' || node.type === 'COMPONENT') {
        return 'frame';
    }
    return 'absolute';
}

/**
 * Check if node has padding
 */
export function hasPadding(node: FigmaNode): boolean {
    return !!(node.paddingTop || node.paddingRight || node.paddingBottom || node.paddingLeft);
}

/**
 * Extract padding information
 */
export function extractPadding(node: FigmaNode): PaddingInfo {
    const top = node.paddingTop || 0;
    const right = node.paddingRight || 0;
    const bottom = node.paddingBottom || 0;
    const left = node.paddingLeft || 0;

    return {
        top,
        right,
        bottom,
        left,
        isUniform: top === right && right === bottom && bottom === left
    };
}

/**
 * Convert fill to color info
 */
export function convertFillToColorInfo(fill: any): ColorInfo {
    const colorInfo: ColorInfo = {
        type: fill.type,
        opacity: fill.opacity
    };

    if (fill.color) {
        colorInfo.color = fill.color;
        colorInfo.hex = rgbaToHex(fill.color);
    }

    if (fill.gradientStops) {
        colorInfo.gradientStops = fill.gradientStops;
    }

    return colorInfo;
}

/**
 * Convert stroke to stroke info
 */
export function convertStrokeInfo(stroke: any): StrokeInfo {
    return {
        type: stroke.type,
        color: stroke.color,
        hex: rgbaToHex(stroke.color),
        weight: (stroke as any).strokeWeight || 1,
        align: (stroke as any).strokeAlign
    };
}

/**
 * Categorize effects for Flutter mapping
 */
export function categorizeEffects(effects: FigmaEffect[]): CategorizedEffects {
    const categorized: CategorizedEffects = {
        dropShadows: [],
        innerShadows: [],
        blurs: []
    };

    effects.forEach(effect => {
        if (effect.type === 'DROP_SHADOW' && effect.visible !== false) {
            categorized.dropShadows.push({
                color: effect.color!,
                hex: rgbaToHex(effect.color!),
                offset: effect.offset || {x: 0, y: 0},
                radius: effect.radius,
                spread: effect.spread,
                opacity: effect.color?.a || 1
            });
        } else if (effect.type === 'INNER_SHADOW' && effect.visible !== false) {
            categorized.innerShadows.push({
                color: effect.color!,
                hex: rgbaToHex(effect.color!),
                offset: effect.offset || {x: 0, y: 0},
                radius: effect.radius,
                spread: effect.spread,
                opacity: effect.color?.a || 1
            });
        } else if ((effect.type === 'LAYER_BLUR' || effect.type === 'BACKGROUND_BLUR') && effect.visible !== false) {
            categorized.blurs.push({
                type: effect.type,
                radius: effect.radius
            });
        }
    });

    return categorized;
}

/**
 * Extract corner radius
 */
export function extractCornerRadius(node: FigmaNode): number | CornerRadii | undefined {
    const nodeAny = node as any;

    if (nodeAny.cornerRadius !== undefined) {
        return nodeAny.cornerRadius;
    }

    // Check for individual corner radii
    if (nodeAny.rectangleCornerRadii && Array.isArray(nodeAny.rectangleCornerRadii)) {
        const [topLeft, topRight, bottomRight, bottomLeft] = nodeAny.rectangleCornerRadii;
        const isUniform = topLeft === topRight && topRight === bottomRight && bottomRight === bottomLeft;

        if (isUniform) {
            return topLeft;
        }

        return {
            topLeft,
            topRight,
            bottomLeft,
            bottomRight,
            isUniform: false
        };
    }

    return undefined;
}

/**
 * Extract basic layout info for non-component children
 */
export function extractBasicLayout(node: FigmaNode): Partial<LayoutInfo> {
    return {
        type: determineLayoutType(node),
        dimensions: {
            width: node.absoluteBoundingBox?.width || 0,
            height: node.absoluteBoundingBox?.height || 0
        }
    };
}

/**
 * Extract basic styling info for non-component children
 */
export function extractBasicStyling(node: FigmaNode): Partial<StylingInfo> {
    const styling: Partial<StylingInfo> = {};

    if (node.fills && node.fills.length > 0) {
        styling.fills = node.fills.slice(0, 1).map(convertFillToColorInfo); // Limit to primary fill
    }

    const cornerRadius = extractCornerRadius(node);
    if (cornerRadius) {
        styling.cornerRadius = cornerRadius;
    }

    return styling;
}

/**
 * Extract enhanced text information
 */
export function extractTextInfo(node: FigmaNode, parent?: FigmaNode, siblings?: FigmaNode[]): TextInfo | undefined {
    if (node.type !== 'TEXT') return undefined;

    const textContent = getActualTextContent(node);
    const isPlaceholder = isPlaceholderText(textContent);

    return {
        content: textContent,
        isPlaceholder,
        fontFamily: node.style?.fontFamily,
        fontSize: node.style?.fontSize,
        fontWeight: node.style?.fontWeight,
        textAlign: node.style?.textAlignHorizontal,
        textCase: detectTextCase(textContent),
        semanticType: detectSemanticType(textContent, node.name, node, parent, siblings),
        placeholder: isPlaceholder
    };
}

/**
 * Get actual text content from various sources
 */
function getActualTextContent(node: FigmaNode): string {
    // 1. Primary source: characters property (official Figma API text content)
    if (node.characters && node.characters.trim().length > 0) {
        return node.characters.trim();
    }

    // 2. Check fills for text content (sometimes stored in fill metadata)
    if (node.fills) {
        for (const fill of node.fills) {
            if ((fill as any).textData || (fill as any).content) {
                const textContent = (fill as any).textData || (fill as any).content;
                if (textContent && textContent.trim().length > 0) {
                    return textContent.trim();
                }
            }
        }
    }

    // 3. Check for text in component properties (for component instances)
    if (node.type === 'INSTANCE' && (node as any).componentProperties) {
        const textProps = extractTextFromComponentProperties((node as any).componentProperties);
        if (textProps && textProps.trim().length > 0) {
            return textProps.trim();
        }
    }

    // 4. Analyze node name for meaningful content
    const nodeName = node.name;

    // If node name looks like actual content (not generic), use it
    if (isLikelyActualContent(nodeName)) {
        return nodeName;
    }

    // 5. Fallback to node name with placeholder flag
    return nodeName;
}

/**
 * Check if node name looks like actual content vs generic label
 */
function isLikelyActualContent(name: string): boolean {
    const genericPatterns = [
        /^text$/i,
        /^label$/i,
        /^heading$/i,
        /^title$/i,
        /^body\s*\d*$/i,
        /^text\s*\d+$/i,
        /^heading\s*\d+$/i,
        /^h\d+$/i,
        /^lorem\s+ipsum/i,
        /^sample\s+text/i,
        /^placeholder/i,
        /^example\s+text/i,
        /^demo\s+text/i,
        /^text\s*layer/i,
        /^component\s*\d+/i
    ];

    // If it matches generic patterns, it's probably not actual content
    if (genericPatterns.some(pattern => pattern.test(name))) {
        return false;
    }

    // If it's very short and common UI text, it might be actual content
    const shortUIText = ['ok', 'yes', 'no', 'save', 'cancel', 'close', 'menu', 'home', 'back', 'next', 'login', 'signup'];
    if (name.length <= 8 && shortUIText.includes(name.toLowerCase())) {
        return true;
    }

    // If it contains real words and is reasonably long, likely actual content
    if (name.length > 3 && name.length < 100) {
        // Check if it has word-like structure
        const hasWords = /\b[a-zA-Z]{2,}\b/.test(name);
        const hasSpaces = name.includes(' ');

        if (hasWords && (hasSpaces || name.length > 8)) {
            return true;
        }
    }

    return false;
}

/**
 * Check if text content is placeholder/dummy text
 */
function isPlaceholderText(content: string): boolean {
    if (!content || content.trim().length === 0) {
        return true;
    }

    const trimmedContent = content.trim().toLowerCase();

    // Common placeholder patterns
    const placeholderPatterns = [
        /lorem\s+ipsum/i,
        /dolor\s+sit\s+amet/i,
        /consectetur\s+adipiscing/i,
        /the\s+quick\s+brown\s+fox/i,
        /sample\s+text/i,
        /placeholder/i,
        /example\s+text/i,
        /demo\s+text/i,
        /test\s+content/i,
        /dummy\s+text/i,
        /text\s+goes\s+here/i,
        /your\s+text\s+here/i,
        /add\s+text\s+here/i,
        /enter\s+text/i,
        /\[.*\]/,  // Text in brackets like [Your text here]
        /^heading\s*\d*$/i,
        /^title\s*\d*$/i,
        /^body\s*\d*$/i,
        /^text\s*\d*$/i,
        /^label\s*\d*$/i,
        /^h[1-6]$/i,
        /^paragraph$/i,
        /^caption$/i,
        /^subtitle$/i,
        /^overline$/i
    ];

    // Check against placeholder patterns
    if (placeholderPatterns.some(pattern => pattern.test(content))) {
        return true;
    }

    // Check for generic single words that are likely placeholders
    const genericWords = [
        'text', 'label', 'title', 'heading', 'body', 'content', 
        'description', 'subtitle', 'caption', 'paragraph', 'copy'
    ];

    if (genericWords.includes(trimmedContent)) {
        return true;
    }

    // Check for repeated characters (like "AAAA" or "xxxx")
    if (content.length > 2 && /^(.)\1+$/.test(content.trim())) {
        return true;
    }

    // Check for Lorem Ipsum variations
    if (/lorem|ipsum|dolor|sit|amet|consectetur|adipiscing|elit/i.test(content)) {
        return true;
    }

    return false;
}

/**
 * Extract text from component properties
 */
function extractTextFromComponentProperties(properties: any): string | null {
    if (!properties || typeof properties !== 'object') {
        return null;
    }

    // Look for common text property names
    const textPropertyNames = ['text', 'label', 'title', 'content', 'value', 'caption'];

    for (const propName of textPropertyNames) {
        if (properties[propName] && typeof properties[propName] === 'string') {
            return properties[propName];
        }
    }

    // Look for any string property that might contain text
    for (const [key, value] of Object.entries(properties)) {
        if (typeof value === 'string' && value.length > 0 && !isPlaceholderText(value)) {
            return value;
        }
    }

    return null;
}

/**
 * Detect text case pattern
 */
function detectTextCase(content: string): 'uppercase' | 'lowercase' | 'capitalize' | 'sentence' | 'mixed' {
    if (content.length === 0) return 'mixed';

    const isAllUpper = content === content.toUpperCase() && content !== content.toLowerCase();
    const isAllLower = content === content.toLowerCase() && content !== content.toUpperCase();

    if (isAllUpper) return 'uppercase';
    if (isAllLower) return 'lowercase';

    // Check if it's title case (first letter of each word capitalized)
    const words = content.split(/\s+/);
    const isTitleCase = words.every(word => {
        return word.length === 0 || word[0] === word[0].toUpperCase();
    });

    if (isTitleCase) return 'capitalize';

    // Check if it's sentence case (first letter capitalized, rest normal)
    if (content[0] === content[0].toUpperCase()) {
        return 'sentence';
    }

    return 'mixed';
}

/**
 * Detect semantic type of text based on content and context
 * Enhanced with multi-factor analysis and confidence scoring
 */
function detectSemanticType(
    content: string, 
    nodeName: string, 
    node?: any, 
    parent?: any, 
    siblings?: any[]
): 'heading' | 'body' | 'label' | 'button' | 'link' | 'caption' | 'error' | 'success' | 'warning' | 'other' {
    // Skip detection for placeholder text
    if (isPlaceholderText(content)) {
        return 'other';
    }

    // Use advanced semantic detection if node properties are available
    if (node) {
        try {
            const context = generateSemanticContext(node, parent, siblings);
            const classification = detectSemanticTypeAdvanced(content, nodeName, context, node);
            
            // Only use advanced classification if confidence is high enough
            if (classification.confidence >= 0.6) {
                return classification.type;
            }
            
            // Log reasoning for debugging (in development)
            if (process.env.NODE_ENV === 'development') {
                console.debug(`Low confidence (${classification.confidence}) for "${content}": ${classification.reasoning.join(', ')}`);
            }
        } catch (error) {
            // Fall back to legacy detection if advanced detection fails
            console.warn('Advanced semantic detection failed, using legacy method:', error);
        }
    }

    // Legacy detection as fallback
    return detectSemanticTypeLegacy(content, nodeName);
}

/**
 * Legacy semantic type detection (fallback)
 */
function detectSemanticTypeLegacy(content: string, nodeName: string): 'heading' | 'body' | 'label' | 'button' | 'link' | 'caption' | 'error' | 'success' | 'warning' | 'other' {
    const lowerContent = content.toLowerCase().trim();
    const lowerNodeName = nodeName.toLowerCase();

    // Button text patterns - exact matches for common button labels
    const buttonPatterns = [
        /^(click|tap|press|submit|send|save|cancel|ok|yes|no|continue|next|back|close|done|finish|start|begin)$/i,
        /^(login|log in|sign in|signup|sign up|register|logout|log out|sign out)$/i,
        /^(buy|purchase|add|remove|delete|edit|update|create|new|get started|learn more|try now)$/i,
        /^(download|upload|share|copy|paste|cut|undo|redo|refresh|reload|search|filter|sort|apply|reset|clear)$/i,
        /^(accept|decline|agree|disagree|confirm|verify|validate|approve|reject)$/i
    ];

    if (buttonPatterns.some(pattern => pattern.test(content)) || lowerNodeName.includes('button')) {
        return 'button';
    }

    // Error/status text patterns - more comprehensive detection
    const errorPatterns = /error|invalid|required|missing|failed|wrong|incorrect|forbidden|unauthorized|not found|unavailable|expired|timeout/i;
    const successPatterns = /success|completed|done|saved|updated|created|uploaded|downloaded|sent|delivered|confirmed|verified|approved/i;
    const warningPatterns = /warning|caution|note|important|attention|notice|alert|reminder|tip|info|information/i;

    if (errorPatterns.test(content)) {
        return 'error';
    }

    if (successPatterns.test(content)) {
        return 'success';
    }

    if (warningPatterns.test(content)) {
        return 'warning';
    }

    // Link patterns - navigation and informational links
    const linkPatterns = [
        /^(learn more|read more|see more|view all|show all|details|click here|view details)$/i,
        /^(about|contact|help|support|faq|terms|privacy|policy|documentation|docs|guide|tutorial)$/i,
        /^(home|dashboard|profile|settings|preferences|account|billing|notifications|security)$/i
    ];

    if (linkPatterns.some(pattern => pattern.test(content)) || lowerNodeName.includes('link')) {
        return 'link';
    }

    // Heading patterns - based on structure and context
    if (content.length < 80 && !content.endsWith('.') && !content.includes('\n')) {
        if (lowerNodeName.includes('heading') || lowerNodeName.includes('title') || /h[1-6]/.test(lowerNodeName)) {
            return 'heading';
        }
        // Check if it looks like a title (short, starts with capital, no sentence punctuation)
        if (content.length < 50 && /^[A-Z]/.test(content) && !/[.!?]$/.test(content)) {
            return 'heading';
        }
    }

    // Label patterns - form labels and descriptive text
    if (content.length < 40 && (content.endsWith(':') || lowerNodeName.includes('label') || lowerNodeName.includes('field'))) {
        return 'label';
    }

    // Caption patterns - short descriptive text
    if (content.length < 120 && (
        lowerNodeName.includes('caption') || 
        lowerNodeName.includes('subtitle') || 
        lowerNodeName.includes('description') ||
        lowerNodeName.includes('meta')
    )) {
        return 'caption';
    }

    // Body text - longer content, paragraphs
    if (content.length > 80 || content.includes('\n') || content.includes('. ')) {
        return 'body';
    }

    return 'other';
}

/**
 * Generate Flutter widget suggestion based on semantic type and text info
 */
export function generateFlutterTextWidget(textInfo: TextInfo): string {
    // Escape single quotes in content for Dart strings
    const escapedContent = textInfo.content.replace(/'/g, "\\'");
    
    if (textInfo.isPlaceholder) {
        return `Text('${escapedContent}') // TODO: Replace with actual content`;
    }

    // Generate style properties based on text info
    const styleProps: string[] = [];
    if (textInfo.fontFamily) {
        styleProps.push(`fontFamily: '${textInfo.fontFamily}'`);
    }
    if (textInfo.fontSize) {
        styleProps.push(`fontSize: ${textInfo.fontSize}`);
    }
    if (textInfo.fontWeight && textInfo.fontWeight !== 400) {
        const fontWeight = textInfo.fontWeight >= 700 ? 'FontWeight.bold' : 
                          textInfo.fontWeight >= 600 ? 'FontWeight.w600' :
                          textInfo.fontWeight >= 500 ? 'FontWeight.w500' :
                          textInfo.fontWeight <= 300 ? 'FontWeight.w300' : 'FontWeight.normal';
        styleProps.push(`fontWeight: ${fontWeight}`);
    }

    const customStyle = styleProps.length > 0 ? `TextStyle(${styleProps.join(', ')})` : null;

    switch (textInfo.semanticType) {
        case 'button':
            return `ElevatedButton(\n  onPressed: () {\n    // TODO: Implement button action\n  },\n  child: Text('${escapedContent}'),\n)`;

        case 'link':
            return `TextButton(\n  onPressed: () {\n    // TODO: Implement navigation\n  },\n  child: Text('${escapedContent}'),\n)`;

        case 'heading':
            const headingStyle = customStyle || 'Theme.of(context).textTheme.headlineMedium';
            return `Text(\n  '${escapedContent}',\n  style: ${headingStyle},\n)`;

        case 'body':
            const bodyStyle = customStyle || 'Theme.of(context).textTheme.bodyMedium';
            return `Text(\n  '${escapedContent}',\n  style: ${bodyStyle},\n)`;

        case 'caption':
            const captionStyle = customStyle || 'Theme.of(context).textTheme.bodySmall';
            return `Text(\n  '${escapedContent}',\n  style: ${captionStyle},\n)`;

        case 'label':
            const labelStyle = customStyle || 'Theme.of(context).textTheme.labelMedium';
            return `Text(\n  '${escapedContent}',\n  style: ${labelStyle},\n)`;

        case 'error':
            const errorStyle = customStyle ? 
                `${customStyle.slice(0, -1)}, color: Theme.of(context).colorScheme.error)` :
                'TextStyle(color: Theme.of(context).colorScheme.error)';
            return `Text(\n  '${escapedContent}',\n  style: ${errorStyle},\n)`;

        case 'success':
            const successStyle = customStyle ?
                `${customStyle.slice(0, -1)}, color: Colors.green)` :
                'TextStyle(color: Colors.green)';
            return `Text(\n  '${escapedContent}',\n  style: ${successStyle},\n)`;

        case 'warning':
            const warningStyle = customStyle ?
                `${customStyle.slice(0, -1)}, color: Colors.orange)` :
                'TextStyle(color: Colors.orange)';
            return `Text(\n  '${escapedContent}',\n  style: ${warningStyle},\n)`;

        default:
            return customStyle ? 
                `Text(\n  '${escapedContent}',\n  style: ${customStyle},\n)` :
                `Text('${escapedContent}')`;
    }
}

/**
 * Get all text content from a component tree
 */
export function extractAllTextContent(node: FigmaNode): Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}> {
    const textNodes: Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}> = [];

    traverseForText(node, textNodes);

    return textNodes;
}

/**
 * Recursively traverse node tree to find all text nodes
 */
function traverseForText(
    node: FigmaNode,
    results: Array<{nodeId: string, textInfo: TextInfo, widgetSuggestion: string}>,
    depth: number = 0
): void {
    if (depth > 5) return; // Prevent infinite recursion

    if (node.type === 'TEXT') {
        const textInfo = extractTextInfo(node);
        if (textInfo) {
            results.push({
                nodeId: node.id,
                textInfo,
                widgetSuggestion: generateFlutterTextWidget(textInfo)
            });
        }
    }

    if (node.children) {
        node.children.forEach(child => {
            traverseForText(child, results, depth + 1);
        });
    }
}

/**
 * Convert RGBA color to hex string
 */
export function rgbaToHex(color: FigmaColor): string {
    const r = Math.round(color.r * 255);
    const g = Math.round(color.g * 255);
    const b = Math.round(color.b * 255);

    return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
}
```

--------------------------------------------------------------------------------
/src/tools/flutter/components/component-tool.ts:
--------------------------------------------------------------------------------

```typescript
// src/tools/flutter/component/component-tool.mts

import {z} from "zod";
import type {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {FigmaService} from "../../../services/figma.js";
import {
    ComponentExtractor,
    VariantAnalyzer,
    parseComponentInput,
    type ComponentAnalysis,
    type ComponentVariant,
    DeduplicatedComponentExtractor,
    type DeduplicatedComponentAnalysis
} from "../../../extractors/components/index.js";
import {FlutterStyleLibrary, OptimizationReport} from "../../../extractors/flutter/style-library.js";
import {Logger} from "../../../utils/logger.js";

import {
    generateVariantSelectionPrompt,
    generateComponentAnalysisReport,
    generateStructureInspectionReport
} from "./helpers.js";
import {
    generateFlutterImplementation,
    generateComprehensiveDeduplicatedReport,
    generateStyleLibraryReport,
    addVisualContextToDeduplicatedReport
} from "./deduplicated-helpers.js";

import {
    createAssetsDirectory,
    generateAssetFilename,
    downloadImage,
    getFileStats,
    updatePubspecAssets,
    generateAssetConstants,
    groupAssetsByBaseName,
    type AssetInfo
} from "../assets/asset-manager.js";
import {join} from 'path';

export function registerComponentTools(server: McpServer, figmaApiKey: string) {

    // Main component analysis tool
    server.registerTool(
        "analyze_figma_component",
        {
            title: "Analyze Figma Component",
            description: "Analyze a Figma component or component set to extract layout, styling, and structure information for Flutter widget creation. Use analyze_full_screen for complete screen layouts.",
            inputSchema: {
                input: z.string().describe("Figma component URL or file ID"),
                nodeId: z.string().optional().describe("Node ID (if providing file ID separately)"),
                userDefinedComponent: z.boolean().optional().describe("Treat a FRAME as a component (when designer hasn't converted to actual component yet) (default: false)"),
                maxChildNodes: z.number().optional().describe("Maximum child nodes to analyze (default: 10)"),
                includeVariants: z.boolean().optional().describe("Include variant analysis for component sets (default: true)"),
                variantSelection: z.array(z.string()).optional().describe("Specific variant names to analyze (if >3 variants)"),
                projectPath: z.string().optional().describe("Path to Flutter project for asset export (defaults to current directory)"),
                exportAssets: z.boolean().optional().describe("Automatically export image assets found in component (default: true)"),
                useDeduplication: z.boolean().optional().describe("Use style deduplication for token efficiency (default: true)"),
                generateFlutterCode: z.boolean().optional().describe("Generate full Flutter implementation code (default: false)"),
                resetStyleLibrary: z.boolean().optional().describe("Reset style library before analysis (default: false)"),
                autoOptimize: z.boolean().optional().describe("Auto-optimize style library during analysis (default: true)")
            }
        },
        async ({input, nodeId, userDefinedComponent = false, maxChildNodes = 10, includeVariants = true, variantSelection, projectPath = process.cwd(), exportAssets = true, useDeduplication = true, generateFlutterCode = false, resetStyleLibrary = false, autoOptimize = true}) => {
            const token = figmaApiKey;
            if (!token) {
                return {
                    content: [{
                        type: "text",
                        text: "Error: Figma access token not configured. Please set FIGMA_API_KEY environment variable."
                    }]
                };
            }

            try {
                // Reset style library if requested
                const styleLibrary = FlutterStyleLibrary.getInstance();
                
                if (resetStyleLibrary) {
                    styleLibrary.reset();
                }
                
                // Configure auto-optimization
                styleLibrary.setAutoOptimization(autoOptimize);
                
                Logger.info(`🎯 Component Analysis Started:`, {
                    input: input.substring(0, 50) + '...',
                    nodeId,
                    useDeduplication,
                    autoOptimize,
                    resetStyleLibrary
                });

                // Parse input to get file ID and node ID
                const parsedInput = parseComponentInput(input, nodeId);

                if (!parsedInput.isValid) {
                    return {
                        content: [{
                            type: "text",
                            text: `Error parsing input: ${parsedInput.error || 'Invalid input format'}`
                        }]
                    };
                }

                const figmaService = new FigmaService(token);

                // Get the component node
                const componentNode = await figmaService.getNode(parsedInput.fileId, parsedInput.nodeId);

                if (!componentNode) {
                    return {
                        content: [{
                            type: "text",
                            text: `Component with node ID "${parsedInput.nodeId}" not found in file.`
                        }]
                    };
                }

                // Validate that this is a component or user-defined component
                const isActualComponent = componentNode.type === 'COMPONENT' || componentNode.type === 'COMPONENT_SET' || componentNode.type === 'INSTANCE';
                const isUserDefinedFrame = componentNode.type === 'FRAME' && userDefinedComponent;
                
                if (!isActualComponent && !isUserDefinedFrame) {
                    if (componentNode.type === 'FRAME') {
                        return {
                            content: [{
                                type: "text",
                                text: `Node "${componentNode.name}" is a FRAME. If this should be treated as a component, set userDefinedComponent: true. For analyzing complete screens, use the analyze_full_screen tool instead.`
                            }]
                        };
                    } else {
                        return {
                            content: [{
                                type: "text",
                                text: `Node "${componentNode.name}" is not a component (type: ${componentNode.type}). For analyzing full screens, use the analyze_full_screen tool instead.`
                            }]
                        };
                    }
                }

                // Check if this is a component set and handle variants
                let variantAnalysis: ComponentVariant[] | undefined;
                let selectedVariants: ComponentVariant[] = [];

                if (componentNode.type === 'COMPONENT_SET' && includeVariants) {
                    const variantAnalyzer = new VariantAnalyzer();
                    variantAnalysis = await variantAnalyzer.analyzeComponentSet(componentNode);

                    if (variantAnalyzer.shouldPromptForVariantSelection(variantAnalysis)) {
                        // More than 3 variants - check if user provided selection
                        if (!variantSelection || variantSelection.length === 0) {
                            const selectionInfo = variantAnalyzer.getVariantSelectionInfo(variantAnalysis);

                            return {
                                content: [{
                                    type: "text",
                                    text: generateVariantSelectionPrompt(componentNode.name, selectionInfo, variantAnalysis)
                                }]
                            };
                        } else {
                            // Filter variants based on user selection
                            selectedVariants = variantAnalyzer.filterVariantsBySelection(variantAnalysis, {
                                variantNames: variantSelection,
                                includeDefault: true
                            });

                            if (selectedVariants.length === 0) {
                                return {
                                    content: [{
                                        type: "text",
                                        text: `No variants found matching selection: ${variantSelection.join(', ')}`
                                    }]
                                };
                            }
                        }
                    } else {
                        // 3 or fewer variants - analyze all
                        selectedVariants = variantAnalysis;
                    }
                }

                // Analyze the main component
                let analysisReport: string;

                if (useDeduplication) {
                    Logger.info(`🔧 Using enhanced deduplication for component analysis`);
                    // Use deduplicated extractor
                    const deduplicatedExtractor = new DeduplicatedComponentExtractor();
                    let deduplicatedAnalysis: DeduplicatedComponentAnalysis;

                    if (componentNode.type === 'COMPONENT_SET') {
                        // For component sets, analyze the default variant or first selected variant
                        const targetVariant = selectedVariants.find(v => v.isDefault) || selectedVariants[0];
                        if (targetVariant) {
                            const variantNode = await figmaService.getNode(parsedInput.fileId, targetVariant.nodeId);
                            deduplicatedAnalysis = await deduplicatedExtractor.analyzeComponent(variantNode, true);
                        } else {
                            // Fallback to analyzing the component set itself
                            deduplicatedAnalysis = await deduplicatedExtractor.analyzeComponent(componentNode, true);
                        }
                    } else {
                        // Regular component, instance, or user-defined frame
                        deduplicatedAnalysis = await deduplicatedExtractor.analyzeComponent(componentNode, true);
                    }

                    Logger.info(`📊 Deduplication analysis complete:`, {
                        styleRefs: Object.keys(deduplicatedAnalysis.styleRefs).length,
                        children: deduplicatedAnalysis.children.length,
                        nestedComponents: deduplicatedAnalysis.nestedComponents.length,
                        newStyleDefinitions: deduplicatedAnalysis.newStyleDefinitions ? Object.keys(deduplicatedAnalysis.newStyleDefinitions).length : 0
                    });
                    
                    analysisReport = generateComprehensiveDeduplicatedReport(deduplicatedAnalysis, true);
                    
                    // Add visual context for deduplicated analysis
                    if (parsedInput.source === 'url') {
                        // Reconstruct the Figma URL from the parsed input
                        const figmaUrl = `https://www.figma.com/design/${parsedInput.fileId}/?node-id=${parsedInput.nodeId}`;
                        analysisReport += "\n\n" + addVisualContextToDeduplicatedReport(
                            deduplicatedAnalysis, 
                            figmaUrl, 
                            parsedInput.nodeId
                        );
                    }
                    
                    if (generateFlutterCode) {
                        analysisReport += "\n\n" + generateFlutterImplementation(deduplicatedAnalysis);
                    }
                } else {
                    // Use original extractor
                    const componentExtractor = new ComponentExtractor({
                        maxChildNodes,
                        extractTextContent: true,
                        prioritizeComponents: true
                    });
                    
                    let componentAnalysis: ComponentAnalysis;

                    if (componentNode.type === 'COMPONENT_SET') {
                        // For component sets, analyze the default variant or first selected variant
                        const targetVariant = selectedVariants.find(v => v.isDefault) || selectedVariants[0];
                        if (targetVariant) {
                            const variantNode = await figmaService.getNode(parsedInput.fileId, targetVariant.nodeId);
                            componentAnalysis = await componentExtractor.analyzeComponent(variantNode, userDefinedComponent);
                        } else {
                            // Fallback to analyzing the component set itself
                            componentAnalysis = await componentExtractor.analyzeComponent(componentNode, userDefinedComponent);
                        }
                    } else {
                        // Regular component, instance, or user-defined frame
                        componentAnalysis = await componentExtractor.analyzeComponent(componentNode, userDefinedComponent);
                    }

                    analysisReport = generateComponentAnalysisReport(
                        componentAnalysis,
                        variantAnalysis,
                        selectedVariants,
                        parsedInput
                    );
                }

                // Detect and export image assets if enabled
                let assetExportInfo = '';
                if (exportAssets) {
                    try {
                        // Use the existing filterImageNodes logic from assets.mts
                        const imageNodes = await filterImageNodesInComponent(parsedInput.fileId, [parsedInput.nodeId], figmaService);
                        if (imageNodes.length > 0) {
                            const exportedAssets = await exportComponentAssets(
                                imageNodes,
                                parsedInput.fileId,
                                figmaService,
                                projectPath
                            );
                            assetExportInfo = generateAssetExportReport(exportedAssets);
                        }
                    } catch (assetError) {
                        assetExportInfo = `\nAsset Export Warning: ${assetError instanceof Error ? assetError.message : String(assetError)}\n`;
                    }
                }

                return {
                    content: [{
                        type: "text",
                        text: analysisReport + assetExportInfo
                    }]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Error analyzing component: ${error instanceof Error ? error.message : String(error)}`
                    }]
                };
            }
        }
    );

    // Helper tool to list component variants
    server.registerTool(
        "list_component_variants",
        {
            title: "List Component Variants",
            description: "List all variants in a Figma component set to help with variant selection",
            inputSchema: {
                input: z.string().describe("Figma component set URL or file ID"),
                nodeId: z.string().optional().describe("Node ID (if providing file ID separately)")
            }
        },
        async ({input, nodeId}) => {
            const token = figmaApiKey;
            if (!token) {
                return {
                    content: [{
                        type: "text",
                        text: "Error: Figma access token not configured."
                    }]
                };
            }

            try {
                const parsedInput = parseComponentInput(input, nodeId);

                if (!parsedInput.isValid) {
                    return {
                        content: [{
                            type: "text",
                            text: `Error parsing input: ${parsedInput.error}`
                        }]
                    };
                }

                const figmaService = new FigmaService(token);
                const componentNode = await figmaService.getNode(parsedInput.fileId, parsedInput.nodeId);

                if (!componentNode) {
                    return {
                        content: [{
                            type: "text",
                            text: `Component set with node ID "${parsedInput.nodeId}" not found.`
                        }]
                    };
                }

                if (componentNode.type !== 'COMPONENT_SET') {
                    return {
                        content: [{
                            type: "text",
                            text: `Node "${componentNode.name}" is not a component set (type: ${componentNode.type}). This tool is only for component sets with variants.`
                        }]
                    };
                }

                const variantAnalyzer = new VariantAnalyzer();
                const variants = await variantAnalyzer.analyzeComponentSet(componentNode);
                const summary = variantAnalyzer.generateVariantSummary(variants);
                const selectionInfo = variantAnalyzer.getVariantSelectionInfo(variants);

                let output = `Component Set: ${componentNode.name}\n\n${summary}\n`;

                if (variants.length > 3) {
                    output += `\nTo analyze specific variants, use the analyze_figma_component tool with variantSelection parameter.\n`;
                    output += `Example variant names you can select:\n`;
                    selectionInfo.variantNames.slice(0, 5).forEach(name => {
                        output += `- "${name}"\n`;
                    });
                }

                return {
                    content: [{type: "text", text: output}]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Error listing variants: ${error instanceof Error ? error.message : String(error)}`
                    }]
                };
            }
        }
    );

    // Helper tool to inspect component structure
    server.registerTool(
        "inspect_component_structure",
        {
            title: "Inspect Component Structure",
            description: "Get a quick overview of component structure, children, and nested components. Use inspect_screen_structure for full screens.",
            inputSchema: {
                input: z.string().describe("Figma component URL or file ID"),
                nodeId: z.string().optional().describe("Node ID (if providing file ID separately)"),
                userDefinedComponent: z.boolean().optional().describe("Treat a FRAME as a component (when designer hasn't converted to actual component yet) (default: false)"),
                showAllChildren: z.boolean().optional().describe("Show all children regardless of limits (default: false)")
            }
        },
        async ({input, nodeId, userDefinedComponent = false, showAllChildren = false}) => {
            const token = figmaApiKey;
            if (!token) {
                return {
                    content: [{
                        type: "text",
                        text: "Error: Figma access token not configured."
                    }]
                };
            }

            try {
                const parsedInput = parseComponentInput(input, nodeId);

                if (!parsedInput.isValid) {
                    return {
                        content: [{
                            type: "text",
                            text: `Error parsing input: ${parsedInput.error}`
                        }]
                    };
                }

                const figmaService = new FigmaService(token);
                const componentNode = await figmaService.getNode(parsedInput.fileId, parsedInput.nodeId);

                if (!componentNode) {
                    return {
                        content: [{
                            type: "text",
                            text: `Component with node ID "${parsedInput.nodeId}" not found.`
                        }]
                    };
                }

                // Validate that this is a component or user-defined component
                const isActualComponent = componentNode.type === 'COMPONENT' || componentNode.type === 'COMPONENT_SET' || componentNode.type === 'INSTANCE';
                const isUserDefinedFrame = componentNode.type === 'FRAME' && userDefinedComponent;
                
                if (!isActualComponent && !isUserDefinedFrame) {
                    if (componentNode.type === 'FRAME') {
                        return {
                            content: [{
                                type: "text",
                                text: `Node "${componentNode.name}" is a FRAME. If this should be treated as a component, set userDefinedComponent: true. For inspecting complete screens, use the inspect_screen_structure tool instead.`
                            }]
                        };
                    } else {
                        return {
                            content: [{
                                type: "text",
                                text: `Node "${componentNode.name}" is not a component (type: ${componentNode.type}). For inspecting full screens, use the inspect_screen_structure tool instead.`
                            }]
                        };
                    }
                }

                const output = generateStructureInspectionReport(componentNode, showAllChildren);

                return {
                    content: [{type: "text", text: output}]
                };

            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Error inspecting structure: ${error instanceof Error ? error.message : String(error)}`
                    }]
                };
            }
        }
    );

    // Dedicated Flutter code generation tool
    server.registerTool(
        "generate_flutter_implementation",
        {
            title: "Generate Flutter Implementation",
            description: "Generate complete Flutter widget code using cached style definitions",
            inputSchema: {
                componentNodeId: z.string().describe("Node ID of the analyzed component"),
                includeStyleDefinitions: z.boolean().optional().describe("Include style definitions in output (default: true)"),
                widgetName: z.string().optional().describe("Custom widget class name")
            }
        },
        async ({ componentNodeId, includeStyleDefinitions = true, widgetName }) => {
            try {
                const styleLibrary = FlutterStyleLibrary.getInstance();
                const styles = styleLibrary.getAllStyles();
                
                let output = "🏗️  Flutter Implementation\n";
                output += `${'='.repeat(50)}\n\n`;
                
                if (includeStyleDefinitions && styles.length > 0) {
                    output += "📋 Style Definitions:\n";
                    output += `${'─'.repeat(30)}\n`;
                    styles.forEach(style => {
                        output += `// ${style.id} (${style.category}, used ${style.usageCount} times)\n`;
                        output += `final ${style.id} = ${style.flutterCode};\n\n`;
                    });
                    output += "\n";
                } else if (styles.length === 0) {
                    output += "⚠️  No cached styles found. Please analyze a component first.\n\n";
                }
                
                output += generateWidgetClass(componentNodeId, widgetName || 'CustomWidget', styles);
                
                // Add usage summary
                if (styles.length > 0) {
                    output += "\n\n📊 Style Library Summary:\n";
                    output += `${'─'.repeat(30)}\n`;
                    output += `• Total unique styles: ${styles.length}\n`;
                    
                    const categoryStats = styles.reduce((acc, style) => {
                        acc[style.category] = (acc[style.category] || 0) + 1;
                        return acc;
                    }, {} as Record<string, number>);
                    
                    Object.entries(categoryStats).forEach(([category, count]) => {
                        output += `• ${category}: ${count} style(s)\n`;
                    });
                    
                    const totalUsage = styles.reduce((sum, style) => sum + style.usageCount, 0);
                    output += `• Total style usage: ${totalUsage}\n`;
                    const efficiency = styles.length > 0 ? ((totalUsage - styles.length) / totalUsage * 100).toFixed(1) : '0.0';
                    output += `• Deduplication efficiency: ${efficiency}% reduction\n`;
                }
                
                return {
                    content: [{ type: "text", text: output }]
                };
                
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Error generating Flutter implementation: ${error instanceof Error ? error.message : String(error)}`
                    }]
                };
            }
        }
    );

    // Style library status tool
    server.registerTool(
        "style_library_status",
        {
            title: "Style Library Status",
            description: "Get comprehensive status report of the cached style library",
            inputSchema: {}
        },
        async () => {
            try {
                const report = generateStyleLibraryReport();
                
                return {
                    content: [{ type: "text", text: report }]
                };
                
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Error generating style library report: ${error instanceof Error ? error.message : String(error)}`
                    }]
                };
            }
        }
    );
}

/**
 * OPTIMIZED: Filter image nodes within a component - only searches within target nodes
 */
async function filterImageNodesInComponent(fileId: string, targetNodeIds: string[], figmaService: FigmaService): Promise<Array<{id: string, name: string, node: any}>> {
    // OPTIMIZED: Only get the target nodes instead of the entire file (massive performance improvement)
    const targetNodes = await figmaService.getNodes(fileId, targetNodeIds);

    const allNodesWithImages: Array<{id: string, name: string, node: any}> = [];

    function extractImageNodes(node: any, nodeId: string = node.id): void {
        // Check if this node has image fills
        if (node.fills && node.fills.some((fill: any) => fill.type === 'IMAGE' && fill.visible !== false)) {
            allNodesWithImages.push({
                id: nodeId,
                name: node.name,
                node: node
            });
        }

        // Check if this is a vector/illustration that should be exported
        if (node.type === 'VECTOR' && node.name) {
            const name = node.name.toLowerCase();
            if ((name.includes('image') || name.includes('illustration') || name.includes('graphic')) &&
                !name.includes('icon') && !name.includes('button')) {
                allNodesWithImages.push({
                    id: nodeId,
                    name: node.name,
                    node: node
                });
            }
        }

        // Recursively check children
        if (node.children) {
            node.children.forEach((child: any) => {
                extractImageNodes(child, child.id);
            });
        }
    }

    // OPTIMIZED: Extract only from target nodes instead of entire file
    // This eliminates the need for expensive boundary checking since we only search within target nodes
    Object.values(targetNodes).forEach((node: any) => {
        extractImageNodes(node);
    });

    // OPTIMIZED: No filtering needed since we only searched within target nodes
    return allNodesWithImages;
}

// REMOVED: isNodeWithinTarget function no longer needed since we only search within target nodes

/**
 * Export component assets to Flutter project
 */
async function exportComponentAssets(
    imageNodes: Array<{id: string, name: string, node: any}>,
    fileId: string,
    figmaService: FigmaService,
    projectPath: string
): Promise<AssetInfo[]> {
    if (imageNodes.length === 0) {
        return [];
    }

    // Create assets directory structure
    const assetsDir = await createAssetsDirectory(projectPath);
    const downloadedAssets: AssetInfo[] = [];

    // Export images at 2x scale (standard for Flutter)
    const imageUrls = await figmaService.getImageExportUrls(fileId, imageNodes.map(n => n.id), {
        format: 'png',
        scale: 2
    });

    for (const imageNode of imageNodes) {
        const imageUrl = imageUrls[imageNode.id];
        if (!imageUrl) continue;

        const filename = generateAssetFilename(imageNode.name, 'png', 2, false);
        const filepath = join(assetsDir, filename);

        try {
            // Download the image
            await downloadImage(imageUrl, filepath);

            // Get file size for reporting
            const stats = await getFileStats(filepath);

            downloadedAssets.push({
                nodeId: imageNode.id,
                nodeName: imageNode.name,
                filename,
                path: `assets/images/${filename}`,
                size: stats.size
            });
        } catch (downloadError) {
            console.warn(`Failed to download image ${imageNode.name}:`, downloadError);
        }
    }

    if (downloadedAssets.length > 0) {
        // Update pubspec.yaml
        const pubspecPath = join(projectPath, 'pubspec.yaml');
        await updatePubspecAssets(pubspecPath, downloadedAssets);

        // Generate asset constants file
        await generateAssetConstants(downloadedAssets, projectPath);
    }

    return downloadedAssets;
}

/**
 * Generate asset export report
 */
function generateAssetExportReport(exportedAssets: AssetInfo[]): string {
    if (exportedAssets.length === 0) {
        return '';
    }

    let report = `\n${'='.repeat(50)}\n`;
    report += `🖼️  AUTOMATIC ASSET EXPORT\n`;
    report += `${'='.repeat(50)}\n\n`;
    
    report += `Found and exported ${exportedAssets.length} image asset(s) from the component:\n\n`;

    // Group by base name for cleaner output
    const groupedAssets = groupAssetsByBaseName(exportedAssets);
    Object.entries(groupedAssets).forEach(([baseName, assets]) => {
        report += `📁 ${baseName}:\n`;
        assets.forEach(asset => {
            report += `   • ${asset.filename} (${asset.size})\n`;
        });
    });

    report += `\n✅ Assets Configuration:\n`;
    report += `   • Images saved to: assets/images/\n`;
    report += `   • pubspec.yaml updated with asset declarations\n`;
    report += `   • Asset constants generated in: lib/constants/assets.dart\n\n`;

    report += `🚀 Usage in Flutter:\n`;
    report += `   import 'package:your_app/constants/assets.dart';\n\n`;
    
    exportedAssets.forEach(asset => {
        const constantName = asset.filename
            .replace(/\.[^/.]+$/, '') // Remove extension
            .replace(/[^a-zA-Z0-9]/g, ' ') // Replace special chars with space
            .replace(/\s+/g, ' ') // Replace multiple spaces with single
            .trim()
            .split(' ')
            .map((word, index) => index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
            .join('');
        
        report += `   Image.asset(Assets.${constantName}) // ${asset.nodeName}\n`;
    });

    report += `\n${'='.repeat(50)}\n`;

    return report;
}


/**
 * Generate widget class implementation
 */
function generateWidgetClass(componentNodeId: string, widgetName: string, styles: Array<any>): string {
    let output = `🎯 Widget Implementation:\n`;
    output += `${'─'.repeat(30)}\n`;
    
    // Widget composition best practices
    output += `🏗️  Widget Composition Guidelines:\n`;
    output += `- Start with inline widget tree composition in build() method\n`;
    output += `- Continue composing inline until you reach ~200 lines of code\n`;
    output += `- Only then extract parts into private StatelessWidget classes\n`;
    output += `- Use private widgets (prefix with _) for internal breakdown\n`;
    output += `- Avoid functional widgets - always use StatelessWidget classes\n\n`;
    
    output += `class ${widgetName} extends StatelessWidget {\n`;
    output += `  const ${widgetName}({Key? key}) : super(key: key);\n\n`;
    output += `  @override\n`;
    output += `  Widget build(BuildContext context) {\n`;

    // Find relevant styles for this component
    const decorationStyles = styles.filter(s => s.category === 'decoration');
    const paddingStyles = styles.filter(s => s.category === 'padding');
    const textStyles = styles.filter(s => s.category === 'text');

    if (decorationStyles.length > 0 || paddingStyles.length > 0) {
        output += `    return Container(\n`;
        
        // Add decoration if available
        if (decorationStyles.length > 0) {
            const decorationStyle = decorationStyles[0]; // Use first decoration style
            output += `      decoration: ${decorationStyle.id},\n`;
        }
        
        // Add padding if available
        if (paddingStyles.length > 0) {
            const paddingStyle = paddingStyles[0]; // Use first padding style
            output += `      padding: ${paddingStyle.id},\n`;
        }
        
        // Add child content
        if (textStyles.length > 0) {
            const textStyle = textStyles[0]; // Use first text style
            output += `      child: Text(\n`;
            output += `        'Sample Text', // TODO: Replace with actual content\n`;
            output += `        style: ${textStyle.id},\n`;
            output += `      ),\n`;
        } else {
            output += `      child: Column(\n`;
            output += `        children: [\n`;
            output += `          // TODO: Add your widget content here\n`;
            output += `          Text('Component Content'),\n`;
            output += `        ],\n`;
            output += `      ),\n`;
        }
        
        output += `    );\n`;
    } else if (textStyles.length > 0) {
        // Just a text widget if only text styles are available
        const textStyle = textStyles[0];
        output += `    return Text(\n`;
        output += `      'Sample Text', // TODO: Replace with actual content\n`;
        output += `      style: ${textStyle.id},\n`;
        output += `    );\n`;
    } else {
        // Fallback for when no cached styles are available
        output += `    return Container(\n`;
        output += `      // TODO: Implement widget using component node ID: ${componentNodeId}\n`;
        output += `      // No cached styles found - please analyze a component first\n`;
        output += `      child: Text('Widget Placeholder'),\n`;
        output += `    );\n`;
    }

    output += `  }\n`;
    output += `}\n`;

    // Add usage instructions
    output += `\n💡 Usage Instructions:\n`;
    output += `${'─'.repeat(30)}\n`;
    output += `1. Start by building the complete widget tree inline in build() method\n`;
    output += `2. Keep composing widgets inline until you reach ~200 lines\n`;
    output += `3. Only then extract reusable parts into private StatelessWidget classes\n`;
    output += `4. Replace 'Sample Text' with actual content from Figma\n`;
    output += `5. Customize the widget structure and add any missing properties\n\n`;

    if (styles.length > 0) {
        output += `📦 Available Style References:\n`;
        output += `${'─'.repeat(30)}\n`;
        styles.forEach(style => {
            output += `• ${style.id} (${style.category})\n`;
        });
    }

    return output;
}

```
Page 3/3FirstPrevNextLast