/*
 * Critical Path Analysis for distributed traces
 * Computes self-time, critical path, and bottleneck identification
 */
export default class CriticalPathUtil {
    /**
     * Compute self-time for each span.
     * Self-time = span duration minus the time covered by direct children,
     * accounting for overlapping children.
     */
    static computeSelfTimes(spans) {
        const result = new Map();
        // Build parent -> children index
        const childrenMap = new Map();
        for (const span of spans) {
            if (span.parentSpanId) {
                const children = childrenMap.get(span.parentSpanId) || [];
                children.push(span);
                childrenMap.set(span.parentSpanId, children);
            }
        }
        for (const span of spans) {
            const children = childrenMap.get(span.spanId) || [];
            const childTimeUnixNano = CriticalPathUtil.computeMergedChildDuration(children, span.startTimeUnixNano, span.endTimeUnixNano);
            const selfTimeUnixNano = Math.max(0, span.durationUnixNano - childTimeUnixNano);
            result.set(span.spanId, {
                spanId: span.spanId,
                selfTimeUnixNano,
                childTimeUnixNano,
                totalTimeUnixNano: span.durationUnixNano,
                selfTimePercent: span.durationUnixNano > 0
                    ? (selfTimeUnixNano / span.durationUnixNano) * 100
                    : 0,
            });
        }
        return result;
    }
    /**
     * Compute the merged duration of child spans within the parent's time window.
     * Handles overlapping children by merging intervals.
     */
    static computeMergedChildDuration(children, parentStart, parentEnd) {
        if (children.length === 0) {
            return 0;
        }
        // Clamp children to parent boundaries and create intervals
        const intervals = children
            .map((child) => {
            return {
                start: Math.max(child.startTimeUnixNano, parentStart),
                end: Math.min(child.endTimeUnixNano, parentEnd),
            };
        })
            .filter((interval) => {
            return interval.end > interval.start;
        });
        if (intervals.length === 0) {
            return 0;
        }
        // Sort by start time
        intervals.sort((a, b) => {
            return a.start - b.start;
        });
        // Merge overlapping intervals
        let mergedDuration = 0;
        let currentStart = intervals[0].start;
        let currentEnd = intervals[0].end;
        for (let i = 1; i < intervals.length; i++) {
            const interval = intervals[i];
            if (interval.start <= currentEnd) {
                // Overlapping - extend
                currentEnd = Math.max(currentEnd, interval.end);
            }
            else {
                // Non-overlapping - add previous and start new
                mergedDuration += currentEnd - currentStart;
                currentStart = interval.start;
                currentEnd = interval.end;
            }
        }
        mergedDuration += currentEnd - currentStart;
        return mergedDuration;
    }
    /**
     * Compute the critical path through the trace.
     * The critical path is the longest sequential chain of spans,
     * accounting for parallelism (parallel children don't add to the critical path together).
     */
    static computeCriticalPath(spans) {
        if (spans.length === 0) {
            return {
                criticalPathSpanIds: [],
                totalTraceDurationUnixNano: 0,
                criticalPathDurationUnixNano: 0,
            };
        }
        // Find total trace duration
        let traceStart = spans[0].startTimeUnixNano;
        let traceEnd = spans[0].endTimeUnixNano;
        for (const span of spans) {
            if (span.startTimeUnixNano < traceStart) {
                traceStart = span.startTimeUnixNano;
            }
            if (span.endTimeUnixNano > traceEnd) {
                traceEnd = span.endTimeUnixNano;
            }
        }
        // Build parent -> children index
        const childrenMap = new Map();
        const spanMap = new Map();
        const allSpanIds = new Set();
        for (const span of spans) {
            spanMap.set(span.spanId, span);
            allSpanIds.add(span.spanId);
        }
        for (const span of spans) {
            if (span.parentSpanId && allSpanIds.has(span.parentSpanId)) {
                const children = childrenMap.get(span.parentSpanId) || [];
                children.push(span);
                childrenMap.set(span.parentSpanId, children);
            }
        }
        // Find root spans
        const rootSpans = spans.filter((span) => {
            return !span.parentSpanId || !allSpanIds.has(span.parentSpanId);
        });
        if (rootSpans.length === 0) {
            return {
                criticalPathSpanIds: [],
                totalTraceDurationUnixNano: traceEnd - traceStart,
                criticalPathDurationUnixNano: 0,
            };
        }
        // For each span, compute the critical path weight (longest path through this span and descendants)
        const criticalPathCache = new Map();
        const computeWeight = (spanId) => {
            const cached = criticalPathCache.get(spanId);
            if (cached) {
                return cached;
            }
            const span = spanMap.get(spanId);
            if (!span) {
                return { weight: 0, path: [] };
            }
            const children = childrenMap.get(spanId) || [];
            if (children.length === 0) {
                const result = {
                    weight: span.durationUnixNano,
                    path: [spanId],
                };
                criticalPathCache.set(spanId, result);
                return result;
            }
            // Find the child with the longest critical path
            let maxChildWeight = 0;
            let maxChildPath = [];
            for (const child of children) {
                const childResult = computeWeight(child.spanId);
                if (childResult.weight > maxChildWeight) {
                    maxChildWeight = childResult.weight;
                    maxChildPath = childResult.path;
                }
            }
            // Self time contribution
            const selfTimes = CriticalPathUtil.computeSelfTimes([span, ...children]);
            const selfTime = selfTimes.get(spanId);
            const selfTimeValue = selfTime ? selfTime.selfTimeUnixNano : 0;
            const result = {
                weight: selfTimeValue + maxChildWeight,
                path: [spanId, ...maxChildPath],
            };
            criticalPathCache.set(spanId, result);
            return result;
        };
        // Find the root with the longest critical path
        let maxWeight = 0;
        let criticalPath = [];
        for (const rootSpan of rootSpans) {
            const result = computeWeight(rootSpan.spanId);
            if (result.weight > maxWeight) {
                maxWeight = result.weight;
                criticalPath = result.path;
            }
        }
        return {
            criticalPathSpanIds: criticalPath,
            totalTraceDurationUnixNano: traceEnd - traceStart,
            criticalPathDurationUnixNano: maxWeight,
        };
    }
    /**
     * Compute latency breakdown by service.
     */
    static computeServiceBreakdown(spans) {
        const selfTimes = CriticalPathUtil.computeSelfTimes(spans);
        // Find total trace duration
        let traceStart = Number.MAX_SAFE_INTEGER;
        let traceEnd = 0;
        for (const span of spans) {
            if (span.startTimeUnixNano < traceStart) {
                traceStart = span.startTimeUnixNano;
            }
            if (span.endTimeUnixNano > traceEnd) {
                traceEnd = span.endTimeUnixNano;
            }
        }
        const totalDuration = traceEnd - traceStart;
        // Aggregate by service
        const serviceMap = new Map();
        for (const span of spans) {
            const primaryEntityId = span.primaryEntityId || "unknown";
            const entry = serviceMap.get(primaryEntityId) || {
                totalDuration: 0,
                selfTime: 0,
                spanCount: 0,
            };
            entry.totalDuration += span.durationUnixNano;
            const selfTime = selfTimes.get(span.spanId);
            entry.selfTime += selfTime ? selfTime.selfTimeUnixNano : 0;
            entry.spanCount += 1;
            serviceMap.set(primaryEntityId, entry);
        }
        const result = [];
        for (const [primaryEntityId, data] of serviceMap.entries()) {
            result.push({
                primaryEntityId,
                totalDurationUnixNano: data.totalDuration,
                selfTimeUnixNano: data.selfTime,
                spanCount: data.spanCount,
                percentOfTrace: totalDuration > 0 ? (data.selfTime / totalDuration) * 100 : 0,
            });
        }
        // Sort by self-time descending (biggest contributors first)
        result.sort((a, b) => {
            return b.selfTimeUnixNano - a.selfTimeUnixNano;
        });
        return result;
    }
}
//# sourceMappingURL=CriticalPath.js.map