import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
/**
 * OtelSpanTree – Collapsible tree view of nested spans within a trace.
 *
 * Renders spans as an indented, expandable tree with duration bars.
 * Uses Primer React components for consistent theming.
 *
 * @module otel/OtelSpanTree
 */
import { useState, useMemo, useCallback } from 'react';
import { Box, Text } from '@primer/react';
import { ChevronDownIcon, ChevronRightIcon } from '@primer/octicons-react';
import { formatDuration, serviceColor, toMs } from '../utils.js';
const INDENT_PX = 20;
const SpanNode = ({ span, depth, selectedSpanId, onSelectSpan, expandedIds, toggleExpanded, traceMinTime, traceDuration, }) => {
    const hasChildren = (span.children?.length ?? 0) > 0;
    const isExpanded = expandedIds.has(span.span_id);
    const isSelected = selectedSpanId === span.span_id;
    const color = serviceColor(span.service_name);
    const startPct = ((toMs(span.start_time) - traceMinTime) / traceDuration) * 100;
    const widthPct = Math.max(((span.duration_ms || 0) / traceDuration) * 100, 0.5);
    return (_jsxs(_Fragment, { children: [_jsxs(Box, { sx: {
                    display: 'flex',
                    alignItems: 'center',
                    height: 28,
                    pl: `${depth * INDENT_PX}px`,
                    pr: 2,
                    cursor: 'pointer',
                    bg: isSelected ? 'accent.subtle' : 'canvas.default',
                    borderBottom: '1px solid',
                    borderColor: 'border.muted',
                    ':hover': {
                        bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
                    },
                }, onClick: () => onSelectSpan?.(span), children: [_jsx(Box, { sx: {
                            width: 20,
                            textAlign: 'center',
                            color: 'fg.muted',
                            userSelect: 'none',
                            flexShrink: 0,
                            cursor: hasChildren ? 'pointer' : 'default',
                        }, onClick: e => {
                            e.stopPropagation();
                            if (hasChildren)
                                toggleExpanded(span.span_id);
                        }, children: hasChildren ? (isExpanded ? (_jsx(ChevronDownIcon, { size: 12 })) : (_jsx(ChevronRightIcon, { size: 12 }))) : (_jsx(Text, { sx: { color: 'fg.subtle', fontSize: 0 }, children: "\u00B7" })) }), _jsx(Box, { sx: {
                            width: 8,
                            height: 8,
                            borderRadius: '50%',
                            bg: color,
                            flexShrink: 0,
                            mr: 1,
                        } }), _jsx(Text, { sx: {
                            flex: '0 0 200px',
                            overflow: 'hidden',
                            textOverflow: 'ellipsis',
                            whiteSpace: 'nowrap',
                            fontSize: 1,
                            fontWeight: isSelected ? 'bold' : 'normal',
                        }, title: `${span.service_name} / ${span.span_name}`, children: span.span_name }), _jsx(Box, { sx: {
                            flex: 1,
                            position: 'relative',
                            height: 12,
                            bg: 'canvas.subtle',
                            borderRadius: 1,
                            mx: 2,
                            overflow: 'hidden',
                        }, children: _jsx(Box, { sx: {
                                position: 'absolute',
                                left: `${startPct}%`,
                                width: `${widthPct}%`,
                                height: '100%',
                                bg: color,
                                borderRadius: 1,
                                opacity: isSelected ? 1 : 0.7,
                            } }) }), _jsx(Text, { sx: {
                            minWidth: 60,
                            textAlign: 'right',
                            fontSize: 0,
                            fontFamily: 'mono',
                            color: 'fg.muted',
                            flexShrink: 0,
                        }, children: formatDuration(span.duration_ms) })] }), hasChildren &&
                isExpanded &&
                span.children.map(child => (_jsx(SpanNode, { span: child, depth: depth + 1, selectedSpanId: selectedSpanId, onSelectSpan: onSelectSpan, expandedIds: expandedIds, toggleExpanded: toggleExpanded, traceMinTime: traceMinTime, traceDuration: traceDuration }, child.span_id)))] }));
};
export const OtelSpanTree = ({ spans, selectedSpanId, onSelectSpan, defaultExpandDepth = 2, }) => {
    // Pre-compute which IDs should be expanded by default
    const defaultExpanded = useMemo(() => {
        const ids = new Set();
        function walk(nodes, depth) {
            for (const n of nodes) {
                if (depth < defaultExpandDepth) {
                    ids.add(n.span_id);
                }
                if (n.children)
                    walk(n.children, depth + 1);
            }
        }
        walk(spans, 0);
        return ids;
    }, [spans, defaultExpandDepth]);
    const [expandedIds, setExpandedIds] = useState(defaultExpanded);
    const toggleExpanded = useCallback((id) => {
        setExpandedIds(prev => {
            const next = new Set(prev);
            if (next.has(id)) {
                next.delete(id);
            }
            else {
                next.add(id);
            }
            return next;
        });
    }, []);
    // Compute trace time bounds
    const { traceMinTime, traceDuration } = useMemo(() => {
        let min = Infinity;
        let max = -Infinity;
        function walk(nodes) {
            for (const s of nodes) {
                const start = toMs(s.start_time);
                const end = toMs(s.end_time);
                if (start < min)
                    min = start;
                if (end > max)
                    max = end;
                if (s.children)
                    walk(s.children);
            }
        }
        walk(spans);
        if (min === Infinity)
            return { traceMinTime: 0, traceDuration: 1 };
        return { traceMinTime: min, traceDuration: max - min || 1 };
    }, [spans]);
    if (spans.length === 0) {
        return (_jsx(Box, { sx: { p: 3, color: 'fg.muted', textAlign: 'center' }, children: _jsx(Text, { children: "No span tree to display." }) }));
    }
    return (_jsxs(Box, { sx: { width: '100%', overflow: 'auto' }, children: [_jsxs(Box, { sx: {
                    display: 'flex',
                    alignItems: 'center',
                    height: 28,
                    px: 2,
                    borderBottom: '2px solid',
                    borderColor: 'border.default',
                    bg: 'canvas.subtle',
                }, children: [_jsx(Box, { sx: { width: 20 } }), _jsx(Box, { sx: { width: 8, mr: 1 } }), _jsx(Text, { sx: {
                            flex: '0 0 200px',
                            fontSize: 0,
                            fontWeight: 'bold',
                            color: 'fg.muted',
                            textTransform: 'uppercase',
                            letterSpacing: '0.05em',
                        }, children: "Span" }), _jsx(Text, { sx: {
                            flex: 1,
                            mx: 2,
                            fontSize: 0,
                            fontWeight: 'bold',
                            color: 'fg.muted',
                            textTransform: 'uppercase',
                            letterSpacing: '0.05em',
                        }, children: "Timeline" }), _jsx(Text, { sx: {
                            minWidth: 60,
                            textAlign: 'right',
                            fontSize: 0,
                            fontWeight: 'bold',
                            color: 'fg.muted',
                            textTransform: 'uppercase',
                            letterSpacing: '0.05em',
                        }, children: "Duration" })] }), spans.map(root => (_jsx(SpanNode, { span: root, depth: 0, selectedSpanId: selectedSpanId, onSelectSpan: onSelectSpan, expandedIds: expandedIds, toggleExpanded: toggleExpanded, traceMinTime: traceMinTime, traceDuration: traceDuration }, root.span_id)))] }));
};
