"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpanProcessor = void 0;
const trace_1 = require("../../../shared/src/types/trace");
const timeUtils_1 = require("../../../shared/src/utils/timeUtils");
const objectUtils_1 = require("../../../shared/src/utils/objectUtils");
const trace_2 = require("./opentelemetry/proto/trace/v1/trace");
const StatusCode = trace_2.opentelemetry.proto.trace.v1.Status.StatusCode;
class SpanProcessor {
    static validateOTLPSpan(span) {
        try {
            if (!span.trace_id || !span.span_id || !span.name) {
                console.error('[SpanProcessor] Missing required span fields');
                return false;
            }
            if (!span.start_time_unix_nano || !span.end_time_unix_nano) {
                console.error('[SpanProcessor] Missing span time fields');
                return false;
            }
            if (isNaN(Number(span.start_time_unix_nano)) ||
                isNaN(Number(span.end_time_unix_nano))) {
                console.error('[SpanProcessor] Invalid timestamp format');
                return false;
            }
            return true;
        }
        catch (error) {
            console.error('[SpanProcessor] Validation error:', error);
            return false;
        }
    }
    static decodeOTLPSpan(span) {
        // Sdk-handled data attributes
        const traceId = this.decodeIdentifier(span.trace_id);
        const spanId = this.decodeIdentifier(span.span_id);
        const parentId = this.decodeIdentifier(span.parent_span_id);
        const startTime = this.decodeUnixNano(span.start_time_unix_nano);
        const endTime = this.decodeUnixNano(span.end_time_unix_nano);
        // The self-calculated attributes
        const latencyMs = this.getTimeDifference(startTime, endTime);
        const attributes = this.unflattenAttributes(this.loadJsonStrings(this.decodeKeyValues(span.attributes)));
        const spanKind = this.getSpanKind(attributes);
        const runId = this.getRunId(attributes);
        const events = Array.isArray(span.events)
            ? span.events.map((event) => this.decodeEvent(event))
            : [];
        const status = this.decodeStatus(span.status);
        return {
            id: spanId,
            traceId: traceId,
            parentSpanId: parentId,
            runId: runId,
            name: span.name,
            spanKind: spanKind,
            startTime: startTime,
            endTime: endTime,
            latencyMs: latencyMs,
            attributes: attributes,
            status: status.code,
            statusMessage: status.message,
            events: events,
        };
    }
    static getAttributeValue(attributes, key, separator = '.') {
        return (0, objectUtils_1.getNestedValue)(attributes, key, separator);
    }
    static getSpanKind(attributes) {
        const kindValue = this.getAttributeValue(attributes, "span.kind" /* SpanAttributes.SPAN_KIND */);
        if (kindValue &&
            Object.values(trace_1.SpanKind).includes(kindValue)) {
            return kindValue;
        }
        return trace_1.SpanKind.COMMON;
    }
    static getRunId(attributes) {
        return this.getAttributeValue(attributes, "project.run_id" /* SpanAttributes.PROJECT_RUN_ID */);
    }
    static decodeIdentifier(identifier) {
        if (!identifier)
            return '';
        if (typeof identifier === 'string')
            return identifier;
        return Buffer.from(identifier).toString('hex');
    }
    static decodeUnixNano(timeUnixNano) {
        if (typeof timeUnixNano === 'number') {
            timeUnixNano = timeUnixNano.toString();
        }
        return (0, timeUtils_1.decodeUnixNano)(timeUnixNano);
    }
    static getTimeDifference(startTime, endTime) {
        return (0, timeUtils_1.getTimeDifference)(startTime, endTime);
    }
    static decodeKeyValues(keyValues) {
        const result = {};
        for (const kv of keyValues) {
            result[kv.key] = this.decodeAnyValue(kv.value);
        }
        return result;
    }
    static decodeAnyValue(value) {
        if (value.string_value !== undefined)
            return value.string_value;
        if (value.bool_value !== undefined)
            return value.bool_value;
        if (value.int_value !== undefined)
            return value.int_value;
        if (value.double_value !== undefined)
            return value.double_value;
        if (value.array_value)
            return value.array_value.values.map((v) => this.decodeAnyValue(v));
        if (value.kvlist_value)
            return this.decodeKeyValues(value.kvlist_value.values);
        if (value.bytes_value)
            return value.bytes_value;
        return null;
    }
    static decodeStatus(status) {
        if (!status) {
            return { code: trace_1.TraceStatus.UNSET, message: '' };
        }
        const statusMap = {
            [StatusCode.STATUS_CODE_UNSET]: trace_1.TraceStatus.UNSET,
            [StatusCode.STATUS_CODE_OK]: trace_1.TraceStatus.OK,
            [StatusCode.STATUS_CODE_ERROR]: trace_1.TraceStatus.ERROR,
        };
        return {
            code: statusMap[status.code] || trace_1.TraceStatus.UNSET,
            message: status.message || '',
        };
    }
    static decodeEvent(event) {
        return {
            name: event.name,
            timestamp: this.decodeUnixNano(event.time_unix_nano),
            attributes: this.decodeKeyValues(event.attributes),
        };
    }
    static unflattenAttributes(flat) {
        return (0, objectUtils_1.unflattenObject)(flat);
    }
    static loadJsonStrings(attributes) {
        const result = {};
        for (const [key, value] of Object.entries(attributes)) {
            if (typeof value === 'string') {
                try {
                    result[key] = JSON.parse(value);
                }
                catch (_a) {
                    result[key] = value;
                }
            }
            else {
                result[key] = value;
            }
        }
        return result;
    }
    static safeDecodeOTLPSpan(span) {
        try {
            if (!this.validateOTLPSpan(span)) {
                return null;
            }
            return this.decodeOTLPSpan(span);
        }
        catch (error) {
            console.error('[SpanProcessor] Failed to decode span:', error);
            return null;
        }
    }
    static batchProcessOTLPTraces(resourceSpans) {
        const spans = [];
        try {
            for (const resourceSpan of resourceSpans) {
                if (!resourceSpan.scope_spans) {
                    continue;
                }
                for (const scopeSpan of resourceSpan.scope_spans) {
                    if (!scopeSpan.spans) {
                        continue;
                    }
                    for (const span of scopeSpan.spans) {
                        const processedSpan = SpanProcessor.safeDecodeOTLPSpan(span);
                        if (processedSpan) {
                            spans.push(processedSpan);
                        }
                    }
                }
            }
        }
        catch (error) {
            console.error('[SpanProcessor] Failed to batch process spans:', error);
        }
        return spans;
    }
}
exports.SpanProcessor = SpanProcessor;
