"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SfgeEngine = void 0;
const node_path_1 = __importDefault(require("node:path"));
const node_fs_1 = __importDefault(require("node:fs"));
const code_analyzer_engine_api_1 = require("@salesforce/code-analyzer-engine-api");
const utils_1 = require("@salesforce/code-analyzer-engine-api/utils");
const utils_2 = require("./utils");
const messages_1 = require("./messages");
const sfge_wrapper_1 = require("./sfge-wrapper");
const SFGE_RELEVANT_FILE_EXTENSIONS = ['.cls', '.trigger', '-meta.xml', '.page', '.component'];
const DEV_PREVIEW_TAG = 'DevPreview';
class SfgeEngine extends code_analyzer_engine_api_1.Engine {
    static NAME = 'sfge';
    config;
    sfgeWrapper;
    sfgeRuleInfoListCache = new Map();
    relevantFilesByWorkspaceId = new Map();
    constructor(config, clock = new utils_2.RealClock()) {
        super();
        this.config = config;
        const javaCommandExecutor = new utils_1.JavaCommandExecutor(this.config.java_command, this.emitLogEvent.bind(this));
        this.sfgeWrapper = new sfge_wrapper_1.RuntimeSfgeWrapper(javaCommandExecutor, clock, this.emitLogEvent.bind(this), this.emitTelemetryEvent.bind(this));
    }
    getName() {
        return SfgeEngine.NAME;
    }
    async getEngineVersion() {
        const pathToPackageJson = node_path_1.default.join(__dirname, '..', 'package.json');
        const packageJson = JSON.parse(await node_fs_1.default.promises.readFile(pathToPackageJson, 'utf-8'));
        return packageJson.version;
    }
    async describeRules(describeOptions) {
        this.emitDescribeRulesProgressEvent(5);
        const ruleInfoList = await this.getSfgeRuleInfoList(describeOptions, (innerPerc) => this.emitDescribeRulesProgressEvent(5 + (90 * innerPerc / 100)) // 5%-95%
        );
        const ruleDescriptions = ruleInfoList.map(toRuleDescription);
        ruleDescriptions.sort((rd1, rd2) => rd1.name.localeCompare(rd2.name));
        this.emitDescribeRulesProgressEvent(100);
        return ruleDescriptions;
    }
    async runRules(selectedRuleNames, runOptions) {
        this.emitRunRulesProgressEvent(2);
        if (selectedRuleNames.length === 0) {
            this.emitRunRulesProgressEvent(100);
            return { violations: [] };
        }
        // Get targeted files and return early if empty - prevents SFGE from analyzing all workspace files
        const targetedFiles = await runOptions.workspace.getTargetedFiles();
        if (targetedFiles.length === 0) {
            this.emitRunRulesProgressEvent(100);
            return { violations: [] };
        }
        await this.validateWorkspaceCompleteness(runOptions.workspace);
        const allRulesInfoList = await this.getSfgeRuleInfoList(runOptions, (innerPerc) => this.emitRunRulesProgressEvent(2 + 3 * (innerPerc / 100)) // 2%-5%
        );
        const selectedRuleInfoList = allRulesInfoList
            .filter(ruleInfo => selectedRuleNames.some(name => name.toLowerCase() === ruleInfo.name.toLowerCase()));
        if (selectedRuleInfoList.length === 0) {
            this.emitRunRulesProgressEvent(100);
            return { violations: [] };
        }
        const relevantWorkspaceFiles = await this.getRelevantFilesInWorkspace(runOptions.workspace);
        const sfgeRunOptions = {
            heapSizeArg: this.config.java_max_heap_size,
            logFolder: runOptions.logFolder,
            disableLimitReachedViolations: this.config.disable_limit_reached_violations,
            threadCount: this.config.java_thread_count,
            threadTimeout: this.config.java_thread_timeout
        };
        const sfgeResults = await this.sfgeWrapper.invokeRunCommand(selectedRuleInfoList, targetedFiles, relevantWorkspaceFiles, sfgeRunOptions, runOptions.workingFolder, (innerPerc, message) => this.emitRunRulesProgressEvent(5 + 93 * innerPerc / 100, message) // 5%-98%
        );
        sfgeResults.filter(r => r.ruleName === 'InternalExecutionError').map(r => this.emitLogEvent(code_analyzer_engine_api_1.LogLevel.Error, (0, messages_1.getMessage)('InternalExecutionErrorMessageTemplate', r.sourceFileName, r.sourceLineNumber, r.sourceColumnNumber, r.message)));
        const violations = sfgeResults.filter(r => r.ruleName !== 'InternalExecutionError').map(toViolation);
        this.emitRunRulesProgressEvent(100);
        return {
            violations
        };
    }
    async validateWorkspaceCompleteness(workspace) {
        const allRelevantFiles = await this.getRelevantFilesInWorkspace(workspace);
        const knownRelevantFileCountByDirectory = new Map();
        for (const relevantFile of allRelevantFiles) {
            const dirName = node_path_1.default.dirname(relevantFile);
            const knownRelevantFilesInDirectory = knownRelevantFileCountByDirectory.get(dirName) ?? 0;
            knownRelevantFileCountByDirectory.set(dirName, knownRelevantFilesInDirectory + 1);
        }
        for (const [dirName, knownRelevantFileCount] of knownRelevantFileCountByDirectory.entries()) {
            const dirEntries = await node_fs_1.default.promises.readdir(dirName);
            const allRelevantFileCount = dirEntries.filter(isFileRelevantToSfge).length;
            if (allRelevantFileCount !== knownRelevantFileCount) {
                this.emitLogEvent(code_analyzer_engine_api_1.LogLevel.Warn, (0, messages_1.getMessage)('WorkspaceAppearsIncomplete', allRelevantFileCount - knownRelevantFileCount, dirName));
            }
        }
    }
    async getSfgeRuleInfoList(options, emitProgress) {
        const workspace = options.workspace;
        const logFolder = options.logFolder;
        const cacheKey = getCacheKey(workspace);
        if (!this.sfgeRuleInfoListCache.has(cacheKey)) {
            if (workspace && (await this.getRelevantFilesInWorkspace(workspace)).length === 0) {
                this.sfgeRuleInfoListCache.set(cacheKey, []);
            }
            else {
                const ruleInfoList = await this.sfgeWrapper.invokeDescribeCommand(options.workingFolder, emitProgress, logFolder);
                this.sfgeRuleInfoListCache.set(cacheKey, ruleInfoList);
            }
        }
        return this.sfgeRuleInfoListCache.get(cacheKey);
    }
    async getRelevantFilesInWorkspace(workspace) {
        if (!this.relevantFilesByWorkspaceId.has(workspace.getWorkspaceId())) {
            const relevantFiles = (await workspace.getWorkspaceFiles()).filter(isFileRelevantToSfge);
            this.relevantFilesByWorkspaceId.set(workspace.getWorkspaceId(), relevantFiles);
        }
        return this.relevantFilesByWorkspaceId.get(workspace.getWorkspaceId());
    }
}
exports.SfgeEngine = SfgeEngine;
function toViolation(sfgeViolation) {
    const codeLocations = [{
            file: sfgeViolation.sourceFileName,
            startLine: sfgeViolation.sourceLineNumber,
            startColumn: sfgeViolation.sourceColumnNumber
        }];
    if (sfgeViolation.sinkFileName) {
        codeLocations.push({
            file: sfgeViolation.sinkFileName,
            startLine: sfgeViolation.sinkLineNumber,
            startColumn: sfgeViolation.sinkColumnNumber
        });
    }
    return {
        ruleName: sfgeViolation.ruleName,
        message: sfgeViolation.message,
        codeLocations,
        primaryLocationIndex: codeLocations.length - 1
    };
}
function getCacheKey(workspace) {
    return workspace ? workspace.getWorkspaceId() : process.cwd();
}
function isFileRelevantToSfge(fileName) {
    return SFGE_RELEVANT_FILE_EXTENSIONS.some(extension => fileName.toLowerCase().endsWith(extension));
}
function toRuleDescription(sfgeRuleInfo) {
    const tags = [DEV_PREVIEW_TAG, sfgeRuleInfo.category.replaceAll(' ', '')];
    tags.push(code_analyzer_engine_api_1.COMMON_TAGS.LANGUAGES.APEX);
    return {
        name: sfgeRuleInfo.name,
        severityLevel: sfgeRuleInfo.severity,
        tags,
        description: (0, messages_1.getMessage)('DeveloperPreviewRuleNotification', sfgeRuleInfo.description),
        resourceUrls: [sfgeRuleInfo.url]
    };
}
//# sourceMappingURL=engine.js.map