import type { WorkbenchRealtimeEvent } from "../api/workbench-events";
import type { TraceSnapshot } from "../composables/workbench-trace-snapshot";
import { normalizeProjectionDiagnostic } from "../utils/workbench-error-runtime";
import type { AgentChatResultResponse, ChatMessage, TraceEvent } from "../types";
import { asAgentRun, firstFiniteNumber, messageTimingPatch, optionalString, recordValue, traceSnapshotError } from "./workbench-message-projection-runtime";

export function traceDetailProjectedSeq(trace: ChatMessage["runnerTrace"]): number {
  const cursor = traceDetailCursor(trace);
  if (cursor !== null) return cursor;
  const events = Array.isArray(trace?.events) ? trace.events : [];
  return eventsProjectedSeq(events, 0);
}

export function traceNextProjectedSeq(result: AgentChatResultResponse, fallback: number): number {
  const cursor = traceDetailCursor(result);
  if (cursor !== null) return cursor;
  const events = Array.isArray(result.events) ? result.events : Array.isArray(result.traceEvents) ? result.traceEvents : [];
  return eventsProjectedSeq(events, fallback);
}

export function terminalSealResultWithoutTraceEvents(result: AgentChatResultResponse): AgentChatResultResponse {
  const { events: _events, traceEvents: _traceEvents, runnerTrace, ...rest } = result;
  if (!runnerTrace) return rest;
  const { events: _runnerEvents, ...traceRest } = runnerTrace;
  return { ...rest, runnerTrace: traceRest };
}

export function realtimeSnapshotToTraceSnapshot(traceId: string, snapshot: WorkbenchRealtimeEvent["snapshot"], eventPageEvents?: TraceEvent[], options: { eventSource?: string } = {}): TraceSnapshot {
  const source = snapshot ?? { traceId };
  const events = Array.isArray(eventPageEvents) ? eventPageEvents : Array.isArray(source.events) ? source.events : [];
  const { traceId: _sourceTraceId, error: _sourceError, ...sourceRest } = source;
  return {
    ...sourceRest,
    traceId: optionalString(source.traceId, traceId),
    status: text(source.status) ?? undefined,
    events,
    eventCount: firstFiniteNumber(source.eventCount, events.length),
    fullTraceLoaded: source.fullTraceLoaded === true,
    hasMore: source.hasMore === true,
    truncated: source.truncated === true,
    nextProjectedSeq: firstFiniteNumber(source.nextProjectedSeq) ?? null,
    range: recordValue(source.range) as TraceSnapshot["range"],
    agentRun: asAgentRun(source.agentRun) ?? undefined,
    traceStatus: text(source.traceStatus) ?? undefined,
    retention: source.retention,
    terminalEvidence: source.terminalEvidence,
    traceSummary: source.traceSummary,
    error: traceSnapshotError(source.error),
    projection: normalizeProjectionDiagnostic(source.projection ?? source) ?? null,
    projectionStatus: text(source.projectionStatus) ?? text(recordValue(source.projection)?.projectionStatus) ?? undefined,
    projectionHealth: text(source.projectionHealth) ?? text(recordValue(source.projection)?.projectionHealth) ?? undefined,
    staleMs: firstFiniteNumber(source.staleMs, recordValue(source.projection)?.staleMs),
    blocker: recordValue(source.blocker) ?? recordValue(recordValue(source.projection)?.blocker) ?? undefined,
    ...messageTimingPatch(source),
    lastEventLabel: text(source.lastEventLabel) ?? undefined,
    eventSource: options.eventSource ?? "trace-api",
    updatedAt: text(source.updatedAt) ?? new Date().toISOString()
  } as TraceSnapshot;
}

function traceDetailCursor(source: { nextProjectedSeq?: unknown; range?: { toProjectedSeq?: unknown } | null } | null | undefined): number | null {
  return finiteProjectedSeq(source?.nextProjectedSeq) ?? finiteProjectedSeq(source?.range?.toProjectedSeq);
}

function finiteProjectedSeq(value: unknown): number | null {
  const number = Number(value);
  return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : null;
}

function eventsProjectedSeq(events: TraceEvent[], fallback: number): number {
  return events.reduce((max, event) => {
    const seq = traceEventProjectedSeq(event);
    return Number.isFinite(seq) && seq > max ? seq : max;
  }, fallback);
}

function traceEventProjectedSeq(event: TraceEvent): number {
  const seq = Number(event.projectedSeq);
  return Number.isFinite(seq) ? Math.trunc(seq) : Number.NaN;
}

function text(value: unknown): string | null {
  const normalized = typeof value === "string" ? value.trim() : "";
  return normalized || null;
}
