"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CpdEngine = void 0;
const code_analyzer_engine_api_1 = require("@salesforce/code-analyzer-engine-api");
const utils_1 = require("@salesforce/code-analyzer-engine-api/utils");
const constants_1 = require("./constants");
const messages_1 = require("./messages");
const utils_2 = require("./utils");
const config_1 = require("./config");
const cpd_wrapper_1 = require("./cpd-wrapper");
const node_path_1 = __importDefault(require("node:path"));
const promises_1 = __importDefault(require("node:fs/promises"));
const RULE_NAME_PREFIX = 'DetectCopyPasteFor';
class CpdEngine extends code_analyzer_engine_api_1.Engine {
    cpdWrapperInvoker;
    config;
    extensionToLanguageMap;
    workspaceLiaisonCache = new Map();
    constructor(config) {
        super();
        const javaCommandExecutor = new utils_1.JavaCommandExecutor(config.java_command, this.emitLogEvent.bind(this));
        this.cpdWrapperInvoker = new cpd_wrapper_1.CpdWrapperInvoker(javaCommandExecutor, this.emitLogEvent.bind(this));
        this.config = config;
        this.extensionToLanguageMap = (0, utils_2.toExtensionsToLanguageMap)(config.file_extensions);
    }
    getName() {
        return constants_1.CPD_ENGINE_NAME;
    }
    async getEngineVersion() {
        const pathToPackageJson = node_path_1.default.join(__dirname, '..', 'package.json');
        const packageJson = JSON.parse(await promises_1.default.readFile(pathToPackageJson, 'utf-8'));
        return packageJson.version;
    }
    async describeRules(describeOptions) {
        const workspaceLiaison = this.getWorkspaceLiaison(describeOptions.workspace);
        this.emitDescribeRulesProgressEvent(33);
        const relevantLanguages = await workspaceLiaison.getRelevantLanguages();
        const ruleDescriptions = relevantLanguages.map(createRuleForLanguage);
        this.emitDescribeRulesProgressEvent(100);
        return ruleDescriptions;
    }
    async runRules(ruleNames, runOptions) {
        const workspaceLiaison = this.getWorkspaceLiaison(runOptions.workspace);
        const relevantLanguageToFilesMap = await workspaceLiaison.getRelevantLanguageToFilesMap();
        this.emitRunRulesProgressEvent(2);
        const inputData = {
            runDataPerLanguage: {},
            skipDuplicateFiles: this.config.skip_duplicate_files
        };
        const relevantLanguages = new Set(ruleNames.map(getLanguageFromRuleName));
        for (const language of relevantLanguages) {
            const filesToScanForLanguage = relevantLanguageToFilesMap.get(language) || [];
            if (filesToScanForLanguage.length > 0) {
                inputData.runDataPerLanguage[toCpdLanguageId(language)] = {
                    filesToScan: filesToScanForLanguage,
                    minimumTokens: this.config.minimum_tokens[language]
                };
            }
        }
        if (Object.keys(inputData.runDataPerLanguage).length === 0) {
            this.emitRunRulesProgressEvent(100);
            return { violations: [] };
        }
        this.emitRunRulesProgressEvent(5);
        const cpdRunResults = await this.cpdWrapperInvoker.invokeRunCommand(inputData, runOptions.workingFolder, (innerPerc) => this.emitRunRulesProgressEvent(5 + 93 * (innerPerc / 100))); // 5 to 98%
        const violations = [];
        for (const cpdLanguageId in cpdRunResults) {
            const language = toLanguageEnum(cpdLanguageId);
            const cpdLanguageRunResults = cpdRunResults[cpdLanguageId];
            for (const cpdMatch of cpdLanguageRunResults.matches) {
                violations.push(toViolation(language, cpdMatch));
            }
            for (const cpdProcessingError of cpdLanguageRunResults.processingErrors) {
                /* istanbul ignore next */
                if (cpdProcessingError.detail == '[TERMINATING_EXCEPTION]') {
                    this.emitLogEvent(code_analyzer_engine_api_1.LogLevel.Error, (0, messages_1.getMessage)('CpdTerminatingExceptionThrown', language, (0, utils_1.indent)(cpdProcessingError.message)));
                }
                else {
                    this.emitLogEvent(code_analyzer_engine_api_1.LogLevel.Error, (0, messages_1.getMessage)('ProcessingErrorForFile', 'CPD', cpdProcessingError.file, (0, utils_1.indent)(cpdProcessingError.message)));
                }
            }
        }
        this.emitRunRulesProgressEvent(100);
        return {
            violations: violations
        };
    }
    getWorkspaceLiaison(workspace) {
        const cacheKey = getCacheKey(workspace);
        if (!this.workspaceLiaisonCache.has(cacheKey)) {
            this.workspaceLiaisonCache.set(cacheKey, new utils_2.WorkspaceLiaison(workspace, this.config.rule_languages, this.extensionToLanguageMap));
        }
        return this.workspaceLiaisonCache.get(cacheKey);
    }
}
exports.CpdEngine = CpdEngine;
function createRuleForLanguage(language) {
    const languageTag = language.charAt(0).toUpperCase() + language.slice(1);
    // We agreed that html and xml can be noisy and are less important for users to be made aware of duplicate code
    // so we will be just adding Recommended tag to programming languages: apex, javascript, typescript, and visualforce
    const recommendedLanguages = new Set([constants_1.Language.APEX, constants_1.Language.JAVASCRIPT, constants_1.Language.TYPESCRIPT, constants_1.Language.VISUALFORCE]);
    return {
        name: getRuleNameFromLanguage(language),
        severityLevel: code_analyzer_engine_api_1.SeverityLevel.Info,
        tags: [
            ...(recommendedLanguages.has(language) ? [code_analyzer_engine_api_1.COMMON_TAGS.RECOMMENDED] : []),
            code_analyzer_engine_api_1.COMMON_TAGS.CATEGORIES.DESIGN,
            languageTag
        ],
        description: (0, messages_1.getMessage)('DetectCopyPasteForLanguageRuleDescription', language),
        resourceUrls: ['https://docs.pmd-code.org/latest/pmd_userdocs_cpd.html#refactoring-duplicates']
    };
}
function getRuleNameFromLanguage(language) {
    return RULE_NAME_PREFIX + makeFirstCharUpperCase(language);
}
function makeFirstCharUpperCase(text) {
    return text.charAt(0).toUpperCase() + text.slice(1);
}
function getLanguageFromRuleName(ruleName) {
    const language = ruleName.slice(RULE_NAME_PREFIX.length).toLowerCase();
    if (!config_1.CPD_AVAILABLE_LANGUAGES.includes(language)) {
        throw new Error(`Unexpected error: The rule '${ruleName}' does not map to a supported CPD language: ${JSON.stringify(config_1.CPD_AVAILABLE_LANGUAGES)}`);
    }
    return language;
}
function getCacheKey(workspace) {
    return workspace ? workspace.getWorkspaceId() : process.cwd();
}
function toCpdLanguageId(language) {
    // We must convert 'javascript' to 'ecmascript' since CPD actually uses 'ecmascript' as the identifier instead of 'javascript'
    return language == constants_1.Language.JAVASCRIPT ? 'ecmascript' : language;
}
function toLanguageEnum(cpdLanguageId) {
    // We must convert 'ecmascript' back to 'javascrijpt'
    return cpdLanguageId == 'ecmascript' ? constants_1.Language.JAVASCRIPT : cpdLanguageId;
}
function toViolation(language, cpdMatch) {
    return {
        ruleName: getRuleNameFromLanguage(language),
        message: (0, messages_1.getMessage)('DetectCopyPasteForLanguageViolationMessage', language, cpdMatch.numBlocks, cpdMatch.numTokensInBlock, cpdMatch.numNonemptyLinesInBlock),
        primaryLocationIndex: 0,
        codeLocations: cpdMatch.blockLocations.map(toCodeLocation)
    };
}
function toCodeLocation(blockLocation) {
    return {
        file: blockLocation.file,
        startLine: blockLocation.startLine,
        startColumn: blockLocation.startCol,
        endLine: blockLocation.endLine,
        endColumn: blockLocation.endCol
    };
}
//# sourceMappingURL=cpd-engine.js.map