/**
 * Tree-sitter parser initialization and utilities
 *
 * This module provides a universal parser that works in Node.js, browsers, and Workers.
 */
import { Parser, Language, Tree } from 'web-tree-sitter';
// Lazy-loaded Node.js modules for WASM path resolution
// These are only used in Node.js environments, not in browsers
let nodeModules = null;
let moduleDir = null;
/**
 * Initialize Node.js modules for path resolution (lazy, only in Node.js)
 */
async function getNodeModules() {
    if (nodeModules)
        return nodeModules;
    try {
        // Use Function constructor to hide imports from bundlers (esbuild can't
        // statically analyze through `new Function`, so 'url'/'path'/'fs' do not
        // leak into browser bundles).
        //
        // Caveat: `new Function('m', 'return import(m)')` creates an isolated
        // script context. In Node VM sandboxes that don't set a dynamic-import
        // callback (e.g., Vitest's default pool), this throws
        // "A dynamic import callback was not specified". Consumers running in
        // such environments must pass `wasmPath` explicitly to `initParser` /
        // `loadLanguage` to bypass this discovery path.
        const dynamicImport = new Function('m', 'return import(m)');
        const [urlMod, pathMod, fsMod] = await Promise.all([
            dynamicImport('url'),
            dynamicImport('path'),
            dynamicImport('fs'),
        ]);
        nodeModules = {
            fileURLToPath: urlMod.fileURLToPath,
            dirname: pathMod.dirname,
            join: pathMod.join,
            existsSync: fsMod.existsSync,
        };
        // Calculate module directory
        moduleDir = nodeModules.dirname(nodeModules.fileURLToPath(import.meta.url));
        return nodeModules;
    }
    catch {
        // Not in Node.js environment (browser/worker), or imports failed
        return null;
    }
}
// Re-export types using the correct names
export { Language, Tree };
let parserInitialized = false;
let parserInitializing = null;
const loadedLanguages = new Map();
const loadingLanguages = new Map();
// One reusable Parser per language. `new Parser()` allocates a struct in the
// tree-sitter WASM heap; reusing the instance avoids unbounded heap growth
// when many files are parsed in a single process (see issue #16).
const cachedParsers = new Map();
let configuredLanguagePaths = {};
let configuredLanguageModules = {};
/**
 * Initialize the Tree-sitter parser runtime.
 * Must be called before parsing any code.
 * Thread-safe: handles concurrent initialization attempts.
 */
export async function initParser(options = {}) {
    if (parserInitialized) {
        return;
    }
    // If already initializing, wait for that to complete
    if (parserInitializing) {
        return parserInitializing;
    }
    // Store language paths/modules for later use in loadLanguage
    if (options.languagePaths) {
        configuredLanguagePaths = options.languagePaths;
    }
    if (options.languageModules) {
        configuredLanguageModules = options.languageModules;
    }
    // Create initialization promise and store it
    parserInitializing = (async () => {
        if (options.wasmModule) {
            // Use pre-compiled module (for Cloudflare Workers where dynamic WASM compilation is blocked)
            // instantiateWasm bypasses emscripten's default WebAssembly.instantiate(bytes) path
            await Parser.init({
                locateFile: () => 'web-tree-sitter.wasm',
                instantiateWasm(imports, callback) {
                    const instance = new WebAssembly.Instance(options.wasmModule, imports);
                    // Emscripten's receiveInstance expects (instance, module) for getDylinkMetadata
                    callback(instance, options.wasmModule);
                    return instance.exports;
                },
            });
        }
        else {
            const wasmPath = options.wasmPath ?? await getDefaultWasmPath();
            await Parser.init({
                locateFile: () => wasmPath,
            });
        }
        parserInitialized = true;
        parserInitializing = null;
    })();
    return parserInitializing;
}
/**
 * Load a language grammar for parsing.
 * Thread-safe: handles concurrent load attempts for the same language.
 */
export async function loadLanguage(language, wasmPath) {
    if (!parserInitialized) {
        throw new Error('Parser not initialized. Call initParser() first.');
    }
    // Check cache first
    const cached = loadedLanguages.get(language);
    if (cached) {
        return cached;
    }
    // If already loading this language, wait for that to complete
    const loading = loadingLanguages.get(language);
    if (loading) {
        return loading;
    }
    // Check for pre-compiled module first (Cloudflare Workers)
    const wasmModule = configuredLanguageModules[language];
    if (wasmModule) {
        const loadPromise = (async () => {
            // Pass WebAssembly.Module directly - web-tree-sitter's internal code
            // handles this via `binary instanceof WebAssembly.Module` check
            const lang = await Language.load(wasmModule);
            loadedLanguages.set(language, lang);
            return lang;
        })();
        loadingLanguages.set(language, loadPromise);
        return loadPromise;
    }
    // Use explicit wasmPath, configured languagePaths, or default
    const path = wasmPath ?? configuredLanguagePaths[language] ?? await getDefaultLanguagePath(language);
    // Create loading promise and store it
    const loadPromise = (async () => {
        const lang = await Language.load(path);
        loadedLanguages.set(language, lang);
        loadingLanguages.delete(language);
        return lang;
    })();
    loadingLanguages.set(language, loadPromise);
    return loadPromise;
}
/**
 * Get a parser instance configured for the specified language. The Parser is
 * cached per-language and reused across calls — see `cachedParsers` above.
 * Use `createFreshParser()` if you need a non-shared instance.
 */
export async function createParser(language) {
    const cached = cachedParsers.get(language);
    if (cached) {
        return cached;
    }
    const lang = await loadLanguage(language);
    const parser = new Parser();
    parser.setLanguage(lang);
    cachedParsers.set(language, parser);
    return parser;
}
/**
 * Create a non-cached parser. Caller is responsible for `parser.delete()` when
 * done. Prefer `createParser` unless isolation is required.
 */
export async function createFreshParser(language) {
    const lang = await loadLanguage(language);
    const parser = new Parser();
    parser.setLanguage(lang);
    return parser;
}
/**
 * Parse source code and return the syntax tree.
 *
 * IMPORTANT: The returned Tree holds memory in the tree-sitter WASM heap. Call
 * `disposeTree(tree)` once you no longer need its nodes to free that memory.
 * Failing to dispose causes unbounded WASM heap growth at scale (#16).
 */
export async function parse(code, language) {
    const parser = await createParser(language);
    const tree = parser.parse(code);
    if (!tree) {
        throw new Error('Failed to parse code');
    }
    return tree;
}
/**
 * Free the WASM memory backing a parsed Tree. Safe to call multiple times.
 */
export function disposeTree(tree) {
    if (!tree)
        return;
    try {
        tree.delete();
    }
    catch {
        // Already deleted or invalid handle — ignore.
    }
}
/**
 * Walk the syntax tree and call the visitor for each node.
 */
export function walkTree(node, visitor) {
    visitor(node);
    for (let i = 0; i < node.childCount; i++) {
        const child = node.child(i);
        if (child) {
            walkTree(child, visitor);
        }
    }
}
/**
 * Find all nodes of a specific type in the tree.
 */
export function findNodes(node, type) {
    const results = [];
    walkTree(node, (n) => {
        if (n.type === type) {
            results.push(n);
        }
    });
    return results;
}
/**
 * Collect all nodes of specified types in a single tree traversal.
 * Much more efficient than calling findNodes multiple times.
 */
export function collectAllNodes(node, types) {
    const cache = new Map();
    for (const type of types) {
        cache.set(type, []);
    }
    walkTree(node, (n) => {
        if (types.has(n.type)) {
            cache.get(n.type).push(n);
        }
    });
    return cache;
}
/**
 * Get nodes from cache, falling back to findNodes if not cached.
 */
export function getNodesFromCache(node, type, cache) {
    if (cache?.has(type)) {
        return cache.get(type);
    }
    return findNodes(node, type);
}
/**
 * Find the first ancestor of a node that matches the given type.
 */
export function findAncestor(node, type) {
    let current = node.parent;
    while (current) {
        if (current.type === type) {
            return current;
        }
        current = current.parent;
    }
    return null;
}
/**
 * Get the text of a node from the source code.
 */
export function getNodeText(node) {
    return node.text;
}
/**
 * Get default WASM path for the Tree-sitter runtime.
 * This is environment-specific and may need to be overridden.
 */
async function getDefaultWasmPath() {
    const mods = await getNodeModules();
    if (mods && moduleDir) {
        // In Node.js, resolve relative to this module's location
        // This works whether circle-ir is in node_modules or run from source
        const packageRoot = mods.join(moduleDir, '..', '..');
        // First, try the package's own dist/wasm/ directory (shipped with npm package).
        // This is the most reliable location when circle-ir is installed as a dependency,
        // since it doesn't depend on node_modules hoisting structure.
        const distWasmPath = mods.join(packageRoot, 'dist', 'wasm', 'web-tree-sitter.wasm');
        if (mods.existsSync(distWasmPath)) {
            return distWasmPath;
        }
        // Then try the package's node_modules (installed package)
        const packageNodeModulesPath = mods.join(packageRoot, 'node_modules', 'web-tree-sitter', 'web-tree-sitter.wasm');
        if (mods.existsSync(packageNodeModulesPath)) {
            return packageNodeModulesPath;
        }
    }
    // Fallback to CWD node_modules (development or browser)
    return 'node_modules/web-tree-sitter/web-tree-sitter.wasm';
}
/**
 * Get default path for a language grammar WASM file.
 */
async function getDefaultLanguagePath(language) {
    const mods = await getNodeModules();
    if (mods && moduleDir) {
        // In Node.js, resolve relative to this module's location
        const packageRoot = mods.join(moduleDir, '..', '..');
        // First, try dist/wasm/ (shipped with npm package, works regardless of hoisting)
        const distWasmPath = mods.join(packageRoot, 'dist', 'wasm', `tree-sitter-${language}.wasm`);
        if (mods.existsSync(distWasmPath)) {
            return distWasmPath;
        }
        // Then try the source wasm/ directory (development)
        const packageWasmPath = mods.join(packageRoot, 'wasm', `tree-sitter-${language}.wasm`);
        if (mods.existsSync(packageWasmPath)) {
            return packageWasmPath;
        }
    }
    // Fallback to relative path (development or browser)
    return `wasm/tree-sitter-${language}.wasm`;
}
/**
 * Check if the parser has been initialized.
 */
export function isInitialized() {
    return parserInitialized;
}
/**
 * Check if a language has been loaded.
 */
export function isLanguageLoaded(language) {
    return loadedLanguages.has(language);
}
/**
 * Reset the parser state (mainly for testing).
 *
 * Disposes any cached Parser instances and clears the language cache so the
 * next `initParser()` call starts with a clean WASM heap.
 */
export function resetParser() {
    for (const parser of cachedParsers.values()) {
        try {
            parser.delete();
        }
        catch { /* ignore */ }
    }
    cachedParsers.clear();
    loadedLanguages.clear();
    loadingLanguages.clear();
    configuredLanguagePaths = {};
    configuredLanguageModules = {};
    parserInitialized = false;
    parserInitializing = null;
}
//# sourceMappingURL=parser.js.map