// SPEC: PJ2026-010401080313 Workbench实时权威 draft-2026-07-09-p1-single-step-debug.
// Responsibility: Cloud Web clients for Workbench fake SSE and raw Kafka stream diagnostics.

import { fetchJson, type ApiRequestOptions } from "@/api/client";
import type { ApiResult } from "@/types";
import type { WorkbenchRealtimeEvent } from "./workbench-events";

export interface WorkbenchDebugFakeSseSequence {
  sequenceId: string;
  label: string;
  eventCount: number;
}

export interface WorkbenchDebugFakeSseQueue {
  queueId: string;
  sequenceId: string;
  cursor: number;
  eventCount: number;
  remaining: number;
  connectedClients: number;
  updatedAt?: string | null;
}

export interface WorkbenchDebugFakeSseResponse {
  ok?: boolean;
  contractVersion?: string;
  sequences?: WorkbenchDebugFakeSseSequence[];
  queue?: WorkbenchDebugFakeSseQueue;
  appended?: number;
  delivered?: {
    ok?: boolean;
    count?: number;
    reason?: string;
    eventName?: string;
    eventType?: string | null;
    traceId?: string | null;
    deliveredTo?: number;
  };
  error?: { code?: string; message?: string; [key: string]: unknown };
}

export interface WorkbenchDebugFakeSseStreamOptions {
  queueId: string;
  onEvent: (event: WorkbenchRealtimeEvent, eventName: string) => void;
  onOpen?: () => void;
  onError?: (event: Event) => void;
}

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

export type WorkbenchKafkaSseDebugStreamName = "stdio" | "agentrun" | "hwlab" | "hwlab-debug";

export interface WorkbenchKafkaDebugOffsetBarrier {
  partition: number;
  firstOffset: string;
  lastOffset: string;
}

export interface WorkbenchKafkaDebugReplayCounts {
  scanned?: number;
  barrierScanned?: number;
  parsed?: number;
  traceMatched?: number;
  replayMatched?: number;
  matched?: number;
  delivered?: number;
  clientReceived?: number | null;
  decoded?: number | null;
  applied?: number | null;
}

export interface WorkbenchKafkaDebugReplayResult {
  contractVersion?: string;
  replayId?: string | null;
  traceId?: string | null;
  phase?: string;
  code?: string;
  ok?: boolean;
  message?: string;
  terminalObserved?: boolean;
  counts?: WorkbenchKafkaDebugReplayCounts;
  offsetRange?: Record<string, unknown> | null;
  [key: string]: unknown;
}

export interface WorkbenchKafkaSseDebugIngressFrame {
  eventName: string;
  decodeStatus: "accepted" | "invalid-json" | "not-object";
}

export interface WorkbenchKafkaSseDebugEvent {
  ok?: boolean;
  contractVersion?: string;
  consumerReady?: boolean;
  debugIsolation?: boolean;
  deliverySemantics?: string;
  liveOnly?: boolean;
  replay?: boolean;
  replayComplete?: boolean;
  reason?: string;
  count?: number;
  limit?: number;
  timeoutMs?: number;
  terminalObserved?: boolean;
  replayId?: string | null;
  phase?: string;
  code?: string;
  result?: WorkbenchKafkaDebugReplayResult | null;
  counts?: WorkbenchKafkaDebugReplayCounts;
  offsetRange?: Record<string, unknown> | null;
  traceId?: string | null;
  groupId?: string;
  stream?: WorkbenchKafkaSseDebugStreamName;
  topic?: string;
  partition?: number;
  offset?: string;
  key?: string | null;
  timestamp?: string | null;
  value?: unknown;
  filters?: Record<string, string>;
  resolvedFilters?: Record<string, string>;
  serverSentAt?: string;
  valuesPrinted?: boolean;
  error?: { code?: string; message?: string; [key: string]: unknown };
}

export interface WorkbenchKafkaSseDebugStreamOptions {
  stream: WorkbenchKafkaSseDebugStreamName;
  fromBeginning?: boolean;
  traceId?: string | null;
  sessionId?: string | null;
  runId?: string | null;
  commandId?: string | null;
  replayId?: string | null;
  outputBarrier?: WorkbenchKafkaDebugOffsetBarrier | null;
  onIngress?: (frame: WorkbenchKafkaSseDebugIngressFrame) => void;
  onEvent: (event: WorkbenchKafkaSseDebugEvent, eventName: string) => void;
  onOpen?: () => void;
  onError?: (event: Event) => void;
}

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

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

const DEBUG_KAFKA_SSE_EVENTS = ["hwlab.kafka.connected", "hwlab.kafka.event", "hwlab.kafka.replay-complete", "hwlab.kafka.error"];

export const workbenchDebugAPI = {
  describe: (queueId = "trace-card", options: ApiRequestOptions = {}): Promise<ApiResult<WorkbenchDebugFakeSseResponse>> => fetchJson(debugPath("", { queueId }), { ...options, timeoutName: "workbench debug fake sse" }),
  reset: (input: { queueId: string; sequenceId: string }, options: ApiRequestOptions = {}): Promise<ApiResult<WorkbenchDebugFakeSseResponse>> => fetchJson(debugPath("/reset"), { ...options, method: "POST", body: JSON.stringify(input), timeoutName: "workbench debug fake sse reset" }),
  append: (input: { queueId: string; events: WorkbenchRealtimeEvent[] }, options: ApiRequestOptions = {}): Promise<ApiResult<WorkbenchDebugFakeSseResponse>> => fetchJson(debugPath("/append"), { ...options, method: "POST", body: JSON.stringify(input), timeoutName: "workbench debug fake sse append" }),
  next: (input: { queueId: string }, options: ApiRequestOptions = {}): Promise<ApiResult<WorkbenchDebugFakeSseResponse>> => fetchJson(debugPath("/next"), { ...options, method: "POST", body: JSON.stringify(input), timeoutName: "workbench debug fake sse next" }),
  run: (input: { queueId: string }, options: ApiRequestOptions = {}): Promise<ApiResult<WorkbenchDebugFakeSseResponse>> => fetchJson(debugPath("/run"), { ...options, method: "POST", body: JSON.stringify(input), timeoutName: "workbench debug fake sse run" })
};

export function connectWorkbenchDebugFakeSse(options: WorkbenchDebugFakeSseStreamOptions): WorkbenchDebugFakeSseStream | null {
  if (typeof EventSource === "undefined") return null;
  const source = new EventSource(debugPath("/events", { queueId: options.queueId }), { withCredentials: true });
  source.onopen = () => options.onOpen?.();
  source.onerror = (event) => options.onError?.(event);
  const listeners = DEBUG_FAKE_SSE_EVENTS.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);
      source.close();
    }
  };
}

export function connectWorkbenchKafkaSseDebug(options: WorkbenchKafkaSseDebugStreamOptions): WorkbenchKafkaSseDebugStream | null {
  if (typeof EventSource === "undefined") return null;
  const source = new EventSource(workbenchKafkaSseDebugPath(options), { withCredentials: true });
  source.onerror = (event) => options.onError?.(event);
  const listeners = DEBUG_KAFKA_SSE_EVENTS.map((name) => {
    const listener = (event: MessageEvent) => {
      deliverKafkaSseDebugFrame(event.data, name, event, options);
    };
    source.addEventListener(name, listener);
    return { name, listener };
  });
  source.onmessage = (event) => {
    deliverKafkaSseDebugFrame(event.data, "message", event, options);
  };
  return {
    close() {
      for (const { name, listener } of listeners) source.removeEventListener(name, listener);
      source.close();
    }
  };
}

export function workbenchKafkaSseDebugPath(options: Pick<WorkbenchKafkaSseDebugStreamOptions, "stream" | "fromBeginning" | "traceId" | "sessionId" | "runId" | "commandId" | "replayId" | "outputBarrier">): string {
  const query = new URLSearchParams({ stream: options.stream });
  if (options.fromBeginning === true) query.set("fromBeginning", "true");
  appendDebugFilter(query, "traceId", options.traceId);
  appendDebugFilter(query, "sessionId", options.sessionId);
  appendDebugFilter(query, "runId", options.runId);
  appendDebugFilter(query, "commandId", options.commandId);
  appendDebugFilter(query, "replayId", options.replayId);
  if (options.outputBarrier) {
    query.set("partition", String(options.outputBarrier.partition));
    query.set("firstOffset", options.outputBarrier.firstOffset);
    query.set("lastOffset", options.outputBarrier.lastOffset);
  }
  return `/v1/workbench/debug/kafka-sse/events?${query.toString()}`;
}

export function workbenchKafkaSseDebugCorrelationIds(event: WorkbenchKafkaSseDebugEvent): string[] {
  const value = recordValue(event.value);
  const context = recordValue(value.context);
  const metadata = recordValue(value.metadata);
  const run = recordValue(value.run);
  const command = recordValue(value.command);
  const payload = recordValue(value.payload);
  return uniqueStrings([
    value.traceId, value.trace_id, context.traceId, context.trace_id, metadata.traceId, metadata.trace_id, payload.traceId, payload.trace_id,
    value.hwlabSessionId, value.sessionId, value.session_id, context.hwlabSessionId, context.sessionId, context.session_id, metadata.sessionId, metadata.session_id, payload.hwlabSessionId, payload.sessionId, payload.session_id,
    value.runId, value.run_id, context.runId, context.run_id, metadata.runId, metadata.run_id, run.runId, run.run_id, payload.runId, payload.run_id,
    value.commandId, value.command_id, context.commandId, context.command_id, metadata.commandId, metadata.command_id, command.commandId, command.command_id, payload.commandId, payload.command_id
  ]);
}

function debugPath(suffix: string, params: Record<string, string | null | undefined> = {}): string {
  const query = new URLSearchParams();
  for (const [key, value] of Object.entries(params)) {
    const text = typeof value === "string" ? value.trim() : "";
    if (text) query.set(key, text);
  }
  const qs = query.toString();
  return `/v1/workbench/debug/fake-sse${suffix}${qs ? `?${qs}` : ""}`;
}

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

function deliverKafkaSseDebugFrame(raw: unknown, eventName: string, transportEvent: MessageEvent, options: WorkbenchKafkaSseDebugStreamOptions): void {
  const decoded = parseKafkaSseDebugEvent(typeof raw === "string" ? raw : String(raw ?? ""));
  options.onIngress?.({ eventName, decodeStatus: decoded.status });
  if (!decoded.payload) return;
  options.onEvent(decoded.payload, eventName);
  if (eventName === "hwlab.kafka.connected" && decoded.payload.consumerReady === true) options.onOpen?.();
  if (eventName === "hwlab.kafka.error") options.onError?.(transportEvent);
}

function parseKafkaSseDebugEvent(raw: string): { payload: WorkbenchKafkaSseDebugEvent | null; status: WorkbenchKafkaSseDebugIngressFrame["decodeStatus"] } {
  try {
    const value = JSON.parse(raw);
    return value && typeof value === "object" && !Array.isArray(value)
      ? { payload: value as WorkbenchKafkaSseDebugEvent, status: "accepted" }
      : { payload: null, status: "not-object" };
  } catch {
    return { payload: null, status: "invalid-json" };
  }
}

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

function recordValue(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function uniqueStrings(values: unknown[]): string[] {
  return [...new Set(values.map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean))];
}
