import { millisecondsToNanoseconds } from "@trigger.dev/core/v3";
import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView";
import { createTimelineSpanEventsFromSpanEvents } from "~/utils/timelineSpanEvents";
import type { SpanSummary } from "~/v3/eventRepository/eventRepository.types";
import type { SyntheticRun } from "./readFallback.server";

// Build a single-span trace for a buffered run so the run-detail page
// renders a meaningful timeline before the drainer materialises the
// row. Mirrors the shape produced by `RunPresenter` when its trace
// store lookup returns no spans, so the dashboard consumer treats the
// buffered run identically to a freshly enqueued PG run that hasn't
// emitted any events yet.
export function buildSyntheticTraceForBufferedRun(run: SyntheticRun) {
  const spanId = run.spanId ?? "";
  const isCancelled = run.status === "CANCELED";
  const isFailed = run.status === "FAILED";
  const span: SpanSummary = {
    id: spanId,
    parentId: run.parentSpanId,
    runId: run.friendlyId,
    data: {
      message: run.taskIdentifier ?? "Task",
      style: { icon: "task", variant: "primary" },
      events: [],
      startTime: run.createdAt,
      duration: 0,
      isError: isFailed,
      // CANCELED and FAILED are terminal; only a still-queued buffered run
      // is partial. A partial failed span would otherwise render as
      // "executing" forever in the timeline.
      isPartial: !isCancelled && !isFailed,
      isCancelled,
      isDebug: false,
      level: "TRACE",
    },
  };

  const tree = createTreeFromFlatItems([span], spanId);
  const treeRootStartTimeMs = tree?.data.startTime.getTime() ?? 0;
  const totalDuration = Math.max(tree?.data.duration ?? 0, millisecondsToNanoseconds(1));

  const events = tree
    ? flattenTree(tree).map((n) => {
        const offset = millisecondsToNanoseconds(n.data.startTime.getTime() - treeRootStartTimeMs);
        // Mirror RunPresenter: raw span events stay server-side, only
        // timelineEvents ship to the client.
        const { events: spanEvents, ...data } = n.data;
        return {
          ...n,
          data: {
            ...data,
            timelineEvents: createTimelineSpanEventsFromSpanEvents(
              spanEvents,
              false,
              treeRootStartTimeMs
            ),
            duration: n.data.isPartial ? null : n.data.duration,
            offset,
            isRoot: n.id === spanId,
            // Synthetic traces represent buffered/queued/canceled runs that
            // haven't executed yet — they can't be agent runs. Keeping this
            // field present (instead of omitting it) keeps the trace shape
            // structurally identical to `RunPresenter`'s, which downstream
            // consumers like the agent-icon check in runs/$runParam/route
            // require to typecheck against the JsonifyObject union.
            isAgentRun: false,
          },
        };
      })
    : [];

  return {
    // Matches RunPresenter's derivation: failed root span -> "failed",
    // otherwise a terminal (non-partial) span -> "completed", else
    // "executing". CANCELED is terminal-but-not-error, so "completed".
    rootSpanStatus: (isFailed ? "failed" : isCancelled ? "completed" : "executing") as
      | "executing"
      | "completed"
      | "failed",
    events,
    duration: totalDuration,
    rootStartedAt: tree?.data.startTime,
    startedAt: null,
    queuedDuration: undefined,
    overridesBySpanId: undefined,
    linkedRunIdBySpanId: {} as Record<string, string>,
  };
}
