import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
/**
 * OtelTimelineRangeSlider – A modern dual-handle timeline range slider
 * for selecting time windows over OTEL signals.
 *
 * Built with Primer React `Box` / `Text` + native pointer events for
 * smooth drag interaction.  Fully theme-aware — inherits all colors
 * from the active Primer theme via CSS functional tokens.
 *
 * Inspired by react-timeline-range-slider (d3-scale + react-compound-slider)
 * but implemented from scratch with zero external dependencies beyond Primer.
 *
 * @module otel/OtelTimelineRangeSlider
 */
import { useCallback, useRef, useState, useMemo } from 'react';
import { Box, Text } from '@primer/react';
// ── Helpers ─────────────────────────────────────────────────────────
function clamp(v, min, max) {
    return Math.max(min, Math.min(max, v));
}
function lerp(a, b, t) {
    return a + (b - a) * t;
}
function defaultFormatTick(d) {
    const h = d.getHours().toString().padStart(2, '0');
    const m = d.getMinutes().toString().padStart(2, '0');
    return `${h}:${m}`;
}
function generateTicks(start, end, count) {
    const ticks = [];
    const s = start.getTime();
    const e = end.getTime();
    for (let i = 0; i <= count; i++) {
        ticks.push(new Date(lerp(s, e, i / count)));
    }
    return ticks;
}
// ── Component ───────────────────────────────────────────────────────
export const OtelTimelineRangeSlider = ({ timelineStart, timelineEnd, selectedStart, selectedEnd, onRangeChange, onRangeCommit, tickCount = 8, formatTick = defaultFormatTick, height = 56, histogram, }) => {
    const railRef = useRef(null);
    const dragging = useRef(null);
    const dragOrigin = useRef({ pct: 0, startPct: 0, endPct: 0 });
    const [isDragging, setIsDragging] = useState(false);
    const [hoverPct, setHoverPct] = useState(null);
    // Compute percentages
    const tlStart = timelineStart.getTime();
    const tlEnd = timelineEnd.getTime();
    const tlRange = tlEnd - tlStart || 1;
    const startPct = ((selectedStart.getTime() - tlStart) / tlRange) * 100;
    const endPct = ((selectedEnd.getTime() - tlStart) / tlRange) * 100;
    const ticks = useMemo(() => generateTicks(timelineStart, timelineEnd, tickCount), [timelineStart, timelineEnd, tickCount]);
    // Histogram bars
    const histMax = useMemo(() => {
        if (!histogram?.length)
            return 0;
        return Math.max(...histogram.map(h => h.count));
    }, [histogram]);
    // Convert pointer position to percentage
    const pointerToPct = useCallback((clientX) => {
        if (!railRef.current)
            return 0;
        const rect = railRef.current.getBoundingClientRect();
        return clamp(((clientX - rect.left) / rect.width) * 100, 0, 100);
    }, []);
    const pctToDate = useCallback((pct) => new Date(tlStart + (pct / 100) * tlRange), [tlStart, tlRange]);
    // ── Pointer handlers ──
    const handlePointerDown = useCallback((e, target) => {
        e.preventDefault();
        e.stopPropagation();
        e.target.setPointerCapture(e.pointerId);
        dragging.current = target;
        const pct = pointerToPct(e.clientX);
        dragOrigin.current = { pct, startPct, endPct };
        setIsDragging(true);
    }, [pointerToPct, startPct, endPct]);
    const handlePointerMove = useCallback((e) => {
        if (!dragging.current)
            return;
        const pct = pointerToPct(e.clientX);
        const origin = dragOrigin.current;
        let newStart = startPct;
        let newEnd = endPct;
        if (dragging.current === 'start') {
            newStart = clamp(pct, 0, endPct - 1);
        }
        else if (dragging.current === 'end') {
            newEnd = clamp(pct, startPct + 1, 100);
        }
        else if (dragging.current === 'track') {
            const delta = pct - origin.pct;
            const span = origin.endPct - origin.startPct;
            let s = origin.startPct + delta;
            let e = origin.endPct + delta;
            if (s < 0) {
                s = 0;
                e = span;
            }
            if (e > 100) {
                e = 100;
                s = 100 - span;
            }
            newStart = s;
            newEnd = e;
        }
        onRangeChange(pctToDate(newStart), pctToDate(newEnd));
    }, [pointerToPct, startPct, endPct, onRangeChange, pctToDate]);
    const handlePointerUp = useCallback((e) => {
        if (!dragging.current)
            return;
        e.target.releasePointerCapture(e.pointerId);
        dragging.current = null;
        setIsDragging(false);
        onRangeCommit?.(selectedStart, selectedEnd);
    }, [selectedStart, selectedEnd, onRangeCommit]);
    // Hover handlers – show dotted line + tooltip while not dragging
    const handleMouseMove = useCallback((e) => {
        if (dragging.current)
            return;
        setHoverPct(pointerToPct(e.clientX));
    }, [pointerToPct]);
    const handleMouseLeave = useCallback(() => {
        setHoverPct(null);
    }, []);
    // Format date for hover tooltip (date + time with seconds)
    const formatHoverDate = useCallback((pct) => {
        const d = pctToDate(pct);
        const date = d.toLocaleDateString(undefined, {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
        });
        const time = d.toLocaleTimeString(undefined, {
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
        });
        return `${date}  ${time}`;
    }, [pctToDate]);
    // Rail click to jump
    const handleRailClick = useCallback((e) => {
        if (isDragging)
            return;
        const pct = pointerToPct(e.clientX);
        // Jump the closer handle
        const distToStart = Math.abs(pct - startPct);
        const distToEnd = Math.abs(pct - endPct);
        if (distToStart < distToEnd) {
            const newStart = clamp(pct, 0, endPct - 1);
            onRangeChange(pctToDate(newStart), selectedEnd);
            onRangeCommit?.(pctToDate(newStart), selectedEnd);
        }
        else {
            const newEnd = clamp(pct, startPct + 1, 100);
            onRangeChange(selectedStart, pctToDate(newEnd));
            onRangeCommit?.(selectedStart, pctToDate(newEnd));
        }
    }, [
        isDragging,
        pointerToPct,
        startPct,
        endPct,
        onRangeChange,
        onRangeCommit,
        pctToDate,
        selectedStart,
        selectedEnd,
    ]);
    const RAIL_HEIGHT = height - 24; // Reserve 24px for tick labels
    const HANDLE_WIDTH = 10;
    const HANDLE_HEIGHT = RAIL_HEIGHT;
    return (_jsxs(Box, { sx: {
            width: '100%',
            height,
            px: 3,
            py: 1,
            userSelect: 'none',
            bg: 'canvas.subtle',
            borderBottom: '1px solid',
            borderColor: 'border.default',
        }, children: [_jsxs(Box, { ref: railRef, onClick: handleRailClick, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, sx: {
                    position: 'relative',
                    width: '100%',
                    height: RAIL_HEIGHT,
                    cursor: 'pointer',
                }, children: [_jsx(Box, { sx: {
                            position: 'absolute',
                            inset: 0,
                            bg: 'canvas.inset',
                            borderRadius: 1,
                            border: '1px solid',
                            borderColor: 'border.muted',
                        } }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'track'), sx: {
                            position: 'absolute',
                            top: 0,
                            bottom: 0,
                            left: `${startPct}%`,
                            width: `${endPct - startPct}%`,
                            bg: 'accent.muted',
                            opacity: 0.45,
                            borderTop: '2px solid',
                            borderBottom: '2px solid',
                            borderColor: 'accent.fg',
                            cursor: 'grab',
                            zIndex: 2,
                            transition: isDragging
                                ? 'none'
                                : 'left 0.05s ease, width 0.05s ease',
                            ':active': { cursor: 'grabbing' },
                        } }), histogram &&
                        histMax > 0 &&
                        histogram.map((h, i) => {
                            const pct = ((h.time.getTime() - tlStart) / tlRange) * 100;
                            const barWidth = 100 / (histogram.length || 1);
                            const barH = (h.count / histMax) * 100;
                            return (_jsx(Box, { sx: {
                                    position: 'absolute',
                                    bottom: 0,
                                    left: `${pct}%`,
                                    width: `${barWidth}%`,
                                    height: `${barH}%`,
                                    bg: 'accent.fg',
                                    opacity: 0.35,
                                    borderRadius: '1px 1px 0 0',
                                    pointerEvents: 'none',
                                    zIndex: 4,
                                } }, i));
                        }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), sx: {
                            position: 'absolute',
                            top: 0,
                            left: `${startPct}%`,
                            width: HANDLE_WIDTH,
                            height: HANDLE_HEIGHT,
                            ml: `-${HANDLE_WIDTH / 2}px`,
                            bg: 'accent.fg',
                            borderRadius: 1,
                            cursor: 'ew-resize',
                            zIndex: 3,
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            transition: isDragging ? 'none' : 'left 0.05s ease',
                            boxShadow: 'shadow.medium',
                            ':hover': { bg: 'accent.emphasis' },
                        }, children: _jsx(Box, { sx: {
                                width: '2px',
                                height: '40%',
                                bg: 'fg.onEmphasis',
                                borderRadius: 1,
                            } }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), sx: {
                            position: 'absolute',
                            top: 0,
                            left: `${endPct}%`,
                            width: HANDLE_WIDTH,
                            height: HANDLE_HEIGHT,
                            ml: `-${HANDLE_WIDTH / 2}px`,
                            bg: 'accent.fg',
                            borderRadius: 1,
                            cursor: 'ew-resize',
                            zIndex: 3,
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            transition: isDragging ? 'none' : 'left 0.05s ease',
                            boxShadow: 'shadow.medium',
                            ':hover': { bg: 'accent.emphasis' },
                        }, children: _jsx(Box, { sx: {
                                width: '2px',
                                height: '40%',
                                bg: 'fg.onEmphasis',
                                borderRadius: 1,
                            } }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { sx: {
                                    position: 'absolute',
                                    top: 0,
                                    left: `${hoverPct}%`,
                                    width: '1px',
                                    height: RAIL_HEIGHT,
                                    borderLeft: '1px dashed',
                                    borderColor: 'fg.default',
                                    opacity: 0.55,
                                    pointerEvents: 'none',
                                    zIndex: 5,
                                } }), _jsx(Box, { sx: {
                                    position: 'absolute',
                                    top: -36,
                                    left: `${hoverPct}%`,
                                    transform: 'translateX(-50%)',
                                    bg: 'canvas.overlay',
                                    border: '1px solid',
                                    borderColor: 'border.default',
                                    borderRadius: 2,
                                    px: 2,
                                    py: '2px',
                                    pointerEvents: 'none',
                                    zIndex: 10,
                                    boxShadow: 'shadow.medium',
                                    whiteSpace: 'nowrap',
                                }, children: _jsx(Text, { sx: {
                                        fontSize: '11px',
                                        color: 'fg.default',
                                        fontFamily: 'mono',
                                        lineHeight: '18px',
                                    }, children: formatHoverDate(hoverPct) }) })] })), ticks.map((t, i) => {
                        const pct = ((t.getTime() - tlStart) / tlRange) * 100;
                        return (_jsx(Box, { sx: {
                                position: 'absolute',
                                top: 0,
                                left: `${pct}%`,
                                width: '1px',
                                height: RAIL_HEIGHT,
                                bg: 'border.muted',
                                pointerEvents: 'none',
                                opacity: 0.5,
                                zIndex: 1,
                            } }, i));
                    })] }), _jsx(Box, { sx: {
                    position: 'relative',
                    width: '100%',
                    height: 20,
                    mt: '2px',
                }, children: ticks.map((t, i) => {
                    const pct = ((t.getTime() - tlStart) / tlRange) * 100;
                    return (_jsx(Text, { sx: {
                            position: 'absolute',
                            left: `${pct}%`,
                            transform: 'translateX(-50%)',
                            fontSize: '10px',
                            color: 'fg.subtle',
                            whiteSpace: 'nowrap',
                            fontFamily: 'mono',
                            lineHeight: '16px',
                            pointerEvents: 'none',
                        }, children: formatTick(t) }, i));
                }) })] }));
};
