"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SFGE_ENGINE_CONFIG_DESCRIPTION = exports.DEFAULT_SFGE_ENGINE_CONFIG = void 0;
exports.validateAndNormalizeConfig = validateAndNormalizeConfig;
const utils_1 = require("@salesforce/code-analyzer-engine-api/utils");
const messages_1 = require("./messages");
const node_path_1 = __importDefault(require("node:path"));
const DEFAULT_JAVA_COMMAND = 'java';
const MINIMUM_JAVA_VERSION = '11.0.0';
exports.DEFAULT_SFGE_ENGINE_CONFIG = {
    java_command: DEFAULT_JAVA_COMMAND,
    disable_limit_reached_violations: false,
    java_max_heap_size: undefined,
    java_thread_count: 4,
    java_thread_timeout: 900000
};
exports.SFGE_ENGINE_CONFIG_DESCRIPTION = {
    overview: (0, messages_1.getMessage)('ConfigOverview'),
    fieldDescriptions: {
        // Whether to prevent 'sfge' from throwing LimitReached violations for complex paths.
        // By default, Salesforce Graph Engine attempts to detect complex paths that might cause OutOfMemory errors,
        // and throws LimitReached violations for these paths to continue evaluating other paths safely. The allowed
        // complexity is dynamically calculated based on the max Java heap size available, but in some cases you may
        // desire to disable this check in addition to increasing java_max_heap_size.
        disable_limit_reached_violations: {
            descriptionText: (0, messages_1.getMessage)('ConfigFieldDescription_disable_limit_reached_violations'),
            valueType: "boolean",
            defaultValue: exports.DEFAULT_SFGE_ENGINE_CONFIG.disable_limit_reached_violations
        },
        // Indicates the specific 'java' command associated with the JRE or JDK to use for the 'sfge' engine.
        // May be provided as the name of a command that exists on the path, or an absolute file path location.
        // If unspecified, or specified as null, then an attempt will be made to automatically discover a 'java' command from your environment.
        java_command: {
            descriptionText: (0, messages_1.getMessage)('ConfigFieldDescription_java_command'),
            valueType: "string",
            defaultValue: null // Using null for doc and since it indicates that the value is calculated based on the environment
        },
        // Specifies the maximum size (in bytes) of the Java heap. The specified value is appended to the '-Xmx' Java
        // command option. The value must be a multiple of 1024, and greater than 2MB. Append the letters 'k', 'K', 'kb',
        // or 'KB' to indicate kilobytes, 'm', 'M', 'mb', or 'MB' to indicate megabytes, and 'g', 'G', 'gb', or 'GB' to
        // indicate gigabytes. If unspecified, or specified as null, then the JVM will dynamically choose a default value
        // at runtime based on system configuration.
        java_max_heap_size: {
            descriptionText: (0, messages_1.getMessage)('ConfigFieldDescription_java_max_heap_size'),
            valueType: "string",
            defaultValue: null
        },
        // Specifies the number of Java threads available for parallel execution. Increasing the thread count allows for
        // Salesforce Graph Engine to evaluate more paths at the same time.
        java_thread_count: {
            descriptionText: (0, messages_1.getMessage)('ConfigFieldDescription_java_thread_count'),
            valueType: "number",
            defaultValue: exports.DEFAULT_SFGE_ENGINE_CONFIG.java_thread_count
        },
        // Specifies the maximum time (in milliseconds) a specific Java thread may execute before Salesforce Graph Engine
        // issues a Timeout violation.
        java_thread_timeout: {
            descriptionText: (0, messages_1.getMessage)('ConfigFieldDescription_java_thread_timeout'),
            valueType: "number",
            defaultValue: exports.DEFAULT_SFGE_ENGINE_CONFIG.java_thread_timeout
        }
    }
};
const JAVA_HEAP_SIZE_REGEX = /^\d+[kmg]?b?$/i;
async function validateAndNormalizeConfig(cve, javaVersionIdentifier) {
    cve.validateContainsOnlySpecifiedKeys(['disable_limit_reached_violations', 'java_command', 'java_max_heap_size', 'java_thread_count', 'java_thread_timeout']);
    const sfgeConfigValueExtractor = new SfgeConfigValueExtractor(cve, javaVersionIdentifier);
    return {
        java_command: await sfgeConfigValueExtractor.extractJavaCommand(),
        disable_limit_reached_violations: sfgeConfigValueExtractor.extractBooleanValue('disable_limit_reached_violations'),
        java_max_heap_size: sfgeConfigValueExtractor.extractJavaMaxHeapSize(),
        java_thread_count: sfgeConfigValueExtractor.extractNumericValue('java_thread_count'),
        java_thread_timeout: sfgeConfigValueExtractor.extractNumericValue('java_thread_timeout')
    };
}
class SfgeConfigValueExtractor {
    delegateExtractor;
    javaVersionIdentifier;
    constructor(delegateExtractor, javaVersionIdentifier) {
        this.delegateExtractor = delegateExtractor;
        this.javaVersionIdentifier = javaVersionIdentifier;
    }
    async extractJavaCommand() {
        const javaCommand = this.delegateExtractor.extractString('java_command');
        if (!javaCommand) {
            return await this.attemptToAutoDetectJavaCommand();
        }
        try {
            await this.validateJavaCommandContainsValidVersion(javaCommand);
        }
        catch (err) {
            throw new Error((0, messages_1.getMessage)('InvalidConfigValue', this.delegateExtractor.getFieldPath('java_command'), err.message));
        }
        return javaCommand;
    }
    async attemptToAutoDetectJavaCommand() {
        const commandsToAttempt = [
            // Environment variables specifying JAVA HOME take precedence (if they exist)
            ...['JAVA_HOME', 'JRE_HOME', 'JDK_HOME'].filter(v => process.env[v]) // only keep vars that have a non-empty defined value
                .map(/* istanbul ignore next */ /* istanbul ignore next */ v => node_path_1.default.join(process.env[v], 'bin', 'java')),
            // Attempt to just use the default java command that might be already on the path as a last attempt
            DEFAULT_JAVA_COMMAND
        ];
        const errorMessages = [];
        for (const possibleJavaCommand of commandsToAttempt) {
            try {
                // Yes we want to have an await statement in a loop in this case since we want to try one at a time
                await this.validateJavaCommandContainsValidVersion(possibleJavaCommand);
                return possibleJavaCommand;
            }
            catch (err) {
                errorMessages.push(err.message);
            }
        }
        const consolidatedErrorMessages = errorMessages.map((msg, idx) => (0, utils_1.indent)(`Attempt ${idx + 1}:\n${(0, utils_1.indent)(msg)}`, '  | ')).join('\n');
        throw new Error((0, messages_1.getMessage)('CouldNotLocateJava', MINIMUM_JAVA_VERSION, consolidatedErrorMessages, this.delegateExtractor.getFieldPath('java_command'), this.delegateExtractor.getFieldPath('disable_engine')));
    }
    async validateJavaCommandContainsValidVersion(javaCommand) {
        let version;
        try {
            version = await this.javaVersionIdentifier.identifyJavaVersion(javaCommand);
        }
        catch (err) {
            /* istanbul ignore next */
            const errMsg = err instanceof Error ? err.message : String(err);
            throw new Error((0, messages_1.getMessage)('JavaVersionCheckProducedError', javaCommand, (0, utils_1.indent)(errMsg, '  | ')));
        }
        if (!version) {
            throw new Error((0, messages_1.getMessage)('UnrecognizableJavaVersion', javaCommand));
        }
        else if (version.compare(MINIMUM_JAVA_VERSION) < 0) {
            throw new Error((0, messages_1.getMessage)('JavaBelowMinimumVersion', javaCommand, version.toString(), MINIMUM_JAVA_VERSION));
        }
    }
    extractJavaMaxHeapSize() {
        const javaMaxHeapSize = this.delegateExtractor.extractString('java_max_heap_size', undefined, JAVA_HEAP_SIZE_REGEX);
        if (!javaMaxHeapSize) {
            return undefined;
        }
        const normalizedHeapSize = normalizeHeapSize(javaMaxHeapSize);
        if (normalizedHeapSize.endsWith('g')) {
            // A value expressed in gigabytes is always fine.
            return normalizedHeapSize;
        }
        const numericPortion = parseInt(normalizedHeapSize);
        if (numericPortion < expressTwoMegabytesInRelevantUnit(normalizedHeapSize)) {
            throw new Error((0, messages_1.getMessage)('InvalidConfigValue', this.delegateExtractor.getFieldPath('java_max_heap_size'), (0, messages_1.getMessage)('InsufficientMemorySpecified')));
        }
        const isStrictlyNumeric = /^\d+$/i.test(normalizedHeapSize);
        if (isStrictlyNumeric && numericPortion % 1024 !== 0) {
            throw new Error((0, messages_1.getMessage)('InvalidConfigValue', this.delegateExtractor.getFieldPath('java_max_heap_size'), (0, messages_1.getMessage)('InvalidMemoryMultiple')));
        }
        return normalizedHeapSize;
    }
    extractBooleanValue(fieldName) {
        return this.delegateExtractor.extractBoolean(fieldName, exports.DEFAULT_SFGE_ENGINE_CONFIG[fieldName]);
    }
    extractNumericValue(fieldName) {
        return this.delegateExtractor.extractNumber(fieldName, exports.DEFAULT_SFGE_ENGINE_CONFIG[fieldName]);
    }
}
function normalizeHeapSize(heapSize) {
    const normalizedHeapSize = heapSize.toLowerCase();
    return normalizedHeapSize.endsWith('b')
        ? normalizedHeapSize.slice(0, normalizedHeapSize.length - 1)
        : normalizedHeapSize;
}
function expressTwoMegabytesInRelevantUnit(val) {
    if (val.endsWith('m')) {
        return 2;
    }
    else if (val.endsWith('k')) {
        return 2048; // 2MB === 2048KB
    }
    else {
        return 2 ** 21; // 2MB === 2^21 bytes
    }
}
//# sourceMappingURL=config.js.map