import { CodeIntelligence } from '../../intelligence/index.js';
import { collectTextFiles, createFindDiagnostics, groupByKey, loadFileLines, matchesSymbolQuery, makeCountResult, makeFilesResult, toSymbolKind, validateSearchPath, readTextFile, withFindWarnings, addFindWarning, } from './shared.js';
import { summarizeError } from '../../utils/error-display.js';
import { compileSafeRegExp } from '../../utils/safe-regex.js';
export async function executeSymbolsQuery(query, output, projectRoot) {
    const validatedPath = validateSearchPath(query.path, projectRoot);
    if (typeof validatedPath === 'object')
        return validatedPath;
    const basePath = validatedPath;
    const maxResults = output.max_results ?? 100;
    const kindFilter = query.kinds ? new Set(query.kinds) : null;
    const diagnostics = createFindDiagnostics();
    const linePatterns = [
        { kind: 'function', regex: /^export\s+(?:async\s+)?function\s+(\w+)/, exported: true },
        { kind: 'class', regex: /^export\s+(?:abstract\s+)?class\s+(\w+)/, exported: true },
        { kind: 'interface', regex: /^export\s+interface\s+(\w+)/, exported: true },
        { kind: 'type', regex: /^export\s+type\s+(\w+)\s*[=<{]/, exported: true },
        { kind: 'enum', regex: /^export\s+enum\s+(\w+)/, exported: true },
        { kind: 'constant', regex: /^export\s+const\s+(\w+)/, exported: true },
        { kind: 'variable', regex: /^export\s+(?:let|var)\s+(\w+)/, exported: true },
        { kind: 'function', regex: /^(?:async\s+)?function\s+(\w+)/, exported: false },
        { kind: 'class', regex: /^(?:abstract\s+)?class\s+(\w+)/, exported: false },
    ];
    const activePatterns = kindFilter ? linePatterns.filter((p) => kindFilter.has(p.kind)) : linePatterns;
    const files = await collectTextFiles(basePath, diagnostics);
    const symbols = [];
    let queryRegex = null;
    if (query.query) {
        try {
            queryRegex = compileSafeRegExp(query.query, 'i', { operation: 'find symbols query' });
        }
        catch {
            return { error: `Invalid symbol query pattern: ${query.query}` };
        }
    }
    const ci = new CodeIntelligence({});
    let treeSitterFiles = 0;
    let regexFallbackFiles = 0;
    for (const file of files) {
        if (symbols.length >= maxResults)
            break;
        const content = await readTextFile(file, diagnostics);
        if (content === null)
            continue;
        let usedTreeSitter = false;
        try {
            const tsSymbols = await ci.getSymbols(file, content);
            if (tsSymbols.length > 0) {
                usedTreeSitter = true;
                treeSitterFiles++;
                for (const sym of tsSymbols) {
                    if (symbols.length >= maxResults)
                        break;
                    const kind = toSymbolKind(sym.kind);
                    if (kindFilter && !kindFilter.has(kind))
                        continue;
                    if (query.exported_only && !query.include_private && !sym.exported)
                        continue;
                    if (!matchesSymbolQuery(sym.name, queryRegex))
                        continue;
                    symbols.push({ name: sym.name, kind, file, line: sym.line, exported: sym.exported });
                }
            }
        }
        catch (err) {
            addFindWarning(diagnostics, `Tree-sitter symbol extraction failed for '${file}'; used regex fallback: ${summarizeError(err)}`);
        }
        if (usedTreeSitter)
            continue;
        regexFallbackFiles++;
        const lines = content.split('\n');
        for (let i = 0; i < lines.length; i++) {
            if (symbols.length >= maxResults)
                break;
            const line = (lines[i] ?? '').trimStart();
            for (const { kind, regex, exported } of activePatterns) {
                if (query.exported_only && !query.include_private && !exported)
                    continue;
                const match = line.match(regex);
                if (match) {
                    const name = match[1] ?? '';
                    if (!name)
                        continue;
                    if (!matchesSymbolQuery(name, queryRegex))
                        continue;
                    symbols.push({ name, kind, file, line: i + 1, exported });
                    break;
                }
            }
        }
    }
    if (output.format === 'count_only') {
        return withFindWarnings({ ...makeCountResult(symbols.length), source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
    }
    if (output.format === 'files_only') {
        const uniqueFiles = [...new Set(symbols.map((s) => s.file))];
        return withFindWarnings({ ...makeFilesResult(uniqueFiles, symbols.length), source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
    }
    if (output.format === 'signatures' || output.format === 'full') {
        const fileContents = new Map();
        const enriched = [];
        for (const sym of symbols) {
            let fileLines = fileContents.get(sym.file);
            if (!fileLines) {
                fileLines = await loadFileLines(sym.file);
                fileContents.set(sym.file, fileLines);
            }
            const entry = {
                name: sym.name,
                kind: sym.kind,
                file: sym.file,
                line: sym.line,
                exported: sym.exported,
            };
            const sigLines = [];
            for (let i = sym.line - 1; i < Math.min(sym.line + 10, fileLines.length); i++) {
                const l = fileLines[i];
                sigLines.push(l.trimEnd());
                if (/[{;]/.test(l))
                    break;
            }
            entry.signature = sigLines.join('\n');
            if (output.format === 'full') {
                let jsdoc = '';
                let j = sym.line - 2;
                if (j >= 0 && fileLines[j]?.trimStart().startsWith('*/')) {
                    const docLines = [];
                    while (j >= 0 && !(fileLines[j] ?? '').trimStart().startsWith('/**')) {
                        docLines.unshift(fileLines[j]);
                        j--;
                    }
                    if (j >= 0)
                        docLines.unshift(fileLines[j]);
                    jsdoc = docLines.join('\n');
                }
                if (jsdoc)
                    entry.jsdoc = jsdoc;
                let container = '';
                for (let k = sym.line - 2; k >= Math.max(0, sym.line - 50); k--) {
                    const cl = fileLines[k]?.trimStart() ?? '';
                    if (/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/.test(cl)) {
                        const m = cl.match(/class\s+(\w+)/);
                        if (m) {
                            container = m[1];
                            break;
                        }
                    }
                    if (/^(?:export\s+)?(?:namespace|module)\s+(\w+)/.test(cl)) {
                        const m = cl.match(/(?:namespace|module)\s+(\w+)/);
                        if (m) {
                            container = m[1];
                            break;
                        }
                    }
                }
                if (container)
                    entry.container = container;
            }
            enriched.push(entry);
        }
        const groupBy = query.group_by ?? 'none';
        const grouped = groupByKey(enriched, groupBy);
        if (grouped) {
            return withFindWarnings({ symbols: grouped, count: symbols.length, source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
        }
        return withFindWarnings({ symbols: enriched, count: symbols.length, source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
    }
    const groupBy = query.group_by ?? 'none';
    const grouped = groupByKey(symbols, groupBy);
    if (grouped) {
        return withFindWarnings({ symbols: grouped, count: symbols.length, source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
    }
    return withFindWarnings({ symbols, count: symbols.length, source: symbolSource(treeSitterFiles, regexFallbackFiles) }, diagnostics.warnings);
}
function symbolSource(treeSitterFiles, regexFallbackFiles) {
    if (treeSitterFiles > 0 && regexFallbackFiles > 0)
        return 'mixed';
    if (treeSitterFiles > 0)
        return 'tree_sitter';
    return 'regex_fallback';
}
