"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AzureFunctionsHook = void 0;
var Logging = require("../Library/Logging");
var CorrelationContextManager_1 = require("./CorrelationContextManager");
/** Node.js Azure Functions handle incoming HTTP requests before Application Insights SDK is available,
 * this code generate incoming request telemetry and generate correlation context to be used
 * by outgoing requests and other telemetry, we rely on hooks provided by Azure Functions
*/
var AzureFunctionsHook = /** @class */ (function () {
    function AzureFunctionsHook(client) {
        this._client = client;
        this._autoGenerateIncomingRequests = false;
        try {
            this._functionsCoreModule = require("@azure/functions-core");
            this._addPreInvocationHook();
            this._addPostInvocationHook();
        }
        catch (error) {
            Logging.info("AzureFunctionsHook failed to load, not running in Azure Functions");
        }
    }
    /**
     * NOTE: The programming model can be changed any time until the first invocation
     * For that reason, we delay setting the model helper until the first hook is called, but we can cache it after that
     */
    AzureFunctionsHook.prototype._getFuncModelHelper = function () {
        if (this._cachedModelHelper === undefined) {
            var funcProgModel = this._functionsCoreModule.getProgrammingModel();
            if (funcProgModel.name === "@azure/functions") {
                if (funcProgModel.version.startsWith("3.")) {
                    this._cachedModelHelper = new FuncModelV3Helper();
                }
                else if (funcProgModel.version.startsWith("4.")) {
                    this._cachedModelHelper = new FuncModelV4Helper();
                }
            }
            if (!this._cachedModelHelper) {
                this._cachedModelHelper = null;
                Logging.warn("AzureFunctionsHook does not support model \"".concat(funcProgModel.name, "\" version \"").concat(funcProgModel.version, "\""));
            }
        }
        return this._cachedModelHelper;
    };
    AzureFunctionsHook.prototype.enable = function (isEnabled) {
        this._autoGenerateIncomingRequests = isEnabled;
    };
    AzureFunctionsHook.prototype.dispose = function () {
        this.enable(false);
        this._removeInvocationHooks();
        this._functionsCoreModule = undefined;
    };
    AzureFunctionsHook.prototype._addPreInvocationHook = function () {
        var _this = this;
        if (!this._preInvocationHook) {
            this._preInvocationHook = this._functionsCoreModule.registerHook("preInvocation", function (preInvocationContext) { return __awaiter(_this, void 0, void 0, function () {
                var modelHelper, sharedContext, extractedContext, traceContext;
                return __generator(this, function (_a) {
                    try {
                        modelHelper = this._getFuncModelHelper();
                        if (modelHelper) {
                            sharedContext = preInvocationContext.invocationContext;
                            extractedContext = CorrelationContextManager_1.CorrelationContextManager.startOperation(sharedContext);
                            if (extractedContext) { // Will be null if CorrelationContextManager is not enabled, we should not try to propagate context in that case
                                extractedContext.customProperties.setProperty("InvocationId", sharedContext.invocationId);
                                traceContext = sharedContext.traceContext;
                                if (traceContext.attributes) {
                                    extractedContext.customProperties.setProperty("ProcessId", traceContext.attributes["ProcessId"]);
                                    extractedContext.customProperties.setProperty("LogLevel", traceContext.attributes["LogLevel"]);
                                    extractedContext.customProperties.setProperty("Category", traceContext.attributes["Category"]);
                                    extractedContext.customProperties.setProperty("HostInstanceId", traceContext.attributes["HostInstanceId"]);
                                    extractedContext.customProperties.setProperty("AzFuncLiveLogsSessionId", traceContext.attributes["#AzFuncLiveLogsSessionId"]);
                                }
                                preInvocationContext.functionCallback = CorrelationContextManager_1.CorrelationContextManager.wrapCallback(preInvocationContext.functionCallback, extractedContext);
                                if (modelHelper.isHttpTrigger(preInvocationContext) && this._autoGenerateIncomingRequests) {
                                    preInvocationContext.hookData.appInsightsExtractedContext = extractedContext;
                                    preInvocationContext.hookData.appInsightsStartTime = Date.now(); // Start trackRequest timer
                                }
                            }
                        }
                    }
                    catch (err) {
                        Logging.warn("Failed to propagate context in Azure Functions", err);
                        return [2 /*return*/];
                    }
                    return [2 /*return*/];
                });
            }); });
        }
    };
    AzureFunctionsHook.prototype._addPostInvocationHook = function () {
        var _this = this;
        if (!this._postInvocationHook) {
            this._postInvocationHook = this._functionsCoreModule.registerHook("postInvocation", function (postInvocationContext) { return __awaiter(_this, void 0, void 0, function () {
                var modelHelper, request_1, startTime_1, extractedContext_1;
                var _this = this;
                return __generator(this, function (_a) {
                    try {
                        modelHelper = this._getFuncModelHelper();
                        if (modelHelper) {
                            if (this._autoGenerateIncomingRequests) {
                                if (modelHelper.isHttpTrigger(postInvocationContext)) {
                                    request_1 = postInvocationContext.inputs[0];
                                    if (request_1) {
                                        startTime_1 = postInvocationContext.hookData.appInsightsStartTime || Date.now();
                                        extractedContext_1 = postInvocationContext.hookData.appInsightsExtractedContext;
                                        if (!extractedContext_1) {
                                            this._createIncomingRequestTelemetry(request_1, postInvocationContext, startTime_1, null);
                                        }
                                        else {
                                            CorrelationContextManager_1.CorrelationContextManager.runWithContext(extractedContext_1, function () {
                                                _this._createIncomingRequestTelemetry(request_1, postInvocationContext, startTime_1, extractedContext_1.operation.parentId);
                                            });
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (err) {
                        Logging.warn("Error creating automatic incoming request in Azure Functions", err);
                    }
                    return [2 /*return*/];
                });
            }); });
        }
    };
    AzureFunctionsHook.prototype._createIncomingRequestTelemetry = function (request, hookContext, startTime, parentId) {
        var values = this._getFuncModelHelper().getStatusCodes(hookContext);
        var statusCode = 200; //Default
        if (values) {
            for (var _i = 0, values_1 = values; _i < values_1.length; _i++) {
                var value = values_1[_i];
                if (typeof value === "number" && Number.isInteger(value)) {
                    statusCode = value;
                    break;
                }
                else if (typeof value === "string") {
                    var parsedVal = parseInt(value);
                    if (!isNaN(parsedVal)) {
                        statusCode = parsedVal;
                        break;
                    }
                }
            }
        }
        else {
            statusCode = undefined;
        }
        this._client.trackRequest({
            name: request.method + " " + request.url,
            resultCode: statusCode,
            success: typeof (statusCode) === "number" ? (0 < statusCode) && (statusCode < 400) : undefined,
            url: request.url,
            time: new Date(startTime),
            duration: Date.now() - startTime,
            id: parentId
        });
        this._client.flush();
    };
    AzureFunctionsHook.prototype._removeInvocationHooks = function () {
        if (this._preInvocationHook) {
            this._preInvocationHook.dispose();
            this._preInvocationHook = undefined;
        }
        if (this._postInvocationHook) {
            this._postInvocationHook.dispose();
            this._postInvocationHook = undefined;
        }
    };
    return AzureFunctionsHook;
}());
exports.AzureFunctionsHook = AzureFunctionsHook;
var FuncModelV3Helper = /** @class */ (function () {
    function FuncModelV3Helper() {
    }
    FuncModelV3Helper.prototype._getInvocationContext = function (hookContext) {
        return hookContext.invocationContext;
    };
    FuncModelV3Helper.prototype.getStatusCodes = function (hookContext) {
        var ctx = this._getInvocationContext(hookContext);
        var response;
        var httpOutputBinding = ctx.bindingDefinitions.find(function (b) { return b.direction === "out" && b.type.toLowerCase() === "http"; });
        if ((httpOutputBinding === null || httpOutputBinding === void 0 ? void 0 : httpOutputBinding.name) === "$return") {
            response = hookContext.result;
        }
        else if (httpOutputBinding && ctx.bindings && ctx.bindings[httpOutputBinding.name] !== undefined) {
            response = ctx.bindings[httpOutputBinding.name];
        }
        else {
            response = ctx.res;
        }
        return response ? [response.statusCode, response.status] : undefined;
    };
    FuncModelV3Helper.prototype.isHttpTrigger = function (hookContext) {
        var ctx = this._getInvocationContext(hookContext);
        return !!ctx.bindingDefinitions.find(function (b) { var _a; return ((_a = b.type) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "httptrigger"; });
    };
    return FuncModelV3Helper;
}());
/**
 * V4 is only supported on Node.js v18+
 * Unfortunately that means we can't use the "@azure/functions" types for v4 or we break the build on Node <v18
 */
var FuncModelV4Helper = /** @class */ (function () {
    function FuncModelV4Helper() {
    }
    FuncModelV4Helper.prototype._getInvocationContext = function (hookContext) {
        return hookContext.invocationContext;
    };
    FuncModelV4Helper.prototype.getStatusCodes = function (hookContext) {
        var response = hookContext.result;
        return response ? [response.status] : undefined;
    };
    FuncModelV4Helper.prototype.isHttpTrigger = function (hookContext) {
        var ctx = this._getInvocationContext(hookContext);
        return ctx.options.trigger.type.toLowerCase() === "httptrigger";
    };
    return FuncModelV4Helper;
}());
//# sourceMappingURL=AzureFunctionsHook.js.map