var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import logger from "../Logger";
import CaptureSpan from "../Telemetry/CaptureSpan";
import VMRunner from "./VMRunner";
export default class VMUtil {
    static async runCodeInSandbox(data) {
        return VMRunner.runCodeInSandbox(data);
    }
    static replaceValueInPlace(storageMap, valueToReplaceInPlace, isJSON) {
        let didStringify = false;
        if (typeof valueToReplaceInPlace === "object") {
            try {
                valueToReplaceInPlace = JSON.stringify(valueToReplaceInPlace);
                didStringify = true;
            }
            catch (err) {
                logger.error(err);
                return valueToReplaceInPlace;
            }
        }
        if (typeof valueToReplaceInPlace === "string" &&
            valueToReplaceInPlace.toString().includes("{{") &&
            valueToReplaceInPlace.toString().includes("}}")) {
            let valueToReplaceInPlaceCopy = valueToReplaceInPlace.toString();
            // First, expand {{#each path}}...{{/each}} loops before variable substitution
            valueToReplaceInPlaceCopy = VMUtil.expandEachLoops(storageMap, valueToReplaceInPlaceCopy, isJSON);
            const variablesInArgument = [];
            const regex = /{{(.*?)}}/g; // Find all matches of the regular expression and capture the word between the braces {{x}} => x
            let match = null;
            while ((match = regex.exec(valueToReplaceInPlaceCopy)) !== null) {
                if (match[1]) {
                    variablesInArgument.push(match[1]);
                }
            }
            for (const variable of variablesInArgument) {
                const foundValue = VMUtil.deepFind(storageMap, variable);
                // Skip replacement if the variable is not found in the storageMap.
                if (foundValue === undefined) {
                    continue;
                }
                let valueToReplaceInPlace;
                // Properly serialize objects to JSON strings
                if (typeof foundValue === "object" && foundValue !== null) {
                    valueToReplaceInPlace = JSON.stringify(foundValue, null, 2);
                }
                else {
                    valueToReplaceInPlace = foundValue;
                }
                if (valueToReplaceInPlaceCopy.trim() === "{{" + variable + "}}") {
                    valueToReplaceInPlaceCopy = valueToReplaceInPlace;
                }
                else {
                    valueToReplaceInPlaceCopy = valueToReplaceInPlaceCopy.replace("{{" + variable + "}}", isJSON
                        ? VMUtil.serializeValueForJSON(valueToReplaceInPlace)
                        : `${valueToReplaceInPlace}`);
                }
            }
            valueToReplaceInPlace = valueToReplaceInPlaceCopy;
        }
        if (didStringify) {
            try {
                valueToReplaceInPlace = JSON.parse(valueToReplaceInPlace);
            }
            catch (err) {
                logger.error(err);
                return valueToReplaceInPlace;
            }
        }
        return valueToReplaceInPlace;
    }
    /**
     * Expand {{#each path}}...{{/each}} loop blocks by iterating over arrays.
     *
     * Supports:
     *  - {{variableName}} inside the loop body resolves relative to the current array element
     *  - {{@index}} resolves to the 0-based index of the current iteration
     *  - {{this}} resolves to the current element value (useful for primitive arrays)
     *  - Nested {{#each}} blocks for multi-level array traversal
     *  - If the resolved path is not an array, the block is removed (replaced with empty string)
     *
     * Example:
     *   {{#each requestBody.alerts}}
     *     Alert {{@index}}: {{labels.label}} - {{status}}
     *   {{/each}}
     */
    static expandEachLoops(storageMap, template, isJSON) {
        let result = template;
        const maxIterations = 100; // safety limit to prevent infinite loops
        let iterations = 0;
        while (iterations < maxIterations) {
            iterations++;
            // Find the first (outermost) {{#each ...}} tag
            const openTag = /\{\{#each\s+(.*?)\}\}/;
            const openMatch = openTag.exec(result);
            if (!openMatch) {
                break; // no more {{#each}} blocks
            }
            const blockStart = openMatch.index;
            const arrayPath = openMatch[1].trim();
            const bodyStart = blockStart + openMatch[0].length;
            // Find the matching {{/each}} by counting nesting depth
            let depth = 1;
            let searchPos = bodyStart;
            let matchEnd = -1;
            let bodyEnd = -1;
            while (depth > 0 && searchPos < result.length) {
                const nextOpen = result.indexOf("{{#each ", searchPos);
                const nextClose = result.indexOf("{{/each}}", searchPos);
                if (nextClose === -1) {
                    // Unmatched {{#each}} — break out to avoid infinite loop
                    break;
                }
                if (nextOpen !== -1 && nextOpen < nextClose) {
                    // Found a nested {{#each}} before the next {{/each}}
                    depth++;
                    searchPos = nextOpen + 8; // skip past "{{#each "
                }
                else {
                    // Found {{/each}}
                    depth--;
                    if (depth === 0) {
                        bodyEnd = nextClose;
                        matchEnd = nextClose + "{{/each}}".length;
                    }
                    searchPos = nextClose + "{{/each}}".length;
                }
            }
            if (matchEnd === -1 || bodyEnd === -1) {
                // Unmatched {{#each}} — remove it to prevent infinite loop
                result =
                    result.slice(0, blockStart) +
                        result.slice(blockStart + openMatch[0].length);
                continue;
            }
            const loopBody = result.slice(bodyStart, bodyEnd);
            // Resolve the array from the storage map
            const arrayValue = VMUtil.deepFind(storageMap, arrayPath);
            if (!Array.isArray(arrayValue)) {
                // Not an array — remove the block entirely
                result = result.slice(0, blockStart) + result.slice(matchEnd);
                continue;
            }
            // Expand the loop body for each element in the array
            const expandedParts = [];
            for (let i = 0; i < arrayValue.length; i++) {
                const element = arrayValue[i];
                let iterationBody = loopBody;
                // Replace {{@index}} with the current index
                iterationBody = iterationBody.replace(/\{\{@index\}\}/g, i.toString());
                if (typeof element === "object" && element !== null) {
                    /*
                     * Merge element properties into a scoped storageMap so that:
                     * 1. Element properties can be accessed directly (e.g., {{status}})
                     * 2. Parent storageMap properties are still accessible (e.g., {{requestBody.receiver}})
                     */
                    const scopedStorageMap = Object.assign(Object.assign({}, storageMap), element);
                    // Recursively expand any nested {{#each}} blocks within the iteration body
                    iterationBody = VMUtil.expandEachLoops(scopedStorageMap, iterationBody, isJSON);
                    // Replace remaining {{variable}} placeholders
                    iterationBody = VMUtil.replaceLoopVariables(element, storageMap, iterationBody, isJSON);
                }
                else {
                    // For primitive array elements, replace {{this}} with the value
                    iterationBody = iterationBody.replace(/\{\{this\}\}/g, isJSON ? VMUtil.serializeValueForJSON(`${element}`) : `${element}`);
                }
                expandedParts.push(iterationBody);
            }
            result =
                result.slice(0, blockStart) +
                    expandedParts.join("") +
                    result.slice(matchEnd);
        }
        return result;
    }
    /**
     * Replace {{variable}} placeholders inside a loop body.
     * Variables are resolved first against the current element (scoped),
     * then fall back to the parent storageMap.
     */
    static replaceLoopVariables(element, parentStorageMap, body, isJSON) {
        const variableRegex = /\{\{((?!#each\b|\/each\b|@index\b).*?)\}\}/g;
        let match = null;
        const variables = [];
        while ((match = variableRegex.exec(body)) !== null) {
            if (match[1]) {
                variables.push(match[1]);
            }
        }
        for (const variable of variables) {
            // First try resolving relative to the current element
            let foundValue = VMUtil.deepFind(element, variable.trim());
            // Fall back to the parent storage map (for absolute paths)
            if (foundValue === undefined) {
                foundValue = VMUtil.deepFind(parentStorageMap, variable.trim());
            }
            if (foundValue === undefined) {
                continue; // leave unresolved
            }
            let replacement;
            if (typeof foundValue === "object" && foundValue !== null) {
                replacement = JSON.stringify(foundValue, null, 2);
            }
            else {
                replacement = `${foundValue}`;
            }
            body = body.replace("{{" + variable + "}}", isJSON ? VMUtil.serializeValueForJSON(replacement) : replacement);
        }
        return body;
    }
    static serializeValueForJSON(value) {
        if (!value) {
            return value;
        }
        if (typeof value !== "string") {
            value = JSON.stringify(value);
        }
        else {
            value = value
                .split("\t")
                .join("\\t")
                .split("\n")
                .join("\\n")
                .split("\r")
                .join("\\r")
                .split("\b")
                .join("\\b")
                .split("\f")
                .join("\\f")
                .split('"')
                .join('\\"');
        }
        return value;
    }
    static deepFind(obj, path) {
        const paths = path.split(".");
        let current = JSON.parse(JSON.stringify(obj));
        for (let i = 0; i < paths.length; ++i) {
            const key = paths[i];
            if (!key) {
                return undefined;
            }
            const openBracketIndex = key.indexOf("[");
            const closeBracketIndex = key.indexOf("]");
            if (openBracketIndex !== -1 && closeBracketIndex !== -1) {
                const arrayKey = key.slice(0, openBracketIndex);
                const indexString = key.slice(openBracketIndex + 1, closeBracketIndex);
                let index = 0;
                if (indexString !== "last") {
                    index = parseInt(indexString);
                }
                else {
                    index = current[arrayKey].length - 1;
                }
                if (Array.isArray(current[arrayKey]) && current[arrayKey][index]) {
                    current = current[arrayKey][index];
                }
                else {
                    return undefined;
                }
            }
            else if (current && current[key] !== undefined) {
                current = current[key];
            }
            else {
                return undefined;
            }
        }
        return current;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], VMUtil, "runCodeInSandbox", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, String, Object]),
    __metadata("design:returntype", String)
], VMUtil, "replaceValueInPlace", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, String, Object]),
    __metadata("design:returntype", String)
], VMUtil, "expandEachLoops", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, String, Object]),
    __metadata("design:returntype", String)
], VMUtil, "replaceLoopVariables", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", String)
], VMUtil, "serializeValueForJSON", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, String]),
    __metadata("design:returntype", Object)
], VMUtil, "deepFind", null);
//# sourceMappingURL=VMAPI.js.map