import { parentPort } from 'node:worker_threads';
import Parser from 'tree-sitter';
import JavaScript from 'tree-sitter-javascript';
import TypeScript from 'tree-sitter-typescript';
import Python from 'tree-sitter-python';
import Java from 'tree-sitter-java';
import C from 'tree-sitter-c';
import CPP from 'tree-sitter-cpp';
// Explicit subpath import — see parser-loader.ts for rationale (#1013).
import CSharp from 'tree-sitter-c-sharp/bindings/node/index.js';
import Go from 'tree-sitter-go';
import Rust from 'tree-sitter-rust';
import PHP from 'tree-sitter-php';
import Ruby from 'tree-sitter-ruby';
import Julia from 'tree-sitter-julia';
import { createRequire } from 'node:module';
import { SupportedLanguages } from '../../../_shared/index.js';
import { getProvider } from '../languages/index.js';
import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from '../constants.js';
import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
// tree-sitter-swift is an optionalDependency — may not be installed
const _require = createRequire(import.meta.url);
let Swift = null;
try {
    Swift = _require('tree-sitter-swift');
}
catch { }
// tree-sitter-dart is an optionalDependency — may not be installed
let Dart = null;
try {
    Dart = _require('tree-sitter-dart');
}
catch { }
// tree-sitter-kotlin is an optionalDependency — may not be installed
let Kotlin = null;
try {
    Kotlin = _require('tree-sitter-kotlin');
}
catch { }
import { getLanguageFromFilename } from '../../../_shared/index.js';
import { FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, findObjectLiteralBindingInfo, getLabelFromCaptures, genericFuncName, inferFunctionLabel, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
import { extractCallArgTypes } from '../utils/call-analysis.js';
import { buildTypeEnv } from '../type-env.js';
import { detectFrameworkFromAST } from '../framework-detection.js';
import { generateId } from '../../../lib/utils.js';
import { preprocessImportPath } from '../import-processor.js';
import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
import { extractTemplateArguments, templateArgumentsIdTag } from '../utils/template-arguments.js';
import { extractParsedFile } from '../scope-extractor-bridge.js';
import { extractLaravelRoutes } from '../route-extractors/laravel.js';
import { logger } from '../../logger.js';
// ============================================================================
// Worker-local parser + language map
// ============================================================================
const parser = new Parser();
const languageMap = {
    [SupportedLanguages.JavaScript]: JavaScript,
    [SupportedLanguages.TypeScript]: TypeScript.typescript,
    [`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
    [SupportedLanguages.Python]: Python,
    [SupportedLanguages.Java]: Java,
    [SupportedLanguages.C]: C,
    [SupportedLanguages.CPlusPlus]: CPP,
    [SupportedLanguages.CSharp]: CSharp,
    [SupportedLanguages.Go]: Go,
    [SupportedLanguages.Rust]: Rust,
    ...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
    [SupportedLanguages.PHP]: PHP.php_only,
    [SupportedLanguages.Ruby]: Ruby,
    [SupportedLanguages.Julia]: Julia,
    [SupportedLanguages.Vue]: TypeScript.typescript,
    ...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
    ...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
};
/**
 * Check if a language grammar is available in this worker.
 * Duplicated from parser-loader.ts because workers can't import from the main thread.
 * Extra filePath parameter needed to distinguish .tsx from .ts (different grammars
 * under the same SupportedLanguages.TypeScript key).
 */
const isLanguageAvailable = (language, filePath) => {
    const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
        ? `${language}:tsx`
        : language;
    return key in languageMap && languageMap[key] != null;
};
const setLanguage = (language, filePath) => {
    const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
        ? `${language}:tsx`
        : language;
    const lang = languageMap[key];
    if (!lang)
        throw new Error(`Unsupported language: ${language}`);
    parser.setLanguage(lang);
};
// ============================================================================
// Per-file O(1) memoization — avoids repeated parent-chain walks per symbol.
// Three bare Maps cleared at file boundaries. Map.get() returns undefined for
// missing keys, so `cached !== undefined` distinguishes "not computed" from
// a stored null (enclosing class/function not found = top-level).
// ============================================================================
const classIdCache = new Map();
const functionIdCache = new Map();
const exportCache = new Map();
const clearCaches = () => {
    classIdCache.clear();
    functionIdCache.clear();
    exportCache.clear();
    fieldInfoCache.clear();
    methodInfoCache.clear();
};
// ============================================================================
// FieldExtractor cache — extract field metadata once per class, reuse for each property.
// Keyed by class node startIndex (unique per AST node within a file).
// ============================================================================
const fieldInfoCache = new Map();
/**
 * Walk up from a definition node to find the nearest enclosing class/struct/interface
 * AST node. Returns the SyntaxNode itself (not an ID) for passing to FieldExtractor.
 */
function findEnclosingClassNode(node) {
    let current = node.parent;
    while (current) {
        if (CLASS_CONTAINER_TYPES.has(current.type)) {
            // Return singleton_class directly so the method extractor sees it as
            // the owner node and correctly marks methods as static. Name resolution
            // for qualified names is handled separately by findEnclosingClassInfo.
            return current;
        }
        current = current.parent;
    }
    return null;
}
/**
 * For C++ out-of-class method definitions (e.g. `void Foo::bar() {}`), extract the
 * class name from the qualified_identifier scope and find the class declaration in the
 * file's AST. Returns the class SyntaxNode or null if not found.
 *
 * Handles pointer/reference return types where function_declarator is nested inside
 * pointer_declarator or reference_declarator.
 */
function findClassNodeByQualifiedName(node) {
    const declarator = node.childForFieldName('declarator');
    if (!declarator)
        return null;
    // Find the function_declarator, recursively unwrapping pointer_declarator /
    // reference_declarator chains (e.g. int** Foo::bar() has
    // pointer_declarator → pointer_declarator → function_declarator).
    let funcDecl = null;
    if (declarator.type === 'function_declarator') {
        funcDecl = declarator;
    }
    else {
        let current = declarator;
        while (current && !funcDecl) {
            for (let i = 0; i < current.namedChildCount; i++) {
                const child = current.namedChild(i);
                if (child?.type === 'function_declarator') {
                    funcDecl = child;
                    break;
                }
            }
            if (!funcDecl) {
                const next = current.namedChildren.find((c) => c.type === 'pointer_declarator' || c.type === 'reference_declarator');
                current = next ?? null;
            }
        }
    }
    if (!funcDecl)
        return null;
    // Check if the inner declarator is a qualified_identifier (Foo::bar)
    const innerDecl = funcDecl.childForFieldName('declarator');
    if (!innerDecl || innerDecl.type !== 'qualified_identifier')
        return null;
    const scope = innerDecl.childForFieldName('scope');
    if (!scope)
        return null;
    const className = scope.text;
    // Search the file for a matching class/struct specifier, including inside
    // namespace_definition blocks (the majority of production C++ uses namespaces).
    const root = node.tree.rootNode;
    const classTypes = new Set(['class_specifier', 'struct_specifier']);
    const searchIn = (parent) => {
        for (let i = 0; i < parent.namedChildCount; i++) {
            const child = parent.namedChild(i);
            if (!child)
                continue;
            if (classTypes.has(child.type)) {
                const nameNode = child.childForFieldName('name');
                if (nameNode?.text === className)
                    return child;
            }
            // Recurse into namespace blocks
            if (child.type === 'namespace_definition') {
                const found = searchIn(child);
                if (found)
                    return found;
            }
        }
        return null;
    };
    return searchIn(root);
}
/**
 * Minimal no-op SymbolTable stub for FieldExtractorContext in the worker.
 * Field extraction only uses symbolTable.lookupExactAll for optional type
 * resolution — returning [] causes the extractor to use the raw type
 * string, which is fine for us. Every other method is a no-op so the
 * stub remains safe if a future FieldExtractor consults it through the
 * full {@link SymbolTableReader} surface.
 */
const NOOP_SYMBOL_TABLE = {
    lookupExact: () => undefined,
    lookupExactFull: () => undefined,
    lookupExactAll: () => [],
    lookupCallableByName: () => [],
    getFiles: () => [][Symbol.iterator](),
    getStats: () => ({ fileCount: 0 }),
};
/**
 * Get (or extract and cache) field info for a class node.
 * Returns a name→FieldInfo map, or undefined if the provider has no field extractor
 * or the class yielded no fields.
 */
function getFieldInfo(classNode, provider, context) {
    if (!provider.fieldExtractor)
        return undefined;
    const cacheKey = classNode.startIndex;
    let cached = fieldInfoCache.get(cacheKey);
    if (cached)
        return cached;
    const result = provider.fieldExtractor.extract(classNode, context);
    if (!result?.fields?.length)
        return undefined;
    cached = new Map();
    for (const field of result.fields) {
        cached.set(field.name, field);
    }
    fieldInfoCache.set(cacheKey, cached);
    return cached;
}
// ============================================================================
// MethodExtractor cache — extract method metadata once per class, reuse for each method.
// Keyed by class node startIndex (unique per AST node within a file).
// ============================================================================
const methodInfoCache = new Map();
/**
 * Get (or extract and cache) method info for a class node.
 * Returns a "name:line" → MethodInfo map, or undefined if the provider has no method extractor
 * or the class yielded no methods.
 * Keyed by name:line (not name alone) to support overloaded methods in Java/Kotlin.
 */
function getMethodInfo(classNode, provider, context) {
    if (!provider.methodExtractor)
        return undefined;
    const cacheKey = classNode.startIndex;
    let cached = methodInfoCache.get(cacheKey);
    if (cached)
        return cached;
    const result = provider.methodExtractor.extract(classNode, context);
    if (!result?.methods?.length)
        return undefined;
    cached = new Map();
    for (const method of result.methods) {
        cached.set(`${method.name}:${method.line}`, method);
    }
    methodInfoCache.set(cacheKey, cached);
    return cached;
}
// ============================================================================
// Enclosing function detection (for call extraction) — cached
// ============================================================================
/** Walk up AST to find enclosing function, return its generateId or null for top-level.
 *  Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
const findEnclosingFunctionId = (node, filePath, provider) => {
    const cached = functionIdCache.get(node);
    if (cached !== undefined)
        return cached;
    let current = node.parent;
    while (current) {
        if (FUNCTION_NODE_TYPES.has(current.type)) {
            const efnResult = provider.methodExtractor?.extractFunctionName?.(current);
            const funcName = efnResult?.funcName ?? genericFuncName(current);
            const label = efnResult?.label ?? inferFunctionLabel(current.type);
            if (funcName) {
                // Apply labelOverride so label matches definition phase (e.g., Kotlin Function→Method).
                // null means "skip as definition" — keep original label for scope identification.
                let finalLabel = label;
                if (provider.labelOverride) {
                    const override = provider.labelOverride(current, label);
                    if (override !== null)
                        finalLabel = override;
                }
                // Qualify with enclosing class to match definition-phase node IDs
                const classInfo = cachedFindEnclosingClassInfo(current, filePath, provider.resolveEnclosingOwner);
                const encLang = getLanguageFromFilename(filePath);
                const standaloneMethodInfo = (finalLabel === 'Method' || finalLabel === 'Constructor') &&
                    encLang === SupportedLanguages.Go &&
                    provider.methodExtractor?.extractFromNode
                    ? provider.methodExtractor.extractFromNode(current, {
                        filePath,
                        language: encLang,
                    })
                    : null;
                const ownerName = classInfo?.className ?? standaloneMethodInfo?.receiverType ?? undefined;
                const qualifiedName = ownerName ? `${ownerName}.${funcName}` : funcName;
                // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
                // Use the same MethodExtractor (getMethodInfo) as the definition phase.
                // When same-arity collisions exist, also append ~type1,type2.
                let arity;
                let encTypeTag = '';
                if (finalLabel === 'Method' || finalLabel === 'Constructor') {
                    if (standaloneMethodInfo) {
                        arity = standaloneMethodInfo.parameters.some((p) => p.isVariadic)
                            ? undefined
                            : standaloneMethodInfo.parameters.length;
                    }
                    else {
                        const classNode = findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current);
                        if (classNode && encLang) {
                            const methodMap = getMethodInfo(classNode, provider, {
                                filePath,
                                language: encLang,
                            });
                            const defLine = current.startPosition.row + 1;
                            const info = methodMap?.get(`${funcName}:${defLine}`);
                            if (info) {
                                arity = info.parameters.some((p) => p.isVariadic)
                                    ? undefined
                                    : info.parameters.length;
                                if (methodMap && arity !== undefined) {
                                    const g = buildCollisionGroups(methodMap);
                                    encTypeTag =
                                        typeTagForId(methodMap, funcName, arity, info, encLang, g) +
                                            constTagForId(methodMap, funcName, arity, info, g);
                                }
                            }
                        }
                    }
                }
                const arityTag = arity !== undefined ? `#${arity}${encTypeTag}` : '';
                const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag}`);
                functionIdCache.set(node, result);
                return result;
            }
        }
        // Language-specific enclosing function resolution (e.g., Dart where
        // function_body is a sibling of function_signature, not a child).
        if (provider.enclosingFunctionFinder) {
            const customResult = provider.enclosingFunctionFinder(current);
            if (customResult) {
                let finalLabel = customResult.label;
                if (provider.labelOverride) {
                    const override = provider.labelOverride(current.previousSibling, finalLabel);
                    if (override !== null)
                        finalLabel = override;
                }
                // Qualify custom result with enclosing class
                const classInfo = cachedFindEnclosingClassInfo(current.previousSibling ?? current, filePath, provider.resolveEnclosingOwner);
                const qualifiedName = classInfo
                    ? `${classInfo.className}.${customResult.funcName}`
                    : customResult.funcName;
                // Include #<arity> suffix to match definition-phase Method/Constructor IDs.
                // When same-arity collisions exist, also append ~type1,type2.
                const sigNode = current.previousSibling ?? current;
                let arity2;
                let encTypeTag2 = '';
                if (finalLabel === 'Method' || finalLabel === 'Constructor') {
                    const encLang2 = getLanguageFromFilename(filePath);
                    const classNode2 = findEnclosingClassNode(sigNode) ?? findClassNodeByQualifiedName(sigNode);
                    if (classNode2 && encLang2) {
                        const methodMap2 = getMethodInfo(classNode2, provider, {
                            filePath,
                            language: encLang2,
                        });
                        const defLine2 = sigNode.startPosition.row + 1;
                        const info2 = methodMap2?.get(`${customResult.funcName}:${defLine2}`);
                        if (info2) {
                            arity2 = info2.parameters.some((p) => p.isVariadic)
                                ? undefined
                                : info2.parameters.length;
                            if (methodMap2 && arity2 !== undefined) {
                                const g2 = buildCollisionGroups(methodMap2);
                                encTypeTag2 =
                                    typeTagForId(methodMap2, customResult.funcName, arity2, info2, encLang2, g2) +
                                        constTagForId(methodMap2, customResult.funcName, arity2, info2, g2);
                            }
                        }
                    }
                }
                const arityTag2 = arity2 !== undefined ? `#${arity2}${encTypeTag2}` : '';
                const result = generateId(finalLabel, `${filePath}:${qualifiedName}${arityTag2}`);
                functionIdCache.set(node, result);
                return result;
            }
        }
        current = current.parent;
    }
    functionIdCache.set(node, null);
    return null;
};
/** Cached wrapper for findEnclosingClassInfo — avoids repeated parent walks. */
const cachedFindEnclosingClassInfo = (node, filePath, resolveEnclosingOwner) => {
    const cached = classIdCache.get(node);
    if (cached !== undefined)
        return cached;
    const result = findEnclosingClassInfo(node, filePath, resolveEnclosingOwner);
    classIdCache.set(node, result);
    return result;
};
/** Cached wrapper for export checking — avoids repeated parent walks per symbol. */
const cachedExportCheck = (checker, node, name) => {
    const cached = exportCache.get(node);
    if (cached !== undefined)
        return cached;
    const result = checker(node, name);
    exportCache.set(node, result);
    return result;
};
// Label detection moved to shared getLabelFromCaptures in utils.ts
// DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
// ============================================================================
// Process a batch of files
// ============================================================================
const processBatch = (files, onProgress) => {
    const result = {
        nodes: [],
        relationships: [],
        symbols: [],
        imports: [],
        calls: [],
        assignments: [],
        heritage: [],
        routes: [],
        fetchCalls: [],
        decoratorRoutes: [],
        toolDefs: [],
        ormQueries: [],
        constructorBindings: [],
        fileScopeBindings: [],
        parsedFiles: [],
        skippedLanguages: {},
        fileCount: 0,
    };
    // Group by language to minimize setLanguage calls
    const byLanguage = new Map();
    for (const file of files) {
        const lang = getLanguageFromFilename(file.path);
        if (!lang)
            continue;
        let list = byLanguage.get(lang);
        if (!list) {
            list = [];
            byLanguage.set(lang, list);
        }
        list.push(file);
    }
    let totalProcessed = 0;
    let lastReported = 0;
    const PROGRESS_INTERVAL = Math.max(1, Math.min(100, Math.ceil(files.length / 10)));
    const onFileProcessed = onProgress
        ? () => {
            totalProcessed++;
            if (totalProcessed - lastReported >= PROGRESS_INTERVAL) {
                lastReported = totalProcessed;
                onProgress(totalProcessed);
            }
        }
        : undefined;
    for (const [language, langFiles] of byLanguage) {
        const provider = getProvider(language);
        const queryString = provider.treeSitterQueries;
        if (!queryString)
            continue;
        // Track if we need to handle tsx separately
        const tsxFiles = [];
        const regularFiles = [];
        if (language === SupportedLanguages.TypeScript) {
            for (const f of langFiles) {
                if (f.path.endsWith('.tsx')) {
                    tsxFiles.push(f);
                }
                else {
                    regularFiles.push(f);
                }
            }
        }
        else {
            // Manual loop (not spread) — `push(...arr)` blows the stack on very
            // large arrays when langFiles has tens of thousands of entries.
            for (const f of langFiles)
                regularFiles.push(f);
        }
        // Process regular files for this language
        if (regularFiles.length > 0) {
            if (isLanguageAvailable(language, regularFiles[0].path)) {
                try {
                    setLanguage(language, regularFiles[0].path);
                    processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
                }
                catch {
                    // parser unavailable — skip this language group
                }
            }
            else {
                result.skippedLanguages[language] =
                    (result.skippedLanguages[language] || 0) + regularFiles.length;
            }
        }
        // Process tsx files separately (different grammar)
        if (tsxFiles.length > 0) {
            if (isLanguageAvailable(language, tsxFiles[0].path)) {
                try {
                    setLanguage(language, tsxFiles[0].path);
                    processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
                }
                catch {
                    // parser unavailable — skip this language group
                }
            }
            else {
                result.skippedLanguages[language] =
                    (result.skippedLanguages[language] || 0) + tsxFiles.length;
            }
        }
    }
    if (onProgress && totalProcessed !== lastReported) {
        onProgress(totalProcessed);
    }
    return result;
};
// Express/Hono method names that register routes
const EXPRESS_ROUTE_METHODS = new Set([
    'get',
    'post',
    'put',
    'delete',
    'patch',
    'all',
    'use',
    'route',
]);
// HTTP client methods that are ONLY used by clients, not Express route registration.
// Methods like get/post/put/delete/patch overlap with Express — those are captured by
// the express_route handler as route definitions, not consumers. The fetch() global
// function is captured separately by the route.fetch query.
const HTTP_CLIENT_ONLY_METHODS = new Set(['head', 'options', 'request', 'ajax']);
// Known HTTP client receivers u2014 skip these, they're API consumers not routes
const HTTP_CLIENT_RECEIVERS = new Set([
    'axios',
    'request',
    'fetch',
    'http',
    'https',
    'got',
    'ky',
    'superagent',
    'needle',
    'undici',
    'apiclient',
    'client',
    'httpclient',
    'api',
    '$http',
    'session',
    'httpservice',
    'conn',
]);
// Decorator names that indicate HTTP route handlers (NestJS, Flask, FastAPI, Spring)
const ROUTE_DECORATOR_NAMES = new Set([
    'Get',
    'Post',
    'Put',
    'Delete',
    'Patch',
    'Route',
    'get',
    'post',
    'put',
    'delete',
    'patch',
    'route',
    'RequestMapping',
    'GetMapping',
    'PostMapping',
    'PutMapping',
    'DeleteMapping',
]);
// ============================================================================
// ORM Query Detection (Prisma + Supabase)
// ============================================================================
const PRISMA_QUERY_RE = /\bprisma\.(\w+)\.(findMany|findFirst|findUnique|findUniqueOrThrow|findFirstOrThrow|create|createMany|update|updateMany|delete|deleteMany|upsert|count|aggregate|groupBy)\s*\(/g;
const SUPABASE_QUERY_RE = /\bsupabase\.from\s*\(\s*['"](\w+)['"]\s*\)\s*\.(select|insert|update|delete|upsert)\s*\(/g;
/**
 * Extract ORM query calls from file content via regex.
 * Appends results to the provided array (avoids allocation when no matches).
 */
export function extractORMQueries(filePath, content, out) {
    const hasPrisma = content.includes('prisma.');
    const hasSupabase = content.includes('supabase.from');
    if (!hasPrisma && !hasSupabase)
        return;
    if (hasPrisma) {
        PRISMA_QUERY_RE.lastIndex = 0;
        let m;
        while ((m = PRISMA_QUERY_RE.exec(content)) !== null) {
            const model = m[1];
            if (model.startsWith('$'))
                continue;
            out.push({
                filePath,
                orm: 'prisma',
                model,
                method: m[2],
                lineNumber: content.substring(0, m.index).split('\n').length - 1,
            });
        }
    }
    if (hasSupabase) {
        SUPABASE_QUERY_RE.lastIndex = 0;
        let m;
        while ((m = SUPABASE_QUERY_RE.exec(content)) !== null) {
            out.push({
                filePath,
                orm: 'supabase',
                model: m[1],
                method: m[2],
                lineNumber: content.substring(0, m.index).split('\n').length - 1,
            });
        }
    }
}
const processFileGroup = (files, language, queryString, result, onFileProcessed) => {
    let query;
    try {
        const lang = parser.getLanguage();
        query = new Parser.Query(lang, queryString);
    }
    catch (err) {
        const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
        if (parentPort) {
            parentPort.postMessage({ type: 'warning', message });
        }
        else {
            logger.warn(message);
        }
        return;
    }
    for (const file of files) {
        // Skip files larger than the max tree-sitter buffer (32 MB)
        if (getTreeSitterContentByteLength(file.content) > TREE_SITTER_MAX_BUFFER)
            continue;
        // Authoritative in-flight signal for the pool: lets `WorkerPool` exclude
        // exactly this file if the worker dies during parse/extract, instead of
        // guessing from `items[lastProgress]` (which the language-grouped order
        // here would defeat). The pool gracefully ignores this when running an
        // older worker build that doesn't emit it.
        if (parentPort) {
            parentPort.postMessage({ type: 'starting-file', path: file.path });
        }
        // Vue SFC preprocessing: extract <script> block content
        let parseContent = file.content;
        let lineOffset = 0;
        let isVueSetup = false;
        if (language === SupportedLanguages.Vue) {
            const extracted = extractVueScript(file.content);
            if (!extracted)
                continue; // skip .vue files with no script block
            parseContent = extracted.scriptContent;
            lineOffset = extracted.lineOffset;
            isVueSetup = extracted.isSetup;
        }
        // Per-language source-text transform (e.g., UE macro stripping for C++).
        // Length-preserving — see LanguageProvider.preprocessSource contract.
        parseContent =
            getProvider(language).preprocessSource?.(parseContent, file.path) ?? parseContent;
        clearCaches(); // Reset memoization before each new file
        let tree;
        try {
            tree = parseSourceSafe(parser, parseContent, undefined, {
                bufferSize: getTreeSitterBufferSize(parseContent),
            });
        }
        catch (err) {
            logger.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
            continue;
        }
        result.fileCount++;
        onFileProcessed?.();
        let matches;
        try {
            matches = query.matches(tree.rootNode);
        }
        catch (err) {
            logger.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
            continue;
        }
        const provider = getProvider(language);
        // RFC #909 Ring 2: produce a `ParsedFile` for the new scope-based
        // resolution pipeline. No-op (returns undefined) for every language
        // today — only fires once a provider implements `emitScopeCaptures`.
        // Runs BEFORE legacy extraction and its result is independent: a
        // failure here is caught inside `extractParsedFile` and does NOT
        // affect the legacy DAG path that follows.
        const parsedFile = extractParsedFile(provider, parseContent, file.path, (message) => {
            if (parentPort) {
                parentPort.postMessage({ type: 'warning', message });
            }
            else {
                logger.warn(message);
            }
        }, tree);
        if (parsedFile !== undefined)
            result.parsedFiles.push(parsedFile);
        // Pre-pass: extract heritage from query matches to build parentMap for buildTypeEnv.
        // Heritage edges (EXTENDS/IMPLEMENTS) are created by heritage-processor which runs
        // in PARALLEL with call-processor, so the graph edges don't exist when buildTypeEnv
        // runs. This pre-pass makes parent class information available for type resolution.
        const fileParentMap = new Map();
        if (provider.heritageExtractor) {
            for (const match of matches) {
                const captureMap = {};
                for (const c of match.captures) {
                    captureMap[c.name] = c.node;
                }
                if (captureMap['heritage.class']) {
                    const heritageItems = provider.heritageExtractor.extract(captureMap, {
                        filePath: file.path,
                        language,
                    });
                    for (const item of heritageItems) {
                        if (item.kind === 'extends') {
                            let parents = fileParentMap.get(item.className);
                            if (!parents) {
                                parents = [];
                                fileParentMap.set(item.className, parents);
                            }
                            if (!parents.includes(item.parentName))
                                parents.push(item.parentName);
                        }
                    }
                }
            }
        }
        // Build per-file type environment + constructor bindings in a single AST walk.
        // Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
        const parentMap = fileParentMap;
        const typeEnv = buildTypeEnv(tree, language, {
            parentMap,
            enclosingFunctionFinder: provider?.enclosingFunctionFinder,
            extractFunctionName: provider?.methodExtractor?.extractFunctionName,
        });
        const callRouter = provider.callRouter;
        if (typeEnv.constructorBindings.length > 0) {
            result.constructorBindings.push({
                filePath: file.path,
                bindings: [...typeEnv.constructorBindings],
            });
        }
        // Serialize file-scope bindings for BindingAccumulator. These feed the
        // ExportedTypeMap enrichment loop in pipeline.ts — the only current
        // consumer of worker-path binding data.
        //
        // Historical note: we previously serialized all scopes
        // (`typeEnv.allScopes()`), which pushed ~4.9 MB of function-scope
        // bindings across the IPC boundary on every worker batch with zero
        // downstream readers. Narrowing to `fileScope()` recovers that cost.
        // See the `FileScopeBindings` JSDoc above for the Phase 9 reversion
        // path when a function-scope consumer lands.
        const fileScope = typeEnv.fileScope();
        if (fileScope.size > 0) {
            const scopeBindings = [];
            for (const [varName, typeName] of fileScope) {
                scopeBindings.push([varName, typeName]);
            }
            result.fileScopeBindings.push({ filePath: file.path, bindings: scopeBindings });
        }
        // Per-file map: decorator end-line → decorator info, for associating with definitions
        const fileDecorators = new Map();
        // Track start indices of definition nodes already processed by higher-priority captures
        // (e.g. @definition.function) to avoid duplicate nodes when @definition.const/@definition.variable
        // patterns overlap with the same source range.
        const processedDefinitionNodes = new Set();
        for (const match of matches) {
            const captureMap = {};
            for (const c of match.captures) {
                captureMap[c.name] = c.node;
            }
            // Extract import paths before skipping
            if (captureMap['import'] && captureMap['import.source']) {
                const rawImportPath = preprocessImportPath(captureMap['import.source'].text, captureMap['import'], provider);
                if (!rawImportPath)
                    continue;
                const extractor = provider.namedBindingExtractor;
                const namedBindings = extractor ? extractor(captureMap['import']) : undefined;
                result.imports.push({
                    filePath: file.path,
                    rawImportPath,
                    language: language,
                    ...(namedBindings ? { namedBindings } : {}),
                });
                continue;
            }
            // Extract assignment sites (field write access)
            if (captureMap['assignment'] &&
                captureMap['assignment.receiver'] &&
                captureMap['assignment.property']) {
                const receiverText = captureMap['assignment.receiver'].text;
                const propertyName = captureMap['assignment.property'].text;
                if (receiverText && propertyName) {
                    const srcId = findEnclosingFunctionId(captureMap['assignment'], file.path, provider) ||
                        generateId('File', file.path);
                    let receiverTypeName;
                    if (typeEnv) {
                        receiverTypeName = typeEnv.lookup(receiverText, captureMap['assignment']) ?? undefined;
                    }
                    result.assignments.push({
                        filePath: file.path,
                        sourceId: srcId,
                        receiverText,
                        propertyName,
                        line: captureMap['assignment'].startPosition.row + 1,
                        ...(receiverTypeName ? { receiverTypeName } : {}),
                    });
                }
                if (!captureMap['call'])
                    continue;
            }
            // Store decorator metadata for later association with definitions
            if (captureMap['decorator'] && captureMap['decorator.name']) {
                const decoratorName = captureMap['decorator.name'].text;
                const decoratorArg = captureMap['decorator.arg']?.text;
                const decoratorNode = captureMap['decorator'];
                // Store by the decorator's end line — the definition follows immediately after
                fileDecorators.set(decoratorNode.endPosition.row, {
                    name: decoratorName,
                    arg: decoratorArg,
                });
                if (ROUTE_DECORATOR_NAMES.has(decoratorName)) {
                    const routePath = decoratorArg || '';
                    const method = decoratorName.replace('Mapping', '').toUpperCase();
                    const httpMethod = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)
                        ? method
                        : 'GET';
                    result.decoratorRoutes.push({
                        filePath: file.path,
                        routePath,
                        httpMethod,
                        decoratorName,
                        lineNumber: decoratorNode.startPosition.row + lineOffset,
                    });
                }
                // MCP/RPC tool detection: @mcp.tool(), @app.tool(), @server.tool()
                if (decoratorName === 'tool') {
                    // Re-store with isTool flag for the definition handler
                    fileDecorators.set(decoratorNode.endPosition.row, {
                        name: decoratorName,
                        arg: decoratorArg,
                        isTool: true,
                    });
                }
                continue;
            }
            // Extract HTTP consumer URLs: fetch(), axios.get(), $.get(), requests.get(), etc.
            if (captureMap['route.fetch']) {
                const urlNode = captureMap['route.url'] ?? captureMap['route.template_url'];
                if (urlNode) {
                    result.fetchCalls.push({
                        filePath: file.path,
                        fetchURL: urlNode.text,
                        lineNumber: captureMap['route.fetch'].startPosition.row + lineOffset,
                    });
                }
                continue;
            }
            // HTTP client calls: axios.get('/path'), $.post('/path'), requests.get('/path')
            // Skip methods also in EXPRESS_ROUTE_METHODS to avoid double-registering Express
            // routes as both route definitions AND consumers (both queries match same AST node)
            if (captureMap['http_client'] && captureMap['http_client.url']) {
                const method = captureMap['http_client.method']?.text;
                const url = captureMap['http_client.url'].text;
                if (method && HTTP_CLIENT_ONLY_METHODS.has(method) && url.startsWith('/')) {
                    result.fetchCalls.push({
                        filePath: file.path,
                        fetchURL: url,
                        lineNumber: captureMap['http_client'].startPosition.row + lineOffset,
                    });
                }
                continue;
            }
            // Express/Hono route registration: app.get('/path', handler)
            if (captureMap['express_route'] &&
                captureMap['express_route.method'] &&
                captureMap['express_route.path']) {
                const method = captureMap['express_route.method'].text;
                const routePath = captureMap['express_route.path'].text;
                if (EXPRESS_ROUTE_METHODS.has(method) && routePath.startsWith('/')) {
                    // Extract the receiver (the object the method is called on) to filter out
                    // HTTP client calls like axios.get('/api/users') that match the same pattern
                    // as Express route registrations.
                    const callNode = captureMap['express_route'];
                    const funcNode = callNode.childForFieldName?.('function') ?? callNode.children?.[0];
                    // Walk through nested member_expressions and call_expressions to
                    // reach the innermost receiver identifier.  Handles chains like:
                    //   this.httpService.get('/path')   -> member chain    -> 'httpservice'
                    //   getClient().get('/path')         -> call_expression -> 'getclient'
                    //   axios.get('/path')               -> bare identifier -> 'axios'
                    let receiverNode = funcNode?.childForFieldName?.('object') ?? funcNode?.children?.[0];
                    while (receiverNode?.type === 'member_expression' ||
                        receiverNode?.type === 'call_expression') {
                        if (receiverNode.type === 'member_expression') {
                            // Drill into the property (rightmost part) of the member expression
                            const propNode = receiverNode.childForFieldName?.('property');
                            if (propNode) {
                                receiverNode = propNode;
                            }
                            else {
                                break;
                            }
                        }
                        else {
                            // call_expression: unwrap to the function being called
                            const innerFunc = receiverNode.childForFieldName?.('function') ?? receiverNode.children?.[0];
                            if (innerFunc && innerFunc !== receiverNode) {
                                receiverNode = innerFunc;
                            }
                            else {
                                break;
                            }
                        }
                    }
                    const receiverText = receiverNode?.text?.toLowerCase() ?? '';
                    if (HTTP_CLIENT_RECEIVERS.has(receiverText)) {
                        // This is an HTTP client call, not a route definition u2014 skip it
                        continue;
                    }
                    const httpMethod = method === 'all' || method === 'use' || method === 'route'
                        ? 'GET'
                        : method.toUpperCase();
                    result.decoratorRoutes.push({
                        filePath: file.path,
                        routePath,
                        httpMethod,
                        decoratorName: `express.${method}`,
                        lineNumber: captureMap['express_route'].startPosition.row + lineOffset,
                    });
                }
                continue;
            }
            // Extract call sites
            if (captureMap['call']) {
                const callNode = captureMap['call'];
                const callNameNode = captureMap['call.name'];
                const callExtractor = provider.callExtractor;
                if (callExtractor) {
                    // ── Path 1: Language-specific call site (bypasses routing) ────
                    // Try language-specific extraction (e.g. Java `::` method references)
                    // without callNameNode.  If successful, skip routing and the generic
                    // path entirely.
                    const langCallSite = callExtractor.extract(callNode, undefined);
                    if (langCallSite) {
                        if (!provider.isBuiltInName(langCallSite.calledName)) {
                            const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
                                generateId('File', file.path);
                            const receiverName = langCallSite.callForm === 'member' ? langCallSite.receiverName : undefined;
                            let receiverTypeName = receiverName
                                ? typeEnv.lookup(receiverName, callNode)
                                : undefined;
                            // Type-as-receiver heuristic (e.g. Java `User::getName`)
                            if (langCallSite.typeAsReceiverHeuristic &&
                                receiverName !== undefined &&
                                receiverTypeName === undefined &&
                                langCallSite.callForm === 'member') {
                                const c0 = receiverName.charCodeAt(0);
                                if (c0 >= 65 && c0 <= 90)
                                    receiverTypeName = receiverName;
                            }
                            result.calls.push({
                                filePath: file.path,
                                calledName: langCallSite.calledName,
                                sourceId,
                                callForm: langCallSite.callForm,
                                ...(receiverName !== undefined ? { receiverName } : {}),
                                ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
                            });
                        }
                        continue;
                    }
                    // ── Path 2: Generic extraction via @call.name ────────────────
                    if (callNameNode) {
                        const calledName = callNameNode.text;
                        // Check heritage extractor for call-based heritage (e.g., Ruby include/extend/prepend)
                        if (provider.heritageExtractor?.extractFromCall) {
                            const heritageItems = provider.heritageExtractor.extractFromCall(calledName, callNode, { filePath: file.path, language });
                            if (heritageItems !== null) {
                                for (const item of heritageItems) {
                                    result.heritage.push({
                                        filePath: file.path,
                                        className: item.className,
                                        parentName: item.parentName,
                                        kind: item.kind,
                                    });
                                }
                                continue;
                            }
                        }
                        // Dispatch: route language-specific calls (properties, imports)
                        // Heritage routing is handled by heritageExtractor.extractFromCall above.
                        const routed = callRouter?.(calledName, captureMap['call']);
                        if (routed) {
                            if (routed.kind === 'skip')
                                continue;
                            if (routed.kind === 'import') {
                                result.imports.push({
                                    filePath: file.path,
                                    rawImportPath: routed.importPath,
                                    language,
                                });
                                continue;
                            }
                            if (routed.kind === 'properties') {
                                const propEnclosingInfo = cachedFindEnclosingClassInfo(captureMap['call'], file.path, provider.resolveEnclosingOwner);
                                const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
                                // Enrich routed properties with FieldExtractor metadata
                                let routedFieldMap;
                                if (provider.fieldExtractor && typeEnv) {
                                    const classNode = findEnclosingClassNode(captureMap['call']);
                                    if (classNode) {
                                        routedFieldMap = getFieldInfo(classNode, provider, {
                                            typeEnv,
                                            symbolTable: NOOP_SYMBOL_TABLE,
                                            filePath: file.path,
                                            language,
                                        });
                                    }
                                }
                                for (const item of routed.items) {
                                    const routedFieldInfo = routedFieldMap?.get(item.propName);
                                    const propQualifiedName = propEnclosingInfo
                                        ? `${propEnclosingInfo.className}.${item.propName}`
                                        : item.propName;
                                    const nodeId = generateId('Property', `${file.path}:${propQualifiedName}`);
                                    result.nodes.push({
                                        id: nodeId,
                                        label: 'Property',
                                        properties: {
                                            name: item.propName,
                                            filePath: file.path,
                                            startLine: item.startLine,
                                            endLine: item.endLine,
                                            language,
                                            isExported: true,
                                            description: item.accessorType,
                                            ...(item.declaredType
                                                ? { declaredType: item.declaredType }
                                                : routedFieldInfo?.type
                                                    ? { declaredType: routedFieldInfo.type }
                                                    : {}),
                                            ...(routedFieldInfo?.visibility !== undefined
                                                ? { visibility: routedFieldInfo.visibility }
                                                : {}),
                                            ...(routedFieldInfo?.isStatic !== undefined
                                                ? { isStatic: routedFieldInfo.isStatic }
                                                : {}),
                                            ...(routedFieldInfo?.isReadonly !== undefined
                                                ? { isReadonly: routedFieldInfo.isReadonly }
                                                : {}),
                                        },
                                    });
                                    result.symbols.push({
                                        filePath: file.path,
                                        name: item.propName,
                                        nodeId,
                                        type: 'Property',
                                        ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
                                        ...(item.declaredType
                                            ? { declaredType: item.declaredType }
                                            : routedFieldInfo?.type
                                                ? { declaredType: routedFieldInfo.type }
                                                : {}),
                                        ...(routedFieldInfo?.visibility !== undefined
                                            ? { visibility: routedFieldInfo.visibility }
                                            : {}),
                                        ...(routedFieldInfo?.isStatic !== undefined
                                            ? { isStatic: routedFieldInfo.isStatic }
                                            : {}),
                                        ...(routedFieldInfo?.isReadonly !== undefined
                                            ? { isReadonly: routedFieldInfo.isReadonly }
                                            : {}),
                                    });
                                    const fileId = generateId('File', file.path);
                                    const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
                                    result.relationships.push({
                                        id: relId,
                                        sourceId: fileId,
                                        targetId: nodeId,
                                        type: 'DEFINES',
                                        confidence: 1.0,
                                        reason: '',
                                    });
                                    if (propEnclosingClassId) {
                                        result.relationships.push({
                                            id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
                                            sourceId: propEnclosingClassId,
                                            targetId: nodeId,
                                            type: 'HAS_PROPERTY',
                                            confidence: 1.0,
                                            reason: '',
                                        });
                                    }
                                }
                                continue;
                            }
                            // kind === 'call' — fall through to normal call processing below
                        }
                        if (!provider.isBuiltInName(calledName)) {
                            const callSite = callExtractor.extract(callNode, callNameNode);
                            if (callSite) {
                                const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
                                    generateId('File', file.path);
                                let receiverTypeName = callSite.receiverName
                                    ? typeEnv.lookup(callSite.receiverName, callNode)
                                    : undefined;
                                // Type-as-receiver heuristic
                                if (callSite.typeAsReceiverHeuristic &&
                                    callSite.receiverName !== undefined &&
                                    receiverTypeName === undefined &&
                                    callSite.callForm === 'member') {
                                    const c0 = callSite.receiverName.charCodeAt(0);
                                    if (c0 >= 65 && c0 <= 90)
                                        receiverTypeName = callSite.receiverName;
                                }
                                const inferLiteralType = provider.typeConfig?.inferLiteralType;
                                // Skip when no arg list / zero args: nothing to infer for overload typing
                                const argTypes = inferLiteralType && callSite.argCount !== undefined && callSite.argCount > 0
                                    ? extractCallArgTypes(callNode, inferLiteralType, (varName, cn) => typeEnv.lookup(varName, cn))
                                    : undefined;
                                result.calls.push({
                                    filePath: file.path,
                                    calledName: callSite.calledName,
                                    sourceId,
                                    ...(callSite.argCount !== undefined ? { argCount: callSite.argCount } : {}),
                                    ...(callSite.callForm !== undefined ? { callForm: callSite.callForm } : {}),
                                    ...(callSite.receiverName !== undefined
                                        ? { receiverName: callSite.receiverName }
                                        : {}),
                                    ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
                                    ...(callSite.receiverMixedChain !== undefined
                                        ? { receiverMixedChain: callSite.receiverMixedChain }
                                        : {}),
                                    ...(argTypes !== undefined ? { argTypes } : {}),
                                });
                            }
                        }
                    }
                }
                continue;
            }
            // Extract heritage (extends/implements) via provider heritage extractor
            if (captureMap['heritage.class']) {
                if (provider.heritageExtractor) {
                    const heritageItems = provider.heritageExtractor.extract(captureMap, {
                        filePath: file.path,
                        language,
                    });
                    for (const item of heritageItems) {
                        result.heritage.push({
                            filePath: file.path,
                            className: item.className,
                            parentName: item.parentName,
                            kind: item.kind,
                        });
                    }
                    // When the extractor consumes the match, skip symbol processing below.
                    if (heritageItems.length > 0) {
                        continue;
                    }
                }
                // Fallback: the extractor returned [] (or is absent), but the match still
                // carries a heritage-specific capture. The match belongs to a heritage
                // clause and must not fall through to generic symbol processing.
                if (captureMap['heritage.extends'] ||
                    captureMap['heritage.implements'] ||
                    captureMap['heritage.trait']) {
                    continue;
                }
            }
            const definitionNode = getDefinitionNodeFromCaptures(captureMap);
            const defaultNodeLabel = getLabelFromCaptures(captureMap, provider);
            if (!defaultNodeLabel)
                continue;
            const nameNode = captureMap['name'];
            const extractedClassSymbol = definitionNode && provider.classExtractor?.isTypeDeclaration(definitionNode)
                ? provider.classExtractor.extract(definitionNode, {
                    name: nameNode?.text,
                    type: defaultNodeLabel,
                })
                : null;
            const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
            const isClassLikeLabel = nodeLabel === 'Class' ||
                nodeLabel === 'Struct' ||
                nodeLabel === 'Interface' ||
                nodeLabel === 'Enum' ||
                nodeLabel === 'Record';
            if (isClassLikeLabel &&
                provider.classExtractor?.shouldSkipClassCapture?.({
                    captureMap,
                    definitionNode,
                    nameNode,
                    nodeLabel,
                }) === true) {
                continue;
            }
            // Dedup: variable captures (Const/Static/Variable) may overlap with higher-priority
            // captures (e.g. `const fn = () => {}` matches both @definition.function and @definition.const).
            // Skip variable captures whose definition node was already processed.
            if ((nodeLabel === 'Const' || nodeLabel === 'Static' || nodeLabel === 'Variable') &&
                definitionNode &&
                processedDefinitionNodes.has(definitionNode.startIndex)) {
                continue;
            }
            if (definitionNode) {
                processedDefinitionNodes.add(definitionNode.startIndex);
            }
            // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
            if (!nameNode && nodeLabel !== 'Constructor' && !extractedClassSymbol)
                continue;
            const nodeName = extractedClassSymbol?.name ?? (nameNode ? nameNode.text : 'init');
            const startLine = definitionNode
                ? definitionNode.startPosition.row + lineOffset
                : nameNode
                    ? nameNode.startPosition.row + lineOffset
                    : lineOffset;
            // Compute enclosing class BEFORE node ID — needed to qualify method IDs
            const needsOwner = nodeLabel === 'Method' ||
                nodeLabel === 'Constructor' ||
                nodeLabel === 'Property' ||
                nodeLabel === 'Function';
            const enclosingClassInfo = needsOwner
                ? cachedFindEnclosingClassInfo(nameNode || definitionNode, file.path, provider.resolveEnclosingOwner)
                : null;
            const enclosingClassId = enclosingClassInfo?.classId ?? null;
            const objectLiteralOwnerInfo = !enclosingClassId && nodeLabel === 'Method' && definitionNode
                ? findObjectLiteralBindingInfo(definitionNode, file.path)
                : null;
            // Qualify method/property IDs with enclosing class name to avoid collisions
            const qualifiedName = enclosingClassInfo
                ? `${enclosingClassInfo.className}.${nodeName}`
                : nodeName;
            // Extract method metadata BEFORE generating node ID — parameterCount is needed
            // to disambiguate overloaded methods via #<arity> suffix in the ID.
            let declaredType;
            let methodProps = {};
            let arityForId; // raw param count for ID, even for variadic
            let defMethodMap;
            let defMethodInfo;
            if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
                // Use MethodExtractor for method metadata — provides parameterCount, parameterTypes,
                // returnType, isAbstract/isFinal/annotations, visibility, and more.
                let enrichedByMethodExtractor = false;
                if (provider.methodExtractor && definitionNode) {
                    const classNode = findEnclosingClassNode(definitionNode) ?? findClassNodeByQualifiedName(definitionNode);
                    if (classNode) {
                        const methodMap = getMethodInfo(classNode, provider, {
                            filePath: file.path,
                            language,
                        });
                        const defLine = definitionNode.startPosition.row + 1;
                        const info = methodMap?.get(`${nodeName}:${defLine}`);
                        if (info) {
                            enrichedByMethodExtractor = true;
                            arityForId = arityForIdFromInfo(info);
                            methodProps = buildMethodProps(info);
                            defMethodMap = methodMap;
                            defMethodInfo = info;
                        }
                    }
                }
                // For top-level methods (e.g. Go method_declaration), try extractFromNode
                if (!enrichedByMethodExtractor &&
                    provider.methodExtractor?.extractFromNode &&
                    definitionNode) {
                    const info = provider.methodExtractor.extractFromNode(definitionNode, {
                        filePath: file.path,
                        language,
                    });
                    if (info) {
                        enrichedByMethodExtractor = true;
                        arityForId = arityForIdFromInfo(info);
                        methodProps = buildMethodProps(info);
                    }
                }
            }
            // Append #<paramCount> to owned callable IDs to disambiguate overloads.
            // Top-level Function IDs stay stable; functions inside an owner may overload.
            // When same-arity collisions exist, append ~type1,type2 for further disambiguation.
            const needsAritySuffix = nodeLabel === 'Method' ||
                nodeLabel === 'Constructor' ||
                (nodeLabel === 'Function' && enclosingClassId !== null);
            let arityTag = needsAritySuffix && arityForId !== undefined ? `#${arityForId}` : '';
            if (arityTag && defMethodMap && defMethodInfo) {
                const groups = buildCollisionGroups(defMethodMap);
                arityTag += typeTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, language, groups);
                arityTag += constTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, groups);
            }
            const classTemplateArguments = extractedClassSymbol?.templateArguments ??
                provider.classExtractor?.extractTemplateArgumentsFromCapture?.({
                    captureMap,
                    definitionNode,
                    nameNode,
                }) ??
                (captureMap['template-arguments']
                    ? extractTemplateArguments(captureMap['template-arguments'].text)
                    : undefined) ??
                (nameNode && nameNode.text ? extractTemplateArguments(nameNode.text) : undefined);
            const classTemplateTag = (nodeLabel === 'Class' ||
                nodeLabel === 'Struct' ||
                nodeLabel === 'Interface' ||
                nodeLabel === 'Enum' ||
                nodeLabel === 'Record') &&
                classTemplateArguments !== undefined &&
                classTemplateArguments.length > 0
                ? templateArgumentsIdTag(classTemplateArguments)
                : '';
            const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
            const classNodeForSymbol = definitionNode || nameNode;
            const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
                (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
                    ? (provider.classExtractor.extractQualifiedName(classNodeForSymbol, nodeName) ?? nodeName)
                    : undefined);
            const description = provider.descriptionExtractor?.(nodeLabel, nodeName, captureMap);
            let frameworkHint = definitionNode
                ? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
                : null;
            // Suppress Spring framework hint for methods inside interfaces
            // (Feign clients, JAX-RS proxies are consumers, not providers)
            if (frameworkHint && definitionNode) {
                let classCheck = definitionNode.parent;
                while (classCheck) {
                    if (classCheck.type === 'interface_declaration') {
                        frameworkHint = null;
                        break;
                    }
                    if (classCheck.type === 'class_declaration' || classCheck.type === 'program') {
                        break;
                    }
                    classCheck = classCheck.parent;
                }
            }
            // Decorators appear on lines immediately before their definition; allow up to
            // MAX_DECORATOR_SCAN_LINES gap for blank lines / multi-line decorator stacks.
            const MAX_DECORATOR_SCAN_LINES = 5;
            if (definitionNode) {
                const defStartLine = definitionNode.startPosition.row;
                for (let checkLine = defStartLine - 1; checkLine >= Math.max(0, defStartLine - MAX_DECORATOR_SCAN_LINES); checkLine--) {
                    const dec = fileDecorators.get(checkLine);
                    if (dec) {
                        // Use first (closest) decorator found for framework hint
                        if (!frameworkHint) {
                            frameworkHint = {
                                framework: 'decorator',
                                entryPointMultiplier: 1.2,
                                reason: `@${dec.name}${dec.arg ? `("${dec.arg}")` : ''}`,
                            };
                        }
                        // Emit tool definition if this is a @tool decorator
                        if (dec.isTool) {
                            result.toolDefs.push({
                                filePath: file.path,
                                toolName: nodeName,
                                description: (dec.arg || description || '').slice(0, 200),
                                lineNumber: definitionNode.startPosition.row + lineOffset,
                                handlerNodeId: nodeId,
                            });
                        }
                        fileDecorators.delete(checkLine);
                    }
                }
            }
            // Property metadata extraction (not needed before nodeId — Properties don't overload)
            if (nodeLabel === 'Property' && definitionNode) {
                // FieldExtractor is the single source of truth when available
                if (provider.fieldExtractor && typeEnv) {
                    const classNode = findEnclosingClassNode(definitionNode);
                    if (classNode) {
                        const fieldMap = getFieldInfo(classNode, provider, {
                            typeEnv,
                            symbolTable: NOOP_SYMBOL_TABLE,
                            filePath: file.path,
                            language,
                        });
                        const info = fieldMap?.get(nodeName);
                        if (info) {
                            declaredType = info.type ?? undefined;
                            methodProps.visibility = info.visibility;
                            methodProps.isStatic = info.isStatic;
                            methodProps.isReadonly = info.isReadonly;
                        }
                    }
                }
            }
            // Variable/Const/Static metadata extraction via VariableExtractor
            if ((nodeLabel === 'Const' || nodeLabel === 'Static' || nodeLabel === 'Variable') &&
                definitionNode &&
                provider.variableExtractor) {
                const varCtx = {
                    filePath: file.path,
                    language,
                };
                const varInfo = provider.variableExtractor.extract(definitionNode, varCtx);
                if (varInfo) {
                    if (varInfo.type)
                        declaredType = varInfo.type;
                    methodProps.visibility = varInfo.visibility;
                    methodProps.isStatic = varInfo.isStatic;
                    methodProps.isConst = varInfo.isConst;
                    methodProps.isMutable = varInfo.isMutable;
                    methodProps.scope = varInfo.scope;
                }
            }
            result.nodes.push({
                id: nodeId,
                label: nodeLabel,
                properties: {
                    name: nodeName,
                    filePath: file.path,
                    startLine: definitionNode ? definitionNode.startPosition.row + lineOffset : startLine,
                    endLine: definitionNode ? definitionNode.endPosition.row + lineOffset : startLine,
                    language: language,
                    isExported: language === SupportedLanguages.Vue && isVueSetup
                        ? isVueSetupTopLevel(nameNode || definitionNode)
                        : cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
                    ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
                    ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
                        ? { templateArguments: classTemplateArguments }
                        : {}),
                    ...(frameworkHint
                        ? {
                            astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
                            astFrameworkReason: frameworkHint.reason,
                        }
                        : {}),
                    ...(description !== undefined ? { description } : {}),
                    ...methodProps,
                    ...(declaredType !== undefined ? { declaredType } : {}),
                },
            });
            // enclosingClassId already computed above (before nodeId generation)
            const ownerId = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId;
            result.symbols.push({
                filePath: file.path,
                name: nodeName,
                nodeId,
                type: nodeLabel,
                ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
                parameterCount: methodProps.parameterCount,
                requiredParameterCount: methodProps.requiredParameterCount,
                parameterTypes: methodProps.parameterTypes,
                parameterTypeClasses: methodProps.parameterTypeClasses,
                returnType: methodProps.returnType,
                ...(declaredType !== undefined ? { declaredType } : {}),
                ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
                    ? { templateArguments: classTemplateArguments }
                    : {}),
                ...(ownerId !== undefined ? { ownerId } : {}),
                visibility: methodProps.visibility,
                isStatic: methodProps.isStatic,
                isReadonly: methodProps.isReadonly,
                isAbstract: methodProps.isAbstract,
                isFinal: methodProps.isFinal,
                ...(methodProps.isVirtual !== undefined
                    ? { isVirtual: methodProps.isVirtual }
                    : {}),
                ...(methodProps.isOverride !== undefined
                    ? { isOverride: methodProps.isOverride }
                    : {}),
                ...(methodProps.isAsync !== undefined ? { isAsync: methodProps.isAsync } : {}),
                ...(methodProps.isPartial !== undefined
                    ? { isPartial: methodProps.isPartial }
                    : {}),
                ...(methodProps.annotations !== undefined
                    ? { annotations: methodProps.annotations }
                    : {}),
            });
            const fileId = generateId('File', file.path);
            const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
            result.relationships.push({
                id: relId,
                sourceId: fileId,
                targetId: nodeId,
                type: 'DEFINES',
                confidence: 1.0,
                reason: '',
            });
            // ── HAS_METHOD / HAS_PROPERTY: link member to enclosing class ──
            if (ownerId !== undefined) {
                const memberEdgeType = nodeLabel === 'Property' ? 'HAS_PROPERTY' : 'HAS_METHOD';
                result.relationships.push({
                    id: generateId(memberEdgeType, `${ownerId}->${nodeId}`),
                    sourceId: ownerId,
                    targetId: nodeId,
                    type: memberEdgeType,
                    confidence: 1.0,
                    reason: objectLiteralOwnerInfo
                        ? 'object literal method belongs to exported object binding'
                        : '',
                });
            }
        }
        // Extract framework routes via provider detection (e.g., Laravel routes.php)
        if (provider.isRouteFile?.(file.path)) {
            const extractedRoutes = extractLaravelRoutes(tree, file.path);
            for (const r of extractedRoutes)
                result.routes.push(r);
        }
        // Extract ORM queries (Prisma, Supabase)
        extractORMQueries(file.path, parseContent, result.ormQueries);
        // Vue: emit CALLS edges for components used in <template>
        if (language === SupportedLanguages.Vue) {
            const templateComponents = extractTemplateComponents(file.content);
            for (const componentName of templateComponents) {
                result.calls.push({
                    filePath: file.path,
                    calledName: componentName,
                    sourceId: generateId('File', file.path),
                    callForm: 'free',
                });
            }
        }
    }
};
// ============================================================================
// Worker message handler — supports sub-batch streaming
// ============================================================================
/** Accumulated result across sub-batches */
let accumulated = {
    nodes: [],
    relationships: [],
    symbols: [],
    imports: [],
    calls: [],
    assignments: [],
    heritage: [],
    routes: [],
    fetchCalls: [],
    decoratorRoutes: [],
    toolDefs: [],
    ormQueries: [],
    constructorBindings: [],
    fileScopeBindings: [],
    parsedFiles: [],
    skippedLanguages: {},
    fileCount: 0,
};
let cumulativeProcessed = 0;
// Use a loop instead of push(...spread) to avoid hitting V8's argument limit
// when merging large result sets (push(...arr) calls apply() under the hood
// and blows the stack when arr has >~65k elements).
const appendAll = (target, src) => {
    for (let i = 0; i < src.length; i++)
        target.push(src[i]);
};
const mergeResult = (target, src) => {
    appendAll(target.nodes, src.nodes);
    appendAll(target.relationships, src.relationships);
    appendAll(target.symbols, src.symbols);
    appendAll(target.imports, src.imports);
    appendAll(target.calls, src.calls);
    appendAll(target.assignments, src.assignments);
    appendAll(target.heritage, src.heritage);
    appendAll(target.routes, src.routes);
    appendAll(target.fetchCalls, src.fetchCalls);
    appendAll(target.decoratorRoutes, src.decoratorRoutes);
    appendAll(target.toolDefs, src.toolDefs);
    appendAll(target.ormQueries, src.ormQueries);
    appendAll(target.constructorBindings, src.constructorBindings);
    appendAll(target.fileScopeBindings, src.fileScopeBindings);
    appendAll(target.parsedFiles, src.parsedFiles);
    for (const [lang, count] of Object.entries(src.skippedLanguages)) {
        target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
    }
    target.fileCount += src.fileCount;
};
// Signal the pool that worker-side initialization (parser imports, language
// grammars, type-env setup, all helper modules) is complete and the message
// handler below is about to be attached. The pool's `waitForWorkerReady`
// resolves on this handshake — without it, a worker that crashes during
// top-of-script init slips past pool startup (Node's `online` event fires
// before the script body runs) and the pool only notices via the first
// dispatch's idle timeout (~30s). Emit once; the dispatch handler treats
// any subsequent `ready` message as a benign no-op.
//
// Native postMessage carries the ready handshake — Node's structured
// clone delivers `{type:'ready'}` to the pool's waitForWorkerReady
// listener directly. The pool drops the slot if this isn't seen within
// `WORKER_READY_TIMEOUT_MS` (5s), so emitting it AFTER all top-of-script
// init (imports, native binding loads, type-env setup) completes is the
// load-bearing signal that this worker is ready for dispatch.
parentPort.postMessage({ type: 'ready' });
// Module-scope `TextDecoder` for sub-batch content. The pool sends each
// file's content as a `Uint8Array` (zero-copy ArrayBuffer transfer); we
// decode to string lazily here, once per file, before handing to
// tree-sitter. Hoisted to module scope so we don't allocate a new
// ICU-backed decoder per sub-batch — `TextDecoder.decode()` is
// stateless across calls and safe to share.
const sharedContentDecoder = new TextDecoder('utf-8');
/**
 * Convert the pool's sub-batch `files` array (content as `Uint8Array`,
 * transferred zero-copy) into the `ParseWorkerInput[]` shape
 * `processBatch` expects (content as `string`). This is the one place
 * the UTF-8 decode happens — runs on the worker thread in parallel with
 * continued main-thread work.
 */
function decodeSubBatchFiles(files) {
    return files.map((f) => ({
        path: f.path,
        // Test scaffolding (the writeReadyWorker preamble that wraps
        // parentPort.on) may already convert content to string before
        // calling here; tolerate both shapes so the same worker code
        // exercises real and synthetic dispatches.
        content: typeof f.content === 'string' ? f.content : sharedContentDecoder.decode(f.content),
    }));
}
parentPort.on('message', (msg) => {
    try {
        // Sub-batch mode: { type: 'sub-batch', files: [...] }
        if (msg.type === 'sub-batch') {
            const files = decodeSubBatchFiles(msg.files);
            const result = processBatch(files, (filesProcessed) => {
                parentPort.postMessage({
                    type: 'progress',
                    filesProcessed: cumulativeProcessed + filesProcessed,
                });
            });
            cumulativeProcessed += result.fileCount;
            mergeResult(accumulated, result);
            // Signal ready for next sub-batch
            parentPort.postMessage({ type: 'sub-batch-done' });
            return;
        }
        // Flush: send accumulated results
        if (msg.type === 'flush') {
            parentPort.postMessage({ type: 'result', data: accumulated });
            // Reset for potential reuse
            accumulated = {
                nodes: [],
                relationships: [],
                symbols: [],
                imports: [],
                calls: [],
                assignments: [],
                heritage: [],
                routes: [],
                fetchCalls: [],
                decoratorRoutes: [],
                toolDefs: [],
                ormQueries: [],
                constructorBindings: [],
                fileScopeBindings: [],
                parsedFiles: [],
                skippedLanguages: {},
                fileCount: 0,
            };
            cumulativeProcessed = 0;
            return;
        }
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        parentPort.postMessage({ type: 'error', error: message });
    }
});
