"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.traceMethod = traceMethod;
exports.traceFunction = traceFunction;
exports.executeTraced = executeTraced;
exports.addTracingToMethods = addTracingToMethods;
const api_1 = require("@opentelemetry/api");
const errorSanitizer_js_1 = require("./errorSanitizer.js");
/**
 * helper function to wrap a class method with tracing
 * @param className - The class name
 * @param methodName - The method name
 * @param descriptor - The method descriptor
 * @param tracer - The tracer
 */
function traceMethod(className, methodName, descriptor, tracer) {
    const originalMethod = descriptor.value;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    descriptor.value = function (...args) {
        // Potentially, we can pass this from tracing middleware to req and then to ActionHandler
        const parentSpan = api_1.trace.getActiveSpan();
        let ctx;
        if (parentSpan) {
            ctx = api_1.trace.setSpan(api_1.context.active(), parentSpan);
        }
        else {
            ctx = api_1.context.active();
        }
        // Use `startActiveSpan` to start and manage the span lifecycle
        return api_1.context.with(ctx, () => {
            return tracer.startActiveSpan(`${className}.${methodName}`, {}, async (span) => {
                try {
                    const result = originalMethod.apply(this, args);
                    if (result instanceof Promise) {
                        return result
                            .then((res) => {
                            span.end();
                            return res;
                        })
                            .catch((err) => {
                            span.recordException((0, errorSanitizer_js_1.sanitizeError)(err));
                            span.end();
                            throw err;
                        });
                    }
                    // For synchronous methods, end the span and return the result
                    span.end();
                    return result;
                }
                catch (err) {
                    span.recordException((0, errorSanitizer_js_1.sanitizeError)(err));
                    span.end();
                    throw err;
                }
            });
        });
    };
    return descriptor;
}
function traceFunction({ name, tracer, fn, parent }) {
    const parentSpan = parent ?? api_1.trace.getActiveSpan();
    const ctx = parentSpan ? api_1.trace.setSpan(api_1.context.active(), parentSpan) : api_1.context.active();
    return api_1.context.with(ctx, () => tracer.startActiveSpan(name, {}, async (span) => {
        try {
            const result = fn(span);
            if (result instanceof Promise) {
                return result
                    .then((res) => {
                    span.end();
                    return res;
                })
                    .catch((err) => {
                    span.recordException((0, errorSanitizer_js_1.sanitizeError)(err));
                    span.end();
                    throw err;
                });
            }
            span.end();
            return result;
        }
        catch (err) {
            span.recordException((0, errorSanitizer_js_1.sanitizeError)(err));
            span.end();
            throw err;
        }
    }));
}
/**
 * @deprecated Use `traceFunction` instead.
 */
function executeTraced(name, tracer, fn, parent) {
    const parentSpan = parent ?? api_1.trace.getActiveSpan();
    if (parentSpan) {
        // use parent span as context
        return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), parentSpan), () => {
            const span = tracer.startSpan(name);
            return fn(span).finally(() => {
                span.end();
            });
        });
    }
    else {
        // use new span as context
        return api_1.context.with(api_1.context.active(), () => {
            const span = tracer.startSpan(name);
            return fn(span).finally(() => {
                span.end();
            });
        });
    }
}
function findDescriptor(object, method) {
    let currentProto = Object.getPrototypeOf(object);
    // Traverse up the prototype chain to find the method's descriptor
    while (currentProto) {
        const descriptor = Object.getOwnPropertyDescriptor(currentProto, method);
        if (descriptor) {
            return descriptor; // Return the found descriptor
        }
        currentProto = Object.getPrototypeOf(currentProto); // Move up the prototype chain
    }
    return undefined; // Return undefined if not found
}
function addTracingToMethods(object, methods, tracer) {
    methods.forEach((method) => {
        if (typeof object[method] === 'function') {
            const descriptor = findDescriptor(object, method);
            if (descriptor) {
                Object.defineProperty(object, method, traceMethod(object.constructor.name, method, descriptor, tracer));
            }
        }
    });
}
//# sourceMappingURL=methodTracing.js.map