"use strict";
// ---------------------------------------------------------------------------
// Module Analyzer
//
// Per-module forward scan that builds a ModuleEnvironment by processing
// top-level statements in source order using the constant propagation lattice.
// ---------------------------------------------------------------------------
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleEnvironment = void 0;
exports.analyzeModule = analyzeModule;
exports.tryInlineCall = tryInlineCall;
const typescript_1 = __importDefault(require("typescript"));
const lattice_js_1 = require("./lattice.js");
const expressionEvaluator_js_1 = require("./expressionEvaluator.js");
const pureFunctionInliner_js_1 = require("./pureFunctionInliner.js");
class ModuleEnvironment {
    sourceFile;
    values = new Map();
    imports = new Map();
    functions = new Map();
    assignmentCounts = new Map();
    exports = new Map();
    starReExports = [];
    checker;
    importResolver;
    namespaceResolver;
    constructor(sourceFile, checker, importResolver, namespaceResolver) {
        this.sourceFile = sourceFile;
        this.checker = checker;
        this.importResolver = importResolver;
        this.namespaceResolver = namespaceResolver;
    }
    get(symbol) {
        return this.values.get(symbol) ?? (0, lattice_js_1.top)();
    }
    set(symbol, value) {
        this.values.set(symbol, value);
    }
    /**
     * Resolve a symbol's value — handles imports on demand.
     */
    resolve(symbol) {
        // Check local values first
        const local = this.values.get(symbol);
        if (local !== undefined && local.kind !== 'top')
            return local;
        // Check if it's an import
        const importInfo = this.imports.get(symbol);
        if (importInfo && this.importResolver) {
            if (importInfo.isNamespace && this.namespaceResolver) {
                const nsValue = this.namespaceResolver(this.sourceFile, importInfo.moduleSpecifier);
                this.values.set(symbol, nsValue);
                return nsValue;
            }
            const resolved = this.importResolver(this.sourceFile, importInfo.moduleSpecifier, importInfo.importedName);
            this.values.set(symbol, resolved);
            return resolved;
        }
        return local ?? (0, lattice_js_1.bottom)(`Symbol '${symbol.getName()}' not found in module scope`);
    }
    markImport(symbol, info) {
        this.imports.set(symbol, info);
        this.values.set(symbol, (0, lattice_js_1.top)()); // deferred resolution
    }
    markExport(name, symbol) {
        this.exports.set(name, symbol);
    }
    addStarReExport(moduleSpecifier) {
        this.starReExports.push(moduleSpecifier);
    }
    getExport(name) {
        // Check local exports first
        const sym = this.exports.get(name);
        if (sym)
            return this.resolve(sym);
        // Check star re-exports (export * from './mod')
        if (this.importResolver) {
            for (const moduleSpec of this.starReExports) {
                const value = this.importResolver(this.sourceFile, moduleSpec, name);
                if (value.kind !== 'bottom')
                    return value;
            }
        }
        return (0, lattice_js_1.bottom)(`Export '${name}' not found in ${this.sourceFile.fileName}`);
    }
    getAllExports() {
        const result = new Map();
        // Star re-exports first (so local exports override)
        if (this.importResolver && this.namespaceResolver) {
            for (const moduleSpec of this.starReExports) {
                const nsValue = this.namespaceResolver(this.sourceFile, moduleSpec);
                if ((0, lattice_js_1.isConstant)(nsValue) && typeof nsValue.value === 'object' && nsValue.value !== null) {
                    for (const [key, val] of Object.entries(nsValue.value)) {
                        if (!result.has(key)) {
                            result.set(key, (0, lattice_js_1.constant)(val));
                        }
                    }
                }
            }
        }
        // Local exports override
        for (const [name, sym] of this.exports) {
            result.set(name, this.resolve(sym));
        }
        return result;
    }
    setFunction(symbol, decl) {
        this.functions.set(symbol, decl);
    }
    getFunction(symbol) {
        return this.functions.get(symbol);
    }
    incrementAssignment(symbol) {
        const count = (this.assignmentCounts.get(symbol) ?? 0) + 1;
        this.assignmentCounts.set(symbol, count);
        return count;
    }
    getAssignmentCount(symbol) {
        return this.assignmentCounts.get(symbol) ?? 0;
    }
    /** Iterate all symbol → value pairs. */
    entries() {
        return this.values.entries();
    }
}
exports.ModuleEnvironment = ModuleEnvironment;
// ---------------------------------------------------------------------------
// Module analysis
// ---------------------------------------------------------------------------
/**
 * Analyze a source file's top-level statements, building a ModuleEnvironment
 * with lattice values for every top-level variable.
 */
function analyzeModule(sourceFile, checker, importResolver, namespaceResolver, inliningDepth = 1) {
    const env = new ModuleEnvironment(sourceFile, checker, importResolver, namespaceResolver);
    // Build the symbol resolver for the expression evaluator
    const symbolResolver = (sym) => env.resolve(sym);
    // Build the call resolver for pure function inlining
    const callResolver = inliningDepth > 0
        ? (expr, evaluator) => {
            return tryInlineCall(expr, evaluator, env, checker, inliningDepth);
        }
        : undefined;
    const evaluator = new expressionEvaluator_js_1.ExpressionEvaluator(checker, symbolResolver, callResolver);
    for (const stmt of sourceFile.statements) {
        processStatement(stmt, env, evaluator, checker);
    }
    return env;
}
// ---------------------------------------------------------------------------
// Statement processing
// ---------------------------------------------------------------------------
function processStatement(stmt, env, evaluator, checker) {
    // Import declarations
    if (typescript_1.default.isImportDeclaration(stmt)) {
        processImport(stmt, env, checker);
        return;
    }
    // Variable statements
    if (typescript_1.default.isVariableStatement(stmt)) {
        const isConst = !!(stmt.declarationList.flags & typescript_1.default.NodeFlags.Const);
        const isExported = stmt.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) ?? false;
        for (const decl of stmt.declarationList.declarations) {
            processVariableDeclaration(decl, isConst, isExported, env, evaluator, checker);
        }
        return;
    }
    // Function declarations
    if (typescript_1.default.isFunctionDeclaration(stmt) && stmt.name) {
        const sym = checker.getSymbolAtLocation(stmt.name);
        if (sym) {
            env.setFunction(sym, stmt);
            // A function declaration doesn't have a lattice value itself
            // (it's used via call inlining, not direct reference)
        }
        if (stmt.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) && stmt.name) {
            const sym = checker.getSymbolAtLocation(stmt.name);
            if (sym)
                env.markExport(stmt.name.text, sym);
        }
        return;
    }
    // Enum declarations
    if (typescript_1.default.isEnumDeclaration(stmt)) {
        processEnum(stmt, env, checker);
        return;
    }
    // Expression statements (assignments)
    if (typescript_1.default.isExpressionStatement(stmt)) {
        processExpressionStatement(stmt, env, evaluator, checker);
        return;
    }
    // Export declarations (re-exports)
    if (typescript_1.default.isExportDeclaration(stmt)) {
        processExportDeclaration(stmt, env, checker);
        return;
    }
    // Other statement types (class, namespace, etc.) — no action needed
}
// ---------------------------------------------------------------------------
// Import processing
// ---------------------------------------------------------------------------
function processImport(stmt, env, checker) {
    if (!typescript_1.default.isStringLiteral(stmt.moduleSpecifier))
        return;
    const moduleSpec = stmt.moduleSpecifier.text;
    const clause = stmt.importClause;
    if (!clause)
        return;
    // Default import: import X from './mod'
    if (clause.name) {
        const sym = checker.getSymbolAtLocation(clause.name);
        if (sym) {
            env.markImport(sym, { moduleSpecifier: moduleSpec, importedName: 'default', isNamespace: false });
        }
    }
    // Named / namespace bindings
    if (clause.namedBindings) {
        if (typescript_1.default.isNamespaceImport(clause.namedBindings)) {
            // import * as M from './mod'
            const sym = checker.getSymbolAtLocation(clause.namedBindings.name);
            if (sym) {
                env.markImport(sym, { moduleSpecifier: moduleSpec, importedName: '*', isNamespace: true });
            }
        }
        else if (typescript_1.default.isNamedImports(clause.namedBindings)) {
            // import { A, B as C } from './mod'
            for (const spec of clause.namedBindings.elements) {
                const localSym = checker.getSymbolAtLocation(spec.name);
                if (localSym) {
                    const importedName = spec.propertyName ? spec.propertyName.text : spec.name.text;
                    env.markImport(localSym, { moduleSpecifier: moduleSpec, importedName, isNamespace: false });
                }
            }
        }
    }
}
// ---------------------------------------------------------------------------
// Variable declaration processing
// ---------------------------------------------------------------------------
function processVariableDeclaration(decl, isConst, isExported, env, evaluator, checker) {
    // Simple identifier binding
    if (typescript_1.default.isIdentifier(decl.name)) {
        const sym = checker.getSymbolAtLocation(decl.name);
        if (!sym)
            return;
        if (isExported)
            env.markExport(decl.name.text, sym);
        if (decl.initializer) {
            const value = evaluator.evaluate(decl.initializer);
            env.set(sym, value);
            env.incrementAssignment(sym);
        }
        else {
            // Uninitialized: let x;
            env.set(sym, (0, lattice_js_1.top)());
        }
        return;
    }
    // Destructuring: const { a, b } = expr  or  const [a, b] = expr
    if (decl.initializer) {
        const baseValue = evaluator.evaluate(decl.initializer);
        expandBindingPattern(decl.name, baseValue, isExported, env, checker);
    }
}
/**
 * Expand a binding pattern (object or array destructuring) into individual
 * variable entries in the environment.
 */
function expandBindingPattern(pattern, baseValue, isExported, env, checker) {
    if (typescript_1.default.isObjectBindingPattern(pattern)) {
        for (const element of pattern.elements) {
            if (element.dotDotDotToken) {
                // Rest pattern: const { a, ...rest } = obj
                const sym = typescript_1.default.isIdentifier(element.name) ? checker.getSymbolAtLocation(element.name) : undefined;
                if (sym) {
                    env.set(sym, (0, lattice_js_1.bottom)('Object rest patterns in destructuring (e.g., const { a, ...rest } = obj) are not supported for constant propagation. Destructure individual properties instead'));
                    env.incrementAssignment(sym);
                    if (isExported && typescript_1.default.isIdentifier(element.name)) {
                        env.markExport(element.name.text, sym);
                    }
                }
                continue;
            }
            const propName = element.propertyName
                ? (typescript_1.default.isIdentifier(element.propertyName) ? element.propertyName.text : typescript_1.default.isStringLiteral(element.propertyName) ? element.propertyName.text : undefined)
                : (typescript_1.default.isIdentifier(element.name) ? element.name.text : undefined);
            if (!propName)
                continue;
            let elemValue;
            if ((0, lattice_js_1.isConstant)(baseValue)) {
                const obj = baseValue.value;
                if (obj !== null && obj !== undefined && typeof obj === 'object') {
                    const val = obj[propName];
                    elemValue = val !== undefined ? (0, lattice_js_1.constant)(val) : (0, lattice_js_1.bottom)(`Property '${propName}' not found in destructured object`);
                }
                else {
                    elemValue = (0, lattice_js_1.bottom)('Destructuring non-object constant');
                }
            }
            else if ((0, lattice_js_1.isBottom)(baseValue)) {
                elemValue = { kind: 'bottom', reason: `destructured from unresolvable value`, rootCause: baseValue.rootCause ?? baseValue, isSafe: baseValue.isSafe };
            }
            else {
                elemValue = (0, lattice_js_1.bottom)('Destructuring non-constant value');
            }
            // Handle default value: const { a = defaultVal } = obj
            if (element.initializer && (0, lattice_js_1.isConstant)(elemValue) && elemValue.value === undefined) {
                const evaluator = new expressionEvaluator_js_1.ExpressionEvaluator(checker, (sym) => env.resolve(sym));
                elemValue = evaluator.evaluate(element.initializer);
            }
            // Nested destructuring
            if (typescript_1.default.isObjectBindingPattern(element.name) || typescript_1.default.isArrayBindingPattern(element.name)) {
                expandBindingPattern(element.name, elemValue, isExported, env, checker);
            }
            else if (typescript_1.default.isIdentifier(element.name)) {
                const sym = checker.getSymbolAtLocation(element.name);
                if (sym) {
                    env.set(sym, elemValue);
                    env.incrementAssignment(sym);
                    if (isExported)
                        env.markExport(element.name.text, sym);
                }
            }
        }
    }
    else if (typescript_1.default.isArrayBindingPattern(pattern)) {
        for (let i = 0; i < pattern.elements.length; i++) {
            const element = pattern.elements[i];
            if (typescript_1.default.isOmittedExpression(element))
                continue;
            let elemValue;
            if (element.dotDotDotToken) {
                // Rest pattern: const [a, ...rest] = arr
                if ((0, lattice_js_1.isConstant)(baseValue) && Array.isArray(baseValue.value)) {
                    elemValue = (0, lattice_js_1.constant)(baseValue.value.slice(i));
                }
                else {
                    elemValue = (0, lattice_js_1.bottom)('Array rest pattern on non-constant');
                }
            }
            else if ((0, lattice_js_1.isConstant)(baseValue)) {
                if (Array.isArray(baseValue.value)) {
                    const val = baseValue.value[i];
                    elemValue = val !== undefined ? (0, lattice_js_1.constant)(val) : (0, lattice_js_1.bottom)(`Index ${i} out of bounds`);
                }
                else {
                    elemValue = (0, lattice_js_1.bottom)('Array destructuring of non-array');
                }
            }
            else if ((0, lattice_js_1.isBottom)(baseValue)) {
                elemValue = { kind: 'bottom', reason: 'destructured from unresolvable', rootCause: baseValue.rootCause ?? baseValue, isSafe: baseValue.isSafe };
            }
            else {
                elemValue = (0, lattice_js_1.bottom)('Array destructuring of non-constant');
            }
            if (typescript_1.default.isIdentifier(element.name)) {
                const sym = checker.getSymbolAtLocation(element.name);
                if (sym) {
                    env.set(sym, elemValue);
                    env.incrementAssignment(sym);
                    if (isExported)
                        env.markExport(element.name.text, sym);
                }
            }
            else if (typescript_1.default.isObjectBindingPattern(element.name) || typescript_1.default.isArrayBindingPattern(element.name)) {
                expandBindingPattern(element.name, elemValue, isExported, env, checker);
            }
        }
    }
}
// ---------------------------------------------------------------------------
// Enum processing
// ---------------------------------------------------------------------------
function processEnum(stmt, env, checker) {
    const isExported = stmt.modifiers?.some(m => m.kind === typescript_1.default.SyntaxKind.ExportKeyword) ?? false;
    // Build an object representing the enum
    const enumObj = {};
    let autoValue = 0;
    for (const member of stmt.members) {
        const name = typescript_1.default.isIdentifier(member.name) ? member.name.text
            : typescript_1.default.isStringLiteral(member.name) ? member.name.text
                : undefined;
        if (!name)
            continue;
        if (member.initializer) {
            const evaluator = new expressionEvaluator_js_1.ExpressionEvaluator(checker, (sym) => env.resolve(sym));
            const val = evaluator.evaluate(member.initializer);
            if ((0, lattice_js_1.isConstant)(val)) {
                enumObj[name] = val.value;
                if (typeof val.value === 'number')
                    autoValue = val.value + 1;
            }
            else {
                enumObj[name] = undefined;
            }
        }
        else {
            enumObj[name] = autoValue;
            autoValue++;
        }
        // Register individual member as a symbol
        const memberSym = checker.getSymbolAtLocation(member.name);
        if (memberSym) {
            env.set(memberSym, enumObj[name] !== undefined ? (0, lattice_js_1.constant)(enumObj[name]) : (0, lattice_js_1.bottom)('Unresolvable enum value'));
        }
    }
    // Register the enum itself as a constant object
    const enumSym = checker.getSymbolAtLocation(stmt.name);
    if (enumSym) {
        env.set(enumSym, (0, lattice_js_1.constant)(enumObj));
        if (isExported)
            env.markExport(stmt.name.text, enumSym);
    }
}
// ---------------------------------------------------------------------------
// Expression statement processing (assignments)
// ---------------------------------------------------------------------------
function processExpressionStatement(stmt, env, evaluator, checker) {
    const expr = stmt.expression;
    // Assignment: x = expr
    if (typescript_1.default.isBinaryExpression(expr) && expr.operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken) {
        if (typescript_1.default.isIdentifier(expr.left)) {
            const sym = checker.getSymbolAtLocation(expr.left);
            if (!sym)
                return;
            const count = env.incrementAssignment(sym);
            if (count > 1) {
                env.set(sym, (0, lattice_js_1.bottom)(`Multiple assignments to '${expr.left.text}'`));
            }
            else {
                const value = evaluator.evaluate(expr.right);
                env.set(sym, value);
            }
        }
    }
}
// ---------------------------------------------------------------------------
// Export declaration processing (re-exports)
// ---------------------------------------------------------------------------
function processExportDeclaration(stmt, env, checker) {
    if (!stmt.moduleSpecifier || !typescript_1.default.isStringLiteral(stmt.moduleSpecifier))
        return;
    const moduleSpec = stmt.moduleSpecifier.text;
    if (stmt.exportClause && typescript_1.default.isNamedExports(stmt.exportClause)) {
        // export { X, Y as Z } from './mod'
        for (const spec of stmt.exportClause.elements) {
            const localSym = checker.getSymbolAtLocation(spec.name);
            const importedName = spec.propertyName ? spec.propertyName.text : spec.name.text;
            const exportedName = spec.name.text;
            if (localSym) {
                env.markImport(localSym, { moduleSpecifier: moduleSpec, importedName, isNamespace: false });
                env.markExport(exportedName, localSym);
            }
        }
    }
    // export * from './mod'
    if (!stmt.exportClause) {
        env.addStarReExport(moduleSpec);
    }
}
// ---------------------------------------------------------------------------
// Pure function inlining callback
// ---------------------------------------------------------------------------
function tryInlineCall(expr, evaluator, env, checker, depth) {
    // Resolve callee to a symbol
    const calleeSym = getCalleeSymbol(expr.expression, checker);
    if (!calleeSym)
        return undefined;
    // Look up the function declaration
    const funcDecl = env.getFunction(calleeSym);
    if (!funcDecl)
        return undefined;
    // Check eligibility
    if (!(0, pureFunctionInliner_js_1.isEligibleForInlining)(funcDecl))
        return undefined;
    // Evaluate arguments
    const argValues = [];
    for (const arg of expr.arguments) {
        const val = evaluator.evaluate(arg);
        if (!(0, lattice_js_1.isConstant)(val))
            return undefined; // non-constant arg → can't inline
        argValues.push(val);
    }
    // Inline with reduced depth
    if (depth <= 0)
        return undefined;
    return (0, pureFunctionInliner_js_1.inlineFunction)(funcDecl, argValues, checker, (sym) => env.resolve(sym));
}
function getCalleeSymbol(expr, checker) {
    if (typescript_1.default.isIdentifier(expr)) {
        const sym = checker.getSymbolAtLocation(expr);
        if (sym && sym.flags & typescript_1.default.SymbolFlags.Alias) {
            return checker.getAliasedSymbol(sym);
        }
        return sym;
    }
    return undefined;
}
//# sourceMappingURL=moduleAnalyzer.js.map