/**
 * Symbol Table for Cross-File Resolution
 *
 * Tracks what each file exports (classes, methods, fields) and imports,
 * enabling resolution of cross-file references.
 */
/**
 * SymbolTable - Tracks exports and imports across project files
 */
export class SymbolTable {
    // All exported symbols by FQN
    exports = new Map();
    // Simple name -> FQNs (for ambiguous resolution)
    nameToFqns = new Map();
    // File -> its imports
    fileImports = new Map();
    // Package -> exported types in that package
    packageTypes = new Map();
    // FQN -> file path
    fqnToFile = new Map();
    /**
     * Add exports and imports from a CircleIR analysis result
     */
    addFromIR(ir, filePath) {
        const pkg = ir.meta.package || '';
        // Process types and their members
        for (const type of ir.types) {
            this.addTypeExports(type, filePath, pkg);
        }
        // Process imports
        this.addFileImports(ir.imports, filePath);
        // Process explicit exports if available
        for (const exp of ir.exports) {
            this.addExplicitExport(exp, filePath, pkg);
        }
    }
    /**
     * Add type and its members as exports
     */
    addTypeExports(type, filePath, pkg) {
        const fqn = pkg ? `${pkg}.${type.name}` : type.name;
        const visibility = this.getVisibility(type.annotations);
        // Add type itself
        const typeExport = {
            name: type.name,
            fqn,
            kind: type.kind,
            file: filePath,
            line: type.start_line,
            visibility,
        };
        this.registerExport(typeExport);
        // Track in package
        if (!this.packageTypes.has(pkg)) {
            this.packageTypes.set(pkg, new Set());
        }
        this.packageTypes.get(pkg).add(fqn);
        // Add methods
        for (const method of type.methods) {
            this.addMethodExport(method, fqn, filePath);
        }
        // Add fields
        for (const field of type.fields) {
            this.addFieldExport(field, fqn, filePath);
        }
    }
    /**
     * Add a method as an export
     */
    addMethodExport(method, parentFqn, filePath) {
        const methodFqn = `${parentFqn}.${method.name}`;
        const signature = method.parameters.map(p => p.type || 'Object').join(',');
        const visibility = this.getVisibilityFromModifiers(method.modifiers);
        const methodExport = {
            name: method.name,
            fqn: methodFqn,
            kind: 'method',
            file: filePath,
            line: method.start_line,
            visibility,
            parentType: parentFqn,
            signature,
        };
        this.registerExport(methodExport);
    }
    /**
     * Add a field as an export
     */
    addFieldExport(field, parentFqn, filePath) {
        const fieldFqn = `${parentFqn}.${field.name}`;
        const visibility = this.getVisibilityFromModifiers(field.modifiers);
        const fieldExport = {
            name: field.name,
            fqn: fieldFqn,
            kind: 'field',
            file: filePath,
            line: 0, // Fields don't have line numbers in current TypeInfo
            visibility,
            parentType: parentFqn,
        };
        this.registerExport(fieldExport);
    }
    /**
     * Register an export in all indexes
     */
    registerExport(symbol) {
        this.exports.set(symbol.fqn, symbol);
        this.fqnToFile.set(symbol.fqn, symbol.file);
        if (!this.nameToFqns.has(symbol.name)) {
            this.nameToFqns.set(symbol.name, new Set());
        }
        this.nameToFqns.get(symbol.name).add(symbol.fqn);
    }
    /**
     * Add imports for a file
     */
    addFileImports(imports, filePath) {
        const fileImport = {
            imports,
            resolved: new Map(),
            wildcardPackages: [],
        };
        for (const imp of imports) {
            if (imp.is_wildcard && imp.from_package) {
                // Wildcard import: import java.util.*
                fileImport.wildcardPackages.push(imp.from_package);
            }
            else if (imp.from_package && imp.imported_name !== '*') {
                // Specific import: import java.util.ArrayList
                const fqn = `${imp.from_package}.${imp.imported_name}`;
                fileImport.resolved.set(imp.imported_name, fqn);
            }
        }
        this.fileImports.set(filePath, fileImport);
    }
    /**
     * Add explicit export declaration
     */
    addExplicitExport(exp, filePath, pkg) {
        const fqn = pkg ? `${pkg}.${exp.symbol}` : exp.symbol;
        // Only add if not already registered (types take precedence)
        if (!this.exports.has(fqn)) {
            const symbol = {
                name: exp.symbol,
                fqn,
                kind: exp.kind,
                file: filePath,
                line: 0,
                visibility: exp.visibility,
            };
            this.registerExport(symbol);
        }
    }
    /**
     * Resolve a simple name to its FQN from a given file's context
     */
    resolveSymbol(name, fromFile) {
        // Check if already FQN
        if (this.exports.has(name)) {
            return this.exports.get(name);
        }
        // Check file's specific imports
        const fileImport = this.fileImports.get(fromFile);
        if (fileImport) {
            const resolved = fileImport.resolved.get(name);
            if (resolved && this.exports.has(resolved)) {
                return this.exports.get(resolved);
            }
            // Check wildcard imports
            for (const pkg of fileImport.wildcardPackages) {
                const fqn = `${pkg}.${name}`;
                if (this.exports.has(fqn)) {
                    return this.exports.get(fqn);
                }
            }
        }
        // Check by simple name (may be ambiguous)
        const fqns = this.nameToFqns.get(name);
        if (fqns && fqns.size === 1) {
            return this.exports.get(Array.from(fqns)[0]);
        }
        return undefined;
    }
    /**
     * Resolve a type name to FQN, considering imports
     */
    resolveTypeName(name, fromFile) {
        const symbol = this.resolveSymbol(name, fromFile);
        if (symbol && (symbol.kind === 'class' || symbol.kind === 'interface' || symbol.kind === 'enum')) {
            return symbol.fqn;
        }
        return undefined;
    }
    /**
     * Get all methods of a type by FQN
     */
    getMethodsOfType(typeFqn) {
        const results = [];
        for (const symbol of this.exports.values()) {
            if (symbol.kind === 'method' && symbol.parentType === typeFqn) {
                results.push(symbol);
            }
        }
        return results;
    }
    /**
     * Find a method by name in a type
     */
    findMethod(typeFqn, methodName) {
        const methodFqn = `${typeFqn}.${methodName}`;
        return this.exports.get(methodFqn);
    }
    /**
     * Get the file where a symbol is defined
     */
    getFile(fqn) {
        return this.fqnToFile.get(fqn);
    }
    /**
     * Get all exported symbols from a file
     */
    getFileExports(filePath) {
        const results = [];
        for (const symbol of this.exports.values()) {
            if (symbol.file === filePath) {
                results.push(symbol);
            }
        }
        return results;
    }
    /**
     * Get all types in a package
     */
    getPackageTypes(packageName) {
        return Array.from(this.packageTypes.get(packageName) || []);
    }
    /**
     * Get all known packages
     */
    getPackages() {
        return Array.from(this.packageTypes.keys());
    }
    /**
     * Check if a symbol exists
     */
    hasSymbol(fqn) {
        return this.exports.has(fqn);
    }
    /**
     * Get symbol by FQN
     */
    getSymbol(fqn) {
        return this.exports.get(fqn);
    }
    /**
     * Get all possible FQNs for a simple name
     */
    getPossibleFqns(simpleName) {
        return Array.from(this.nameToFqns.get(simpleName) || []);
    }
    /**
     * Get imports for a file
     */
    getFileImports(filePath) {
        return this.fileImports.get(filePath)?.imports || [];
    }
    /**
     * Get statistics
     */
    getStats() {
        let types = 0, methods = 0, fields = 0;
        const files = new Set();
        for (const symbol of this.exports.values()) {
            files.add(symbol.file);
            if (symbol.kind === 'class' || symbol.kind === 'interface' || symbol.kind === 'enum') {
                types++;
            }
            else if (symbol.kind === 'method') {
                methods++;
            }
            else if (symbol.kind === 'field') {
                fields++;
            }
        }
        return {
            totalSymbols: this.exports.size,
            types,
            methods,
            fields,
            files: files.size,
            packages: this.packageTypes.size,
        };
    }
    /**
     * Clear all data
     */
    clear() {
        this.exports.clear();
        this.nameToFqns.clear();
        this.fileImports.clear();
        this.packageTypes.clear();
        this.fqnToFile.clear();
    }
    // --- Private helpers ---
    /**
     * Extract visibility from annotations (for types)
     */
    getVisibility(_annotations) {
        // Types in Java are public by default if in their own file
        // For simplicity, assume public unless we find evidence otherwise
        return 'public';
    }
    /**
     * Extract visibility from modifiers
     */
    getVisibilityFromModifiers(modifiers) {
        if (modifiers.includes('public'))
            return 'public';
        if (modifiers.includes('protected'))
            return 'protected';
        if (modifiers.includes('private'))
            return 'private';
        return 'package';
    }
}
/**
 * Build a symbol table from multiple IR results
 */
export function buildSymbolTable(files) {
    const table = new SymbolTable();
    for (const { ir, path } of files) {
        table.addFromIR(ir, path);
    }
    return table;
}
//# sourceMappingURL=symbol-table.js.map