import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { Language, Parser, Query } from 'web-tree-sitter';
import { debug, warn } from '../infrastructure/logger.js';
import { getNative, getNativePackageVersion, loadNative } from '../infrastructure/native.js';
import { ParseError, toErrorMessage } from '../shared/errors.js';
import { disposeWasmWorkerPool, getWasmWorkerPool } from './wasm-worker-pool.js';
/** Default worker opts: run all analyses so output matches parseFilesFull. */
const FULL_ANALYSIS = {
    ast: true,
    complexity: true,
    cfg: true,
    dataflow: true,
};
/** Extract-only opts: skip visitor walk for typeMap backfill / similar fast paths. */
const EXTRACT_ONLY = {
    ast: false,
    complexity: false,
    cfg: false,
    dataflow: false,
};
// Re-export all extractors for backward compatibility
export { extractBashSymbols, extractClojureSymbols, extractCppSymbols, extractCSharpSymbols, extractCSymbols, extractCudaSymbols, extractDartSymbols, extractElixirSymbols, extractErlangSymbols, extractFSharpSymbols, extractGleamSymbols, extractGoSymbols, extractGroovySymbols, extractHaskellSymbols, extractHCLSymbols, extractJavaSymbols, extractJuliaSymbols, extractKotlinSymbols, extractLuaSymbols, extractObjCSymbols, extractOCamlSymbols, extractPHPSymbols, extractPythonSymbols, extractRSymbols, extractRubySymbols, extractRustSymbols, extractScalaSymbols, extractSoliditySymbols, extractSwiftSymbols, extractSymbols, extractVerilogSymbols, extractZigSymbols, } from '../extractors/index.js';
import { extractBashSymbols, extractClojureSymbols, extractCppSymbols, extractCSharpSymbols, extractCSymbols, extractCudaSymbols, extractDartSymbols, extractElixirSymbols, extractErlangSymbols, extractFSharpSymbols, extractGleamSymbols, extractGoSymbols, extractGroovySymbols, extractHaskellSymbols, extractHCLSymbols, extractJavaSymbols, extractJuliaSymbols, extractKotlinSymbols, extractLuaSymbols, extractObjCSymbols, extractOCamlSymbols, extractPHPSymbols, extractPythonSymbols, extractRSymbols, extractRubySymbols, extractRustSymbols, extractScalaSymbols, extractSoliditySymbols, extractSwiftSymbols, extractSymbols, extractVerilogSymbols, extractZigSymbols, } from '../extractors/index.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function grammarPath(name) {
    return path.join(__dirname, '..', '..', 'grammars', name);
}
let _initialized = false;
// Memoized parsers — avoids reloading WASM grammars on every createParsers() call
let _cachedParsers = null;
// Cached Language objects — WASM-backed, must be .delete()'d explicitly
let _cachedLanguages = null;
// Query cache for JS/TS/TSX extractors (populated during createParsers)
const _queryCache = new Map();
// Tracks whether ALL grammars have been loaded (vs. a lazy subset)
let _allParsersLoaded = false;
// In-flight grammar loads keyed by language id — prevents concurrent duplicate loads
const _loadingPromises = new Map();
// Extensions that need typeMap backfill (type annotations only exist in TS/TSX)
const TS_BACKFILL_EXTS = new Set(['.ts', '.tsx']);
// Shared patterns for all JS/TS/TSX (class_declaration excluded — name type differs)
const COMMON_QUERY_PATTERNS = [
    '(function_declaration name: (identifier) @fn_name) @fn_node',
    '(generator_function_declaration name: (identifier) @fn_name) @fn_node',
    '(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
    '(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
    '(variable_declarator name: (identifier) @varfn_name value: (generator_function) @varfn_value)',
    '(method_definition name: (property_identifier) @meth_name) @meth_node',
    '(method_definition name: (private_property_identifier) @meth_name) @meth_node',
    '(import_statement source: (string) @imp_source) @imp_node',
    '(export_statement) @exp_node',
    '(call_expression function: (identifier) @callfn_name) @callfn_node',
    '(call_expression function: (member_expression) @callmem_fn) @callmem_node',
    '(call_expression function: (subscript_expression) @callsub_fn) @callsub_node',
    '(new_expression constructor: (identifier) @newfn_name) @newfn_node',
    '(new_expression constructor: (member_expression) @newmem_fn) @newmem_node',
    '(expression_statement (assignment_expression left: (member_expression) @assign_left right: (_) @assign_right)) @assign_node',
];
// JS: class name is (identifier) — declarations and expressions
const JS_CLASS_PATTERNS = [
    '(class_declaration name: (identifier) @cls_name) @cls_node',
    // class expressions: `return class Foo extends Bar { ... }` or `const X = class Foo { ... }`
    '(class name: (identifier) @cls_name) @cls_node',
];
// TS/TSX: class name is (type_identifier), plus interface and type alias
// abstract_class_declaration is a separate node type in tree-sitter-typescript
const TS_EXTRA_PATTERNS = [
    '(class_declaration name: (type_identifier) @cls_name) @cls_node',
    '(abstract_class_declaration name: (type_identifier) @cls_name) @cls_node',
    // class expressions: `return class Foo extends Bar { ... }`
    '(class name: (type_identifier) @cls_name) @cls_node',
    '(interface_declaration name: (type_identifier) @iface_name) @iface_node',
    '(type_alias_declaration name: (type_identifier) @type_name) @type_node',
];
/**
 * Load a single language grammar and cache the parser + language + query.
 * Uses in-flight deduplication so concurrent callers awaiting the same grammar
 * share a single load rather than producing orphaned WASM instances.
 * Assumes Parser.init() has already been called and _cachedParsers/_cachedLanguages exist.
 */
async function loadLanguage(entry) {
    if (_cachedParsers.has(entry.id))
        return;
    const inflight = _loadingPromises.get(entry.id);
    if (inflight)
        return inflight;
    const p = doLoadLanguage(entry).finally(() => _loadingPromises.delete(entry.id));
    _loadingPromises.set(entry.id, p);
    return p;
}
async function doLoadLanguage(entry) {
    try {
        const lang = await Language.load(grammarPath(entry.grammarFile));
        const parser = new Parser();
        parser.setLanguage(lang);
        _cachedParsers.set(entry.id, parser);
        _cachedLanguages.set(entry.id, lang);
        if (entry.extractor === extractSymbols && !_queryCache.has(entry.id)) {
            const isTS = entry.id === 'typescript' || entry.id === 'tsx';
            const patterns = isTS
                ? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
                : [...COMMON_QUERY_PATTERNS, ...JS_CLASS_PATTERNS];
            _queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
        }
    }
    catch (e) {
        if (entry.required)
            throw new ParseError(`Required parser ${entry.id} failed to initialize`, {
                file: entry.grammarFile,
                cause: e,
            });
        warn(`${entry.id} parser failed to initialize: ${e.message}. ${entry.id} files will be skipped.`);
        _cachedParsers.set(entry.id, null);
    }
}
async function initParserRuntime() {
    if (!_initialized) {
        await Parser.init();
        _initialized = true;
    }
    if (!_cachedParsers)
        _cachedParsers = new Map();
    if (!_cachedLanguages)
        _cachedLanguages = new Map();
}
/**
 * Load only the WASM grammars needed for the given file paths.
 * Grammars already in cache are reused. This avoids the ~500ms cold-start
 * penalty of loading all 23+ grammars when only 1-2 are needed (e.g. incremental rebuilds).
 */
async function ensureParsersForFiles(filePaths) {
    await initParserRuntime();
    const needed = new Set();
    for (const fp of filePaths) {
        const ext = path.extname(fp).toLowerCase();
        const entry = _extToLang.get(ext);
        if (entry && !_cachedParsers.has(entry.id))
            needed.add(entry);
    }
    for (const entry of needed) {
        await loadLanguage(entry);
    }
    return _cachedParsers;
}
/**
 * Load ALL WASM grammars. Used by full builds and feature modules (CFG, dataflow, complexity)
 * that may process files of any language.
 */
export async function createParsers() {
    if (_cachedParsers && _allParsersLoaded)
        return _cachedParsers;
    await initParserRuntime();
    for (const entry of LANGUAGE_REGISTRY) {
        if (!_cachedParsers.has(entry.id)) {
            await loadLanguage(entry);
        }
    }
    _allParsersLoaded = true;
    return _cachedParsers;
}
/**
 * Dispose all cached WASM parsers and queries to free WASM linear memory.
 * Call this between repeated builds in the same process (e.g. benchmarks)
 * to prevent memory accumulation that can cause segfaults.
 */
function disposeMapEntries(entries, label) {
    for (const [id, item] of entries) {
        if (item && typeof item.delete === 'function') {
            try {
                item.delete();
            }
            catch (e) {
                debug(`Failed to dispose ${label} ${id}: ${e.message}`);
            }
        }
    }
}
export async function disposeParsers() {
    if (_cachedParsers) {
        disposeMapEntries(_cachedParsers, 'parser');
        _cachedParsers = null;
    }
    disposeMapEntries(_queryCache, 'query');
    _queryCache.clear();
    if (_cachedLanguages) {
        disposeMapEntries(_cachedLanguages, 'language');
        _cachedLanguages = null;
    }
    _initialized = false;
    _allParsersLoaded = false;
    _loadingPromises.clear();
    await disposeWasmWorkerPool();
}
export function getParser(parsers, filePath) {
    const ext = path.extname(filePath);
    const entry = _extToLang.get(ext);
    if (!entry)
        return null;
    return parsers.get(entry.id) || null;
}
/**
 * Backfill missing AST-analysis data (astNodes, dataflow, def.complexity,
 * def.cfg) via the WASM worker pool for files that were parsed by the native
 * engine but are missing one or more analyses.
 *
 * Historically this function populated `symbols._tree` so the main-thread
 * visitor walk in `ast-analysis/engine.ts` could run. After the worker-isolation
 * refactor (#965), the worker runs every visitor itself and returns pre-computed
 * analysis data — `_tree` is never set on the main thread.
 *
 * Name is preserved for caller compatibility; the function now ensures
 * *analysis data* rather than *trees*.
 *
 * `needsFn` (optional): when provided, only files for which it returns true are
 * re-parsed. Without it the function falls back to "any WASM-parseable file
 * without _tree", which was the source of #1036 — a single file missing one
 * analysis triggered a full-build re-parse of every WASM-parseable file.
 */
/**
 * Select files from `fileSymbols` that still need analysis data and are
 * parseable by an installed WASM grammar. Pure (no I/O) — safe to unit-test.
 */
function collectBackfillPending(fileSymbols, rootDir, needsFn) {
    const pending = [];
    for (const [relPath, symbols] of fileSymbols) {
        if (symbols._tree)
            continue; // legacy path — leave existing trees alone
        if (!_extToLang.has(path.extname(relPath).toLowerCase()))
            continue;
        if (needsFn && !needsFn(relPath, symbols))
            continue;
        pending.push({ relPath, absPath: path.join(rootDir, relPath), symbols });
    }
    return pending;
}
export async function ensureWasmTrees(fileSymbols, rootDir, needsFn) {
    const pending = collectBackfillPending(fileSymbols, rootDir, needsFn);
    if (pending.length === 0)
        return;
    const pool = getWasmWorkerPool();
    for (const { relPath, absPath, symbols } of pending) {
        let code;
        try {
            code = fs.readFileSync(absPath, 'utf-8');
        }
        catch (e) {
            debug(`ensureWasmTrees: cannot read ${relPath}: ${e.message}`);
            continue;
        }
        const output = await pool.parse(absPath, code, FULL_ANALYSIS);
        if (!output)
            continue; // worker crashed or returned null — skip silently
        mergeAnalysisData(symbols, output);
    }
}
/** Fill gap-only scalar metadata (`_langId`, `_lineCount`) from the worker output. */
function mergeScalarMetadata(symbols, worker) {
    if (!symbols._langId && worker._langId)
        symbols._langId = worker._langId;
    if (!symbols._lineCount && worker._lineCount)
        symbols._lineCount = worker._lineCount;
}
/** Fill gap-only analysis arrays (`astNodes`, `dataflow`) from the worker output. */
function mergeAnalysisArrays(symbols, worker) {
    if (!Array.isArray(symbols.astNodes) && Array.isArray(worker.astNodes)) {
        symbols.astNodes = worker.astNodes;
    }
    if (!symbols.dataflow && worker.dataflow)
        symbols.dataflow = worker.dataflow;
}
/** Merge worker typeMap into existing symbols.typeMap with first-wins semantics. */
function mergeTypeMap(symbols, worker) {
    if (!worker.typeMap || worker.typeMap.size === 0)
        return;
    if (!symbols.typeMap || !(symbols.typeMap instanceof Map)) {
        symbols.typeMap = new Map(worker.typeMap);
        return;
    }
    for (const [k, v] of worker.typeMap) {
        if (!symbols.typeMap.has(k))
            symbols.typeMap.set(k, v);
    }
}
/** Patch existing definitions with worker complexity/cfg when absent. */
function mergeDefinitionAnalysis(symbols, worker) {
    const existingDefs = Array.isArray(symbols.definitions) ? symbols.definitions : [];
    const workerDefs = Array.isArray(worker.definitions) ? worker.definitions : [];
    if (existingDefs.length === 0 || workerDefs.length === 0)
        return;
    // Index existing defs by (kind, name, line) — mirrors engine.ts matching key.
    const byKey = new Map();
    for (const d of existingDefs)
        byKey.set(`${d.kind}|${d.name}|${d.line}`, d);
    for (const wd of workerDefs) {
        const existing = byKey.get(`${wd.kind}|${wd.name}|${wd.line}`);
        if (!existing)
            continue;
        if (!existing.complexity && wd.complexity)
            existing.complexity = wd.complexity;
        if ((!existing.cfg || !Array.isArray(existing.cfg.blocks)) && wd.cfg?.blocks) {
            existing.cfg = wd.cfg;
        }
    }
}
/**
 * Merge pre-computed analysis data from a worker result onto existing symbols.
 * Only fills gaps — never overwrites fields the caller already populated.
 * Used to patch native-parsed symbols with worker-produced astNodes / dataflow /
 * per-definition complexity and cfg.
 */
function mergeAnalysisData(symbols, worker) {
    mergeScalarMetadata(symbols, worker);
    mergeAnalysisArrays(symbols, worker);
    mergeTypeMap(symbols, worker);
    mergeDefinitionAnalysis(symbols, worker);
}
/**
 * Check whether the required WASM grammar files exist on disk.
 */
export function isWasmAvailable() {
    return LANGUAGE_REGISTRY.filter((e) => e.required).every((e) => fs.existsSync(grammarPath(e.grammarFile)));
}
/**
 * Return the set of lowercase file extensions whose WASM grammar is actually
 * installed on disk. Used to scope engine-parity backfill to files that WASM
 * can recover — languages without an installed grammar are skipped by both
 * engines, so they don't represent a native-engine drop.
 *
 * Cached on first call; the grammars directory is shipped immutable.
 */
let _installedWasmExts = null;
export function getInstalledWasmExtensions() {
    if (_installedWasmExts)
        return _installedWasmExts;
    const exts = new Set();
    for (const entry of LANGUAGE_REGISTRY) {
        if (fs.existsSync(grammarPath(entry.grammarFile))) {
            for (const ext of entry.extensions)
                exts.add(ext.toLowerCase());
        }
    }
    _installedWasmExts = exts;
    return exts;
}
/**
 * Lowercase file extensions covered by the native Rust addon.
 *
 * Mirrors `LanguageKind::from_extension` in
 * `crates/codegraph-core/src/parser_registry.rs`. Used to classify why the
 * native orchestrator dropped a file: extensions outside this set are a
 * legitimate parser limit (no Rust extractor exists), while extensions inside
 * it indicate a real native bug (parse/read/extract failure).
 *
 * Keep this list in sync with the Rust enum — the native addon is a separate
 * npm package, so JS has no runtime way to discover its language coverage.
 */
export const NATIVE_SUPPORTED_EXTENSIONS = new Set([
    '.js',
    '.jsx',
    '.mjs',
    '.cjs',
    '.ts',
    '.tsx',
    '.py',
    '.pyi',
    '.tf',
    '.hcl',
    '.go',
    '.rs',
    '.java',
    '.cs',
    '.rb',
    '.rake',
    '.gemspec',
    '.php',
    '.phtml',
    '.c',
    '.h',
    '.cpp',
    '.cc',
    '.cxx',
    '.hpp',
    '.cu',
    '.cuh',
    '.kt',
    '.kts',
    '.swift',
    '.scala',
    '.sh',
    '.bash',
    '.ex',
    '.exs',
    '.lua',
    '.dart',
    '.zig',
    '.hs',
    '.ml',
    '.mli',
    '.fs',
    '.fsx',
    '.fsi',
    '.m',
    '.gleam',
    '.jl',
    '.clj',
    '.cljs',
    '.cljc',
    '.erl',
    '.hrl',
    '.groovy',
    '.gvy',
    '.r',
    '.sol',
    '.v',
    '.sv',
]);
/**
 * Group the missing files (relative paths) by drop reason and extension so the
 * caller can log per-extension counts and a sample path. Pure function — no
 * I/O, safe to unit-test independently of the build pipeline.
 */
export function classifyNativeDrops(relPaths) {
    const byReason = {
        'unsupported-by-native': new Map(),
        'native-extractor-failure': new Map(),
    };
    const totals = {
        'unsupported-by-native': 0,
        'native-extractor-failure': 0,
    };
    for (const rel of relPaths) {
        const ext = path.extname(rel).toLowerCase();
        const reason = NATIVE_SUPPORTED_EXTENSIONS.has(ext)
            ? 'native-extractor-failure'
            : 'unsupported-by-native';
        const bucket = byReason[reason];
        let list = bucket.get(ext);
        if (!list) {
            list = [];
            bucket.set(ext, list);
        }
        list.push(rel);
        totals[reason]++;
    }
    return { byReason, totals };
}
/**
 * Render `{ ext → paths[] }` as a multi-line tabular breakdown for log lines.
 * Each extension occupies its own line so a long warning scans like a table
 * instead of a wall of semicolon-separated slices. Caps at 3 sample paths per
 * extension and 6 extensions total to keep output bounded when many languages
 * are dropped at once. Extensions are sorted by descending file count so the
 * loudest offender shows up first; ties keep insertion order.
 *
 * Returns the empty string for empty input, and otherwise a string that
 * begins with `\n` so callers can append it directly after the header line
 * (`"Backfilling via WASM:" + formatDropExtensionSummary(...)`).
 *
 * Pure function — safe to unit-test independently.
 */
export function formatDropExtensionSummary(buckets) {
    const MAX_EXTS = 6;
    const MAX_SAMPLES = 3;
    const entries = Array.from(buckets.entries()).sort((a, b) => b[1].length - a[1].length);
    if (entries.length === 0)
        return '';
    const shown = entries.slice(0, MAX_EXTS);
    const extWidth = Math.max(...shown.map(([ext]) => ext.length));
    const countWidth = Math.max(...shown.map(([, paths]) => String(paths.length).length));
    const lines = shown.map(([ext, paths]) => {
        const sample = paths.slice(0, MAX_SAMPLES).join(', ');
        const more = paths.length > MAX_SAMPLES ? ` (+${paths.length - MAX_SAMPLES} more)` : '';
        return `  ${ext.padEnd(extWidth)}  ${String(paths.length).padStart(countWidth)}  ${sample}${more}`;
    });
    if (entries.length > MAX_EXTS) {
        lines.push(`  (+${entries.length - MAX_EXTS} more extension(s))`);
    }
    return `\n${lines.join('\n')}`;
}
// ── Unified API ──────────────────────────────────────────────────────────────
function resolveEngine(opts = {}) {
    const pref = opts.engine || 'auto';
    if (pref === 'wasm')
        return { name: 'wasm', native: null };
    if (pref === 'native' || pref === 'auto') {
        const native = loadNative();
        if (native)
            return { name: 'native', native };
        if (pref === 'native') {
            getNative(); // throws with detailed error + install instructions
        }
    }
    return { name: 'wasm', native: null };
}
/**
 * Patch native engine output in-place for the few remaining semantic transforms.
 * With #[napi(js_name)] on Rust types, most fields already arrive as camelCase.
 * This only handles:
 *  - _lineCount compat for builder.js
 *  - Backward compat for older native binaries missing js_name annotations
 *  - dataflow argFlows/mutations bindingType -> binding wrapper
 */
/** Patch definition fields for backward compat with older native binaries. */
function patchDefinitions(definitions) {
    for (const d of definitions) {
        if (d.endLine === undefined && d.end_line !== undefined) {
            d.endLine = d.end_line;
        }
    }
}
/**
 * Field renames applied to each import record to bridge older native binaries
 * that emit snake_case names. Each `[camel, snake]` pair becomes:
 *   `if (imp[camel] === undefined) imp[camel] = imp[snake];`
 * Defined as data so the loop body stays trivially linear in cognitive complexity.
 */
const IMPORT_FIELD_RENAMES = [
    ['typeOnly', 'type_only'],
    ['wildcardReexport', 'wildcard_reexport'],
    ['pythonImport', 'python_import'],
    ['goImport', 'go_import'],
    ['rustUse', 'rust_use'],
    ['javaImport', 'java_import'],
    ['csharpUsing', 'csharp_using'],
    ['rubyRequire', 'ruby_require'],
    ['phpUse', 'php_use'],
    ['cInclude', 'c_include'],
    ['kotlinImport', 'kotlin_import'],
    ['swiftImport', 'swift_import'],
    ['scalaImport', 'scala_import'],
    ['bashSource', 'bash_source'],
    ['dynamicImport', 'dynamic_import'],
];
/** Patch import fields for backward compat with older native binaries. */
function patchImports(imports) {
    for (const i of imports) {
        for (const [camel, snake] of IMPORT_FIELD_RENAMES) {
            if (i[camel] === undefined)
                i[camel] = i[snake];
        }
    }
}
/** Normalize native typeMap array to a Map instance.
 *  Uses first-wins semantics at equal confidence to match the WASM/JS extractor. */
function patchTypeMap(r) {
    if (!r.typeMap) {
        r.typeMap = new Map();
    }
    else if (!(r.typeMap instanceof Map)) {
        const map = new Map();
        for (const e of r.typeMap) {
            if (!map.has(e.name)) {
                map.set(e.name, { type: e.typeName, confidence: e.confidence ?? 0.9 });
            }
        }
        r.typeMap = map;
    }
}
/** Normalize native returnTypeMap array to a Map instance, keeping highest-confidence entry per key. */
function patchReturnTypeMap(r) {
    if (!r.returnTypeMap || r.returnTypeMap instanceof Map)
        return;
    const map = new Map();
    for (const e of r.returnTypeMap) {
        const conf = e.confidence ?? 1.0;
        const existing = map.get(e.name);
        if (!existing || conf > existing.confidence) {
            map.set(e.name, { type: e.typeName, confidence: conf });
        }
    }
    r.returnTypeMap = map.size > 0 ? map : new Map();
}
/** Wrap bindingType into binding object for dataflow argFlows and mutations. */
function patchDataflow(dataflow) {
    if (dataflow.argFlows) {
        for (const f of dataflow.argFlows) {
            f.binding = f.bindingType ? { type: f.bindingType } : null;
        }
    }
    if (dataflow.mutations) {
        for (const m of dataflow.mutations) {
            m.binding = m.bindingType ? { type: m.bindingType } : null;
        }
    }
}
function patchNativeResult(r) {
    // lineCount: napi(js_name) emits "lineCount"; older binaries may emit "line_count"
    r.lineCount = r.lineCount ?? r.line_count ?? null;
    r._lineCount = r.lineCount;
    if (r.definitions)
        patchDefinitions(r.definitions);
    if (r.imports)
        patchImports(r.imports);
    patchTypeMap(r);
    patchReturnTypeMap(r);
    if (r.dataflow)
        patchDataflow(r.dataflow);
    return r;
}
/**
 * Declarative registry of all supported languages.
 * Adding a new language requires only a new entry here + its extractor function.
 */
export const LANGUAGE_REGISTRY = [
    {
        id: 'javascript',
        extensions: ['.js', '.jsx', '.mjs', '.cjs'],
        grammarFile: 'tree-sitter-javascript.wasm',
        extractor: extractSymbols,
        required: true,
    },
    {
        id: 'typescript',
        extensions: ['.ts'],
        grammarFile: 'tree-sitter-typescript.wasm',
        extractor: extractSymbols,
        required: true,
    },
    {
        id: 'tsx',
        extensions: ['.tsx'],
        grammarFile: 'tree-sitter-tsx.wasm',
        extractor: extractSymbols,
        required: true,
    },
    {
        id: 'hcl',
        extensions: ['.tf', '.hcl'],
        grammarFile: 'tree-sitter-hcl.wasm',
        extractor: extractHCLSymbols,
        required: false,
    },
    {
        id: 'python',
        extensions: ['.py', '.pyi'],
        grammarFile: 'tree-sitter-python.wasm',
        extractor: extractPythonSymbols,
        required: false,
    },
    {
        id: 'go',
        extensions: ['.go'],
        grammarFile: 'tree-sitter-go.wasm',
        extractor: extractGoSymbols,
        required: false,
    },
    {
        id: 'rust',
        extensions: ['.rs'],
        grammarFile: 'tree-sitter-rust.wasm',
        extractor: extractRustSymbols,
        required: false,
    },
    {
        id: 'java',
        extensions: ['.java'],
        grammarFile: 'tree-sitter-java.wasm',
        extractor: extractJavaSymbols,
        required: false,
    },
    {
        id: 'csharp',
        extensions: ['.cs'],
        grammarFile: 'tree-sitter-c_sharp.wasm',
        extractor: extractCSharpSymbols,
        required: false,
    },
    {
        id: 'ruby',
        extensions: ['.rb', '.rake', '.gemspec'],
        grammarFile: 'tree-sitter-ruby.wasm',
        extractor: extractRubySymbols,
        required: false,
    },
    {
        id: 'php',
        extensions: ['.php', '.phtml'],
        grammarFile: 'tree-sitter-php.wasm',
        extractor: extractPHPSymbols,
        required: false,
    },
    {
        id: 'c',
        extensions: ['.c', '.h'],
        grammarFile: 'tree-sitter-c.wasm',
        extractor: extractCSymbols,
        required: false,
    },
    {
        id: 'cpp',
        extensions: ['.cpp', '.cc', '.cxx', '.hpp'],
        grammarFile: 'tree-sitter-cpp.wasm',
        extractor: extractCppSymbols,
        required: false,
    },
    {
        id: 'kotlin',
        extensions: ['.kt', '.kts'],
        grammarFile: 'tree-sitter-kotlin.wasm',
        extractor: extractKotlinSymbols,
        required: false,
    },
    {
        id: 'swift',
        extensions: ['.swift'],
        grammarFile: 'tree-sitter-swift.wasm',
        extractor: extractSwiftSymbols,
        required: false,
    },
    {
        id: 'scala',
        extensions: ['.scala'],
        grammarFile: 'tree-sitter-scala.wasm',
        extractor: extractScalaSymbols,
        required: false,
    },
    {
        id: 'bash',
        extensions: ['.sh', '.bash'],
        grammarFile: 'tree-sitter-bash.wasm',
        extractor: extractBashSymbols,
        required: false,
    },
    {
        id: 'elixir',
        extensions: ['.ex', '.exs'],
        grammarFile: 'tree-sitter-elixir.wasm',
        extractor: extractElixirSymbols,
        required: false,
    },
    {
        id: 'lua',
        extensions: ['.lua'],
        grammarFile: 'tree-sitter-lua.wasm',
        extractor: extractLuaSymbols,
        required: false,
    },
    {
        id: 'dart',
        extensions: ['.dart'],
        grammarFile: 'tree-sitter-dart.wasm',
        extractor: extractDartSymbols,
        required: false,
    },
    {
        id: 'zig',
        extensions: ['.zig'],
        grammarFile: 'tree-sitter-zig.wasm',
        extractor: extractZigSymbols,
        required: false,
    },
    {
        id: 'haskell',
        extensions: ['.hs'],
        grammarFile: 'tree-sitter-haskell.wasm',
        extractor: extractHaskellSymbols,
        required: false,
    },
    {
        id: 'ocaml',
        extensions: ['.ml'],
        grammarFile: 'tree-sitter-ocaml.wasm',
        extractor: extractOCamlSymbols,
        required: false,
    },
    {
        id: 'ocaml-interface',
        extensions: ['.mli'],
        grammarFile: 'tree-sitter-ocaml_interface.wasm',
        extractor: extractOCamlSymbols,
        required: false,
    },
    {
        id: 'fsharp',
        extensions: ['.fs', '.fsx'],
        grammarFile: 'tree-sitter-fsharp.wasm',
        extractor: extractFSharpSymbols,
        required: false,
    },
    {
        id: 'fsharp-signature',
        extensions: ['.fsi'],
        grammarFile: 'tree-sitter-fsharp_signature.wasm',
        extractor: extractFSharpSymbols,
        required: false,
    },
    {
        id: 'gleam',
        extensions: ['.gleam'],
        grammarFile: 'tree-sitter-gleam.wasm',
        extractor: extractGleamSymbols,
        required: false,
    },
    {
        id: 'clojure',
        extensions: ['.clj', '.cljs', '.cljc'],
        grammarFile: 'tree-sitter-clojure.wasm',
        extractor: extractClojureSymbols,
        required: false,
    },
    {
        id: 'julia',
        extensions: ['.jl'],
        grammarFile: 'tree-sitter-julia.wasm',
        extractor: extractJuliaSymbols,
        required: false,
    },
    {
        id: 'r',
        extensions: ['.r', '.R'],
        grammarFile: 'tree-sitter-r.wasm',
        extractor: extractRSymbols,
        required: false,
    },
    {
        id: 'erlang',
        extensions: ['.erl', '.hrl'],
        grammarFile: 'tree-sitter-erlang.wasm',
        extractor: extractErlangSymbols,
        required: false,
    },
    {
        id: 'solidity',
        extensions: ['.sol'],
        grammarFile: 'tree-sitter-solidity.wasm',
        extractor: extractSoliditySymbols,
        required: false,
    },
    {
        id: 'objc',
        extensions: ['.m'],
        grammarFile: 'tree-sitter-objc.wasm',
        extractor: extractObjCSymbols,
        required: false,
    },
    {
        id: 'cuda',
        extensions: ['.cu', '.cuh'],
        grammarFile: 'tree-sitter-cuda.wasm',
        extractor: extractCudaSymbols,
        required: false,
    },
    {
        id: 'groovy',
        extensions: ['.groovy', '.gvy'],
        grammarFile: 'tree-sitter-groovy.wasm',
        extractor: extractGroovySymbols,
        required: false,
    },
    {
        id: 'verilog',
        extensions: ['.v', '.sv'],
        grammarFile: 'tree-sitter-verilog.wasm',
        extractor: extractVerilogSymbols,
        required: false,
    },
];
const _extToLang = new Map();
for (const entry of LANGUAGE_REGISTRY) {
    for (const ext of entry.extensions) {
        _extToLang.set(ext, entry);
    }
}
export const SUPPORTED_EXTENSIONS = new Set(_extToLang.keys());
/**
 * WASM-based typeMap backfill for TS/TSX files parsed by the native engine.
 * Serves two purposes:
 * 1. Compatibility with older native binaries that don't emit typeMap (< 3.2.0).
 * 2. Workaround for native parser scope-collision bugs — when the same variable
 *    name appears at multiple scopes, native type extraction can produce
 *    incorrect results. WASM's JS-based extractor handles scope traversal
 *    more accurately. TODO: Remove purpose (2) once the Rust extractor handles
 *    nested scopes correctly.
 *
 * Uses tree-sitter AST extraction instead of regex to avoid false positives from
 * matches inside comments and string literals.
 */
async function backfillTypeMap(filePath, source) {
    let code = source;
    if (!code) {
        try {
            code = fs.readFileSync(filePath, 'utf-8');
        }
        catch (e) {
            debug(`backfillTypeMap: failed to read ${filePath}: ${toErrorMessage(e)}`);
            return { typeMap: new Map(), backfilled: false };
        }
    }
    const pool = getWasmWorkerPool();
    // Extract-only — no visitor walk, we only need the typeMap from this pass.
    const output = await pool.parse(filePath, code, EXTRACT_ONLY);
    if (!output || output.typeMap.size === 0) {
        return { typeMap: new Map(), backfilled: false };
    }
    return { typeMap: output.typeMap, backfilled: true };
}
/**
 * WASM extraction helper: picks the right extractor based on file extension.
 */
function wasmExtractSymbols(parsers, filePath, code) {
    const parser = getParser(parsers, filePath);
    if (!parser)
        return null;
    let tree;
    try {
        tree = parser.parse(code);
    }
    catch (e) {
        warn(`Parse error in ${filePath}: ${e.message}`);
        return null;
    }
    if (!tree)
        return null;
    const ext = path.extname(filePath);
    const entry = _extToLang.get(ext);
    if (!entry)
        return null;
    const query = _queryCache.get(entry.id) ?? undefined;
    // Query (web-tree-sitter) is structurally compatible with TreeSitterQuery at runtime
    let symbols;
    try {
        symbols = entry.extractor(tree, filePath, query);
    }
    catch (e) {
        warn(`Extractor error in ${filePath}: ${e.message}`);
        // Free WASM tree to prevent memory leak — web-tree-sitter trees are backed
        // by WASM linear memory and are not garbage-collected automatically.
        if (typeof tree.delete === 'function')
            tree.delete();
        return null;
    }
    return symbols ? { symbols, tree, langId: entry.id } : null;
}
/**
 * Parse a single file and return normalized symbols.
 */
export async function parseFileAuto(filePath, source, opts = {}) {
    const { native } = resolveEngine(opts);
    if (native) {
        const result = native.parseFile(filePath, source, true, true);
        if (!result)
            return null;
        const patched = patchNativeResult(result);
        // Always backfill typeMap for TS/TSX from WASM — native parser's type
        // extraction can produce incorrect scope-collision results. Non-TS files
        // are skipped to stay consistent with the batch path (backfillTypeMapBatch).
        if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
            const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
            if (backfilled) {
                patched.typeMap = typeMap;
                patched._typeMapBackfilled = true;
            }
        }
        return patched;
    }
    // WASM path — dispatch to isolated worker
    const pool = getWasmWorkerPool();
    return pool.parse(filePath, source, FULL_ANALYSIS);
}
/** Backfill typeMap via WASM for TS/TSX files parsed by the native engine. */
async function backfillTypeMapBatch(needsTypeMap, result) {
    const tsFiles = needsTypeMap.filter(({ filePath }) => TS_BACKFILL_EXTS.has(path.extname(filePath)));
    if (tsFiles.length === 0)
        return;
    const pool = getWasmWorkerPool();
    for (const { filePath, relPath } of tsFiles) {
        let code;
        try {
            code = fs.readFileSync(filePath, 'utf-8');
        }
        catch (e) {
            debug(`batchExtract: cannot read ${filePath}: ${toErrorMessage(e)}`);
            continue;
        }
        const output = await pool.parse(filePath, code, EXTRACT_ONLY);
        if (!output || output.typeMap.size === 0)
            continue;
        const symbols = result.get(relPath);
        if (!symbols)
            continue;
        symbols.typeMap = output.typeMap;
        symbols._typeMapBackfilled = true;
    }
}
/**
 * Parse files via WASM engine, returning a Map<relPath, symbols>.
 *
 * Each file is dispatched to the WASM worker pool. The worker parses, extracts,
 * and runs all AST analyses (complexity, CFG, dataflow, ast-store) in its own
 * thread, returning fully pre-computed ExtractorOutput. V8 fatal errors from
 * tree-sitter WASM (#965) kill only the worker — the pool skips the file and
 * restarts the worker for the next one.
 *
 * `_tree` is NEVER set by this path. All downstream analyses operate on the
 * pre-computed `astNodes` / `dataflow` / `def.complexity` / `def.cfg` fields.
 */
async function parseFilesWasm(filePaths, rootDir, analysis = FULL_ANALYSIS) {
    const result = new Map();
    const pool = getWasmWorkerPool();
    for (const filePath of filePaths) {
        if (!_extToLang.has(path.extname(filePath).toLowerCase()))
            continue;
        let code;
        try {
            code = fs.readFileSync(filePath, 'utf-8');
        }
        catch (err) {
            warn(`Skipping ${path.relative(rootDir, filePath)}: ${err.message}`);
            continue;
        }
        const output = await pool.parse(filePath, code, analysis);
        if (output) {
            const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
            result.set(relPath, output);
        }
    }
    return result;
}
/**
 * Files at or below this count use the inline parse path (no worker spawn).
 *
 * Sized for typical engine-parity drops: a handful of fixture files in one
 * or two languages (the recurring HCL case is 4 files). Above this, the
 * worker-pool's IPC + crash-isolation cost (#965) is amortized over enough
 * parse work to be worth paying; below it, the ~1–2s cold-start dominates.
 */
const INLINE_BACKFILL_THRESHOLD = 16;
/**
 * Inline WASM parse (no worker) for small file batches.
 *
 * Used by the engine-parity backfill path when the native engine drops a
 * handful of files (typically test fixtures). The worker pool's per-call
 * IPC + grammar-init overhead can cost 1–2s on slow CI runners — for a
 * 4-file backfill, that dwarfs the ~10ms of actual parse work.
 *
 * Returns symbols with `_tree` set so `runAnalyses` can run AST/CFG/dataflow
 * visitors via the unified walker (mirrors how WASM-engine results behaved
 * before the worker pool was introduced).
 */
async function parseFilesWasmInline(filePaths, rootDir) {
    const result = new Map();
    if (filePaths.length === 0)
        return result;
    const parsers = await ensureParsersForFiles(filePaths);
    for (const filePath of filePaths) {
        if (!_extToLang.has(path.extname(filePath).toLowerCase()))
            continue;
        let code;
        try {
            code = fs.readFileSync(filePath, 'utf-8');
        }
        catch (err) {
            warn(`Skipping ${path.relative(rootDir, filePath)}: ${err.message}`);
            continue;
        }
        const extracted = wasmExtractSymbols(parsers, filePath, code);
        if (!extracted)
            continue;
        const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
        const symbols = extracted.symbols;
        symbols._tree = extracted.tree;
        symbols._langId = extracted.langId;
        result.set(relPath, symbols);
    }
    return result;
}
/**
 * Backfill helper: small batches use the inline (main-thread) path; larger
 * batches keep the worker-pool isolation against tree-sitter WASM crashes
 * (#965). Threshold matches typical engine-parity drop sizes (a few fixture
 * files in one or two languages).
 *
 * `opts.symbolsOnly` skips the AST/complexity/CFG/dataflow visitors in the
 * worker (and their result serialization across the thread boundary) for
 * callers that only consume definitions/calls/typeMap — the native
 * orchestrator's prototype-methods and this-dispatch post-passes. Callers
 * that ingest the files into the DB (dropped-language backfill) must keep
 * the default full analysis.
 */
export async function parseFilesWasmForBackfill(filePaths, rootDir, opts = {}) {
    if (filePaths.length <= INLINE_BACKFILL_THRESHOLD) {
        return parseFilesWasmInline(filePaths, rootDir);
    }
    return parseFilesWasm(filePaths, rootDir, opts.symbolsOnly ? EXTRACT_ONLY : FULL_ANALYSIS);
}
/**
 * Run the native engine over `filePaths` and ingest the results into `result`.
 * Returns the set of file paths the native engine successfully parsed and the
 * TS/TSX files that need a typeMap backfill pass.
 */
function ingestNativeResults(native, filePaths, rootDir, result) {
    // Always extract all analysis data (dataflow + AST nodes) during native parse.
    // This eliminates the need for any downstream WASM re-parse or native standalone calls.
    const nativeResults = native.parseFilesFull
        ? native.parseFilesFull(filePaths, rootDir)
        : native.parseFiles(filePaths, rootDir, true, true);
    const needsTypeMap = [];
    const nativeParsed = new Set();
    for (const r of nativeResults) {
        if (!r)
            continue;
        nativeParsed.add(r.file);
        const patched = patchNativeResult(r);
        const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
        result.set(relPath, patched);
        // Always backfill TS/TSX type maps from WASM — the native parser's type
        // extraction can produce incorrect results when the same variable name
        // appears at multiple scopes (e.g. `node: TreeSitterNode` in one function
        // vs `node: NodeRow` in another). The WASM JS extractor handles scope
        // traversal order more accurately.
        if (TS_BACKFILL_EXTS.has(path.extname(r.file))) {
            needsTypeMap.push({ filePath: r.file, relPath });
        }
    }
    return { nativeParsed, needsTypeMap };
}
/**
 * Engine parity: native may silently drop files whose extensions are in
 * SUPPORTED_EXTENSIONS (because a WASM grammar exists) but whose Rust
 * extractor/grammar is missing or fails. WASM handles these — fall back so
 * both engines process the same file set (#967). Restrict to installed WASM
 * grammars so we don't warn about files that neither engine can parse.
 */
async function backfillNativeDrops(filePaths, nativeParsed, rootDir, result) {
    const installedExts = getInstalledWasmExtensions();
    const dropped = filePaths.filter((f) => !nativeParsed.has(f) && installedExts.has(path.extname(f).toLowerCase()));
    if (dropped.length === 0)
        return;
    warn(`Native engine dropped ${dropped.length} file(s); falling back to WASM for parity`);
    const wasmResults = await parseFilesWasmForBackfill(dropped, rootDir);
    for (const [relPath, symbols] of wasmResults) {
        result.set(relPath, symbols);
    }
}
/**
 * Parse multiple files in bulk and return a Map<relPath, symbols>.
 */
export async function parseFilesAuto(filePaths, rootDir, opts = {}) {
    const { native } = resolveEngine(opts);
    if (!native)
        return parseFilesWasm(filePaths, rootDir);
    const result = new Map();
    const { nativeParsed, needsTypeMap } = ingestNativeResults(native, filePaths, rootDir, result);
    if (needsTypeMap.length > 0) {
        await backfillTypeMapBatch(needsTypeMap, result);
    }
    await backfillNativeDrops(filePaths, nativeParsed, rootDir, result);
    return result;
}
/**
 * Report which engine is active.
 */
export function getActiveEngine(opts = {}) {
    const { name, native } = resolveEngine(opts);
    const binaryVersion = native && typeof native.engineVersion === 'function' ? native.engineVersion() : null;
    // The display version prefers the platform package.json so the "Using native
    // engine (vX)" log matches the npm release the user installed. The Rust
    // orchestrator's check_version_mismatch compares against CARGO_PKG_VERSION
    // (the binary's own value), so build_meta writes must use `binaryVersion`,
    // not this display value — see pipeline.ts and finalize.ts (#1066).
    let version = binaryVersion;
    if (native) {
        try {
            version = getNativePackageVersion() ?? version;
        }
        catch (e) {
            debug(`getNativePackageVersion failed: ${e.message}`);
        }
    }
    return { name, version, binaryVersion };
}
/**
 * Create a native ParseTreeCache for incremental parsing.
 * Returns null if the native engine is unavailable (WASM fallback).
 */
export function createParseTreeCache() {
    const native = loadNative();
    if (!native?.ParseTreeCache)
        return null;
    return new native.ParseTreeCache();
}
/**
 * Parse a file incrementally using the cache, or fall back to full parse.
 */
export async function parseFileIncremental(cache, filePath, source, opts = {}) {
    if (cache) {
        const result = cache.parseFile(filePath, source);
        if (!result)
            return null;
        const patched = patchNativeResult(result);
        // Always backfill typeMap for TS/TSX from WASM (see parseFileAuto comment).
        if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
            const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
            if (backfilled) {
                patched.typeMap = typeMap;
                patched._typeMapBackfilled = true;
            }
        }
        return patched;
    }
    return parseFileAuto(filePath, source, opts);
}
//# sourceMappingURL=parser.js.map