// Analyze deployment errors to provide tips to user :)
import c from "chalk";
import format from "string-template";
import { getAllTips } from "./deployTipsList.js";
import { stripAnsi, uxLog } from "./index.js";
import { AiProvider } from "../aiProvider/index.js";
import { updatePullRequestResult } from "./deployTips.js";
import { shortenLogLines } from "./deployUtils.js";
export async function analyzeDeployErrorLogsJson(resultJson, log, includeInLog = true, options) {
    const allTips = getAllTips();
    const tips = [];
    // Filter to keep only errors (we don't care about warnings) and build legacy message to match deploymentTips
    const errors = (resultJson?.result?.details?.componentFailures || [])
        .filter(error => error.success === false && error.problemType === "Error")
        .map(error => {
        error.messageInitial = `Error ${error.fullName} ${error.problem}`;
        error.messageInitialDisplay = `${error.componentType} ${error.fullName}: ${error.problem}`;
        error.tips = [];
        return error;
    });
    // Collect errors & tips
    for (const error of errors) {
        for (const tipDefinition of allTips) {
            await matchesTip(tipDefinition, error);
            if (error.tips.length > 0) {
                tips.push(tipDefinition);
            }
        }
        // Add default tip if not found
        if (error.tips.length === 0) {
            error.message = stripAnsi(error.messageInitial);
            const errorBase = Object.assign({}, error);
            delete errorBase.tips;
            error.tips.push({
                error: errorBase
            });
        }
    }
    // Enrich with AI if applicable
    const alreadyProcessedErrors = [];
    for (const error of errors) {
        for (const errorTip of error.tips) {
            const aiTip = await findAiTip(errorTip.error, alreadyProcessedErrors);
            if (aiTip) {
                errorTip.tipFromAi = {
                    promptResponse: aiTip.promptResponse,
                };
            }
        }
    }
    // Gather failing tests
    const failedTests = extractFailedTestsInfo(resultJson?.result?.details?.runTestResult?.failures || []);
    // Build output list of errors & tips
    const errorsAndTips = [];
    for (const error of errors) {
        for (const errorTip of error.tips)
            errorsAndTips.push(errorTip);
    }
    const detailedErrorLines = [];
    // Fallback in case we have not been able to identify errors: Check if there are code coverage warnings
    if (errorsAndTips.length === 0 && failedTests.length === 0 && resultJson?.result?.details?.runTestResult?.codeCoverageWarnings?.length > 0) {
        for (const cvrgWarning of resultJson.result.details.runTestResult.codeCoverageWarnings) {
            const coverageErrorMsg = (cvrgWarning.name ? `${cvrgWarning.name} - ` : "") + cvrgWarning.message;
            errorsAndTips.push(({
                error: { message: coverageErrorMsg },
                tip: {
                    label: "CodeCoverageWarning",
                    message: "Please fix code coverage so your deployment can pass",
                    docUrl: "https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_code_coverage_intro.htm",
                },
            }));
            detailedErrorLines.push(...["", "⛔ " + c.red(c.bold("Coverage issue: " + coverageErrorMsg)), ""]);
        }
    }
    // Fallback : declare an error if we have not been able to identify errors
    if (errorsAndTips.length === 0 && failedTests.length === 0 && resultJson?.result?.errorMessage) {
        errorsAndTips.push(({
            error: { message: resultJson.result.errorMessage },
            tip: {
                label: resultJson.result.errorStatusCode || "UNKNOWN",
                message: "Please fix unknown errors (",
            },
        }));
        detailedErrorLines.push(...["", "⛔ " + c.red(c.bold("Unknown issue: " + resultJson.result.errorMessage)), ""]);
    }
    // Fallback : declare an error if we have not been able to identify errors
    if (errorsAndTips.length === 0 && failedTests.length === 0) {
        errorsAndTips.push(({
            error: { message: "There has been an issue parsing errors, please notify sfdx-hardis maintainers" },
            tip: {
                label: "SfdxHardisInternalError",
                message: "Declare issue on https://github.com/hardisgroupcom/sfdx-hardis/issues",
            },
        }));
        detailedErrorLines.push(...["", "⛔ " + c.red(c.bold("There has been an issue parsing errors, please notify sfdx-hardis maintainers")), ""]);
    }
    // Create output log for errors
    for (const error of errors) {
        detailedErrorLines.push(...["", "⛔ " + c.red(c.bold(error.messageInitialDisplay)), ""]);
        if (error.tips.length > 0 && error.tips.some(err => err.tip || err.tipFromAi)) {
            for (const errorTip of error.tips) {
                if (errorTip.tip) {
                    detailedErrorLines.push(...[
                        c.yellow(c.italic("✏️ Error " + c.bold(errorTip.tip.label)) + ":"),
                        c.yellow(errorTip.tip.messageConsole),
                        c.yellow(`Documentation: ${errorTip.tip.docUrl}`)
                    ]);
                }
                if (errorTip.tipFromAi) {
                    detailedErrorLines.push(...[
                        c.yellow(c.italic("🤖 AI response:")),
                        c.yellow(errorTip.tipFromAi.promptResponse)
                    ]);
                }
            }
        }
        else {
            detailedErrorLines.push(...[c.yellow("No tip found for error. Try asking ChatGPT, Google or a Release Manager :)")]);
        }
    }
    detailedErrorLines.push("");
    // Create output log for test failures
    if (failedTests.length > 0) {
        detailedErrorLines.push(...["", c.red(c.bold("Test failures:"))], "");
        for (const failedTest of failedTests) {
            detailedErrorLines.push(...[
                c.red(`💥 ${c.bold(failedTest.class)}.${c.bold(failedTest.method)}: ${failedTest.error}`),
                c.grey(`Stack: ${failedTest.stack || "none"}`),
                ""
            ]);
        }
    }
    // Update data that will be used for Pull Request comment
    await updatePullRequestResult(errorsAndTips, failedTests, options);
    // Return results
    const newLog = includeInLog ? shortenLogLines(log) + "\n\n" + detailedErrorLines.join("\n") : shortenLogLines(log);
    return { tips, errorsAndTips, failedTests, errLog: newLog };
}
async function matchesTip(tipDefinition, error) {
    matchStringBasedTip(tipDefinition, error);
    matchRegExpBasedTip(tipDefinition, error);
}
function matchStringBasedTip(tipDefinition, error) {
    if (tipDefinition.expressionString &&
        tipDefinition.expressionString.filter((expressionString) => error.messageInitial.includes(expressionString)).length > 0) {
        error.message = stripAnsi(error.messageInitial);
        const errorBase = Object.assign({}, error);
        delete errorBase.tips;
        error.tips.push({
            error: errorBase,
            tip: {
                label: tipDefinition.label,
                docUrl: tipDefinition.docUrl,
                message: tipDefinition.tip,
                messageConsole: tipDefinition.tip,
            },
        });
    }
}
function matchRegExpBasedTip(tipDefinition, error) {
    if (tipDefinition.expressionRegex &&
        tipDefinition.expressionRegex.filter((expressionRegex) => {
            expressionRegex.lastIndex = 0; // reset regex last index to be able to reuse it
            return expressionRegex.test(error.messageInitial);
        }).length > 0) {
        const regex = tipDefinition.expressionRegex.filter((expressionRegex) => {
            expressionRegex.lastIndex = 0; // reset regex last index to be able to reuse it
            return expressionRegex.test(error.messageInitial);
        })[0];
        regex.lastIndex = 0; // reset regex last index to be able to reuse it
        const matches = [...error.messageInitial.matchAll(regex)];
        for (const m of matches) {
            const replacements = m.map((str) => c.bold(str.trim().replace(/'/gm, "")));
            const replacementsMarkdown = m.map((str) => `**${str.trim().replace(/'/gm, "")}**`);
            error.message = stripAnsi(format(error.messageInitial, replacementsMarkdown)).replace(/\*\*.\*\*/gm, ".");
            const errorBase = Object.assign({}, error);
            delete errorBase.tips;
            error.tips.push({
                error: errorBase,
                tip: {
                    label: tipDefinition.label,
                    docUrl: tipDefinition.docUrl,
                    message: stripAnsi(format(tipDefinition.tip, replacementsMarkdown).replace(/\*\*.\*\*/gm, ".")),
                    messageConsole: tipDefinition.tip.split(/\r?\n/).map((str) => format(str, replacements)).join("\n")
                },
            });
        }
    }
}
function extractFailedTestsInfo(failedTestsIn) {
    const failedTests = [];
    for (const failedTestIn of failedTestsIn || []) {
        const failedTestRes = {
            class: (failedTestIn.namespace ? failedTestIn.namespace + "__" : '') + failedTestIn.name,
            method: failedTestIn.methodName,
            error: failedTestIn.message,
        };
        if (failedTestIn?.stackTrace) {
            failedTestRes.stack = failedTestIn.stackTrace;
        }
        failedTests.push(failedTestRes);
    }
    return failedTests;
}
async function findAiTip(error, alreadyProcessedErrors) {
    if (alreadyProcessedErrors.includes(error.message)) {
        return null;
    }
    alreadyProcessedErrors.push(error.message);
    if (AiProvider.isAiAvailable()) {
        if (alreadyProcessedErrors.length > parseInt(process.env.MAX_DEPLOYMENT_TIPS_AI_CALLS || "20")) {
            uxLog("warning", this, c.yellow(`[AI] Maximum number of AI calls for deployment tips reached. Increase with env var MAX_DEPLOYMENT_TIPS_AI_CALLS`));
            return null;
        }
        const prompt = buildPrompt(error);
        try {
            const aiResponse = await AiProvider.promptAi(prompt, "PROMPT_SOLVE_DEPLOYMENT_ERROR");
            return aiResponse;
        }
        catch (e) {
            uxLog("warning", this, c.yellow("[AI] Error while calling AI Provider: " + e.message));
        }
    }
    return null;
}
function buildPrompt(error) {
    const prompt = `You are a Salesforce release manager using Salesforce CLI commands to perform deployments \n` +
        `How to solve the following Salesforce deployment error ?\n` +
        "- Please answer using sfdx source format, not metadata format. \n" +
        "- Please provide XML example if applicable. \n" +
        "- Please skip the part of the response about how to retrieve or deploy the changes with Salesforce CLI.\n" +
        `The error is: \n${JSON.stringify(error, null, 2)}`;
    return prompt;
}
//# sourceMappingURL=deployTipJson.js.map