// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0
// Browser-side Workbench journey probe. Prometheus label shaping is enforced again by cloud-api.

import type { ChatMessage, TraceEvent } from "@/types";

type WorkbenchEventKind = "workbench_journey" | "workbench_event_phase" | "workbench_backend_event_visible" | "workbench_ui_event";
type WorkbenchOutcome = "ok" | "timeout" | "error" | "dropped" | "stale" | "partial" | "empty" | "network" | "denied" | "unknown";
type WorkbenchLoadingScope = "workbench" | "session_list" | "session_detail" | "api" | "sse" | "page";
type WorkbenchUiState = "enter" | "exit" | "request" | "connect" | "open" | "message" | "close" | "error" | "sample";
type WorkbenchUiReason = "hydrate" | "select_session" | "create_session" | "api_request" | "sse_connect" | "sse_open" | "sse_error" | "sse_close" | "pagehide" | "visibility_hidden" | "unknown";

interface WorkbenchPerformanceEvent {
  kind: WorkbenchEventKind;
  valueMs: number;
  route?: string;
  journey?: string;
  phase?: string;
  eventType?: string;
  backend?: string;
  transport?: string;
  entry?: string;
  source?: string;
  targetState?: string;
  cache?: string;
  authState?: string;
  visibility?: string;
  outcome?: WorkbenchOutcome;
  eventName?: string;
  uiTraceId?: string;
  otelTraceId?: string;
  spanId?: string;
  parentSpanId?: string;
  loadingScope?: WorkbenchLoadingScope;
  state?: WorkbenchUiState;
  reason?: WorkbenchUiReason;
  sessionHash?: string;
  traceHash?: string;
  method?: string;
  status?: number;
  statusClass?: string;
  startedAtEpochMs?: number;
  endedAtEpochMs?: number;
  errorName?: string;
  timeoutName?: string;
  activityWaitingFor?: string | null;
  activityLastEventLabel?: string | null;
  activityIdleMs?: number;
  resourceDurationMs?: number;
  resourceFetchToRequestMs?: number;
  resourceRequestWaitMs?: number;
  resourceResponseTransferMs?: number;
  resourceTransferSize?: number;
  resourceEncodedBodySize?: number;
  resourceDecodedBodySize?: number;
  resourceNextHopProtocol?: string;
  resourceServerTiming?: string;
}

interface TraceEventTimingInput {
  traceId: string | null | undefined;
  events: TraceEvent[];
  transport: "sse" | "rest_gap" | "poll";
  serverSentAt?: string | null | undefined;
  eventCreatedAt?: string | null | undefined;
  traceSeq?: number | string | null | undefined;
}

interface OpenJourneyState {
  startAt: number;
  route: string;
  cache: string;
  authState: string;
  firstVisibleReported: boolean;
  fullLoadReported: boolean;
}

interface SessionSwitchState {
  sessionId: string;
  startAt: number;
  route: string;
  source: string;
  targetState: string;
  cache: string;
  firstVisibleReported: boolean;
  fullLoadReported: boolean;
}

interface SubmitJourneyState {
  traceId: string;
  sessionId: string;
  startAt: number;
  route: string;
  entry: string;
  backend: string;
  transport: string;
  firstVisibleReported: boolean;
  apiAcceptedReported: boolean;
}

interface TraceEventState {
  traceId: string;
  receivedAt: number;
  projectedAt: number | null;
  eventCreatedAt: number | null;
  appendedAt: number | null;
  serverSentAt: number | null;
  traceSeq: number | null;
  eventType: string;
  backend: string;
  transport: string;
  visibleOutcome: WorkbenchOutcome;
  visibleReported: boolean;
}

interface WorkbenchUiTrace {
  uiTraceId: string;
  otelTraceId: string;
  traceparent: string;
  startedAtEpochMs: number;
  valuesPrinted: false;
}

interface LoadingStateRecord {
  active: true;
  startAt: number;
  startEpochMs: number;
  reason: WorkbenchUiReason;
  route: string;
  sessionHash?: string;
  traceHash?: string;
}

const SCHEMA_VERSION = "hwlab-web-performance-v2";
const FLUSH_INTERVAL_MS = 10_000;
const FLUSH_BATCH_THRESHOLD = 120;
const MAX_QUEUE = 300;
const submitJourneys = new Map<string, SubmitJourneyState>();
const sessionSwitches = new Map<string, SessionSwitchState>();
const traceEvents = new Map<string, TraceEventState>();
const loadingStates = new Map<WorkbenchLoadingScope, LoadingStateRecord>();
let openJourney: OpenJourneyState | null = null;
let queue: WorkbenchPerformanceEvent[] = [];
let installed = false;
let resourceTimingBufferConfigured = false;
let flushTimer: number | null = null;
let uiTrace: WorkbenchUiTrace | null = null;

export function currentWorkbenchUiTrace(): WorkbenchUiTrace | null {
  if (typeof window === "undefined") return null;
  if (!uiTrace) {
    const otelTraceId = randomHex(16);
    const parentSpanId = randomHex(8);
    uiTrace = {
      uiTraceId: `ui_${Date.now().toString(36)}_${randomHex(6)}`,
      otelTraceId,
      traceparent: `00-${otelTraceId}-${parentSpanId}-01`,
      startedAtEpochMs: Date.now(),
      valuesPrinted: false
    };
  }
  publishWorkbenchUiTrace(uiTrace);
  return uiTrace;
}

export function recordWorkbenchLoadingState(input: { scope: WorkbenchLoadingScope; active: boolean; reason?: WorkbenchUiReason; sessionId?: string | null; traceId?: string | null }): void {
  ensureInstalled();
  const reason = input.reason ?? "unknown";
  const now = monotonicNow();
  const nowEpoch = wallNow();
  const sessionHash = hashIdentifier(input.sessionId, "ses");
  const traceHash = hashIdentifier(input.traceId, "trc");
  const previous = loadingStates.get(input.scope);
  if (input.active) {
    if (previous?.active) return;
    const state: LoadingStateRecord = { active: true, startAt: now, startEpochMs: nowEpoch, reason, route: pageRoute(), sessionHash, traceHash };
    loadingStates.set(input.scope, state);
    enqueueWorkbenchUiEvent({ eventType: "loading_state", loadingScope: input.scope, state: "enter", reason, route: state.route, valueMs: 0, startedAtEpochMs: nowEpoch, endedAtEpochMs: nowEpoch, sessionHash, traceHash, outcome: "ok" });
    return;
  }
  if (!previous?.active) return;
  loadingStates.delete(input.scope);
  const valueMs = Math.max(0, now - previous.startAt);
  enqueueWorkbenchUiEvent({ eventType: "loading_state", loadingScope: input.scope, state: "exit", reason: previous.reason, route: previous.route, valueMs, startedAtEpochMs: previous.startEpochMs, endedAtEpochMs: nowEpoch, sessionHash: previous.sessionHash, traceHash: previous.traceHash, outcome: "ok" });
}

export function recordWorkbenchApiRequest(input: { route: string; method?: string; status?: number; startedAtEpochMs: number; endedAtEpochMs?: number; outcome?: string | null; timeoutName?: string | null; errorName?: string | null; activityWaitingFor?: string | null; activityLastEventLabel?: string | null; activityIdleMs?: number | null }): void {
  if (!isWorkbenchPage()) return;
  const route = safeText(input.route);
  if (!route || route.startsWith("/v1/web-performance")) return;
  ensureInstalled();
  const endedAtEpochMs = input.endedAtEpochMs ?? wallNow();
  const resourceTiming = resourceTimingForApiRequest(route, input.startedAtEpochMs, endedAtEpochMs);
  enqueueWorkbenchUiEvent({
    eventType: "api_request",
    loadingScope: "api",
    state: "request",
    reason: "api_request",
    route,
    method: input.method ?? "GET",
    status: input.status,
    statusClass: statusClass(input.status),
    outcome: normalizeUiOutcome(input.outcome),
    timeoutName: input.timeoutName ? safeText(input.timeoutName).slice(0, 80) : undefined,
    errorName: input.errorName ? safeText(input.errorName).slice(0, 80) : undefined,
    activityWaitingFor: input.activityWaitingFor ? safeText(input.activityWaitingFor).slice(0, 80) : undefined,
    activityLastEventLabel: input.activityLastEventLabel ? safeText(input.activityLastEventLabel).slice(0, 120) : undefined,
    activityIdleMs: Number.isFinite(input.activityIdleMs ?? NaN) ? Math.max(0, Math.round(Number(input.activityIdleMs))) : undefined,
    valueMs: Math.max(0, endedAtEpochMs - input.startedAtEpochMs),
    startedAtEpochMs: input.startedAtEpochMs,
    endedAtEpochMs,
    ...resourceTiming
  });
}

function resourceTimingForApiRequest(route: string, startedAtEpochMs: number, endedAtEpochMs: number): Partial<WorkbenchPerformanceEvent> {
  if (typeof window === "undefined" || typeof performance === "undefined" || typeof performance.getEntriesByType !== "function") return {};
  let target: URL;
  try {
    target = new URL(route, window.location.href);
  } catch {
    return {};
  }
  const timeOrigin = Number.isFinite(performance.timeOrigin) ? performance.timeOrigin : Date.now() - monotonicNow();
  const entries = performance.getEntriesByType("resource") as PerformanceResourceTiming[];
  const lowerBound = startedAtEpochMs - 2000;
  const upperBound = endedAtEpochMs + 2000;
  const candidates = entries
    .map((entry) => resourceTimingCandidate(entry, target, timeOrigin, lowerBound, upperBound, startedAtEpochMs))
    .filter((candidate): candidate is ResourceTimingCandidate => candidate !== null)
    .sort((left, right) => Number(right.queryExact) - Number(left.queryExact) || left.initiatorRank - right.initiatorRank || left.startDistanceMs - right.startDistanceMs);
  const entry = candidates[0]?.entry;
  if (!entry) return {};
  const result: Partial<WorkbenchPerformanceEvent> = {
    resourceDurationMs: roundedNonNegative(entry.duration),
    resourceFetchToRequestMs: roundedDelta(entry.requestStart, entry.fetchStart),
    resourceRequestWaitMs: roundedDelta(entry.responseStart, entry.requestStart),
    resourceResponseTransferMs: roundedDelta(entry.responseEnd, entry.responseStart),
    resourceTransferSize: roundedNonNegative(entry.transferSize),
    resourceEncodedBodySize: roundedNonNegative(entry.encodedBodySize),
    resourceDecodedBodySize: roundedNonNegative(entry.decodedBodySize),
    resourceNextHopProtocol: safeText(entry.nextHopProtocol).slice(0, 40) || undefined,
    resourceServerTiming: serverTimingSummary(entry.serverTiming)
  };
  return Object.fromEntries(Object.entries(result).filter(([, value]) => value !== undefined)) as Partial<WorkbenchPerformanceEvent>;
}

interface ResourceTimingCandidate {
  entry: PerformanceResourceTiming;
  queryExact: boolean;
  initiatorRank: number;
  startDistanceMs: number;
}

function resourceTimingCandidate(entry: PerformanceResourceTiming, target: URL, timeOrigin: number, lowerBound: number, upperBound: number, startedAtEpochMs: number): ResourceTimingCandidate | null {
  if (!entry) return null;
  let url: URL;
  try {
    url = new URL(entry.name);
  } catch {
    return null;
  }
  if (url.origin !== target.origin) return null;
  if (url.pathname !== target.pathname) return null;
  const started = timeOrigin + entry.startTime;
  if (started < lowerBound || started > upperBound) return null;
  return {
    entry,
    queryExact: url.search === target.search,
    initiatorRank: resourceInitiatorRank(entry.initiatorType),
    startDistanceMs: Math.abs(started - startedAtEpochMs)
  };
}

function resourceInitiatorRank(value: string): number {
  const initiator = safeText(value).toLowerCase();
  if (initiator === "fetch") return 0;
  if (initiator === "xmlhttprequest") return 1;
  if (!initiator) return 2;
  return 3;
}

function roundedDelta(end: number, start: number): number | undefined {
  if (!Number.isFinite(end) || !Number.isFinite(start) || end < start) return undefined;
  return Math.round(end - start);
}

function roundedNonNegative(value: number): number | undefined {
  if (!Number.isFinite(value) || value < 0) return undefined;
  return Math.round(value);
}

function serverTimingSummary(entries: readonly PerformanceServerTiming[] | undefined): string | undefined {
  if (!Array.isArray(entries) || entries.length === 0) return undefined;
  const summary = entries.slice(0, 8).map((entry) => {
    const name = safeText(entry.name).replace(/[^A-Za-z0-9_.-]/gu, "_").slice(0, 40) || "metric";
    const duration = Number.isFinite(entry.duration) ? Math.round(entry.duration) : null;
    return duration === null ? name : `${name}:${duration}`;
  }).join(",");
  return summary.slice(0, 240) || undefined;
}

export function recordWorkbenchSseLifecycle(input: { state: "connect" | "open" | "error" | "close"; route: string; sessionId?: string | null; traceId?: string | null; errorName?: string | null; valueMs?: number | null }): void {
  ensureInstalled();
  const state = input.state;
  const endedAt = wallNow();
  const valueMs = roundedNonNegative(Number(input.valueMs ?? 0)) ?? 0;
  enqueueWorkbenchUiEvent({
    eventType: "sse_lifecycle",
    loadingScope: "sse",
    state,
    reason: state === "connect" ? "sse_connect" : state === "open" ? "sse_open" : state === "close" ? "sse_close" : "sse_error",
    route: input.route,
    valueMs,
    startedAtEpochMs: Math.max(0, endedAt - valueMs),
    endedAtEpochMs: endedAt,
    sessionHash: hashIdentifier(input.sessionId, "ses"),
    traceHash: hashIdentifier(input.traceId, "trc"),
    outcome: state === "error" ? "network" : "ok",
    errorName: input.errorName ? safeText(input.errorName).slice(0, 80) : undefined
  });
}

export function startWorkbenchOpenJourney(input: { route?: string; cache?: "warm" | "cold" | "unknown"; authState?: "warm" | "login_redirect" | "unknown" } = {}): void {
  ensureInstalled();
  openJourney = {
    startAt: monotonicNow(),
    route: routeTemplate(input.route ?? pageRoute()),
    cache: input.cache ?? "unknown",
    authState: input.authState ?? "unknown",
    firstVisibleReported: false,
    fullLoadReported: false
  };
}

export function finishWorkbenchOpenFullLoad(outcome: WorkbenchOutcome = "ok"): void {
  if (!openJourney || openJourney.fullLoadReported) return;
  openJourney.fullLoadReported = true;
  enqueue({
    kind: "workbench_journey",
    journey: "workbench_open_full_load",
    route: openJourney.route,
    cache: openJourney.cache,
    authState: openJourney.authState,
    visibility: visibilityState(),
    outcome,
    valueMs: elapsedSince(openJourney.startAt)
  });
}

export function startWorkbenchSessionSwitch(input: { sessionId: string | null | undefined; source: "rail" | "deeplink" | "history" | "direct"; targetState?: string | null; cache?: "warm" | "cold" | "unknown" }): void {
  const sessionId = safeText(input.sessionId);
  if (!sessionId) return;
  ensureInstalled();
  sessionSwitches.set(sessionId, {
    sessionId,
    startAt: monotonicNow(),
    route: pageRoute(),
    source: input.source,
    targetState: normalizeTargetState(input.targetState),
    cache: input.cache ?? "unknown",
    firstVisibleReported: false,
    fullLoadReported: false
  });
}

export function finishWorkbenchSessionSwitchFullLoad(sessionId: string | null | undefined, outcome: WorkbenchOutcome = "ok"): void {
  const state = sessionSwitches.get(safeText(sessionId));
  if (!state || state.fullLoadReported) return;
  state.fullLoadReported = true;
  enqueue({
    kind: "workbench_journey",
    journey: "session_switch_full_load",
    route: state.route,
    source: state.source,
    targetState: state.targetState,
    cache: state.cache,
    visibility: visibilityState(),
    outcome,
    valueMs: elapsedSince(state.startAt)
  });
}

export function failWorkbenchSessionSwitch(sessionId: string | null | undefined): void {
  finishWorkbenchSessionSwitchFullLoad(sessionId, "error");
}

export function startWorkbenchSubmitJourney(input: { traceId: string; sessionId: string; entry: "new" | "existing" | "steer" | "retry"; backend?: string | null; transport?: string | null }): void {
  if (!input.traceId || !input.sessionId) return;
  ensureInstalled();
  submitJourneys.set(input.traceId, {
    traceId: input.traceId,
    sessionId: input.sessionId,
    startAt: monotonicNow(),
    route: pageRoute(),
    entry: input.entry,
    backend: normalizeBackend(input.backend),
    transport: normalizeTransport(input.transport ?? "sse"),
    firstVisibleReported: false,
    apiAcceptedReported: false
  });
}

export function markWorkbenchSubmitApiAccepted(traceId: string | null | undefined): void {
  const state = submitJourneys.get(safeText(traceId));
  if (!state || state.apiAcceptedReported) return;
  state.apiAcceptedReported = true;
  enqueue({
    kind: "workbench_event_phase",
    phase: "user_submit_to_api_accepted",
    eventType: "request",
    backend: state.backend,
    transport: state.transport,
    outcome: "ok",
    valueMs: elapsedSince(state.startAt)
  });
}

export function failWorkbenchSubmitJourney(traceId: string | null | undefined, outcome: WorkbenchOutcome = "error"): void {
  const state = submitJourneys.get(safeText(traceId));
  if (!state || state.firstVisibleReported) return;
  state.firstVisibleReported = true;
  enqueue({
    kind: "workbench_journey",
    journey: "submit_to_failure",
    route: state.route,
    entry: state.entry,
    backend: state.backend,
    transport: state.transport,
    visibility: visibilityState(),
    outcome,
    valueMs: elapsedSince(state.startAt)
  });
  submitJourneys.delete(state.traceId);
}

export function markWorkbenchTraceEventsReceived(input: TraceEventTimingInput): void {
  const traceId = safeText(input.traceId);
  if (!traceId) return;
  const event = input.events.find(isUserVisibleTraceEvent) ?? input.events[0];
  if (!event) return;
  const eventType = eventTypeFromTraceEvent(event);
  const backend = backendFromTraceEvent(event);
  const eventCreatedAt = traceEventCreatedAt(event, input.eventCreatedAt);
  const appendedAt = timestampMs(event.appendedAt);
  const serverSentAt = timestampMs(input.serverSentAt);
  const receivedAt = monotonicNow();
  const receivedWallAt = wallNow();
  const traceSeq = Number(input.traceSeq ?? event.seq ?? event.sourceSeq ?? NaN);
  const visibleOutcome: WorkbenchOutcome = input.transport === "sse" && serverSentAt !== null ? "ok" : "stale";
  traceEvents.set(traceId, { traceId, receivedAt, projectedAt: null, eventCreatedAt, appendedAt, serverSentAt, traceSeq: Number.isFinite(traceSeq) ? Math.trunc(traceSeq) : null, eventType, backend, transport: input.transport, visibleOutcome, visibleReported: false });
  if (eventCreatedAt !== null && appendedAt !== null) enqueue({ kind: "workbench_event_phase", phase: "created_to_append", eventType, backend, transport: input.transport, outcome: "ok", valueMs: Math.max(0, appendedAt - eventCreatedAt) });
  if (appendedAt !== null && serverSentAt !== null) enqueue({ kind: "workbench_event_phase", phase: "append_to_sse", eventType, backend, transport: input.transport, outcome: "ok", valueMs: Math.max(0, serverSentAt - appendedAt) });
  if (serverSentAt !== null) enqueue({ kind: "workbench_event_phase", phase: "sse_to_receive", eventType, backend, transport: input.transport, outcome: "ok", valueMs: Math.max(0, receivedWallAt - serverSentAt) });
}

export function markWorkbenchTraceProjected(traceId: string | null | undefined): void {
  const state = traceEvents.get(safeText(traceId));
  if (!state || state.projectedAt !== null) return;
  const projectedAt = monotonicNow();
  state.projectedAt = projectedAt;
  enqueue({ kind: "workbench_event_phase", phase: "receive_to_project", eventType: state.eventType, backend: state.backend, transport: state.transport, outcome: "ok", valueMs: Math.max(0, projectedAt - state.receivedAt) });
}

export function acknowledgeWorkbenchVisibleAfterPaint(input: { messages: ChatMessage[]; activeSessionId?: string | null; detailLoading?: boolean }): void {
  afterNextPaint(() => acknowledgeWorkbenchVisible(input));
}

export function acknowledgeWorkbenchVisible(input: { messages: ChatMessage[]; activeSessionId?: string | null; detailLoading?: boolean }): void {
  const now = monotonicNow();
  const route = pageRoute();
  if (openJourney && !openJourney.firstVisibleReported) {
    openJourney.firstVisibleReported = true;
    enqueue({ kind: "workbench_journey", journey: "workbench_open_first_visible", route: openJourney.route || route, cache: openJourney.cache, authState: openJourney.authState, visibility: visibilityState(), outcome: "ok", valueMs: Math.max(0, now - openJourney.startAt) });
  }
  const activeSessionId = safeText(input.activeSessionId);
  const session = activeSessionId ? sessionSwitches.get(activeSessionId) : null;
  if (session && !session.firstVisibleReported && !input.detailLoading) {
    session.firstVisibleReported = true;
    enqueue({ kind: "workbench_journey", journey: "session_switch_first_visible", route: session.route, source: session.source, targetState: session.targetState, cache: session.cache, visibility: visibilityState(), outcome: "ok", valueMs: Math.max(0, now - session.startAt) });
  }
  for (const message of input.messages) {
    const traceId = safeText(message.traceId ?? message.runnerTrace?.traceId);
    if (!traceId || message.role !== "agent") continue;
    const submit = submitJourneys.get(traceId);
    const outputVisible = isVisibleAgentOutputMessage(message);
    if (submit && !submit.firstVisibleReported && outputVisible) {
      submit.firstVisibleReported = true;
      enqueue({ kind: "workbench_journey", journey: "submit_to_first_visible", route: submit.route, entry: submit.entry, backend: submit.backend, transport: submit.transport, visibility: visibilityState(), outcome: "ok", valueMs: Math.max(0, now - submit.startAt) });
      submitJourneys.delete(traceId);
    }
    const event = traceEvents.get(traceId);
    if (event && !event.visibleReported && isVisibleTraceEventMessage(message)) {
      event.visibleReported = true;
      if (event.projectedAt !== null) enqueue({ kind: "workbench_event_phase", phase: "project_to_paint", eventType: event.eventType, backend: event.backend, transport: event.transport, outcome: "ok", valueMs: Math.max(0, now - event.projectedAt) });
      if (event.eventCreatedAt !== null && event.visibleOutcome === "ok") enqueue({ kind: "workbench_backend_event_visible", eventType: event.eventType, backend: event.backend, transport: event.transport, outcome: event.visibleOutcome, valueMs: Math.max(0, wallNow() - event.eventCreatedAt) });
      traceEvents.delete(traceId);
    }
  }
}

export function resetWorkbenchPerformanceForTest(): void {
  submitJourneys.clear();
  sessionSwitches.clear();
  traceEvents.clear();
  loadingStates.clear();
  openJourney = null;
  queue = [];
  uiTrace = null;
  if (flushTimer !== null && typeof window !== "undefined") window.clearTimeout(flushTimer);
  flushTimer = null;
  installed = false;
}

export function drainWorkbenchPerformanceEventsForTest(): WorkbenchPerformanceEvent[] {
  const events = queue;
  queue = [];
  return events;
}

function enqueueWorkbenchUiEvent(event: Omit<WorkbenchPerformanceEvent, "kind" | "uiTraceId" | "otelTraceId" | "spanId">): void {
  const trace = currentWorkbenchUiTrace();
  if (!trace) return;
  enqueue({
    ...event,
    kind: "workbench_ui_event",
    uiTraceId: trace.uiTraceId,
    otelTraceId: trace.otelTraceId,
    spanId: randomHex(8),
    visibility: visibilityState()
  });
}

function enqueue(event: WorkbenchPerformanceEvent): void {
  if (!Number.isFinite(event.valueMs) || event.valueMs < 0) return;
  queue.push({ ...event, valueMs: Math.round(event.valueMs * 1000) / 1000 });
  if (queue.length > MAX_QUEUE) queue.splice(0, queue.length - MAX_QUEUE);
  if (queue.length >= FLUSH_BATCH_THRESHOLD) {
    flushWorkbenchPerformance(false);
    return;
  }
  scheduleFlush();
}

function scheduleFlush(): void {
  if (typeof window === "undefined" || flushTimer !== null) return;
  flushTimer = window.setTimeout(() => flushWorkbenchPerformance(false), FLUSH_INTERVAL_MS);
}

function flushWorkbenchPerformance(useBeacon: boolean): void {
  if (flushTimer !== null && typeof window !== "undefined") {
    window.clearTimeout(flushTimer);
    flushTimer = null;
  }
  if (!queue.length || typeof window === "undefined") return;
  const events = queue.splice(0, queue.length).map(enrichWorkbenchEventForFlush);
  const body = JSON.stringify({ schemaVersion: SCHEMA_VERSION, serviceId: "hwlab-cloud-web", page: pageRoute(), events });
  if (useBeacon && navigator.sendBeacon) {
    navigator.sendBeacon("/v1/web-performance", new Blob([body], { type: "application/json" }));
    return;
  }
  void fetch("/v1/web-performance", { method: "POST", headers: { "content-type": "application/json" }, body, credentials: "same-origin", keepalive: true }).catch(() => undefined);
}

function enrichWorkbenchEventForFlush(event: WorkbenchPerformanceEvent): WorkbenchPerformanceEvent {
  if (event.kind !== "workbench_ui_event" || event.eventType !== "api_request") return event;
  if (event.resourceDurationMs !== undefined) return event;
  const route = safeText(event.route);
  if (!route || !Number.isFinite(event.startedAtEpochMs) || !Number.isFinite(event.endedAtEpochMs)) return event;
  const timing = resourceTimingForApiRequest(route, Number(event.startedAtEpochMs), Number(event.endedAtEpochMs));
  return Object.keys(timing).length > 0 ? { ...event, ...timing } : event;
}

function ensureInstalled(): void {
  if (installed || typeof document === "undefined") return;
  installed = true;
  configureResourceTimingBuffer();
  currentWorkbenchUiTrace();
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden") {
      enqueueWorkbenchUiEvent({ eventType: "page_lifecycle", loadingScope: "page", state: "close", reason: "visibility_hidden", route: pageRoute(), valueMs: 0, startedAtEpochMs: wallNow(), endedAtEpochMs: wallNow(), outcome: "ok" });
      flushWorkbenchPerformance(true);
    }
  });
  window.addEventListener("pagehide", () => {
    enqueueWorkbenchUiEvent({ eventType: "page_lifecycle", loadingScope: "page", state: "close", reason: "pagehide", route: pageRoute(), valueMs: 0, startedAtEpochMs: wallNow(), endedAtEpochMs: wallNow(), outcome: "ok" });
    flushWorkbenchPerformance(true);
  });
}

function configureResourceTimingBuffer(): void {
  if (resourceTimingBufferConfigured || typeof performance === "undefined" || typeof performance.setResourceTimingBufferSize !== "function") return;
  resourceTimingBufferConfigured = true;
  performance.setResourceTimingBufferSize(2000);
}

function afterNextPaint(callback: () => void): void {
  if (typeof requestAnimationFrame !== "function") {
    callback();
    return;
  }
  requestAnimationFrame(() => requestAnimationFrame(callback));
}

function isVisibleAgentOutputMessage(message: ChatMessage): boolean {
  if (isTerminalStatusText(message.status) && String(message.text ?? "").trim()) return true;
  return traceEventsFromMessage(message).some(isSubmitFirstVisibleTraceEvent);
}

function isVisibleTraceEventMessage(message: ChatMessage): boolean {
  return traceEventsFromMessage(message).some(isUserVisibleTraceEvent);
}

function traceEventsFromMessage(message: ChatMessage): TraceEvent[] {
  return Array.isArray(message.runnerTrace?.events) ? message.runnerTrace.events : [];
}

function isUserVisibleTraceEvent(event: TraceEvent): boolean {
  const type = eventTypeFromTraceEvent(event);
  return type === "assistant" || type === "tool_call" || type === "backend" || type === "terminal" || type === "error";
}

function isSubmitFirstVisibleTraceEvent(event: TraceEvent): boolean {
  const type = eventTypeFromTraceEvent(event);
  if (type === "tool_call") return true;
  return type === "assistant" && isTerminalTraceEvent(event) && traceEventHasDisplayText(event);
}

function isTerminalTraceEvent(event: TraceEvent): boolean {
  return event.terminal === true || event.final === true || isTerminalStatusText(event.status);
}

function isTerminalStatusText(value: unknown): boolean {
  return ["completed", "failed", "blocked", "timeout", "canceled", "cancelled", "stale", "thread-resume-failed"].includes(safeText(value).toLowerCase().replace(/_/gu, "-"));
}

function traceEventHasDisplayText(event: TraceEvent): boolean {
  return [event.message, event.text, event.outputSummary, event.stdoutSummary, event.stderrSummary, event.command].some((value) => Boolean(safeText(value)));
}

function eventTypeFromTraceEvent(event: TraceEvent | null | undefined): string {
  const raw = safeText(event?.eventType ?? event?.type ?? event?.kind ?? event?.label ?? event?.status).toLowerCase();
  if (/tool/u.test(raw)) return "tool_call";
  if (/assistant|message|delta|reply/u.test(raw)) return "assistant";
  if (/terminal|complete|final/u.test(raw)) return "terminal";
  if (/error|fail|timeout/u.test(raw)) return "error";
  if (/backend|runner|agentrun/u.test(raw)) return "backend";
  if (/request|accepted/u.test(raw)) return "request";
  return "status";
}

function backendFromTraceEvent(event: TraceEvent | null | undefined): string {
  const direct = safeText(event?.backend ?? event?.backendProfile ?? event?.provider ?? event?.runnerKind);
  return normalizeBackend(direct);
}

function normalizeBackend(value: unknown): string {
  const text = safeText(value).toLowerCase();
  if (!text) return "unknown";
  if (text.includes("/")) return text;
  if (/^(?:codex|deepseek|openai|claude|hyue)/u.test(text)) return `agentrun-v01/${text}`;
  return text;
}

function normalizeTransport(value: unknown): string {
  const text = safeText(value).toLowerCase();
  return text === "rest_gap" || text === "poll" || text === "sse" ? text : "unknown";
}

function normalizeTargetState(value: unknown): string {
  const text = safeText(value).toLowerCase();
  if (text === "running" || text === "terminal" || text === "empty") return text;
  if (["completed", "failed", "timeout", "canceled"].includes(text)) return "terminal";
  return "unknown";
}

function traceEventCreatedAt(event: TraceEvent, fallback?: unknown): number | null {
  const raw = safeText(event.createdAt ?? fallback ?? event.ts ?? event.timestamp);
  const parsed = raw ? Date.parse(raw) : NaN;
  return Number.isFinite(parsed) ? parsed : null;
}

function timestampMs(value: unknown): number | null {
  const raw = safeText(value);
  const parsed = raw ? Date.parse(raw) : NaN;
  return Number.isFinite(parsed) ? parsed : null;
}

function pageRoute(): string {
  if (typeof window === "undefined") return "/workbench";
  return routeTemplate(`${window.location.pathname}${window.location.hash || ""}` || "/workbench");
}

function routeTemplate(input: string): string {
  const raw = safeText(input) || "/workbench";
  const pathOnly = raw.startsWith("#") ? raw.slice(1) || "/" : raw.split(/[?#]/u, 1)[0] || "/";
  const segments = pathOnly.split("/").filter(Boolean).map((part) => routeSegmentTemplate(part));
  return `/${segments.join("/")}`;
}

function routeSegmentTemplate(segment: string): string {
  const decoded = safeDecode(segment);
  if (/^(?:trc|trace|thread|run|cmd|job|ses|cnv|conv|gws|box|res|pod|dp|usr)[_-]/iu.test(decoded)) return ":id";
  if (/^[0-9a-f]{8,}(?:-[0-9a-f]{4,}){2,}$/iu.test(decoded)) return ":uuid";
  if (/^[A-Za-z0-9_-]{20,}$/u.test(decoded)) return ":id";
  if (/^\d{5,}$/u.test(decoded)) return ":id";
  return decoded.replace(/[^A-Za-z0-9_.:-]/gu, "_").slice(0, 80) || "_";
}

function safeDecode(value: string): string {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
}

function visibilityState(): string {
  if (typeof document === "undefined") return "unknown";
  return document.visibilityState === "hidden" ? "background" : "foreground";
}

function publishWorkbenchUiTrace(trace: WorkbenchUiTrace): void {
  if (typeof window === "undefined") return;
  window.__HWLAB_WORKBENCH_UI_TRACE__ = { ...trace };
}

function randomHex(bytes: number): string {
  const values = new Uint8Array(Math.max(1, bytes));
  const cryptoSource = typeof crypto !== "undefined" ? crypto : typeof window !== "undefined" ? window.crypto : null;
  if (cryptoSource?.getRandomValues) cryptoSource.getRandomValues(values);
  else for (let index = 0; index < values.length; index += 1) values[index] = Math.floor(Math.random() * 256);
  return [...values].map((value) => value.toString(16).padStart(2, "0")).join("");
}

function hashIdentifier(value: unknown, prefix: string): string | undefined {
  const text = safeText(value);
  if (!text) return undefined;
  let hash = 0x811c9dc5;
  for (let index = 0; index < text.length; index += 1) {
    hash ^= text.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193) >>> 0;
  }
  return `${prefix}_${hash.toString(16).padStart(8, "0")}`;
}

function isWorkbenchPage(): boolean {
  return pageRoute().startsWith("/workbench");
}

function statusClass(status: unknown): string {
  const code = typeof status === "number" ? status : Number(status);
  if (Number.isFinite(code) && code >= 100 && code < 600) return `${Math.floor(code / 100)}xx`;
  if (code === 0) return "network";
  return "unknown";
}

function normalizeUiOutcome(value: unknown): WorkbenchOutcome {
  const text = safeText(value).toLowerCase();
  if (text === "ok" || text === "timeout" || text === "error" || text === "dropped" || text === "stale" || text === "partial" || text === "empty" || text === "network" || text === "unknown") return text;
  if (text === "http_error") return "error";
  if (text === "network_error") return "network";
  return "unknown";
}

function monotonicNow(): number {
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
}

function wallNow(): number {
  return Date.now();
}

function elapsedSince(startAt: number): number {
  return Math.max(0, monotonicNow() - startAt);
}

function safeText(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}
