import {
  JsonView,
  PanelContainer
} from "./chunk-34BKYKWL.mjs";

// src/panels/client/TracingPanel/TracingPanel.tsx
import * as Tabs from "@radix-ui/react-tabs";
import React6, { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef2, useState as useState2 } from "react";
import { Format as Format2 } from "@dxos/echo/internal";
import { log } from "@dxos/log";
import { useClient } from "@dxos/react-client";
import { DynamicTable as DynamicTable2 } from "@dxos/react-ui-table";

// src/panels/client/TracingPanel/LogTable.tsx
import React, { useMemo } from "react";
import { Format } from "@dxos/echo/internal";
import { levels } from "@dxos/log";
import { DynamicTable } from "@dxos/react-ui-table";
var shortFile = (file) => file?.split("/").slice(-1).join("/");
var levelOptions = [
  {
    id: "TRACE",
    title: "TRACE",
    color: "sky"
  },
  {
    id: "DEBUG",
    title: "DEBUG",
    color: "green"
  },
  {
    id: "INFO",
    title: "INFO",
    color: "blue"
  },
  {
    id: "WARN",
    title: "WARN",
    color: "orange"
  },
  {
    id: "ERROR",
    title: "ERROR",
    color: "red"
  }
];
var LogTable = ({ logs = [] }) => {
  const properties = useMemo(() => [
    {
      name: "timestamp",
      format: Format.TypeFormat.DateTime,
      sort: "desc",
      size: 194
    },
    {
      name: "level",
      format: Format.TypeFormat.SingleSelect,
      config: {
        options: levelOptions
      },
      size: 80
    },
    {
      name: "file",
      format: Format.TypeFormat.String,
      size: 160
    },
    {
      name: "message",
      format: Format.TypeFormat.String
    }
  ], []);
  const rows = useMemo(() => logs.map((entry) => ({
    id: `${entry.timestamp}-${Math.random()}`,
    timestamp: entry.timestamp,
    level: Object.entries(levels).find(([, level]) => level === entry.level)?.[0].toUpperCase(),
    file: `${shortFile(entry.meta?.file)}:${entry.meta?.line}`,
    message: entry.message,
    _original: entry
  })), [
    logs
  ]);
  return /* @__PURE__ */ React.createElement(DynamicTable, {
    properties,
    rows
  });
};

// src/panels/client/TracingPanel/MetricsView.tsx
import React3, { useMemo as useMemo2 } from "react";
import { Chart } from "react-charts";

// src/panels/client/TracingPanel/Resource.tsx
import React2 from "react";
import { sanitizeClassName } from "@dxos/tracing";
var ResourceName = ({ className, resource }) => /* @__PURE__ */ React2.createElement("span", {
  className
}, sanitizeClassName(resource.className), /* @__PURE__ */ React2.createElement("span", {
  className: "text-neutral-400"
}, "#", resource.instanceId));

// src/panels/client/TracingPanel/MetricsView.tsx
var MetricsView = ({ resource }) => {
  if (!resource) {
    return null;
  }
  return /* @__PURE__ */ React3.createElement("div", {
    className: "divide-y divide-separator"
  }, /* @__PURE__ */ React3.createElement("div", {
    className: "p-1"
  }, /* @__PURE__ */ React3.createElement(ResourceName, {
    className: "text-lg",
    resource
  }), /* @__PURE__ */ React3.createElement(JsonView, {
    data: resource.info
  })), /* @__PURE__ */ React3.createElement("div", {
    className: "p-1"
  }, /* @__PURE__ */ React3.createElement("h4", null, "Metrics"), resource.metrics?.map((metric, idx) => /* @__PURE__ */ React3.createElement(MetricComponent, {
    key: idx,
    metric
  }))));
};
var MetricComponent = ({ metric }) => {
  if (metric.counter) {
    return /* @__PURE__ */ React3.createElement("span", null, metric.name, ": ", metric.counter.value, " ", metric.counter.units ?? "");
  }
  if (metric.timeSeries) {
    const primaryAxis = useMemo2(() => ({
      scaleType: "linear",
      getValue: (point) => point.idx
    }), []);
    const secondaryAxes = useMemo2(() => [
      {
        elementType: "line",
        getValue: (point) => point.value
      }
    ], []);
    const data = metric.timeSeries.tracks?.map(({ name, points }) => ({
      label: name,
      data: points?.map((p, idx) => ({
        idx: points.length - 1 - idx,
        value: p.value
      })) ?? []
    })) ?? [];
    return /* @__PURE__ */ React3.createElement("div", {
      className: "m-2"
    }, /* @__PURE__ */ React3.createElement("div", null, metric.name), /* @__PURE__ */ React3.createElement("div", {
      className: "flex gap-2"
    }, /* @__PURE__ */ React3.createElement("span", null, "total"), /* @__PURE__ */ React3.createElement("span", null, JSON.stringify(metric.timeSeries.tracks?.reduce((acc, track) => ({
      ...acc,
      [track.name]: track.total
    }), {}), (key, value) => {
      return typeof value === "number" ? value.toFixed(2) : value;
    }))), /* @__PURE__ */ React3.createElement("div", {
      className: "is-full bs-[160px] m-2"
    }, /* @__PURE__ */ React3.createElement(Chart, {
      options: {
        primaryAxis,
        secondaryAxes,
        interactionMode: "closest",
        data
      }
    })));
  }
  if (metric.multiCounter) {
    const records = metric.multiCounter.records ?? [];
    records.sort((a, b) => a.key.localeCompare(b.key));
    return /* @__PURE__ */ React3.createElement(JsonView, {
      data: {
        [metric.name]: Object.fromEntries(records.map(({ key, value }) => [
          key,
          `${value.toString()} ${metric.multiCounter?.units ?? ""}`
        ]))
      }
    });
  }
  if (metric.custom) {
    return /* @__PURE__ */ React3.createElement(JsonView, {
      data: {
        [metric.name]: metric.custom.payload
      }
    });
  }
  return /* @__PURE__ */ React3.createElement(JsonView, {
    data: metric
  });
};

// src/panels/client/TracingPanel/TraceView.tsx
import React5, { useMemo as useMemo3, useState } from "react";
import { IconButton, Input } from "@dxos/react-ui";

// src/components/FlameChart.tsx
import { FlameChart as FlameChartJs } from "flame-chart-js";
import React4, { useCallback, useEffect, useRef } from "react";
import { useResizeDetector } from "react-resize-detector";
var FlameChart = (props) => {
  const canvasRef = useRef(null);
  const flameChart = useRef(null);
  const boxRef = useRef(null);
  useResizeDetector({
    targetRef: boxRef,
    onResize: ({ width = 0, height = 0 }) => {
      if (width && height) {
        flameChart.current?.resize(width, height);
      }
    }
  });
  const initialize = useCallback(() => {
    const { data, marks, waterfall, timeseries, settings, colors, plugins, timeframeTimeseries } = props;
    if (canvasRef.current && boxRef.current) {
      const { width = 0, height = 0 } = boxRef.current.getBoundingClientRect();
      canvasRef.current.width = width;
      canvasRef.current.height = height - 3;
      flameChart.current = new FlameChartJs({
        canvas: canvasRef.current,
        data,
        marks,
        waterfall,
        timeseries,
        timeframeTimeseries,
        settings,
        colors,
        plugins
      });
    }
  }, []);
  const setBoxRef = useCallback((ref) => {
    const isNewRef = ref !== boxRef.current;
    boxRef.current = ref;
    if (isNewRef) {
      initialize();
    }
  }, []);
  const setCanvasRef = useCallback((ref) => {
    const isNewRef = ref !== canvasRef.current;
    canvasRef.current = ref;
    if (isNewRef) {
      initialize();
    }
  }, []);
  useEffect(() => {
    if (props.data) {
      flameChart.current?.setNodes(props.data);
    }
  }, [
    props.data
  ]);
  useEffect(() => {
    if (props.marks) {
      flameChart.current?.setMarks(props.marks);
    }
  }, [
    props.marks
  ]);
  useEffect(() => {
    if (props.waterfall) {
      flameChart.current?.setWaterfall(props.waterfall);
    }
  }, [
    props.waterfall
  ]);
  useEffect(() => {
    if (props.timeseries) {
      flameChart.current?.setTimeseries(props.timeseries);
    }
  }, [
    props.timeseries
  ]);
  useEffect(() => {
    if (props.timeframeTimeseries) {
      flameChart.current?.setTimeframeTimeseries(props.timeframeTimeseries);
    }
  }, [
    props.timeframeTimeseries
  ]);
  useEffect(() => {
    if (props.settings && flameChart.current) {
      flameChart.current.setSettings(props.settings);
      flameChart.current.render();
    }
  }, [
    props.settings
  ]);
  useEffect(() => {
    if (props.position) {
      flameChart.current?.setFlameChartPosition(props.position);
    }
  }, [
    props.position
  ]);
  useEffect(() => {
    if (props.zoom) {
      flameChart.current?.setZoom(props.zoom.start, props.zoom.end);
    }
  }, [
    props.zoom
  ]);
  useEffect(() => {
    if (props.onSelect) {
      flameChart.current?.on("select", props.onSelect);
    }
    return () => {
      if (props.onSelect) {
        flameChart.current?.removeListener("select", props.onSelect);
      }
    };
  }, [
    props.onSelect
  ]);
  return /* @__PURE__ */ React4.createElement("div", {
    ref: setBoxRef,
    className: props.className
  }, /* @__PURE__ */ React4.createElement("canvas", {
    ref: setCanvasRef
  }));
};

// src/panels/client/TracingPanel/TraceView.tsx
var TraceView = ({ state, resourceId, live, onLiveChanged }) => {
  const [selectedFlameIndex, setSelectedFlameIndex] = useState(0);
  const [showThreads, setShowThreads] = useState(true);
  const [groupByResource, setGroupByResource] = useState(true);
  const selectedResource = resourceId !== void 0 ? state.resources.get(resourceId) : void 0;
  const handleBack = () => {
    setSelectedFlameIndex((idx) => Math.max(0, idx - 1));
  };
  const handleForward = () => {
    setSelectedFlameIndex((idx) => Math.min(graphs.length - 1, idx + 1));
  };
  const graphs = useMemo3(() => {
    const spans = resourceId === void 0 || !groupByResource ? [
      ...state.spans.values()
    ].filter((s) => s.parentId === void 0) : selectedResource?.spans ?? [];
    return buildMultiFlameGraph(state, spans.map((s) => s.id));
  }, [
    selectedResource,
    state.spans.size
  ]);
  const flameGraph = showThreads ? graphs[Math.min(selectedFlameIndex, graphs.length - 1)] : graphs.flat();
  return /* @__PURE__ */ React5.createElement("div", {
    className: "bs-full"
  }, /* @__PURE__ */ React5.createElement("div", {
    className: "flex items-center p-2"
  }, /* @__PURE__ */ React5.createElement("div", {
    className: "flex items-center gap-2"
  }, /* @__PURE__ */ React5.createElement(Input.Root, null, /* @__PURE__ */ React5.createElement(Input.Checkbox, {
    checked: showThreads,
    onCheckedChange: (showThreads2) => setShowThreads(!!showThreads2)
  }), /* @__PURE__ */ React5.createElement(Input.Label, null, "Separate Threads")), /* @__PURE__ */ React5.createElement(Input.Root, null, /* @__PURE__ */ React5.createElement(Input.Checkbox, {
    checked: groupByResource,
    onCheckedChange: (groupByResource2) => setGroupByResource(!!groupByResource2)
  }), /* @__PURE__ */ React5.createElement(Input.Label, null, "Group by Resource")), /* @__PURE__ */ React5.createElement(Input.Root, null, /* @__PURE__ */ React5.createElement(Input.Checkbox, {
    checked: live,
    onCheckedChange: (live2) => onLiveChanged?.(!!live2)
  }), /* @__PURE__ */ React5.createElement(Input.Label, null, "Live"))), showThreads && graphs.length > 1 && /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(IconButton, {
    icon: "ph--arrow-left--regular",
    classNames: "ml-4",
    iconOnly: true,
    label: "Previous",
    onClick: handleBack
  }), /* @__PURE__ */ React5.createElement("div", {
    className: "flex-1 text-center"
  }, "Thread ", selectedFlameIndex + 1, " / ", graphs.length), /* @__PURE__ */ React5.createElement(IconButton, {
    icon: "ph--arrow-right--regular",
    iconOnly: true,
    label: "Next",
    onClick: handleForward
  }))), /* @__PURE__ */ React5.createElement("div", {
    className: "bs-full"
  }, flameGraph && /* @__PURE__ */ React5.createElement(FlameChart, {
    className: "bs-full",
    data: flameGraph
  })));
};
var SANITIZE_REGEX = /[^_](\d+)$/;
var sanitizeClassName2 = (className) => {
  const m = className.match(SANITIZE_REGEX);
  if (!m) {
    return className;
  } else {
    return className.slice(0, -m[1].length);
  }
};
var buildFlameGraph = (state, rootId) => {
  const span = state.spans.get(rootId);
  if (!span) {
    return [];
  }
  const childSpans = [
    ...state.spans.values()
  ].filter((s) => s.parentId === span.id);
  const resource = span.resourceId !== void 0 ? state.resources.get(span.resourceId)?.resource : void 0;
  const name = resource ? `${sanitizeClassName2(resource.className)}#${resource.instanceId}.${span.methodName}` : span.methodName;
  const duration = span.endTs !== void 0 ? +span.endTs - +span.startTs : void 0;
  const children = childSpans.flatMap((s) => buildFlameGraph(state, s.id));
  const visualDuration = duration ?? (children.at(-1)?.duration ? children.at(-1).start + children.at(-1).duration : 10);
  return [
    {
      name,
      start: +span.startTs,
      duration: visualDuration,
      children
    }
  ];
};
var buildMultiFlameGraph = (state, roots) => {
  const endTimes = [];
  const graphs = [];
  for (const rootId of roots) {
    const nodes = buildFlameGraph(state, rootId);
    const startTime = nodes[0].start;
    let found = false;
    for (const idx in endTimes) {
      if (endTimes[idx] <= startTime) {
        endTimes[idx] = nodes[0].start + nodes[0].duration;
        graphs[idx].push(nodes[0]);
        found = true;
        break;
      }
    }
    if (!found) {
      endTimes.push(nodes[0].start + nodes[0].duration);
      graphs.push(nodes);
    }
  }
  return graphs;
};

// src/panels/client/TracingPanel/TracingPanel.tsx
var __dxlog_file = "/__w/dxos/dxos/packages/devtools/devtools/src/panels/client/TracingPanel/TracingPanel.tsx";
var TracingPanel = () => {
  const client = useClient();
  const [state, setState] = useState2({
    resources: /* @__PURE__ */ new Map(),
    spans: /* @__PURE__ */ new Map()
  });
  const [live, setLive] = useState2(true);
  const [selectedResourceId, setSelectedResourceId] = useState2();
  const selectedResource = selectedResourceId !== void 0 ? state.resources.get(selectedResourceId) : void 0;
  useEffect2(() => {
    const stream = client.services.services.TracingService.streamTrace();
    stream.subscribe((data) => {
      if (!live) {
        return;
      }
      setState((prevState) => {
        const newResources = new Map(prevState.resources);
        const newSpans = new Map(prevState.spans);
        for (const event of data.resourceAdded ?? []) {
          const existing = newResources.get(event.resource.id);
          if (!existing) {
            newResources.set(event.resource.id, {
              resource: event.resource,
              spans: [],
              logs: []
            });
          } else {
            existing.resource = event.resource;
          }
        }
        for (const event of data.resourceRemoved ?? []) {
          newResources.delete(event.id);
        }
        for (const event of data.spanAdded ?? []) {
          newSpans.set(event.span.id, event.span);
          if (event.span.parentId === void 0) {
            const resource = newResources.get(event.span.resourceId);
            if (resource) {
              resource.spans.push(event.span);
            }
          }
        }
        for (const event of data.logAdded ?? []) {
          const resource = newResources.get(event.log.meta.resourceId);
          if (!resource) {
            return prevState;
          }
          resource.logs.push(event.log);
        }
        return {
          resources: newResources,
          spans: newSpans
        };
      });
    }, (err) => {
      log.catch(err, void 0, {
        F: __dxlog_file,
        L: 85,
        S: void 0,
        C: (f, a) => f(...a)
      });
    });
    return () => {
      void stream.close();
    };
  }, [
    client.services.services.TracingService,
    live
  ]);
  const containerRef = useRef2(null);
  const resourceProperties = useMemo4(() => [
    {
      name: "name",
      format: Format2.TypeFormat.JSON,
      size: 200
    },
    {
      name: "logs",
      format: Format2.TypeFormat.Number,
      size: 120
    },
    {
      name: "spans",
      format: Format2.TypeFormat.Number,
      size: 120
    },
    {
      name: "info",
      format: Format2.TypeFormat.JSON
    }
  ], []);
  const rows = useMemo4(() => {
    return Array.from(state.resources.values()).map((resourceState) => ({
      id: String(resourceState.resource.id),
      name: resourceState.resource.className,
      logs: resourceState.logs.length,
      spans: resourceState.spans.length,
      info: JSON.stringify(resourceState.resource.info)
    }));
  }, [
    state.resources
  ]);
  const handleRowClicked = useCallback2((row) => {
    if (!row) {
      setSelectedResourceId(void 0);
      return;
    }
    setSelectedResourceId(Number(row.id));
  }, []);
  const features = useMemo4(() => ({
    selection: {
      enabled: true,
      mode: "single"
    }
  }), []);
  return /* @__PURE__ */ React6.createElement(PanelContainer, {
    classNames: "grid grid-rows-[1fr_1fr] divide-y divide-separator"
  }, /* @__PURE__ */ React6.createElement(DynamicTable2, {
    rows,
    properties: resourceProperties,
    features,
    onRowClick: handleRowClicked
  }), /* @__PURE__ */ React6.createElement(Tabs.Root, {
    defaultValue: "details",
    className: "flex flex-col grow overflow-hidden"
  }, /* @__PURE__ */ React6.createElement(Tabs.List, {
    className: "flex divide-x divide-separator border-be border-separator"
  }, /* @__PURE__ */ React6.createElement(Tabs.Trigger, {
    className: "flex-1",
    value: "details"
  }, "Details"), /* @__PURE__ */ React6.createElement(Tabs.Trigger, {
    className: "flex-1",
    value: "logs"
  }, "Logs"), /* @__PURE__ */ React6.createElement(Tabs.Trigger, {
    className: "flex-1",
    value: "spans"
  }, "Spans")), /* @__PURE__ */ React6.createElement(Tabs.Content, {
    value: "details",
    className: "grow overflow-auto"
  }, /* @__PURE__ */ React6.createElement(MetricsView, {
    resource: selectedResource?.resource
  })), /* @__PURE__ */ React6.createElement(Tabs.Content, {
    ref: containerRef,
    value: "logs",
    className: "grow"
  }, /* @__PURE__ */ React6.createElement(LogTable, {
    logs: selectedResource?.logs ?? []
  })), /* @__PURE__ */ React6.createElement(Tabs.Content, {
    value: "spans",
    className: "grow overflow-hidden"
  }, /* @__PURE__ */ React6.createElement(TraceView, {
    state,
    resourceId: selectedResourceId,
    live,
    onLiveChanged: setLive
  }))));
};

// src/panels/client/TracingPanel/index.ts
var TracingPanel_default = TracingPanel;
export {
  TracingPanel_default as default
};
//# sourceMappingURL=TracingPanel-FFEY5OO5.mjs.map
