// SPEC: PJ2026-010403 API契约 draft-2026-06-18-r1; PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// Responsibility: Workbench SSE client. Realtime events accelerate UI projection; REST snapshots remain gap-fill authority.

import type { ProjectionDiagnostic, TraceEvent } from "@/types";
import { recordWorkbenchSseLifecycle } from "@/utils/workbench-performance";

export interface WorkbenchRealtimeTraceSnapshot {
  traceId?: string | null;
  status?: string | null;
  events?: TraceEvent[];
  eventCount?: number | null;
  lastEvent?: TraceEvent | null;
  updatedAt?: string | null;
  projection?: ProjectionDiagnostic | null;
  projectionStatus?: string | null;
  projectionHealth?: string | null;
  staleMs?: number | null;
  blocker?: ProjectionDiagnostic["blocker"] | null;
  [key: string]: unknown;
}

export interface WorkbenchRealtimeEvent {
  type?: string;
  status?: string;
  serverSentAt?: string | null;
  eventCreatedAt?: string | null;
  sessionId?: string | null;
  threadId?: string | null;
  traceId?: string | null;
  event?: TraceEvent | null;
  snapshot?: WorkbenchRealtimeTraceSnapshot | null;
  turn?: Record<string, unknown> | null;
  reason?: string | null;
  error?: { code?: string; message?: string; [key: string]: unknown } | null;
  traceSeq?: number | null;
  cursor?: { traceSeq?: number | null; [key: string]: unknown };
  [key: string]: unknown;
}

export interface WorkbenchEventStreamOptions {
  sessionId?: string | null;
  traceId?: string | null;
  onEvent: (event: WorkbenchRealtimeEvent, eventName: string) => void;
  onOpen?: () => void;
  onError?: (event: Event) => void;
}

export interface WorkbenchEventStream {
  close: () => void;
}

const WORKBENCH_EVENT_NAMES = [
  "workbench.connected",
  "workbench.trace.snapshot",
  "workbench.trace.event",
  "workbench.trace.unavailable",
  "workbench.turn.snapshot",
  "workbench.heartbeat",
  "workbench.error"
];

export function connectWorkbenchEvents(options: WorkbenchEventStreamOptions): WorkbenchEventStream | null {
  if (typeof EventSource === "undefined") return null;
  const params = new URLSearchParams();
  appendParam(params, "sessionId", options.sessionId);
  appendParam(params, "traceId", options.traceId);
  const eventRoute = `/v1/workbench/events?${params.toString()}`;
  const connectStartedAt = typeof performance === "undefined" ? Date.now() : performance.now();
  recordWorkbenchSseLifecycle({ state: "connect", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId });
  const source = new EventSource(eventRoute, { withCredentials: true });
  source.onopen = () => {
    const openedAt = typeof performance === "undefined" ? Date.now() : performance.now();
    recordWorkbenchSseLifecycle({ state: "open", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId, valueMs: Math.max(0, Math.round(openedAt - connectStartedAt)) });
    options.onOpen?.();
  };
  source.onerror = (event) => {
    recordWorkbenchSseLifecycle({ state: "error", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId, errorName: event.type });
    options.onError?.(event);
  };
  const listeners = WORKBENCH_EVENT_NAMES.map((name) => {
    const listener = (event: MessageEvent) => {
      const payload = parseRealtimeEvent(event.data);
      if (payload) options.onEvent(payload, name);
    };
    source.addEventListener(name, listener);
    return { name, listener };
  });
  source.onmessage = (event) => {
    const payload = parseRealtimeEvent(event.data);
    if (payload) options.onEvent(payload, "message");
  };
  return {
    close() {
      for (const { name, listener } of listeners) source.removeEventListener(name, listener);
      recordWorkbenchSseLifecycle({ state: "close", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId });
      source.close();
    }
  };
}

function appendParam(params: URLSearchParams, key: string, value: string | null | undefined): void {
  const text = typeof value === "string" ? value.trim() : "";
  if (text) params.set(key, text);
}

function parseRealtimeEvent(raw: string): WorkbenchRealtimeEvent | null {
  try {
    const value = JSON.parse(raw);
    return value && typeof value === "object" ? value as WorkbenchRealtimeEvent : null;
  } catch {
    return null;
  }
}
