"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentSearchService = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const glob_1 = require("glob");
const logger_1 = require("../utils/logger");
const MAX_COMPONENT_MATCHES = 5;
/**
 * Sort component matches by depth (lower = closer to element)
 * @param matches Array of ComponentMatch objects to sort
 * @returns Sorted array with closest components first
 */
function sortComponentMatchesByDepth(matches) {
    return matches.sort((a, b) => {
        // Sort by depth (ascending - lower depth is better)
        if (a.depth !== undefined && b.depth !== undefined) {
            return a.depth - b.depth;
        }
        // If one has depth and the other doesn't, prefer the one with depth
        if (a.depth !== undefined && b.depth === undefined)
            return -1;
        if (a.depth === undefined && b.depth !== undefined)
            return 1;
        // If neither has depth, maintain original order
        return 0;
    });
}
class ComponentSearchService {
    log = (0, logger_1.createLogger)('ComponentSearchService');
    /**
     * Extract meaningful classNames from element, filtering out common utilities
     */
    extractMeaningfulClassNames(className) {
        if (!className)
            return [];
        return className.split(/\s+/).filter((cls) => {
            // Ignore very short classes (1-2 chars)
            if (cls.length < 3)
                return false;
            // Ignore common Tailwind/utility patterns
            const utilityPatterns = /^(flex|grid|block|inline|hidden|p-\d+|px-\d+|py-\d+|m-\d+|mx-\d+|my-\d+|text-|bg-|w-\d+|h-\d+|rounded|border|shadow|hover:|focus:|active:|disabled:|opacity-|z-\d+|gap-\d+|space-|justify-|items-|cursor-|overflow-|relative|absolute|fixed|top-|left-|right-|bottom-)/;
            return !utilityPatterns.test(cls);
        });
    }
    /**
     * Capitalize first letter for component name matching
     */
    capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }
    /**
     * Find component files by searching for component definitions
     * Searches all tsx/jsx files for export patterns matching component names
     */
    async findComponentsByName(projectPath, componentTree) {
        if (!componentTree || componentTree.length === 0) {
            return [];
        }
        // Build map of component name -> minimum depth
        // If a component appears multiple times, use the closest one (minimum depth)
        const componentDepthMap = new Map();
        for (const component of componentTree) {
            if (component.name === 'Anonymous')
                continue;
            const currentMin = componentDepthMap.get(component.name);
            if (currentMin === undefined || component.depth < currentMin) {
                componentDepthMap.set(component.name, component.depth);
            }
        }
        const componentNames = Array.from(componentDepthMap.keys());
        this.log.info(`Searching by component names: ${componentNames.join(', ')} (${componentNames.length} unique from ${componentTree.length} total)`);
        // Glob for component files only (tsx, jsx)
        const files = await (0, glob_1.glob)('**/*.{tsx,jsx}', {
            cwd: projectPath,
            ignore: [
                '**/node_modules/**',
                '**/.next/**',
                '**/dist/**',
                '**/*.test.*',
                '**/*.spec.*',
            ],
        });
        const matches = [];
        // Search all files for component definitions
        for (const file of files) {
            const filePath = path_1.default.join(projectPath, file);
            const content = fs_1.default.readFileSync(filePath, 'utf-8');
            // Check if any component from the tree is DEFINED in this file
            for (const componentName of componentNames) {
                // Patterns that match component DEFINITIONS (not imports or usage)
                // NOTE this is specific to React component definitions
                // TODO: add support for other frameworks
                const patterns = [
                    // export function ComponentName
                    new RegExp(`export\\s+function\\s+${componentName}\\b`),
                    // export const ComponentName =
                    new RegExp(`export\\s+const\\s+${componentName}\\s*=`),
                    // class ComponentName extends
                    new RegExp(`export\\s+class\\s+${componentName}\\s+extends`),
                    new RegExp(`class\\s+${componentName}\\s+extends`),
                    // export default function ComponentName
                    new RegExp(`export\\s+default\\s+function\\s+${componentName}\\b`),
                    // const ComponentName = (React component patterns)
                    new RegExp(`const\\s+${componentName}\\s*=\\s*\\(`),
                    new RegExp(`const\\s+${componentName}\\s*:\\s*.*=\\s*\\(`),
                    // function ComponentName(
                    new RegExp(`function\\s+${componentName}\\s*\\(`),
                ];
                const hasDefinition = patterns.some((pattern) => pattern.test(content));
                if (hasDefinition) {
                    const depth = componentDepthMap.get(componentName);
                    this.log.debug(`Component match: ${file} - ${componentName} (depth=${depth})`);
                    matches.push({
                        filePath,
                        depth,
                    });
                    break; // Only add each file once
                }
            }
        }
        // Sort by depth (lower = closer to element)
        const sortedMatches = sortComponentMatchesByDepth(matches).slice(0, MAX_COMPONENT_MATCHES);
        if (sortedMatches.length > 0) {
            const topMatch = sortedMatches[0];
            this.log.info(`Found ${sortedMatches.length} component file matches (top: ${path_1.default.basename(topMatch.filePath)}, depth ${topMatch.depth ?? 'N/A'})`);
        }
        else {
            this.log.info('No component files found matching the component tree');
        }
        return sortedMatches;
    }
    /**
     * Find components that contain the selected element's className, id, or tag
     * Prioritizes Fiber-based component tree search when available
     */
    async findMatchingComponents(projectPath, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    element) {
        // If we have React Fiber component tree data, use it first
        if (element.componentTree && element.componentTree.length > 0) {
            this.log.info(`Using Fiber component tree (${element.componentTree.length} components)`);
            const componentMatches = await this.findComponentsByName(projectPath, element.componentTree);
            // If component-based search found good matches, return them
            if (componentMatches.length > 0) {
                this.log.info(`Component tree search succeeded: ${componentMatches.length} matches`);
                return componentMatches;
            }
            this.log.info('Component tree search found no matches, falling back to string search');
        }
        // Extract meaningful classNames (filter out utilities)
        const classNames = this.extractMeaningfulClassNames(element.className);
        // Build search terms with weights
        const searchTerms = [];
        // ID is highest value (very unique)
        if (element.id && element.id.length > 2) {
            searchTerms.push({ term: element.id, weight: 10, type: 'id' });
        }
        // Meaningful classNames (component-specific)
        for (const className of classNames) {
            searchTerms.push({ term: className, weight: 5, type: 'className' });
        }
        // TextContent with quoted string matching in JSX
        const textContent = element.textContent?.trim();
        if (textContent && textContent.length > 2) {
            // Look for text in quoted contexts (JSX props, children)
            searchTerms.push({ term: `"${textContent}"`, weight: 3, type: 'textContent' }, { term: `'${textContent}'`, weight: 3, type: 'textContent' }, { term: `>${textContent}<`, weight: 3, type: 'textContent' }, { term: `\`${textContent}\``, weight: 3, type: 'textContent' });
        }
        // Tag name for JSX matching
        const tagName = element.tagName?.toLowerCase();
        if (tagName) {
            searchTerms.push({ term: `<${tagName}`, weight: 2, type: 'tagName' }, { term: `<${this.capitalize(tagName)}`, weight: 2, type: 'tagName' });
        }
        if (searchTerms.length === 0) {
            this.log.warn('No search terms extracted from element');
            return [];
        }
        this.log.info(`Searching for: ${searchTerms.map((t) => `${t.term}(${t.type})`).join(', ')}`);
        // Find component files
        const filePatterns = '**/*.{tsx,jsx,ts,js,html,css}';
        const files = await (0, glob_1.glob)(filePatterns, {
            cwd: projectPath,
            ignore: ['**/node_modules/**', '**/.next/**', '**/dist/**'],
        });
        // Temporary type with relevance score for sorting
        const matchesWithScore = [];
        for (const file of files) {
            const filePath = path_1.default.join(projectPath, file);
            const content = fs_1.default.readFileSync(filePath, 'utf-8');
            // Calculate relevance score from weighted term matches
            let relevanceScore = 0;
            const matchedTerms = [];
            for (const { term, weight, type } of searchTerms) {
                if (content.includes(term)) {
                    relevanceScore += weight;
                    matchedTerms.push(`${term}(${type})`);
                }
            }
            if (relevanceScore > 0) {
                const absolutePath = path_1.default.join(projectPath, file);
                this.log.debug(`Match: ${file} - score=${relevanceScore} [${matchedTerms.join(', ')}]`);
                matchesWithScore.push({
                    filePath: absolutePath,
                    score: relevanceScore,
                });
            }
        }
        // Sort by relevance score (descending) and take top matches
        const topMatches = matchesWithScore
            .sort((a, b) => b.score - a.score)
            .slice(0, MAX_COMPONENT_MATCHES)
            .map(({ filePath }) => ({ filePath }));
        this.log.info(`Found ${matchesWithScore.length} matches, returning top ${topMatches.length}`);
        return topMatches;
    }
}
exports.ComponentSearchService = ComponentSearchService;
//# sourceMappingURL=ComponentSearchService.js.map