"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModuleResolver = void 0;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
class ModuleResolver {
    constructor(options = {}) {
        if (!options.baseUrl) {
            throw new Error('ModuleResolver requires explicit baseUrl - process.cwd() is not allowed in production. ' +
                'Pass the base directory explicitly to ensure deterministic module resolution.');
        }
        this.options = {
            baseUrl: options.baseUrl,
            paths: options.paths || {},
            extensions: options.extensions || ['.som', '.js', '.json'],
            moduleDirectories: options.moduleDirectories || ['node_modules'],
            allowJs: options.allowJs ?? true,
            resolveJsonModule: options.resolveJsonModule ?? true,
        };
    }
    /**
     * Resolve a module specifier to an absolute file path
     */
    resolve(specifier, fromFile) {
        // Determine a correct base directory whether 'fromFile' is a file path or a directory path
        let fromDir;
        try {
            if (fs.existsSync(fromFile) && fs.statSync(fromFile).isDirectory()) {
                fromDir = fromFile;
            }
            else {
                fromDir = path.dirname(fromFile);
            }
        }
        catch {
            // Fallback: treat input as a file path
            fromDir = path.dirname(fromFile);
        }
        // Handle already absolute file paths
        // Distinguish between OS-level absolute paths (/Users/..., C:\Users\...) and
        // project-relative absolute paths (/lib/utils/...)
        if (path.isAbsolute(specifier)) {
            const normalizedPath = path.normalize(specifier);
            // Check if this is an OS-level absolute path by seeing if it's outside the project
            // or matches common OS path patterns
            const isOsPath = this.isOsLevelAbsolutePath(normalizedPath);
            if (isOsPath) {
                return {
                    resolvedPath: normalizedPath,
                    isExternalLibrary: false,
                    extension: path.extname(normalizedPath),
                };
            }
            // Otherwise, fall through to project-relative handling
        }
        // Handle relative imports (./module, ../module)
        if (specifier.startsWith('./') || specifier.startsWith('../')) {
            return this.resolveRelative(specifier, fromDir);
        }
        // Handle absolute imports (/module)
        if (specifier.startsWith('/')) {
            return this.resolveAbsolute(specifier);
        }
        // Handle path mapping
        const mappedPath = this.resolveMappedPath(specifier);
        if (mappedPath) {
            return mappedPath;
        }
        // Handle node_modules resolution
        return this.resolveNodeModules(specifier, fromDir);
    }
    resolveRelative(specifier, fromDir) {
        const targetPath = path.resolve(fromDir, specifier);
        return this.resolveFile(targetPath, false);
    }
    resolveAbsolute(specifier) {
        const targetPath = path.resolve(this.options.baseUrl, specifier.slice(1));
        return this.resolveFile(targetPath, false);
    }
    resolveMappedPath(specifier) {
        for (const [pattern, mappings] of Object.entries(this.options.paths)) {
            if (this.matchesPattern(specifier, pattern)) {
                for (const mapping of mappings) {
                    const mappedSpecifier = this.applyMapping(specifier, pattern, mapping);
                    const targetPath = path.resolve(this.options.baseUrl, mappedSpecifier);
                    try {
                        return this.resolveFile(targetPath, false);
                    }
                    catch {
                        continue; // Try next mapping
                    }
                }
            }
        }
        return null;
    }
    resolveNodeModules(specifier, fromDir) {
        let currentDir = fromDir;
        while (currentDir !== path.dirname(currentDir)) {
            for (const moduleDir of this.options.moduleDirectories) {
                const modulePath = path.join(currentDir, moduleDir, specifier);
                try {
                    return this.resolveFile(modulePath, true, specifier);
                }
                catch {
                    // Continue searching
                }
            }
            currentDir = path.dirname(currentDir);
        }
        throw new Error(`Module not found: ${specifier}`);
    }
    resolveFile(targetPath, isExternal, packageName) {
        // Try exact path first
        const exactMatch = this.tryExactPath(targetPath, isExternal, packageName);
        if (exactMatch)
            return exactMatch;
        // Try with extensions
        const withExtension = this.tryWithExtensions(targetPath, isExternal, packageName);
        if (withExtension)
            return withExtension;
        // Try as directory
        const asDirectory = this.tryAsDirectory(targetPath, isExternal, packageName);
        if (asDirectory)
            return asDirectory;
        throw new Error(`Cannot resolve module: ${targetPath}`);
    }
    tryExactPath(targetPath, isExternal, packageName) {
        if (fs.existsSync(targetPath) && fs.statSync(targetPath).isFile()) {
            return {
                resolvedPath: targetPath,
                isExternalLibrary: isExternal,
                packageName,
                extension: path.extname(targetPath),
            };
        }
        return null;
    }
    tryWithExtensions(targetPath, isExternal, packageName) {
        for (const ext of this.options.extensions) {
            const pathWithExt = targetPath + ext;
            if (fs.existsSync(pathWithExt) && fs.statSync(pathWithExt).isFile()) {
                return {
                    resolvedPath: pathWithExt,
                    isExternalLibrary: isExternal,
                    packageName,
                    extension: ext,
                };
            }
        }
        return null;
    }
    tryAsDirectory(targetPath, isExternal, packageName) {
        if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isDirectory()) {
            return null;
        }
        // Try package.json main field
        const fromPackageJson = this.tryPackageJsonMain(targetPath, isExternal, packageName);
        if (fromPackageJson)
            return fromPackageJson;
        // Try index files
        return this.tryIndexFiles(targetPath, isExternal, packageName);
    }
    tryPackageJsonMain(targetPath, isExternal, packageName) {
        const packageJsonPath = path.join(targetPath, 'package.json');
        if (!fs.existsSync(packageJsonPath)) {
            return null;
        }
        const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
        if (packageJson.main) {
            const mainPath = path.resolve(targetPath, packageJson.main);
            return this.resolveFile(mainPath, isExternal, packageName);
        }
        return null;
    }
    tryIndexFiles(targetPath, isExternal, packageName) {
        for (const ext of this.options.extensions) {
            const indexPath = path.join(targetPath, `index${ext}`);
            if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
                return {
                    resolvedPath: indexPath,
                    isExternalLibrary: isExternal,
                    packageName,
                    extension: ext,
                };
            }
        }
        return null;
    }
    matchesPattern(specifier, pattern) {
        if (pattern === '*')
            return true;
        if (pattern.endsWith('/*')) {
            const prefix = pattern.slice(0, -2);
            return specifier.startsWith(prefix);
        }
        return specifier === pattern;
    }
    applyMapping(specifier, pattern, mapping) {
        if (pattern === '*') {
            return mapping.replace('*', specifier);
        }
        if (pattern.endsWith('/*')) {
            const prefix = pattern.slice(0, -2);
            const suffix = specifier.slice(prefix.length);
            return mapping.replace('*', suffix);
        }
        return mapping;
    }
    /**
     * Get all possible file extensions for module resolution
     */
    getExtensions() {
        return [...this.options.extensions];
    }
    /**
     * Check if a file extension is supported
     */
    isSupported(extension) {
        return this.options.extensions.includes(extension);
    }
    /**
     * Update resolver options
     */
    updateOptions(options) {
        this.options = { ...this.options, ...options };
    }
    /**
     * Determine if an absolute path is an OS-level path (like /Users/..., C:\...)
     * vs a project-relative path (like /lib/utils)
     *
     * SECURITY NOTE: This method performs READ-ONLY path classification for module
     * resolution. It does NOT perform file operations or create files in any directory.
     * The actual file operations (reading source files) happen in ModuleLoader with
     * proper error handling and validation. This classification prevents path traversal
     * attacks by ensuring project-relative paths (like /lib/utils) don't escape the
     * project baseUrl.
     *
     * Strategy:
     * 1. Check if path is within or equal to baseUrl (OS path)
     * 2. Check if path matches common OS directory patterns
     * 3. Otherwise assume it's project-relative (resolved relative to baseUrl)
     *
     * @param absolutePath - The absolute path to classify
     * @returns true if this is an OS-level filesystem path, false if project-relative
     */
    isOsLevelAbsolutePath(absolutePath) {
        const normalizedPath = path.normalize(absolutePath);
        const normalizedBase = path.normalize(this.options.baseUrl);
        // If the path starts with the baseUrl, it's an OS-level path within the project
        if (normalizedPath.startsWith(normalizedBase)) {
            return true;
        }
        // Check for common OS-level path patterns
        // These patterns are used for READ-ONLY path classification only
        // Unix-like: /home/, /Users/, /var/, /tmp/, /opt/, /usr/, /etc/
        // Windows: C:\, D:\, etc. (drive letters)
        // Note: Including /tmp/ is necessary to correctly classify temp file paths
        // (e.g., from test suites). No file operations are performed here - only
        // path string matching for resolution logic.
        // NOSONAR: This is read-only pattern matching for path classification, not file I/O
        const unixOsPrefixes = ['/home/', '/Users/', '/var/', '/tmp/', '/opt/', '/usr/', '/etc/']; // NOSONAR
        const windowsDrivePattern = /^[A-Za-z]:[/\\]/;
        // Check Unix patterns
        for (const prefix of unixOsPrefixes) {
            if (normalizedPath.startsWith(prefix)) {
                return true;
            }
        }
        // Check Windows drive pattern
        if (windowsDrivePattern.test(normalizedPath)) {
            return true;
        }
        // If we get here, assume it's a project-relative path like /lib/utils
        return false;
    }
}
exports.ModuleResolver = ModuleResolver;
//# sourceMappingURL=module-resolver.js.map