import { generateUuid } from '../common/utility.js';
const createFallbackStorage = () => {
    let store;
    return {
        run(ctx, callback) {
            const prev = store;
            store = ctx;
            try {
                return callback();
            }
            finally {
                store = prev;
            }
        },
        getStore() {
            return store;
        },
    };
};
const resolveStorage = async () => {
    try {
        const mod = (await import('../node-singletons/async_hooks.js'));
        return new mod.AsyncLocalStorage();
    }
    catch {
        return createFallbackStorage();
    }
};
const getHeaderString = (req, name) => {
    const value = req.getHeader(name);
    return typeof value === 'string' ? value : undefined;
};
const extractTraceIdFromTraceparent = (traceparent) => {
    if (typeof traceparent !== 'string')
        return undefined;
    const trimmed = traceparent.trim();
    if (trimmed === '')
        return undefined;
    // W3C traceparent: version-traceid-spanid-flags
    // Example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
    const parts = trimmed.split('-');
    if (parts.length !== 4)
        return undefined;
    const traceId = parts[1] ?? '';
    if (/^[0-9a-f]{32}$/i.test(traceId) === false)
        return undefined;
    // Disallow all-zero traceId
    if (/^0{32}$/i.test(traceId))
        return undefined;
    return traceId.toLowerCase();
};
const getTraceIdFromMicroserviceTraceContext = (req) => {
    const anyReq = req;
    const trace = anyReq.context?.['trace'];
    if (typeof trace !== 'object' || trace === null)
        return undefined;
    const traceId = trace.traceId;
    return typeof traceId === 'string' && traceId.trim() !== '' ? traceId : undefined;
};
const getOptionalContextString = (req, key) => {
    const anyReq = req;
    const value = anyReq.context?.[key];
    if (typeof value === 'string')
        return value;
    if (typeof value === 'number')
        return String(value);
    return undefined;
};
const setContextField = (req, field, value) => {
    req.context ??= {};
    if (typeof value === 'string' && value.trim() !== '') {
        req.context[field] = value;
    }
    const ctx = RequestContext.get(req);
    if (ctx) {
        ctx[field] = value;
    }
};
const STORAGE_PROMISE = resolveStorage();
export const RequestContext = Object.freeze({
    async run(context, callback) {
        const storage = await STORAGE_PROMISE;
        return storage.run(context, callback);
    },
    async current() {
        const storage = await STORAGE_PROMISE;
        return storage.getStore();
    },
    create(req) {
        req.context ??= {};
        const requestIdFromHeader = getHeaderString(req, 'x-request-id');
        const requestId = requestIdFromHeader ?? generateUuid();
        const traceId = extractTraceIdFromTraceparent(getHeaderString(req, 'traceparent')) ??
            getHeaderString(req, 'x-trace-id') ??
            getTraceIdFromMicroserviceTraceContext(req) ??
            getOptionalContextString(req, 'traceId');
        const userId = getOptionalContextString(req, 'userId');
        const tenantId = getOptionalContextString(req, 'tenantId');
        const ctx = {
            requestId,
            traceId,
            userId,
            tenantId,
            startTime: Date.now(),
            method: req.getMethod(),
            path: req.getPath(),
            userAgent: getHeaderString(req, 'user-agent'),
        };
        req.context['requestId'] = requestId;
        if (traceId !== undefined)
            req.context['traceId'] = traceId;
        if (userId !== undefined)
            req.context['userId'] = userId;
        if (tenantId !== undefined)
            req.context['tenantId'] = tenantId;
        req.context['requestContext'] = ctx;
        return ctx;
    },
    attach(req, context) {
        req.context ??= {};
        req.context['requestContext'] = context;
        req.context['requestId'] = context.requestId;
        if (context.traceId !== undefined)
            req.context['traceId'] = context.traceId;
        if (context.userId !== undefined)
            req.context['userId'] = context.userId;
        if (context.tenantId !== undefined)
            req.context['tenantId'] = context.tenantId;
    },
    get(req) {
        const anyReq = req;
        const value = anyReq.context?.['requestContext'];
        return typeof value === 'object' && value !== null ? value : undefined;
    },
    enrich(context, status) {
        const duration = Date.now() - context.startTime;
        return {
            ...context,
            status,
            duration,
        };
    },
    setUserId(req, userId) {
        setContextField(req, 'userId', userId);
    },
    setTenantId(req, tenantId) {
        setContextField(req, 'tenantId', tenantId);
    },
    setTraceId(req, traceId) {
        setContextField(req, 'traceId', traceId);
    },
});
export default RequestContext;
