// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-19-p0
// Keeps browser-reported performance metrics low-cardinality before Prometheus export.

import { decorateErrorPayloadForHttp, logErrorEnvelope } from "./error-envelope.ts";
import { emitWorkbenchUiOtelSpan } from "./otel-trace.ts";

const DEFAULT_WEB_PERFORMANCE_BODY_LIMIT_BYTES = 64 * 1024;
const DURATION_BUCKETS_SECONDS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30];
const CLS_BUCKETS = [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5];
const WORKBENCH_JOURNEY_BUCKETS_SECONDS = [0.05, 0.1, 0.25, 0.5, 1, 2, 3, 5, 8, 13, 21, 30, 60, 120];
const WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
const WORKBENCH_UI_EVENT_BUCKETS_SECONDS = [0.001, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 120];
const ALLOWED_KINDS = new Set(["navigation", "web_vital", "api", "long_task", "workbench_journey", "workbench_event_phase", "workbench_backend_event_visible", "workbench_ui_event"]);
const DURATION_METRICS = new Set([
  "navigation_ttfb",
  "navigation_dom_content_loaded",
  "navigation_load",
  "lcp",
  "inp",
  "fid",
  "api_request",
  "long_task"
]);
const WORKBENCH_SCHEMA_VERSION = "hwlab-web-performance-v2";
const WORKBENCH_JOURNEYS = new Set([
  "submit_to_first_visible",
  "submit_to_failure",
  "backend_event_to_visible",
  "session_switch_first_visible",
  "session_switch_full_load",
  "workbench_open_first_visible",
  "workbench_open_full_load"
]);
const WORKBENCH_EVENT_PHASES = new Set([
  "created_to_append",
  "append_to_sse",
  "sse_to_receive",
  "receive_to_project",
  "project_to_paint",
  "user_submit_to_api_accepted",
  "api_accepted_to_backend_event"
]);
const WORKBENCH_EVENT_TYPES = new Set(["assistant", "tool_call", "backend", "terminal", "error", "status", "request", "result", "unknown"]);
const WORKBENCH_TRANSPORTS = new Set(["sse", "rest_gap", "poll", "none", "unknown"]);
const WORKBENCH_OUTCOMES = new Set(["ok", "timeout", "error", "dropped", "stale", "partial", "empty", "network", "denied", "unknown"]);
const WORKBENCH_ENTRIES = new Set(["new", "existing", "steer", "retry", "unknown"]);
const WORKBENCH_VISIBILITY = new Set(["foreground", "background", "hidden", "unknown"]);
const WORKBENCH_CACHE_STATES = new Set(["warm", "cold", "unknown"]);
const WORKBENCH_TARGET_STATES = new Set(["running", "terminal", "empty", "unknown"]);
const WORKBENCH_SOURCES = new Set(["rail", "deeplink", "history", "direct", "hydrate", "unknown"]);
const WORKBENCH_AUTH_STATES = new Set(["warm", "login_redirect", "unknown"]);
const WORKBENCH_UI_EVENT_TYPES = new Set(["loading_state", "api_request", "sse_lifecycle", "page_lifecycle", "unknown"]);
const WORKBENCH_UI_SCOPES = new Set(["workbench", "session_list", "session_detail", "api", "sse", "page", "unknown"]);
const WORKBENCH_UI_STATES = new Set(["enter", "exit", "request", "connect", "open", "message", "close", "error", "sample", "unknown"]);
const WORKBENCH_UI_REASONS = new Set(["hydrate", "select_session", "create_session", "api_request", "sse_connect", "sse_open", "sse_error", "sse_close", "pagehide", "visibility_hidden", "unknown"]);
const OTEL_MIN_VALID_EPOCH_MS = Date.UTC(2020, 0, 1);
const LOW_SAMPLE_THRESHOLD = 5;
const DEFAULT_SUMMARY_WINDOW = "15m";
const PERFORMANCE_SUMMARY_WINDOWS = {
  "5m": { id: "5m", label: "最近 5 分钟", milliseconds: 5 * 60 * 1000 },
  "15m": { id: "15m", label: "最近 15 分钟", milliseconds: 15 * 60 * 1000 },
  "1h": { id: "1h", label: "最近 1 小时", milliseconds: 60 * 60 * 1000 },
  "6h": { id: "6h", label: "最近 6 小时", milliseconds: 6 * 60 * 60 * 1000 },
  "24h": { id: "24h", label: "最近 24 小时", milliseconds: 24 * 60 * 60 * 1000 },
  all: { id: "all", label: "全部累计", milliseconds: null }
} as const;

interface WebPerformanceStoreOptions {
  env?: Record<string, unknown>;
  maxSeries?: number;
  maxSamples?: number;
  now?: () => number;
}

interface WebPerformancePayload {
  schemaVersion?: unknown;
  serviceId?: unknown;
  page?: unknown;
  events?: unknown;
}

interface WebPerformanceEvent {
  kind?: unknown;
  metric?: unknown;
  journey?: unknown;
  phase?: unknown;
  eventType?: unknown;
  backend?: unknown;
  transport?: unknown;
  entry?: unknown;
  visibility?: unknown;
  cache?: unknown;
  targetState?: unknown;
  source?: unknown;
  authState?: unknown;
  route?: unknown;
  valueMs?: unknown;
  value?: unknown;
  method?: unknown;
  status?: unknown;
  statusClass?: unknown;
  outcome?: unknown;
  uiTraceId?: unknown;
  otelTraceId?: unknown;
  spanId?: unknown;
  parentSpanId?: unknown;
  eventName?: unknown;
  loadingScope?: unknown;
  state?: unknown;
  reason?: unknown;
  sessionHash?: unknown;
  traceHash?: unknown;
  startedAtEpochMs?: unknown;
  endedAtEpochMs?: unknown;
  errorName?: unknown;
  timeoutName?: unknown;
  activityWaitingFor?: unknown;
  activityLastEventLabel?: unknown;
  activityIdleMs?: unknown;
  resourceDurationMs?: unknown;
  resourceFetchToRequestMs?: unknown;
  resourceRequestWaitMs?: unknown;
  resourceResponseTransferMs?: unknown;
  resourceTransferSize?: unknown;
  resourceEncodedBodySize?: unknown;
  resourceDecodedBodySize?: unknown;
  resourceNextHopProtocol?: unknown;
  resourceServerTiming?: unknown;
}

interface NormalizedPerformanceEvent {
  series: "web_duration" | "layout_shift" | "workbench_journey" | "workbench_event_phase" | "workbench_backend_event_visible" | "workbench_ui_event";
  metric: string;
  value: number;
  labels: Record<string, string>;
  counterLabels?: Record<string, string>;
}

interface RecordedPerformanceSample extends NormalizedPerformanceEvent {
  atMs: number;
}

interface PerformanceSummaryWindow {
  id: keyof typeof PERFORMANCE_SUMMARY_WINDOWS;
  label: string;
  milliseconds: number | null;
}

interface PerformanceAggregate {
  durationSeries: Map<string, HistogramSeries>;
  clsSeries: Map<string, HistogramSeries>;
  sampleSeries: Map<string, CounterSeries>;
  journeyDurationSeries: Map<string, HistogramSeries>;
  journeyTotalSeries: Map<string, CounterSeries>;
  eventPhaseSeries: Map<string, HistogramSeries>;
  backendEventVisibleSeries: Map<string, HistogramSeries>;
  workbenchUiEventSeries: Map<string, HistogramSeries>;
  workbenchUiEventTotalSeries: Map<string, CounterSeries>;
}

interface HistogramSeries {
  labels: Record<string, string>;
  buckets: number[];
  sum: number;
  count: number;
}

interface CounterSeries {
  labels: Record<string, string>;
  value: number;
}

interface PerformanceSummaryRow {
  kind: string;
  metric: string;
  route: string;
  method: string;
  statusClass: string;
  outcome: string;
  count: number;
  average: number;
  p50: number;
  p75: number;
  p95: number;
  unit: "seconds" | "score";
  sourceFamily?: string;
  windowFrom?: string | null;
  windowTo?: string;
  generatedAt?: string;
  sampleCount?: number;
  freshness?: "fresh" | "low-sample" | "empty";
  aggregationKind?: "exact" | "histogram";
  approximation?: "exact" | "bucketed";
  bucketBoundaries?: number[];
  tone: "ok" | "warn" | "blocked" | "source";
  problem: string;
  lowSample?: boolean;
  sampleState?: "ok" | "low-sample" | "empty";
  backend?: string;
  transport?: string;
  targetState?: string;
  cache?: string;
  source?: string;
  entry?: string;
  authState?: string;
  eventType?: string;
  phase?: string;
  backendEvidence?: PerformanceRowBackendEvidence;
}

interface BackendPerformanceEvidenceRow {
  route?: unknown;
  method?: unknown;
  statusClass?: unknown;
  outcome?: unknown;
  count?: unknown;
  average?: unknown;
  p50?: unknown;
  p75?: unknown;
  p95?: unknown;
  firstObservedAt?: unknown;
  lastObservedAt?: unknown;
  responseBytes?: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown } | null;
  phases?: Array<{ phase?: unknown; outcome?: unknown; count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown }>;
}

interface BackendPerformanceCacheEvidenceRow {
  route?: unknown;
  cacheKeyClass?: unknown;
  count?: unknown;
  hitCount?: unknown;
  missCount?: unknown;
  staleCount?: unknown;
  unavailableCount?: unknown;
  dbQueryAvoidedCount?: unknown;
  hitRatio?: unknown;
  dbQueryAvoidedRatio?: unknown;
  unavailableRatio?: unknown;
  operationDuration?: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown; unit?: unknown } | null;
  payloadBytes?: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown; unit?: unknown } | null;
  cacheAgeMs?: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown; unit?: unknown } | null;
  freshnessSloMs?: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown; unit?: unknown } | null;
  projectionSeqMax?: unknown;
  firstObservedAt?: unknown;
  lastObservedAt?: unknown;
}

interface BackendPerformanceEvidenceSnapshot {
  schemaVersion?: unknown;
  source?: unknown;
  observedAt?: unknown;
  sampleCount?: unknown;
  retainedSampleCount?: unknown;
  routeCount?: unknown;
  rows?: BackendPerformanceEvidenceRow[];
  cache?: BackendPerformanceCacheEvidenceRow[];
}

interface PerformanceRowBackendEvidence {
  status: "matched" | "missing" | "unavailable";
  statusLabel: string;
  diagnostic: string;
  diagnosticLabel: string;
  detail: string;
  route: string;
  method: string;
  count: number;
  p50?: number;
  p75?: number;
  p95?: number;
  responseBytesP95?: number | null;
  lastObservedAt?: string | null;
  phases?: Array<{ phase: string; outcome: string; count: number; p95: number }>;
}

interface PerformanceDashboardPoint {
  label: string;
  detail: string;
  value: number;
  p50: number;
  p75: number;
  p95: number;
  count: number;
  tone: string;
  sampleState: string;
}

interface PerformanceDashboardTopRow {
  label: string;
  detail: string;
  value: number;
  unit: string;
  count: number;
  tone: string;
}

interface PerformanceCollectionHealthDiagnostic {
  code: string;
  label: string;
  tone: "ok" | "warn" | "blocked" | "source";
  detail: string;
}

interface PerformanceCollectionHealth {
  schemaVersion: "hwlab-web-performance-collection-health-v1";
  status: "fresh" | "low-sample" | "stale" | "degraded" | "waiting";
  statusLabel: string;
  reason: string;
  reasonLabel: string;
  sampleCount: number;
  retainedSampleCount: number;
  sampleSeries: number;
  lowSampleThreshold: number;
  firstSampleAt: string | null;
  lastSampleAt: string | null;
  lastRetainedSampleAt: string | null;
  secondsSinceLastSample: number | null;
  windowCoverageSeconds: number | null;
  sourceFamilyCounts: Array<{ sourceFamily: string; rowCount: number; sampleCount: number }>;
  unknownAttribution: {
    workbenchRows: number;
    attributableRows: number;
    rowsWithUnknown: number;
    samplesWithUnknown: number;
    backendUnknownRows: number;
    transportUnknownRows: number;
  };
  diagnostics: PerformanceCollectionHealthDiagnostic[];
}

export function createWebPerformanceStore(options: WebPerformanceStoreOptions = {}) {
  const env = options.env ?? process.env;
  const maxSeries = parsePositiveInteger(env.HWLAB_WEB_PERFORMANCE_MAX_SERIES, options.maxSeries ?? 240);
  const maxSamples = parsePositiveInteger(env.HWLAB_WEB_PERFORMANCE_MAX_SAMPLES, options.maxSamples ?? 5000);
  const now = typeof options.now === "function" ? options.now : () => Date.now();
  const durationSeries = new Map<string, HistogramSeries>();
  const clsSeries = new Map<string, HistogramSeries>();
  const sampleSeries = new Map<string, CounterSeries>();
  const droppedSeries = new Map<string, CounterSeries>();
  const journeyDurationSeries = new Map<string, HistogramSeries>();
  const journeyTotalSeries = new Map<string, CounterSeries>();
  const eventPhaseSeries = new Map<string, HistogramSeries>();
  const backendEventVisibleSeries = new Map<string, HistogramSeries>();
  const workbenchUiEventSeries = new Map<string, HistogramSeries>();
  const workbenchUiEventTotalSeries = new Map<string, CounterSeries>();
  const samples: RecordedPerformanceSample[] = [];
  const baseLabels = {
    service: "hwlab-cloud-web",
    namespace: sanitizeLabelValue(env.HWLAB_METRICS_NAMESPACE ?? env.POD_NAMESPACE ?? "hwlab-v02"),
    gitops_target: sanitizeLabelValue(env.HWLAB_GITOPS_TARGET ?? env.HWLAB_GITOPS_PROFILE ?? "v02")
  };

  function record(payload: WebPerformancePayload) {
    const events = Array.isArray(payload.events) ? payload.events.slice(0, 80) : [];
    let accepted = 0;
    let dropped = 0;
    for (const rawEvent of events) {
      const event = normalizeEvent(rawEvent, payload);
      if (!event) {
        incrementDropped("invalid_event");
        dropped += 1;
        continue;
      }
      if (!recordNormalizedEvent(event)) {
        incrementDropped("series_limit");
        dropped += 1;
        continue;
      }
      rememberSample(event);
      accepted += 1;
    }
    return { accepted, dropped, received: events.length };
  }

  function normalizeEvent(rawEvent: unknown, payload: WebPerformancePayload): NormalizedPerformanceEvent | null {
    if (!rawEvent || typeof rawEvent !== "object" || Array.isArray(rawEvent)) return null;
    const input = rawEvent as WebPerformanceEvent;
    const kind = sanitizeMetricName(input.kind, "unknown");
    if (!ALLOWED_KINDS.has(kind)) return null;
    if (kind === "workbench_journey") return normalizeWorkbenchJourneyEvent(input, payload);
    if (kind === "workbench_event_phase") return normalizeWorkbenchEventPhase(input, payload);
    if (kind === "workbench_backend_event_visible") return normalizeWorkbenchBackendEventVisible(input, payload);
    if (kind === "workbench_ui_event") return normalizeWorkbenchUiEvent(input, payload);
    const metric = sanitizeMetricName(input.metric, "unknown");
    const isCls = metric === "cls";
    if (!isCls && !DURATION_METRICS.has(metric)) return null;
    const rawValue = isCls ? finiteNumber(input.value, NaN) : finiteNumber(input.valueMs, NaN) / 1000;
    if (!Number.isFinite(rawValue) || rawValue < 0) return null;
    const value = isCls ? Math.min(rawValue, 10) : Math.min(rawValue, 120);
    const route = webPerformanceRouteTemplate(input.route ?? payload.page ?? "/workspace");
    const pageRoute = webPerformanceRouteTemplate(payload.page ?? "/workspace");
    if (isObservabilityNoise({ kind, metric, route, pageRoute })) return null;
    const labels = {
      ...baseLabels,
      kind,
      metric,
      route,
      method: normalizeMethod(input.method),
      status_class: normalizeStatusClass(input.statusClass ?? (input.status === undefined || input.status === null ? "unknown" : statusClass(input.status))),
      outcome: sanitizeLabelValue(input.outcome ?? "ok", "ok")
    };
    return { series: isCls ? "layout_shift" : "web_duration", metric, value, labels, counterLabels: sampleLabels(labels) };
  }

  function normalizeWorkbenchJourneyEvent(input: WebPerformanceEvent, payload: WebPerformancePayload): NormalizedPerformanceEvent | null {
    if (String(payload.schemaVersion ?? "") !== WORKBENCH_SCHEMA_VERSION) return null;
    const journey = requiredEnum(input.journey ?? input.metric, WORKBENCH_JOURNEYS);
    if (!journey) return null;
    const value = durationValueSeconds(input.valueMs, 120);
    if (value === null) return null;
    const route = webPerformanceRouteTemplate(input.route ?? payload.page ?? "/workbench");
    const pageRoute = webPerformanceRouteTemplate(payload.page ?? "/workbench");
    if (isObservabilityNoise({ kind: "workbench_journey", metric: journey, route, pageRoute })) return null;
    const labels = {
      ...baseLabels,
      journey,
      route,
      entry: optionalEnum(input.entry, WORKBENCH_ENTRIES, "unknown"),
      source: optionalEnum(input.source, WORKBENCH_SOURCES, "unknown"),
      target_state: optionalEnum(input.targetState ?? field(input, "target_state"), WORKBENCH_TARGET_STATES, "unknown"),
      cache: optionalEnum(input.cache, WORKBENCH_CACHE_STATES, "unknown"),
      auth_state: optionalEnum(input.authState ?? field(input, "auth_state"), WORKBENCH_AUTH_STATES, "unknown"),
      backend: normalizeBackendLabel(input.backend),
      transport: optionalEnum(input.transport, WORKBENCH_TRANSPORTS, "unknown"),
      visibility: optionalEnum(input.visibility, WORKBENCH_VISIBILITY, "unknown"),
      outcome: optionalEnum(input.outcome, WORKBENCH_OUTCOMES, "ok")
    };
    return { series: "workbench_journey", metric: journey, value, labels, counterLabels: labels };
  }

  function normalizeWorkbenchEventPhase(input: WebPerformanceEvent, payload: WebPerformancePayload): NormalizedPerformanceEvent | null {
    if (String(payload.schemaVersion ?? "") !== WORKBENCH_SCHEMA_VERSION) return null;
    const phase = requiredEnum(input.phase ?? input.metric, WORKBENCH_EVENT_PHASES);
    if (!phase) return null;
    const value = durationValueSeconds(input.valueMs, 30);
    if (value === null) return null;
    const labels = {
      ...baseLabels,
      phase,
      event_type: normalizeEventType(input.eventType ?? field(input, "event_type")),
      backend: normalizeBackendLabel(input.backend),
      transport: optionalEnum(input.transport, WORKBENCH_TRANSPORTS, "unknown"),
      outcome: optionalEnum(input.outcome, WORKBENCH_OUTCOMES, "ok")
    };
    return { series: "workbench_event_phase", metric: phase, value, labels };
  }

  function normalizeWorkbenchBackendEventVisible(input: WebPerformanceEvent, payload: WebPerformancePayload): NormalizedPerformanceEvent | null {
    if (String(payload.schemaVersion ?? "") !== WORKBENCH_SCHEMA_VERSION) return null;
    const value = durationValueSeconds(input.valueMs, 120);
    if (value === null) return null;
    const labels = {
      ...baseLabels,
      event_type: normalizeEventType(input.eventType ?? field(input, "event_type")),
      backend: normalizeBackendLabel(input.backend),
      transport: optionalEnum(input.transport, WORKBENCH_TRANSPORTS, "unknown"),
      outcome: optionalEnum(input.outcome, WORKBENCH_OUTCOMES, "ok")
    };
    return { series: "workbench_backend_event_visible", metric: "backend_event_to_visible", value, labels };
  }

  function normalizeWorkbenchUiEvent(input: WebPerformanceEvent, payload: WebPerformancePayload): NormalizedPerformanceEvent | null {
    if (String(payload.schemaVersion ?? "") !== WORKBENCH_SCHEMA_VERSION) return null;
    const eventType = optionalEnum(input.eventType ?? field(input, "event_type"), WORKBENCH_UI_EVENT_TYPES, "unknown");
    const value = durationValueSeconds(input.valueMs, 120);
    if (value === null) return null;
    const route = webPerformanceRouteTemplate(input.route ?? payload.page ?? "/workbench");
    const pageRoute = webPerformanceRouteTemplate(payload.page ?? "/workbench");
    if (isObservabilityNoise({ kind: "workbench_ui_event", metric: eventType, route, pageRoute })) return null;
    const labels = {
      ...baseLabels,
      event_type: eventType,
      route,
      scope: optionalEnum(input.loadingScope ?? field(input, "loading_scope"), WORKBENCH_UI_SCOPES, "unknown"),
      state: optionalEnum(input.state, WORKBENCH_UI_STATES, "unknown"),
      reason: optionalEnum(input.reason, WORKBENCH_UI_REASONS, "unknown"),
      method: normalizeMethod(input.method),
      status_class: normalizeStatusClass(input.statusClass ?? (input.status === undefined || input.status === null ? "unknown" : statusClass(input.status))),
      outcome: optionalEnum(input.outcome, WORKBENCH_OUTCOMES, "ok")
    };
    return { series: "workbench_ui_event", metric: "workbench_ui_event", value, labels, counterLabels: labels };
  }

  function recordNormalizedEvent(event: NormalizedPerformanceEvent) {
    if (event.series === "layout_shift") return recordHistogramWithCounter(clsSeries, sampleSeries, event.labels, event.counterLabels ?? sampleLabels(event.labels), event.value, CLS_BUCKETS);
    if (event.series === "web_duration") return recordHistogramWithCounter(durationSeries, sampleSeries, event.labels, event.counterLabels ?? sampleLabels(event.labels), event.value, DURATION_BUCKETS_SECONDS);
    if (event.series === "workbench_journey") return recordHistogramWithCounter(journeyDurationSeries, journeyTotalSeries, event.labels, event.counterLabels ?? event.labels, event.value, WORKBENCH_JOURNEY_BUCKETS_SECONDS);
    if (event.series === "workbench_event_phase") return recordHistogram(eventPhaseSeries, event.labels, event.value, WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS, maxSeries);
    if (event.series === "workbench_backend_event_visible") return recordHistogram(backendEventVisibleSeries, event.labels, event.value, WORKBENCH_JOURNEY_BUCKETS_SECONDS, maxSeries);
    return recordHistogramWithCounter(workbenchUiEventSeries, workbenchUiEventTotalSeries, event.labels, event.counterLabels ?? event.labels, event.value, WORKBENCH_UI_EVENT_BUCKETS_SECONDS);
  }

  function rememberSample(event: NormalizedPerformanceEvent) {
    samples.push({
      ...event,
      atMs: now(),
      labels: { ...event.labels },
      counterLabels: event.counterLabels ? { ...event.counterLabels } : undefined
    });
    if (samples.length > maxSamples) samples.splice(0, samples.length - maxSamples);
  }

  function recordHistogramWithCounter(histograms: Map<string, HistogramSeries>, counters: Map<string, CounterSeries>, histogramLabels: Record<string, string>, counterLabels: Record<string, string>, value: number, buckets: number[]) {
    if (!canRecordSeries(histograms, histogramLabels, maxSeries) || !canRecordSeries(counters, counterLabels, maxSeries)) return false;
    recordHistogram(histograms, histogramLabels, value, buckets, maxSeries);
    incrementCounter(counters, counterLabels, maxSeries);
    return true;
  }

  function incrementDropped(reason: string) {
    incrementCounter(droppedSeries, { ...baseLabels, reason }, Math.max(maxSeries, 16));
  }

  function metricsText() {
    const lines = [
      "# HELP hwlab_webui_performance_sample_total Browser-observed HWLAB Cloud Web performance samples accepted by cloud-api.",
      "# TYPE hwlab_webui_performance_sample_total counter",
      ...renderCounters("hwlab_webui_performance_sample_total", sampleSeries),
      "# HELP hwlab_webui_performance_duration_seconds Browser-observed HWLAB Cloud Web duration metrics in seconds.",
      "# TYPE hwlab_webui_performance_duration_seconds histogram",
      ...renderHistogram("hwlab_webui_performance_duration_seconds", durationSeries, DURATION_BUCKETS_SECONDS),
      "# HELP hwlab_webui_layout_shift_score Browser-observed cumulative layout shift score.",
      "# TYPE hwlab_webui_layout_shift_score histogram",
      ...renderHistogram("hwlab_webui_layout_shift_score", clsSeries, CLS_BUCKETS),
      "# HELP hwlab_webui_performance_dropped_total Browser performance samples dropped before Prometheus export.",
      "# TYPE hwlab_webui_performance_dropped_total counter",
      ...renderCounters("hwlab_webui_performance_dropped_total", droppedSeries),
      "# HELP hwlab_workbench_journey_total Browser-observed HWLAB Workbench journey samples accepted by cloud-api.",
      "# TYPE hwlab_workbench_journey_total counter",
      ...renderCounters("hwlab_workbench_journey_total", journeyTotalSeries),
      "# HELP hwlab_workbench_journey_duration_seconds Browser-observed HWLAB Workbench user-visible journey durations in seconds.",
      "# TYPE hwlab_workbench_journey_duration_seconds histogram",
      ...renderHistogram("hwlab_workbench_journey_duration_seconds", journeyDurationSeries, WORKBENCH_JOURNEY_BUCKETS_SECONDS),
      "# HELP hwlab_workbench_event_phase_duration_seconds Browser/backend phase durations for HWLAB Workbench events in seconds.",
      "# TYPE hwlab_workbench_event_phase_duration_seconds histogram",
      ...renderHistogram("hwlab_workbench_event_phase_duration_seconds", eventPhaseSeries, WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS),
      "# HELP hwlab_workbench_backend_event_visible_latency_seconds Agent backend event generated to browser-visible latency in seconds.",
      "# TYPE hwlab_workbench_backend_event_visible_latency_seconds histogram",
      ...renderHistogram("hwlab_workbench_backend_event_visible_latency_seconds", backendEventVisibleSeries, WORKBENCH_JOURNEY_BUCKETS_SECONDS),
      "# HELP hwlab_workbench_ui_event_total Browser-observed Workbench UI lifecycle events accepted by cloud-api.",
      "# TYPE hwlab_workbench_ui_event_total counter",
      ...renderCounters("hwlab_workbench_ui_event_total", workbenchUiEventTotalSeries),
      "# HELP hwlab_workbench_ui_event_duration_seconds Browser-observed Workbench UI lifecycle event durations in seconds.",
      "# TYPE hwlab_workbench_ui_event_duration_seconds histogram",
      ...renderHistogram("hwlab_workbench_ui_event_duration_seconds", workbenchUiEventSeries, WORKBENCH_UI_EVENT_BUCKETS_SECONDS),
      ""
    ];
    return lines.join("\n");
  }

  function summary(input: { window?: unknown; backendEvidence?: BackendPerformanceEvidenceSnapshot | null } = {}) {
    const sampleWindow = resolvePerformanceSummaryWindow(input.window);
    const generatedAtMs = now();
    const observedAt = new Date(generatedAtMs).toISOString();
    const windowBounds = performanceSummaryWindowBounds(sampleWindow, generatedAtMs, observedAt);
    const aggregate = aggregateForWindow(sampleWindow, generatedAtMs);
    const windowSamples = samplesForWindow(sampleWindow, generatedAtMs, samples);
    const exactStats = exactStatsForSamples(windowSamples);
    const backendEvidence = normalizeBackendEvidence(input.backendEvidence);
    const durationRows = attachBackendEvidenceToRows(applySummaryContract(histogramSummaryRows(aggregate.durationSeries, DURATION_BUCKETS_SECONDS, "seconds"), exactStats, windowBounds), backendEvidence);
    const clsRows = applySummaryContract(histogramSummaryRows(aggregate.clsSeries, CLS_BUCKETS, "score"), exactStats, windowBounds);
    const rows = [...durationRows, ...clsRows];
    const workbenchJourneys = applySummaryContract(workbenchJourneySummaryRows(aggregate.journeyDurationSeries, WORKBENCH_JOURNEY_BUCKETS_SECONDS), exactStats, windowBounds)
      .sort(sortByProblemThenP95)
      .slice(0, 24);
    const workbenchEventPhases = applySummaryContract(workbenchEventPhaseSummaryRows(aggregate.eventPhaseSeries, WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS), exactStats, windowBounds)
      .sort(sortByProblemThenP95)
      .slice(0, 24);
    const workbenchBackendEvents = applySummaryContract(workbenchBackendEventVisibleSummaryRows(aggregate.backendEventVisibleSeries, WORKBENCH_JOURNEY_BUCKETS_SECONDS), exactStats, windowBounds)
      .sort(sortByProblemThenP95)
      .slice(0, 24);
    const workbenchCacheRows = backendCacheEvidenceSummaryRows(backendEvidence, windowBounds);
    const workbenchRows = [...workbenchJourneys, ...workbenchEventPhases, ...workbenchBackendEvents, ...workbenchCacheRows];
    const apiRoutes = durationRows
      .filter((row) => row.metric === "api_request")
      .sort(sortByProblemThenP95)
      .slice(0, 12);
    const webVitals = rows
      .filter((row) => row.kind === "web_vital" || row.metric.startsWith("navigation_"))
      .sort(sortByProblemThenP95)
      .slice(0, 12);
    const longTasks = durationRows
      .filter((row) => row.metric === "long_task")
      .sort(sortByProblemThenP95)
      .slice(0, 12);
    const problems = [...rows, ...workbenchRows]
      .filter(isPerformanceProblemRow)
      .sort(sortByProblemThenP95)
      .slice(0, 16);
    const sampleCount = [...aggregate.sampleSeries.values()].reduce((sum, entry) => sum + entry.value, 0);
    const workbenchSampleCount = [...aggregate.journeyTotalSeries.values()].reduce((sum, entry) => sum + entry.value, 0)
      + histogramSampleCount(aggregate.eventPhaseSeries)
      + histogramSampleCount(aggregate.backendEventVisibleSeries)
      + [...aggregate.workbenchUiEventTotalSeries.values()].reduce((sum, entry) => sum + entry.value, 0);
    const totalSampleCount = sampleCount + workbenchSampleCount;
    const summaryInfo = {
      sampleCount: totalSampleCount,
      sampleSeries: aggregate.sampleSeries.size,
      durationSeries: aggregate.durationSeries.size,
      clsSeries: aggregate.clsSeries.size,
      droppedSeries: droppedSeries.size,
      workbenchJourneySeries: aggregate.journeyDurationSeries.size,
      workbenchEventPhaseSeries: aggregate.eventPhaseSeries.size,
      workbenchBackendEventVisibleSeries: aggregate.backendEventVisibleSeries.size,
      workbenchCacheEvidenceRows: workbenchCacheRows.length,
      workbenchUiEventSeries: aggregate.workbenchUiEventSeries.size,
      routeCount: new Set([...rows, ...workbenchRows].map((row) => row.route)).size,
      problemCount: problems.length,
      status: totalSampleCount === 0 ? "waiting" : totalSampleCount < 10 ? "warming" : problems.length > 0 ? "watch" : "ok",
      lowSampleThreshold: LOW_SAMPLE_THRESHOLD,
      window: sampleWindow.id,
      windowLabel: sampleWindow.label,
      windowSeconds: sampleWindow.milliseconds === null ? null : Math.round(sampleWindow.milliseconds / 1000),
      windowFrom: windowBounds.windowFrom,
      windowTo: windowBounds.windowTo,
      generatedAt: observedAt,
      aggregationKind: "exact",
      approximation: "exact"
    };
    const collectionHealth = buildPerformanceCollectionHealth({
      observedAt,
      generatedAtMs,
      sampleWindow,
      summary: summaryInfo,
      windowSamples,
      retainedSamples: samples,
      rows: [...rows, ...workbenchRows]
    });
    const dashboard = buildPerformanceDashboard({
      observedAt,
      source: "cloud-api-rum-store",
      namespace: baseLabels.namespace,
      gitopsTarget: baseLabels.gitops_target,
      sampleWindow,
      summary: summaryInfo,
      collectionHealth,
      apiRoutes,
      webVitals,
      longTasks,
      problems,
      workbenchJourneys,
      workbenchEventPhases,
      workbenchBackendEvents,
      rows: rows.sort(sortByProblemThenP95).slice(0, 40),
      workbenchRows: workbenchRows.sort(sortByProblemThenP95).slice(0, 40)
    });
    return {
      serviceId: "hwlab-cloud-api",
      route: "/v1/web-performance/summary",
      source: "cloud-api-rum-store",
      observedAt,
      namespace: baseLabels.namespace,
      gitopsTarget: baseLabels.gitops_target,
      sampleWindow: {
        id: sampleWindow.id,
        label: sampleWindow.label,
        seconds: sampleWindow.milliseconds === null ? null : Math.round(sampleWindow.milliseconds / 1000),
        windowFrom: windowBounds.windowFrom,
        windowTo: windowBounds.windowTo,
        generatedAt: observedAt,
        aggregationKind: "exact",
        approximation: "exact"
      },
      summary: summaryInfo,
      backendEvidence,
      collectionHealth,
      dashboard,
      apiRoutes,
      webVitals,
      longTasks,
      problems,
      workbenchJourneys,
      workbenchEventPhases,
      workbenchBackendEvents,
      workbenchCacheRows,
      workbenchRows: dashboard.source.workbenchRows,
      rows: dashboard.source.rows
    };
  }

  function snapshot() {
    return {
      sampleSeries: sampleSeries.size,
      durationSeries: durationSeries.size,
      clsSeries: clsSeries.size,
      droppedSeries: droppedSeries.size,
      workbenchJourneySeries: journeyDurationSeries.size,
      workbenchEventPhaseSeries: eventPhaseSeries.size,
      workbenchBackendEventVisibleSeries: backendEventVisibleSeries.size,
      workbenchUiEventSeries: workbenchUiEventSeries.size,
      sampleCount: [...sampleSeries.values()].reduce((sum, entry) => sum + entry.value, 0)
        + [...journeyTotalSeries.values()].reduce((sum, entry) => sum + entry.value, 0)
        + histogramSampleCount(eventPhaseSeries)
        + histogramSampleCount(backendEventVisibleSeries)
        + [...workbenchUiEventTotalSeries.values()].reduce((sum, entry) => sum + entry.value, 0),
      retainedSamples: samples.length,
      maxSamples
    };
  }

  function aggregateForWindow(sampleWindow: PerformanceSummaryWindow, generatedAtMs = now()): PerformanceAggregate {
    const aggregate = createPerformanceAggregate();
    for (const sample of samplesForWindow(sampleWindow, generatedAtMs, samples)) recordSampleIntoAggregate(aggregate, sample);
    return aggregate;
  }

  return { record, metricsText, snapshot, summary };
}

function createPerformanceAggregate(): PerformanceAggregate {
  return {
    durationSeries: new Map(),
    clsSeries: new Map(),
    sampleSeries: new Map(),
    journeyDurationSeries: new Map(),
    journeyTotalSeries: new Map(),
    eventPhaseSeries: new Map(),
    backendEventVisibleSeries: new Map(),
    workbenchUiEventSeries: new Map(),
    workbenchUiEventTotalSeries: new Map()
  };
}

function recordSampleIntoAggregate(aggregate: PerformanceAggregate, sample: RecordedPerformanceSample) {
  if (sample.series === "layout_shift") {
    recordHistogram(aggregate.clsSeries, sample.labels, sample.value, CLS_BUCKETS, Number.MAX_SAFE_INTEGER);
    incrementCounter(aggregate.sampleSeries, sample.counterLabels ?? sampleLabels(sample.labels), Number.MAX_SAFE_INTEGER);
    return;
  }
  if (sample.series === "web_duration") {
    recordHistogram(aggregate.durationSeries, sample.labels, sample.value, DURATION_BUCKETS_SECONDS, Number.MAX_SAFE_INTEGER);
    incrementCounter(aggregate.sampleSeries, sample.counterLabels ?? sampleLabels(sample.labels), Number.MAX_SAFE_INTEGER);
    return;
  }
  if (sample.series === "workbench_journey") {
    recordHistogram(aggregate.journeyDurationSeries, sample.labels, sample.value, WORKBENCH_JOURNEY_BUCKETS_SECONDS, Number.MAX_SAFE_INTEGER);
    incrementCounter(aggregate.journeyTotalSeries, sample.counterLabels ?? sample.labels, Number.MAX_SAFE_INTEGER);
    return;
  }
  if (sample.series === "workbench_event_phase") {
    recordHistogram(aggregate.eventPhaseSeries, sample.labels, sample.value, WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS, Number.MAX_SAFE_INTEGER);
    return;
  }
  if (sample.series === "workbench_ui_event") {
    recordHistogram(aggregate.workbenchUiEventSeries, sample.labels, sample.value, WORKBENCH_UI_EVENT_BUCKETS_SECONDS, Number.MAX_SAFE_INTEGER);
    incrementCounter(aggregate.workbenchUiEventTotalSeries, sample.counterLabels ?? sample.labels, Number.MAX_SAFE_INTEGER);
    return;
  }
  recordHistogram(aggregate.backendEventVisibleSeries, sample.labels, sample.value, WORKBENCH_JOURNEY_BUCKETS_SECONDS, Number.MAX_SAFE_INTEGER);
}

function samplesForWindow(samplesWindow: PerformanceSummaryWindow, generatedAtMs: number, retainedSamples: RecordedPerformanceSample[] = []) {
  const source = retainedSamples.length ? retainedSamples : [];
  if (samplesWindow.milliseconds === null) return source.slice();
  const cutoff = generatedAtMs - samplesWindow.milliseconds;
  return source.filter((sample) => sample.atMs >= cutoff && sample.atMs <= generatedAtMs);
}

function resolvePerformanceSummaryWindow(input: unknown): PerformanceSummaryWindow {
  const key = String(input ?? DEFAULT_SUMMARY_WINDOW).trim().toLowerCase();
  if (Object.prototype.hasOwnProperty.call(PERFORMANCE_SUMMARY_WINDOWS, key)) return PERFORMANCE_SUMMARY_WINDOWS[key as keyof typeof PERFORMANCE_SUMMARY_WINDOWS];
  return PERFORMANCE_SUMMARY_WINDOWS[DEFAULT_SUMMARY_WINDOW];
}

function performanceSummaryWindowBounds(sampleWindow: PerformanceSummaryWindow, generatedAtMs: number, generatedAt: string) {
  const windowTo = generatedAt;
  const windowFrom = sampleWindow.milliseconds === null ? null : new Date(Math.max(0, generatedAtMs - sampleWindow.milliseconds)).toISOString();
  return { windowFrom, windowTo, generatedAt };
}

export async function handleWebPerformanceIngestHttp(request, response, options = {}) {
  const body = await readBody(request, options.bodyLimitBytes ?? DEFAULT_WEB_PERFORMANCE_BODY_LIMIT_BYTES);
  let payload;
  try {
    payload = body ? JSON.parse(body) : {};
  } catch (error) {
    sendJson(response, 400, { accepted: false, error: { code: "parse_error", message: "Invalid web performance JSON body", reason: error.message } });
    return;
  }
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
    sendJson(response, 400, { accepted: false, error: { code: "invalid_params", message: "web performance body must be a JSON object" } });
    return;
  }
  const result = options.store.record(payload);
  void emitWorkbenchUiOtelSpans(payload, options.env ?? process.env).catch(() => undefined);
  sendJson(response, 202, { accepted: true, ...result });
}

export async function emitWorkbenchUiOtelSpans(payload: WebPerformancePayload, env = process.env) {
  if (String(payload?.schemaVersion ?? "") !== WORKBENCH_SCHEMA_VERSION) return { submitted: 0, valuesPrinted: false };
  const events = Array.isArray(payload.events) ? payload.events.slice(0, 80) : [];
  const tasks = events
    .map((rawEvent) => normalizeWorkbenchUiOtelSpan(rawEvent, payload))
    .filter(Boolean)
    .map((event) => emitWorkbenchUiOtelSpan(event.name, event.uiTraceId, env, {
      otelTraceId: event.otelTraceId,
      spanId: event.spanId,
      parentSpanId: event.parentSpanId,
      startTimeMs: event.startedAtEpochMs,
      endTimeMs: event.endedAtEpochMs,
      status: event.status,
      attributes: event.attributes
    }));
  if (!tasks.length) return { submitted: 0, valuesPrinted: false };
  await Promise.allSettled(tasks);
  return { submitted: tasks.length, valuesPrinted: false };
}

function normalizeWorkbenchUiOtelSpan(rawEvent: unknown, payload: WebPerformancePayload) {
  if (!rawEvent || typeof rawEvent !== "object" || Array.isArray(rawEvent)) return null;
  const input = rawEvent as WebPerformanceEvent;
  if (sanitizeMetricName(input.kind, "unknown") !== "workbench_ui_event") return null;
  const uiTraceId = sanitizeOtelAttribute(input.uiTraceId, 96);
  if (!uiTraceId) return null;
  const eventType = optionalEnum(input.eventType ?? field(input, "event_type"), WORKBENCH_UI_EVENT_TYPES, "unknown");
  const scope = optionalEnum(input.loadingScope ?? field(input, "loading_scope"), WORKBENCH_UI_SCOPES, "unknown");
  const state = optionalEnum(input.state, WORKBENCH_UI_STATES, "unknown");
  const reason = optionalEnum(input.reason, WORKBENCH_UI_REASONS, "unknown");
  const route = webPerformanceRouteTemplate(input.route ?? payload.page ?? "/workbench");
  const valueMs = Math.min(Math.max(finiteNumber(input.valueMs, 0), 0), 120_000);
  const endedAtEpochMs = validEpochMs(input.endedAtEpochMs) ?? Date.now();
  const startedAtEpochMs = validEpochMs(input.startedAtEpochMs) ?? Math.max(OTEL_MIN_VALID_EPOCH_MS, endedAtEpochMs - valueMs);
  const outcome = optionalEnum(input.outcome, WORKBENCH_OUTCOMES, "ok");
  const statusNumber = finiteNumber(input.status, NaN);
  const eventName = sanitizeOtelAttribute(input.eventName, 80);
  const sessionHash = sanitizeOtelAttribute(input.sessionHash, 80);
  const traceHash = sanitizeOtelAttribute(input.traceHash, 80);
  const errorName = sanitizeOtelAttribute(input.errorName, 80);
  const timeoutName = sanitizeOtelAttribute(input.timeoutName, 80);
  const activityWaitingFor = sanitizeOtelAttribute(input.activityWaitingFor, 80);
  const activityLastEventLabel = sanitizeOtelAttribute(input.activityLastEventLabel, 120);
  const activityIdleMs = Math.min(Math.max(finiteNumber(input.activityIdleMs, NaN), 0), 3_600_000);
  const resourceDurationMs = Math.min(Math.max(finiteNumber(input.resourceDurationMs, NaN), 0), 120_000);
  const resourceFetchToRequestMs = Math.min(Math.max(finiteNumber(input.resourceFetchToRequestMs, NaN), 0), 120_000);
  const resourceRequestWaitMs = Math.min(Math.max(finiteNumber(input.resourceRequestWaitMs, NaN), 0), 120_000);
  const resourceResponseTransferMs = Math.min(Math.max(finiteNumber(input.resourceResponseTransferMs, NaN), 0), 120_000);
  const resourceTransferSize = Math.min(Math.max(finiteNumber(input.resourceTransferSize, NaN), 0), 100 * 1024 * 1024);
  const resourceEncodedBodySize = Math.min(Math.max(finiteNumber(input.resourceEncodedBodySize, NaN), 0), 100 * 1024 * 1024);
  const resourceDecodedBodySize = Math.min(Math.max(finiteNumber(input.resourceDecodedBodySize, NaN), 0), 100 * 1024 * 1024);
  const resourceNextHopProtocol = sanitizeOtelAttribute(input.resourceNextHopProtocol, 40);
  const resourceServerTiming = sanitizeOtelAttribute(input.resourceServerTiming, 240);
  return {
    name: ["workbench", eventType, scope, state].filter((part) => part && part !== "unknown").join("."),
    uiTraceId,
    otelTraceId: sanitizeHex(input.otelTraceId, 32),
    spanId: sanitizeHex(input.spanId, 16),
    parentSpanId: sanitizeHex(input.parentSpanId, 16),
    startedAtEpochMs,
    endedAtEpochMs,
    status: state === "error" || ["error", "network", "timeout", "denied"].includes(outcome) ? "error" : "ok",
    attributes: {
      "workbench.ui.event_type": eventType,
      "workbench.ui.scope": scope,
      "workbench.ui.state": state,
      "workbench.ui.reason": reason,
      "workbench.ui.route": route,
      "workbench.ui.outcome": outcome,
      "workbench.ui.value_ms": valueMs,
      "workbench.ui.visibility": optionalEnum(input.visibility, WORKBENCH_VISIBILITY, "unknown"),
      "workbench.ui.event_name": eventName,
      "workbench.ui.session_hash": sessionHash,
      "workbench.ui.trace_hash": traceHash,
      "workbench.ui.error_name": errorName,
      "workbench.ui.timeout_name": timeoutName,
      "workbench.ui.activity_waiting_for": activityWaitingFor,
      "workbench.ui.activity_last_event_label": activityLastEventLabel,
      ...(Number.isFinite(activityIdleMs) ? { "workbench.ui.activity_idle_ms": Math.trunc(activityIdleMs) } : {}),
      ...(Number.isFinite(resourceDurationMs) ? { "workbench.ui.resource_duration_ms": Math.trunc(resourceDurationMs) } : {}),
      ...(Number.isFinite(resourceFetchToRequestMs) ? { "workbench.ui.resource_fetch_to_request_ms": Math.trunc(resourceFetchToRequestMs) } : {}),
      ...(Number.isFinite(resourceRequestWaitMs) ? { "workbench.ui.resource_request_wait_ms": Math.trunc(resourceRequestWaitMs) } : {}),
      ...(Number.isFinite(resourceResponseTransferMs) ? { "workbench.ui.resource_response_transfer_ms": Math.trunc(resourceResponseTransferMs) } : {}),
      ...(Number.isFinite(resourceTransferSize) ? { "workbench.ui.resource_transfer_size": Math.trunc(resourceTransferSize) } : {}),
      ...(Number.isFinite(resourceEncodedBodySize) ? { "workbench.ui.resource_encoded_body_size": Math.trunc(resourceEncodedBodySize) } : {}),
      ...(Number.isFinite(resourceDecodedBodySize) ? { "workbench.ui.resource_decoded_body_size": Math.trunc(resourceDecodedBodySize) } : {}),
      ...(resourceNextHopProtocol ? { "workbench.ui.resource_next_hop_protocol": resourceNextHopProtocol } : {}),
      ...(resourceServerTiming ? { "workbench.ui.resource_server_timing": resourceServerTiming } : {}),
      "workbench.ui.values_printed": false,
      "http.route": route,
      "http.request.method": normalizeMethod(input.method),
      ...(Number.isFinite(statusNumber) ? { "http.response.status_code": Math.trunc(statusNumber) } : {}),
      "http.response.status_class": normalizeStatusClass(input.statusClass ?? (Number.isFinite(statusNumber) ? statusClass(statusNumber) : "unknown"))
    }
  };
}

export function handleWebPerformanceMetricsHttp(request, response, options = {}) {
  if (!isLoopbackMetricsRequest(request)) {
    sendJson(response, 404, { error: { code: "not_found", message: "route is not public" } });
    return;
  }
  const primary = String(options.store.metricsText() ?? "").trimEnd();
  const extra = typeof options.extraMetricsText === "function"
    ? String(options.extraMetricsText() ?? "").trimEnd()
    : String(options.extraMetricsText ?? "").trimEnd();
  const payload = [primary, extra].filter(Boolean).join("\n") + "\n";
  response.writeHead(200, {
    "content-type": "text/plain; version=0.0.4; charset=utf-8",
    "content-length": Buffer.byteLength(payload)
  });
  response.end(payload);
}

export function handleWebPerformanceSummaryHttp(request, response, options = {}) {
  const url = requestUrl(request);
  const backendEvidence = typeof options.backendPerformanceStore?.evidence === "function"
    ? options.backendPerformanceStore.evidence({ window: url.searchParams.get("window") })
    : null;
  sendJson(response, 200, options.store.summary({ window: url.searchParams.get("window"), backendEvidence }));
}

export function webPerformanceRouteTemplate(input: unknown): string {
  const raw = String(input ?? "").trim() || "/";
  const normalizedRaw = raw.startsWith("#") ? raw.slice(1) || "/" : raw;
  const pathOnly = normalizedRaw.split(/[?#]/u, 1)[0] || "/";
  let pathname = pathOnly;
  try {
    pathname = new URL(normalizedRaw, "http://hwlab.local").pathname || pathOnly;
  } catch {
    // Keep pathOnly.
  }
  const parts = pathname.split("/").filter(Boolean).map((part) => routeSegmentTemplate(safeDecode(part)));
  const path = `/${parts.join("/")}`;
  return path.length > 120 ? `${path.slice(0, 117)}...` : path;
}

function sampleLabels(labels: Record<string, string>) {
  return {
    service: labels.service,
    namespace: labels.namespace,
    gitops_target: labels.gitops_target,
    kind: labels.kind,
    metric: labels.metric,
    route: labels.route,
    method: labels.method,
    status_class: labels.status_class,
    outcome: labels.outcome
  };
}

function requestUrl(request) {
  try {
    return new URL(String(request.url ?? "/"), `http://${String(request.headers?.host ?? "hwlab.local")}`);
  } catch {
    return new URL("/", "http://hwlab.local");
  }
}

function isObservabilityNoise(event: { kind: string; metric: string; route: string; pageRoute: string }) {
  if (event.pageRoute === "/performance") return true;
  if (event.route === "/performance" && (event.kind === "navigation" || event.kind === "web_vital" || event.kind === "long_task")) return true;
  return event.route === "/v1/web-performance" || event.route.startsWith("/v1/web-performance/");
}

function canRecordSeries(series: Map<string, HistogramSeries | CounterSeries>, labels: Record<string, string>, maxSeries: number) {
  const key = stableLabelKey(labels);
  return series.has(key) || series.size < maxSeries;
}

function recordHistogram(series: Map<string, HistogramSeries>, labels: Record<string, string>, value: number, buckets: number[], maxSeries: number) {
  const key = stableLabelKey(labels);
  let entry = series.get(key);
  if (!entry) {
    if (series.size >= maxSeries) return false;
    entry = { labels, buckets: buckets.map(() => 0), sum: 0, count: 0 };
    series.set(key, entry);
  }
  for (let index = 0; index < buckets.length; index += 1) {
    if (value <= buckets[index]) entry.buckets[index] += 1;
  }
  entry.sum += value;
  entry.count += 1;
  return true;
}

function incrementCounter(series: Map<string, CounterSeries>, labels: Record<string, string>, maxSeries: number) {
  const key = stableLabelKey(labels);
  let entry = series.get(key);
  if (!entry) {
    if (series.size >= maxSeries) return false;
    entry = { labels, value: 0 };
    series.set(key, entry);
  }
  entry.value += 1;
  return true;
}

function histogramSampleCount(series: Map<string, HistogramSeries>) {
  return [...series.values()].reduce((sum, entry) => sum + entry.count, 0);
}

function renderCounters(metricName: string, series: Map<string, CounterSeries>) {
  return [...series.values()].map((entry) => `${metricName}{${renderLabels(entry.labels)}} ${entry.value}`);
}

function renderHistogram(metricName: string, series: Map<string, HistogramSeries>, buckets: number[]) {
  const lines: string[] = [];
  for (const entry of series.values()) {
    for (let index = 0; index < buckets.length; index += 1) {
      lines.push(`${metricName}_bucket{${renderLabels({ ...entry.labels, le: String(buckets[index]) })}} ${entry.buckets[index]}`);
    }
    lines.push(`${metricName}_bucket{${renderLabels({ ...entry.labels, le: "+Inf" })}} ${entry.count}`);
    lines.push(`${metricName}_sum{${renderLabels(entry.labels)}} ${entry.sum.toFixed(6)}`);
    lines.push(`${metricName}_count{${renderLabels(entry.labels)}} ${entry.count}`);
  }
  return lines;
}

function histogramSummaryRows(series: Map<string, HistogramSeries>, buckets: number[], unit: "seconds" | "score"): PerformanceSummaryRow[] {
  return [...series.values()].map((entry) => {
    const labels = entry.labels;
    const count = entry.count;
    const average = count > 0 ? entry.sum / count : 0;
    const p50 = histogramQuantile(entry, buckets, 0.5);
    const p75 = histogramQuantile(entry, buckets, 0.75);
    const p95 = histogramQuantile(entry, buckets, 0.95);
    const tone = performanceTone(labels, p95, count, unit);
    return {
      kind: labels.kind,
      metric: labels.metric,
      route: labels.route,
      method: labels.method,
      statusClass: labels.status_class,
      outcome: labels.outcome,
      count,
      average: roundMetric(average),
      p50: roundMetric(p50),
      p75: roundMetric(p75),
      p95: roundMetric(p95),
      unit,
      tone,
      problem: performanceProblem(labels, p95, count, unit, tone),
      lowSample: count > 0 && count < LOW_SAMPLE_THRESHOLD,
      sampleState: sampleState(count)
    };
  });
}

function exactStatsForSamples(windowSamples: RecordedPerformanceSample[]) {
  const grouped = new Map<string, { values: number[] }>();
  for (const sample of windowSamples) {
    const identity = summaryRowIdentityForSample(sample);
    if (!identity) continue;
    const key = summaryRowKey(identity);
    let entry = grouped.get(key);
    if (!entry) {
      entry = { values: [] };
      grouped.set(key, entry);
    }
    entry.values.push(sample.value);
  }
  const stats = new Map<string, { count: number; average: number; p50: number; p75: number; p95: number }>();
  for (const [key, entry] of grouped.entries()) stats.set(key, exactStats(entry.values));
  return stats;
}

function exactStats(values: number[]) {
  const sorted = values.filter(Number.isFinite).sort((left, right) => left - right);
  const count = sorted.length;
  const average = count > 0 ? sorted.reduce((sum, value) => sum + value, 0) / count : 0;
  return {
    count,
    average: roundMetric(average),
    p50: roundMetric(exactQuantile(sorted, 0.5)),
    p75: roundMetric(exactQuantile(sorted, 0.75)),
    p95: roundMetric(exactQuantile(sorted, 0.95))
  };
}

function exactQuantile(sortedValues: number[], quantile: number) {
  if (sortedValues.length <= 0) return 0;
  const index = Math.min(sortedValues.length - 1, Math.max(0, Math.ceil(sortedValues.length * quantile) - 1));
  return sortedValues[index] ?? 0;
}

function applySummaryContract(rows: PerformanceSummaryRow[], exactStatsByRow: Map<string, { count: number; average: number; p50: number; p75: number; p95: number }>, windowBounds: { windowFrom: string | null; windowTo: string; generatedAt: string }) {
  return rows.map((row) => {
    const exact = exactStatsByRow.get(summaryRowKey(row));
    const count = exact?.count ?? row.count;
    const p95 = exact?.p95 ?? row.p95;
    const nextRow = {
      ...row,
      ...(exact ? { count, average: exact.average, p50: exact.p50, p75: exact.p75, p95 } : {}),
      sampleCount: count,
      sourceFamily: sourceFamilyForRow(row),
      windowFrom: windowBounds.windowFrom,
      windowTo: windowBounds.windowTo,
      generatedAt: windowBounds.generatedAt,
      freshness: sampleFreshness(count),
      aggregationKind: exact ? "exact" : "histogram",
      approximation: exact ? "exact" : "bucketed",
      bucketBoundaries: exact ? undefined : bucketBoundariesForRow(row),
      lowSample: count > 0 && count < LOW_SAMPLE_THRESHOLD,
      sampleState: sampleState(count)
    };
    const tone = summaryToneForRow(nextRow, p95, count);
    return { ...nextRow, tone, problem: summaryProblemForRow(nextRow, p95, count, tone) };
  });
}

function normalizeBackendEvidence(input?: BackendPerformanceEvidenceSnapshot | null) {
  const rows = Array.isArray(input?.rows) ? input.rows.map(normalizeBackendEvidenceRow).filter(Boolean) as BackendPerformanceEvidenceRow[] : [];
  const cache = Array.isArray(input?.cache) ? input.cache.map(normalizeBackendCacheEvidenceRow).filter(Boolean) as ReturnType<typeof normalizeBackendCacheEvidenceRow>[] : [];
  return {
    schemaVersion: "hwlab-backend-performance-evidence-v1",
    source: sanitizeLabelValue(input?.source ?? "cloud-api-backend-performance-store"),
    observedAt: isoOrNull(input?.observedAt),
    sampleCount: finiteNonNegativeInteger(input?.sampleCount),
    retainedSampleCount: finiteNonNegativeInteger(input?.retainedSampleCount),
    routeCount: finiteNonNegativeInteger(input?.routeCount ?? new Set(rows.map((row) => row.route)).size),
    rows: rows.slice(0, 80),
    cache: cache.slice(0, 80),
    available: Boolean(input && String(input.schemaVersion ?? "") === "hwlab-backend-performance-evidence-v1"),
    valuesRedacted: true
  };
}

function normalizeBackendEvidenceRow(input: BackendPerformanceEvidenceRow) {
  const route = webPerformanceRouteTemplate(input?.route ?? "");
  if (!route || route === "/") return null;
  const responseBytes = input.responseBytes && typeof input.responseBytes === "object" ? {
    count: finiteNonNegativeInteger(input.responseBytes.count),
    average: finiteNonNegativeInteger(input.responseBytes.average),
    p50: finiteNonNegativeInteger(input.responseBytes.p50),
    p75: finiteNonNegativeInteger(input.responseBytes.p75),
    p95: finiteNonNegativeInteger(input.responseBytes.p95)
  } : null;
  return {
    route,
    method: normalizeMethod(input.method),
    statusClass: normalizeStatusClass(input.statusClass),
    outcome: sanitizeLabelValue(input.outcome ?? "unknown", "unknown"),
    count: finiteNonNegativeInteger(input.count),
    average: finiteNonNegativeMetric(input.average),
    p50: finiteNonNegativeMetric(input.p50),
    p75: finiteNonNegativeMetric(input.p75),
    p95: finiteNonNegativeMetric(input.p95),
    firstObservedAt: isoOrNull(input.firstObservedAt),
    lastObservedAt: isoOrNull(input.lastObservedAt),
    responseBytes,
    phases: Array.isArray(input.phases) ? input.phases.map(normalizeBackendEvidencePhase).filter(Boolean).slice(0, 8) : []
  };
}

function normalizeBackendEvidencePhase(input: { phase?: unknown; outcome?: unknown; count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown }) {
  const phase = sanitizeMetricName(input?.phase, "unknown").slice(0, 64) || "unknown";
  return {
    phase,
    outcome: sanitizeLabelValue(input?.outcome ?? "unknown", "unknown"),
    count: finiteNonNegativeInteger(input?.count),
    average: finiteNonNegativeMetric(input?.average),
    p50: finiteNonNegativeMetric(input?.p50),
    p75: finiteNonNegativeMetric(input?.p75),
    p95: finiteNonNegativeMetric(input?.p95)
  };
}

function normalizeBackendCacheEvidenceRow(input: BackendPerformanceCacheEvidenceRow) {
  const route = webPerformanceRouteTemplate(input?.route ?? "");
  if (!route || route === "/") return null;
  return {
    route,
    cacheKeyClass: sanitizeMetricName(input.cacheKeyClass, "unknown").slice(0, 64) || "unknown",
    count: finiteNonNegativeInteger(input.count),
    hitCount: finiteNonNegativeInteger(input.hitCount),
    missCount: finiteNonNegativeInteger(input.missCount),
    staleCount: finiteNonNegativeInteger(input.staleCount),
    unavailableCount: finiteNonNegativeInteger(input.unavailableCount),
    dbQueryAvoidedCount: finiteNonNegativeInteger(input.dbQueryAvoidedCount),
    hitRatio: boundedRatio(input.hitRatio),
    dbQueryAvoidedRatio: boundedRatio(input.dbQueryAvoidedRatio),
    unavailableRatio: boundedRatio(input.unavailableRatio),
    operationDuration: normalizeMetricStats(input.operationDuration, "seconds"),
    payloadBytes: normalizeMetricStats(input.payloadBytes, "bytes"),
    cacheAgeMs: normalizeMetricStats(input.cacheAgeMs, "milliseconds"),
    freshnessSloMs: normalizeMetricStats(input.freshnessSloMs, "milliseconds"),
    projectionSeqMax: finiteNonNegativeInteger(input.projectionSeqMax),
    firstObservedAt: isoOrNull(input.firstObservedAt),
    lastObservedAt: isoOrNull(input.lastObservedAt),
    valuesRedacted: true
  };
}

function normalizeMetricStats(input: { count?: unknown; average?: unknown; p50?: unknown; p75?: unknown; p95?: unknown; unit?: unknown } | null | undefined, unit: string) {
  if (!input || typeof input !== "object") return null;
  return {
    count: finiteNonNegativeInteger(input.count),
    average: finiteNonNegativeMetric(input.average),
    p50: finiteNonNegativeMetric(input.p50),
    p75: finiteNonNegativeMetric(input.p75),
    p95: finiteNonNegativeMetric(input.p95),
    unit
  };
}

function attachBackendEvidenceToRows(rows: PerformanceSummaryRow[], evidence: ReturnType<typeof normalizeBackendEvidence>) {
  return rows.map((row) => {
    if (row.kind !== "api" || row.metric !== "api_request") return row;
    return { ...row, backendEvidence: backendEvidenceForApiRow(row, evidence) };
  });
}

function backendEvidenceForApiRow(row: PerformanceSummaryRow, evidence: ReturnType<typeof normalizeBackendEvidence>): PerformanceRowBackendEvidence {
  const route = webPerformanceRouteTemplate(row.route);
  const method = normalizeMethod(row.method);
  if (!evidence.available) {
    return {
      status: "unavailable",
      statusLabel: "后端证据未接入",
      diagnostic: "backend_evidence_unavailable",
      diagnosticLabel: "后端证据未接入",
      detail: "当前 summary 没有收到 backend evidence snapshot，无法把 RUM 慢样本与服务端 request 对齐。",
      route,
      method,
      count: 0,
      lastObservedAt: null,
      phases: []
    };
  }
  const candidates = evidence.rows.filter((item) => item.route === route && item.method === method);
  const matched = candidates.find((item) => item.statusClass === row.statusClass && item.outcome === row.outcome) ?? candidates[0] ?? null;
  if (!matched) {
    return {
      status: "missing",
      statusLabel: "后端无匹配样本",
      diagnostic: "no_backend_sample_window",
      diagnosticLabel: "窗口内无后端样本",
      detail: `当前窗口没有匹配 ${method} ${route} 的后端 request 样本；优先检查浏览器采集窗口、边缘/缓存路径或后端 evidence 覆盖范围。`,
      route,
      method,
      count: 0,
      lastObservedAt: null,
      phases: []
    };
  }
  const count = finiteNonNegativeInteger(matched.count);
  const backendP95 = finiteNonNegativeMetric(matched.p95);
  const rumP95 = finiteNonNegativeMetric(row.p95);
  const diagnostic = backendEvidenceDiagnostic({ count, backendP95, rumP95 });
  return {
    status: "matched",
    statusLabel: "已匹配后端",
    diagnostic,
    diagnosticLabel: backendEvidenceDiagnosticLabel(diagnostic),
    detail: backendEvidenceDetail({ diagnostic, count, backendP95, rumP95, phases: matched.phases ?? [] }),
    route: matched.route,
    method: matched.method,
    count,
    p50: finiteNonNegativeMetric(matched.p50),
    p75: finiteNonNegativeMetric(matched.p75),
    p95: backendP95,
    responseBytesP95: matched.responseBytes?.p95 ?? null,
    lastObservedAt: isoOrNull(matched.lastObservedAt),
    phases: (matched.phases ?? []).slice(0, 5).map((phase) => ({ phase: String(phase.phase ?? "unknown"), outcome: String(phase.outcome ?? "unknown"), count: finiteNonNegativeInteger(phase.count), p95: finiteNonNegativeMetric(phase.p95) }))
  };
}

function backendEvidenceDiagnostic(input: { count: number; backendP95: number; rumP95: number }) {
  if (input.count > 0 && input.count < LOW_SAMPLE_THRESHOLD) return "backend_low_sample";
  if (input.rumP95 >= 1 && input.backendP95 > 0 && input.backendP95 <= Math.max(0.25, input.rumP95 * 0.5)) return "rum_slower_than_backend";
  if (input.backendP95 >= 1 && input.backendP95 >= input.rumP95 * 0.75) return "backend_slow_aligned";
  return "backend_matched";
}

function backendEvidenceDiagnosticLabel(value: string) {
  const labels: Record<string, string> = {
    backend_low_sample: "后端低样本",
    backend_slow_aligned: "后端慢已对齐",
    rum_slower_than_backend: "RUM 慢于后端",
    backend_matched: "后端样本已匹配"
  };
  return labels[value] ?? value;
}

function backendEvidenceDetail(input: { diagnostic: string; count: number; backendP95: number; rumP95: number; phases: Array<{ phase?: unknown; p95?: unknown }> }) {
  const topPhase = input.phases?.[0];
  const phaseText = topPhase ? `；最慢阶段 ${String(topPhase.phase ?? "unknown")} P95 ${formatBackendSeconds(finiteNonNegativeMetric(topPhase.p95))}` : "";
  if (input.diagnostic === "rum_slower_than_backend") return `RUM P95 ${formatBackendSeconds(input.rumP95)}，后端 P95 ${formatBackendSeconds(input.backendP95)}，差值更可能在浏览器网络、排队、边缘代理或采集端${phaseText}。`;
  if (input.diagnostic === "backend_slow_aligned") return `RUM P95 ${formatBackendSeconds(input.rumP95)} 与后端 P95 ${formatBackendSeconds(input.backendP95)} 同向，优先看后端阶段${phaseText}。`;
  if (input.diagnostic === "backend_low_sample") return `仅 ${input.count} 个后端样本，已匹配但不足以稳定判断${phaseText}。`;
  return `${input.count} 个后端样本已匹配，后端 P95 ${formatBackendSeconds(input.backendP95)}${phaseText}。`;
}

function backendCacheEvidenceSummaryRows(evidence: ReturnType<typeof normalizeBackendEvidence>, windowBounds: { windowFrom: string | null; windowTo: string; generatedAt: string }): PerformanceSummaryRow[] {
  if (!evidence.available) return [];
  const rows: PerformanceSummaryRow[] = [];
  for (const item of evidence.cache) {
    rows.push(cacheEvidenceRow({ metric: "cache_hit_ratio", route: item.route, cache: item.cacheKeyClass, value: item.hitRatio, count: item.count, unit: "score", windowBounds, outcome: "ok" }));
    rows.push(cacheEvidenceRow({ metric: "db_query_avoided", route: item.route, cache: item.cacheKeyClass, value: item.dbQueryAvoidedRatio, count: item.count, unit: "score", windowBounds, outcome: "ok" }));
    rows.push(cacheEvidenceRow({ metric: "cache_unavailable", route: item.route, cache: item.cacheKeyClass, value: item.unavailableRatio, count: item.count, unit: "score", windowBounds, outcome: item.unavailableCount > 0 ? "error" : "ok" }));
    if (item.route === "/v1/workbench/sessions" && item.operationDuration) {
      rows.push(cacheEvidenceRow({
        metric: "sessions_api_p95",
        route: item.route,
        cache: item.cacheKeyClass,
        value: item.operationDuration.p95,
        p50: item.operationDuration.p50,
        p75: item.operationDuration.p75,
        p95: item.operationDuration.p95,
        count: item.operationDuration.count || item.count,
        unit: "seconds",
        windowBounds,
        outcome: "ok"
      }));
    }
  }
  return rows;
}

function cacheEvidenceRow(input: { metric: string; route: string; cache: string; value: number; p50?: number; p75?: number; p95?: number; count: number; unit: "seconds" | "score"; outcome: string; windowBounds: { windowFrom: string | null; windowTo: string; generatedAt: string } }): PerformanceSummaryRow {
  const count = finiteNonNegativeInteger(input.count);
  const value = finiteNonNegativeMetric(input.value);
  const p50 = input.p50 === undefined ? value : finiteNonNegativeMetric(input.p50);
  const p75 = input.p75 === undefined ? value : finiteNonNegativeMetric(input.p75);
  const p95 = input.p95 === undefined ? value : finiteNonNegativeMetric(input.p95);
  const sample = sampleState(count);
  let tone: "ok" | "warn" | "blocked" | "source" = count < LOW_SAMPLE_THRESHOLD ? "source" : "ok";
  if (sample === "ok" && input.metric === "cache_unavailable" && p95 > 0) tone = "blocked";
  if (sample === "ok" && input.metric === "sessions_api_p95") tone = thresholdTone(p95, 1, 2.5);
  const problem = tone === "source" ? (count <= 0 ? "no-sample" : "low-sample") : tone === "ok" ? "ok" : input.metric === "cache_unavailable" ? "cache-unavailable" : `slow-${input.metric}`;
  return {
    kind: "workbench_cache",
    metric: input.metric,
    route: input.route,
    method: "-",
    statusClass: "-",
    outcome: input.outcome,
    count,
    average: value,
    p50,
    p75,
    p95,
    unit: input.unit,
    tone,
    problem,
    lowSample: count > 0 && count < LOW_SAMPLE_THRESHOLD,
    sampleState: sample,
    sourceFamily: "backend_cache_evidence",
    windowFrom: input.windowBounds.windowFrom,
    windowTo: input.windowBounds.windowTo,
    generatedAt: input.windowBounds.generatedAt,
    freshness: sampleFreshness(count),
    aggregationKind: "exact",
    approximation: "exact",
    cache: input.cache
  };
}

function formatBackendSeconds(value: number) {
  if (!Number.isFinite(value)) return "未知";
  if (value < 1) return `${Math.round(value * 1000)}ms`;
  return `${roundMetric(value)}s`;
}

function finiteNonNegativeMetric(value: unknown) {
  const number = Number(value);
  return Number.isFinite(number) && number >= 0 ? roundMetric(number) : 0;
}

function boundedRatio(value: unknown) {
  const number = Number(value);
  if (!Number.isFinite(number) || number < 0) return 0;
  return Math.min(1, roundMetric(number));
}

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

function isoOrNull(value: unknown): string | null {
  const parsed = Date.parse(String(value ?? ""));
  return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
}

function summaryRowIdentityForSample(sample: RecordedPerformanceSample): Record<string, unknown> | null {
  const labels = sample.labels;
  if (sample.series === "web_duration" || sample.series === "layout_shift") {
    return {
      kind: labels.kind,
      metric: labels.metric,
      route: labels.route,
      method: labels.method,
      statusClass: labels.status_class,
      outcome: labels.outcome
    };
  }
  if (sample.series === "workbench_journey") {
    return {
      kind: "workbench_journey",
      metric: labels.journey,
      route: labels.route,
      method: "-",
      statusClass: "-",
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      targetState: labels.target_state,
      cache: labels.cache,
      source: labels.source,
      entry: labels.entry,
      authState: labels.auth_state
    };
  }
  if (sample.series === "workbench_event_phase") {
    return {
      kind: "workbench_event_phase",
      metric: labels.phase,
      route: "workbench-events",
      method: "-",
      statusClass: "-",
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      eventType: labels.event_type,
      phase: labels.phase
    };
  }
  if (sample.series === "workbench_backend_event_visible") {
    return {
      kind: "workbench_backend_event_visible",
      metric: "backend_event_to_visible",
      route: "workbench-events",
      method: "-",
      statusClass: "-",
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      eventType: labels.event_type
    };
  }
  return null;
}

function summaryRowKey(row: Record<string, unknown>) {
  const fields = ["kind", "metric", "route", "method", "statusClass", "outcome", "backend", "transport", "targetState", "cache", "source", "entry", "authState", "eventType", "phase"];
  const labels: Record<string, string> = {};
  for (const field of fields) labels[field] = String(row[field] ?? "");
  return stableLabelKey(labels);
}

function summaryToneForRow(row: PerformanceSummaryRow, p95: number, count: number): "ok" | "warn" | "blocked" | "source" {
  if (count <= 0 || count < LOW_SAMPLE_THRESHOLD) return "source";
  if (row.kind === "workbench_journey") {
    const threshold = workbenchJourneyThreshold(row.metric);
    return workbenchTone(row.outcome, p95, count, threshold.warn, threshold.blocked);
  }
  if (row.kind === "workbench_event_phase") {
    const threshold = workbenchEventPhaseThreshold(row.metric);
    return workbenchTone(row.outcome, p95, count, threshold.warn, threshold.blocked);
  }
  if (row.kind === "workbench_backend_event_visible") {
    const threshold = workbenchJourneyThreshold("backend_event_to_visible");
    return workbenchTone(row.outcome, p95, count, threshold.warn, threshold.blocked);
  }
  return performanceTone({ metric: row.metric, outcome: row.outcome, status_class: row.statusClass }, p95, count, row.unit === "score" ? "score" : "seconds");
}

function summaryProblemForRow(row: PerformanceSummaryRow, p95: number, count: number, tone: string) {
  if (count <= 0) return "no-sample";
  if (row.kind === "workbench_journey" || row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible" || row.kind === "workbench_cache") return workbenchProblem(row.outcome, row.metric, count, tone);
  return performanceProblem({ metric: row.metric, outcome: row.outcome, status_class: row.statusClass }, p95, count, row.unit === "score" ? "score" : "seconds", tone);
}

function isPerformanceProblemRow(row: PerformanceSummaryRow) {
  if ((row.kind === "workbench_journey" || row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible" || row.kind === "workbench_cache") && row.sampleState !== "ok") return false;
  return row.tone === "blocked" || row.tone === "warn" || row.outcome !== "ok" || row.statusClass === "network" || row.statusClass === "5xx" || row.statusClass === "4xx";
}

function sourceFamilyForRow(row: PerformanceSummaryRow) {
  if (row.kind === "api") return "rum_api_timing";
  if (row.kind === "web_vital") return "rum_web_vitals";
  if (row.kind === "navigation") return "rum_navigation";
  if (row.kind === "long_task") return "rum_long_task";
  if (row.kind === "workbench_journey") return "workbench_journey";
  if (row.kind === "workbench_event_phase") return "workbench_event_phase";
  if (row.kind === "workbench_backend_event_visible") return "workbench_backend_event_visible";
  if (row.kind === "workbench_cache") return "backend_cache_evidence";
  return "unknown";
}

function sampleFreshness(count: number): "fresh" | "low-sample" | "empty" {
  if (count <= 0) return "empty";
  return count < LOW_SAMPLE_THRESHOLD ? "low-sample" : "fresh";
}

function bucketBoundariesForRow(row: PerformanceSummaryRow) {
  if (row.unit === "score") return CLS_BUCKETS;
  if (row.kind === "workbench_event_phase") return WORKBENCH_EVENT_PHASE_BUCKETS_SECONDS;
  if (row.kind === "workbench_journey" || row.kind === "workbench_backend_event_visible") return WORKBENCH_JOURNEY_BUCKETS_SECONDS;
  return DURATION_BUCKETS_SECONDS;
}

function workbenchJourneySummaryRows(series: Map<string, HistogramSeries>, buckets: number[]): PerformanceSummaryRow[] {
  return [...series.values()].map((entry) => {
    const labels = entry.labels;
    const metric = labels.journey;
    const row = workbenchSummaryRow(entry, buckets, {
      kind: "workbench_journey",
      metric,
      route: labels.route,
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      targetState: labels.target_state,
      cache: labels.cache,
      source: labels.source,
      entry: labels.entry,
      authState: labels.auth_state,
      warn: workbenchJourneyThreshold(metric).warn,
      blocked: workbenchJourneyThreshold(metric).blocked
    });
    return row;
  });
}

function workbenchEventPhaseSummaryRows(series: Map<string, HistogramSeries>, buckets: number[]): PerformanceSummaryRow[] {
  return [...series.values()].map((entry) => {
    const labels = entry.labels;
    const metric = labels.phase;
    const threshold = workbenchEventPhaseThreshold(metric);
    return workbenchSummaryRow(entry, buckets, {
      kind: "workbench_event_phase",
      metric,
      route: "workbench-events",
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      eventType: labels.event_type,
      phase: labels.phase,
      warn: threshold.warn,
      blocked: threshold.blocked
    });
  });
}

function workbenchBackendEventVisibleSummaryRows(series: Map<string, HistogramSeries>, buckets: number[]): PerformanceSummaryRow[] {
  return [...series.values()].map((entry) => {
    const labels = entry.labels;
    const threshold = workbenchJourneyThreshold("backend_event_to_visible");
    return workbenchSummaryRow(entry, buckets, {
      kind: "workbench_backend_event_visible",
      metric: "backend_event_to_visible",
      route: "workbench-events",
      outcome: labels.outcome,
      backend: labels.backend,
      transport: labels.transport,
      eventType: labels.event_type,
      warn: threshold.warn,
      blocked: threshold.blocked
    });
  });
}

function workbenchSummaryRow(entry: HistogramSeries, buckets: number[], input: {
  kind: string;
  metric: string;
  route: string;
  outcome: string;
  warn: number;
  blocked: number;
  backend?: string;
  transport?: string;
  targetState?: string;
  cache?: string;
  source?: string;
  entry?: string;
  authState?: string;
  eventType?: string;
  phase?: string;
}): PerformanceSummaryRow {
  const count = entry.count;
  const average = count > 0 ? entry.sum / count : 0;
  const p50 = histogramQuantile(entry, buckets, 0.5);
  const p75 = histogramQuantile(entry, buckets, 0.75);
  const p95 = histogramQuantile(entry, buckets, 0.95);
  const lowSample = count > 0 && count < LOW_SAMPLE_THRESHOLD;
  const tone = workbenchTone(input.outcome, p95, count, input.warn, input.blocked);
  return {
    kind: input.kind,
    metric: input.metric,
    route: input.route,
    method: "-",
    statusClass: "-",
    outcome: input.outcome,
    count,
    average: roundMetric(average),
    p50: roundMetric(p50),
    p75: roundMetric(p75),
    p95: roundMetric(p95),
    unit: "seconds",
    tone,
    problem: workbenchProblem(input.outcome, input.metric, count, tone),
    lowSample,
    sampleState: sampleState(count),
    backend: input.backend,
    transport: input.transport,
    targetState: input.targetState,
    cache: input.cache,
    source: input.source,
    entry: input.entry,
    authState: input.authState,
    eventType: input.eventType,
    phase: input.phase
  };
}

function histogramQuantile(entry: HistogramSeries, buckets: number[], quantile: number) {
  if (entry.count <= 0) return 0;
  const rank = Math.max(1, Math.ceil(entry.count * quantile));
  for (let index = 0; index < entry.buckets.length; index += 1) {
    if (entry.buckets[index] >= rank) return buckets[index];
  }
  return buckets[buckets.length - 1] ?? 0;
}

function performanceTone(labels: Record<string, string>, p95: number, count: number, unit: "seconds" | "score") {
  if (labels.outcome !== "ok" || labels.status_class === "network" || labels.status_class === "5xx") return "blocked";
  if (labels.status_class === "4xx") return "warn";
  if (count <= 0) return "source";
  const metric = labels.metric;
  if (unit === "score") {
    if (p95 >= 0.25) return "blocked";
    if (p95 >= 0.1) return "warn";
    return "ok";
  }
  if (metric === "lcp") return thresholdTone(p95, 2.5, 4);
  if (metric === "inp" || metric === "fid") return thresholdTone(p95, 0.2, 0.5);
  if (metric === "api_request") return thresholdTone(p95, 1, 2.5);
  if (metric === "long_task") return thresholdTone(p95, 0.2, 0.5);
  if (metric === "navigation_dom_content_loaded") return thresholdTone(p95, 2, 4);
  if (metric === "navigation_load") return thresholdTone(p95, 3, 6);
  if (metric === "navigation_ttfb") return thresholdTone(p95, 0.8, 1.8);
  return p95 > 1 ? "warn" : "ok";
}

function workbenchTone(outcome: string, p95: number, count: number, warn: number, blocked: number) {
  if (outcome !== "ok") return "blocked";
  if (count <= 0 || count < LOW_SAMPLE_THRESHOLD) return "source";
  return thresholdTone(p95, warn, blocked);
}

function workbenchJourneyThreshold(metric: string) {
  if (metric === "session_switch_first_visible") return { warn: 1, blocked: 3 };
  if (metric === "session_switch_full_load") return { warn: 2.5, blocked: 8 };
  if (metric === "workbench_open_first_visible") return { warn: 2.5, blocked: 5 };
  if (metric === "workbench_open_full_load") return { warn: 5, blocked: 13 };
  if (metric === "backend_event_to_visible") return { warn: 3, blocked: 10 };
  return { warn: 5, blocked: 15 };
}

function workbenchEventPhaseThreshold(metric: string) {
  if (metric === "append_to_sse") return { warn: 0.25, blocked: 1 };
  if (metric === "created_to_append" || metric === "receive_to_project" || metric === "project_to_paint") return { warn: 0.5, blocked: 2 };
  if (metric === "sse_to_receive") return { warn: 1, blocked: 3 };
  if (metric === "user_submit_to_api_accepted") return { warn: 1, blocked: 3 };
  return { warn: 5, blocked: 15 };
}

function thresholdTone(value: number, warn: number, blocked: number) {
  if (value >= blocked) return "blocked";
  if (value >= warn) return "warn";
  return "ok";
}

function performanceProblem(labels: Record<string, string>, p95: number, count: number, unit: "seconds" | "score", tone: string) {
  if (count <= 0) return "no-sample";
  if (labels.outcome !== "ok") return labels.outcome;
  if (labels.status_class === "network") return "network";
  if (labels.status_class === "5xx") return "server-error";
  if (labels.status_class === "4xx") return "client-error";
  if (tone === "blocked" || tone === "warn") return unit === "score" ? "layout-shift" : `slow-${labels.metric}`;
  return "ok";
}

function workbenchProblem(outcome: string, metric: string, count: number, tone: string) {
  if (count <= 0) return "no-sample";
  if (count < LOW_SAMPLE_THRESHOLD) return "low-sample";
  if (outcome !== "ok") return outcome;
  if (tone === "blocked" || tone === "warn") return `slow-${metric}`;
  return "ok";
}

function sampleState(count: number): "ok" | "low-sample" | "empty" {
  if (count <= 0) return "empty";
  return count < LOW_SAMPLE_THRESHOLD ? "low-sample" : "ok";
}

function sortByProblemThenP95(left: PerformanceSummaryRow, right: PerformanceSummaryRow) {
  return toneWeight(right.tone) - toneWeight(left.tone) || right.p95 - left.p95 || right.count - left.count;
}

function toneWeight(tone: string) {
  if (tone === "blocked") return 4;
  if (tone === "warn") return 3;
  if (tone === "ok") return 2;
  return 1;
}

function buildPerformanceCollectionHealth(input: {
  observedAt: string;
  generatedAtMs: number;
  sampleWindow: PerformanceSummaryWindow;
  summary: Record<string, number | string | null>;
  windowSamples: RecordedPerformanceSample[];
  retainedSamples: RecordedPerformanceSample[];
  rows: PerformanceSummaryRow[];
}): PerformanceCollectionHealth {
  const sampleCount = numberField(input.summary.sampleCount);
  const sampleSeries = numberField(input.summary.sampleSeries)
    + numberField(input.summary.workbenchJourneySeries)
    + numberField(input.summary.workbenchEventPhaseSeries)
    + numberField(input.summary.workbenchBackendEventVisibleSeries)
    + numberField(input.summary.workbenchUiEventSeries);
  const firstSampleMs = minSampleAt(input.windowSamples);
  const lastSampleMs = maxSampleAt(input.windowSamples);
  const lastRetainedSampleMs = maxSampleAt(input.retainedSamples);
  const sourceFamilyCounts = collectionSourceFamilyCounts(input.rows);
  const unknownAttribution = collectionUnknownAttribution(input.rows);
  const diagnostics: PerformanceCollectionHealthDiagnostic[] = [];
  let status: PerformanceCollectionHealth["status"] = "fresh";
  let reason = "window_has_samples";
  let reasonLabel = "当前窗口采集正常";

  if (input.retainedSamples.length <= 0) {
    status = "waiting";
    reason = "no_samples_retained";
    reasonLabel = "采集器尚未上报样本";
    diagnostics.push({ code: reason, label: "暂无采集样本", tone: "source", detail: "Cloud API RUM store 当前没有保留任何浏览器性能样本。" });
  } else if (sampleCount <= 0) {
    status = "stale";
    reason = "no_samples_in_window";
    reasonLabel = `${input.sampleWindow.label}无样本`;
    diagnostics.push({ code: reason, label: "当前窗口无样本", tone: "source", detail: "存在历史保留样本，但当前所选时间窗口没有样本；请切换更大窗口或等待新上报。" });
  } else if (sampleCount < LOW_SAMPLE_THRESHOLD) {
    status = "low-sample";
    reason = "low_sample_window";
    reasonLabel = "当前窗口低样本";
    diagnostics.push({ code: reason, label: "低样本窗口", tone: "warn", detail: `当前窗口只有 ${sampleCount} 个样本，低于 ${LOW_SAMPLE_THRESHOLD} 个样本阈值。` });
  }

  if (unknownAttribution.rowsWithUnknown > 0) {
    if (status === "fresh") {
      status = "degraded";
      reason = "unknown_attribution";
      reasonLabel = "归因字段不完整";
    }
    diagnostics.push({
      code: "unknown_attribution",
      label: "工作台归因不完整",
      tone: "warn",
      detail: `${unknownAttribution.rowsWithUnknown} 条工作台序列存在 backend 或 transport unknown，影响慢路径归因。`
    });
  }

  if (sourceFamilyCounts.length > 1) {
    diagnostics.push({ code: "mixed_source_family", label: "多来源样本", tone: "ok", detail: `当前窗口包含 ${sourceFamilyCounts.length} 类低基数来源。` });
  }

  if (!diagnostics.length) diagnostics.push({ code: "ok", label: "采集健康", tone: "ok", detail: "当前窗口有足够样本，且没有发现低样本、缺样或 unknown 归因问题。" });

  return {
    schemaVersion: "hwlab-web-performance-collection-health-v1",
    status,
    statusLabel: collectionStatusLabel(status),
    reason,
    reasonLabel,
    sampleCount,
    retainedSampleCount: input.retainedSamples.length,
    sampleSeries,
    lowSampleThreshold: LOW_SAMPLE_THRESHOLD,
    firstSampleAt: isoFromMs(firstSampleMs),
    lastSampleAt: isoFromMs(lastSampleMs),
    lastRetainedSampleAt: isoFromMs(lastRetainedSampleMs),
    secondsSinceLastSample: lastRetainedSampleMs === null ? null : Math.max(0, Math.round((input.generatedAtMs - lastRetainedSampleMs) / 1000)),
    windowCoverageSeconds: firstSampleMs === null || lastSampleMs === null ? null : Math.max(0, Math.round((lastSampleMs - firstSampleMs) / 1000)),
    sourceFamilyCounts,
    unknownAttribution,
    diagnostics
  };
}

function collectionSourceFamilyCounts(rows: PerformanceSummaryRow[]) {
  const counts = new Map<string, { sourceFamily: string; rowCount: number; sampleCount: number }>();
  for (const row of rows) {
    const sourceFamily = String(row.sourceFamily ?? sourceFamilyForRow(row) ?? "unknown");
    const entry = counts.get(sourceFamily) ?? { sourceFamily, rowCount: 0, sampleCount: 0 };
    entry.rowCount += 1;
    entry.sampleCount += Math.max(0, Number(row.count) || 0);
    counts.set(sourceFamily, entry);
  }
  return [...counts.values()].sort((left, right) => right.sampleCount - left.sampleCount || left.sourceFamily.localeCompare(right.sourceFamily));
}

function collectionUnknownAttribution(rows: PerformanceSummaryRow[]) {
  const workbenchRows = rows.filter((row) => row.kind === "workbench_journey" || row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible");
  const attributionRows = workbenchRows.filter(rowNeedsWorkbenchAttribution);
  const backendUnknownRows = attributionRows.filter((row) => unknownAttributionValue(row.backend)).length;
  const transportUnknownRows = attributionRows.filter((row) => unknownAttributionValue(row.transport)).length;
  const rowsWithUnknown = attributionRows.filter((row) => unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)).length;
  const samplesWithUnknown = attributionRows.reduce((sum, row) => sum + ((unknownAttributionValue(row.backend) || unknownAttributionValue(row.transport)) ? Math.max(0, Number(row.count) || 0) : 0), 0);
  return { workbenchRows: workbenchRows.length, attributableRows: attributionRows.length, rowsWithUnknown, samplesWithUnknown, backendUnknownRows, transportUnknownRows };
}

function rowNeedsWorkbenchAttribution(row: PerformanceSummaryRow) {
  if (row.kind === "workbench_event_phase" || row.kind === "workbench_backend_event_visible") return true;
  if (row.kind !== "workbench_journey") return false;
  return row.metric === "submit_to_first_visible" || row.metric === "submit_to_failure" || row.metric === "backend_event_to_visible";
}

function unknownAttributionValue(value: unknown) {
  const text = String(value ?? "").trim().toLowerCase();
  return !text || text === "-" || text === "unknown";
}

function minSampleAt(samples: RecordedPerformanceSample[]) {
  const values = samples.map((sample) => sample.atMs).filter(Number.isFinite);
  return values.length ? Math.min(...values) : null;
}

function maxSampleAt(samples: RecordedPerformanceSample[]) {
  const values = samples.map((sample) => sample.atMs).filter(Number.isFinite);
  return values.length ? Math.max(...values) : null;
}

function isoFromMs(value: number | null) {
  return value === null ? null : new Date(value).toISOString();
}

function collectionStatusLabel(status: PerformanceCollectionHealth["status"]) {
  if (status === "fresh") return "采集正常";
  if (status === "low-sample") return "低样本";
  if (status === "stale") return "当前窗口无样本";
  if (status === "degraded") return "归因不完整";
  return "等待数据";
}

function buildPerformanceDashboard(input: {
  observedAt: string;
  source: string;
  namespace: string;
  gitopsTarget: string;
  sampleWindow: PerformanceSummaryWindow;
  summary: Record<string, number | string | null>;
  collectionHealth: PerformanceCollectionHealth;
  apiRoutes: PerformanceSummaryRow[];
  webVitals: PerformanceSummaryRow[];
  longTasks: PerformanceSummaryRow[];
  problems: PerformanceSummaryRow[];
  workbenchJourneys: PerformanceSummaryRow[];
  workbenchEventPhases: PerformanceSummaryRow[];
  workbenchBackendEvents: PerformanceSummaryRow[];
  rows: PerformanceSummaryRow[];
  workbenchRows: PerformanceSummaryRow[];
}) {
  const allRows = [...input.rows, ...input.workbenchRows];
  const sampleCount = numberField(input.summary.sampleCount);
  const problemCount = numberField(input.summary.problemCount);
  const status = String(input.summary.status ?? "waiting");
  const collectionHealth = input.collectionHealth;
  const trends = [
    dashboardTrend("workbench-experience", "工作台体感趋势", "首屏、会话切换和提交首个可见结果的 P95", "seconds", input.workbenchJourneys),
    dashboardTrend("backend-visible", "后端事件可见延迟", "AgentRun/backend 事件到浏览器可见的 P95", "seconds", input.workbenchBackendEvents),
    dashboardTrend("api-timing", "同源 API 耗时", "Cloud Web 同源 API 请求 P95", "seconds", input.apiRoutes),
    dashboardTrend("web-vitals", "浏览器核心指标", "LCP/INP/CLS/FCP/TTFB 当前窗口", "mixed", input.webVitals)
  ].filter((trend) => trend.points.length > 0);
  return {
    schemaVersion: "hwlab-web-performance-dashboard-v1",
    generatedAt: input.observedAt,
    sampleWindow: {
      id: input.sampleWindow.id,
      label: input.sampleWindow.label,
      seconds: input.sampleWindow.milliseconds === null ? null : Math.round(input.sampleWindow.milliseconds / 1000),
      windowFrom: input.summary.windowFrom ?? null,
      windowTo: input.summary.windowTo ?? input.observedAt,
      generatedAt: input.summary.generatedAt ?? input.observedAt,
      aggregationKind: input.summary.aggregationKind ?? "exact",
      approximation: input.summary.approximation ?? "exact",
      buckets: [{ id: input.sampleWindow.id, label: input.sampleWindow.label, observedAt: input.observedAt, sampleCount }]
    },
    freshness: {
      observedAt: input.observedAt,
      source: input.source,
      namespace: input.namespace,
      gitopsTarget: input.gitopsTarget,
      status: collectionHealth.status,
      statusLabel: collectionHealth.statusLabel,
      reason: collectionHealth.reason,
      reasonLabel: collectionHealth.reasonLabel,
      lowSampleThreshold: numberField(input.summary.lowSampleThreshold),
      generatedAt: input.summary.generatedAt ?? input.observedAt,
      windowFrom: input.summary.windowFrom ?? null,
      windowTo: input.summary.windowTo ?? input.observedAt,
      sampleCount,
      sourceFamily: "mixed",
      lastSampleAt: collectionHealth.lastSampleAt,
      lastRetainedSampleAt: collectionHealth.lastRetainedSampleAt,
      secondsSinceLastSample: collectionHealth.secondsSinceLastSample
    },
    collectionHealth,
    cards: [
      { id: "health", label: "采集状态", value: collectionHealth.statusLabel, unit: "状态", tone: dashboardTone(collectionHealth.status), detail: collectionHealth.reasonLabel },
      { id: "samples", label: "样本数", value: sampleCount, unit: "样本", tone: sampleCount === 0 ? "source" : "ok", detail: `${input.sampleWindow.label} · ${numberField(input.summary.sampleSeries)} 个序列` },
      { id: "workbench", label: "工作台序列", value: numberField(input.summary.workbenchJourneySeries) + numberField(input.summary.workbenchEventPhaseSeries) + numberField(input.summary.workbenchBackendEventVisibleSeries), unit: "序列", tone: "ok", detail: `${input.namespace} / ${input.gitopsTarget}` },
      { id: "problems", label: "问题行", value: problemCount, unit: "条", tone: problemCount > 0 ? "warn" : "ok", detail: "预警、阻塞或非正常结果" }
    ],
    trends,
    distributions: [
      dashboardDistribution("status", "状态分布", "按健康状态聚合当前窗口行数", countRowsBy(allRows, (row) => toneLabel(row.tone))),
      dashboardDistribution("problems", "问题分布", "按问题类型聚合预警和阻塞行", countRowsBy(input.problems, (row) => problemLabel(row.problem || row.outcome))),
      dashboardDistribution("samples", "样本状态", "正常、低样本和暂无数据分布", countRowsBy(allRows, (row) => sampleStateLabel(row.sampleState)))
    ].filter((item) => item.buckets.length > 0),
    topN: [
      dashboardTopN("top-api", "慢 API TopN", "同源 API P95 最慢路由", "seconds", input.apiRoutes),
      dashboardTopN("top-workbench", "工作台慢路径 TopN", "工作台 journey 和事件可见延迟", "seconds", [...input.workbenchJourneys, ...input.workbenchBackendEvents]),
      dashboardTopN("top-long-task", "长任务 TopN", "主线程长任务当前窗口", "seconds", input.longTasks)
    ].filter((item) => item.rows.length > 0),
    source: {
      rows: input.rows,
      workbenchRows: input.workbenchRows
    }
  };
}

function dashboardTrend(id: string, title: string, subtitle: string, unit: string, rows: PerformanceSummaryRow[]) {
  return {
    id,
    title,
    subtitle,
    unit,
    points: rows.slice(0, 8).map(dashboardPoint)
  };
}

function dashboardPoint(row: PerformanceSummaryRow): PerformanceDashboardPoint {
  return {
    label: metricLabel(row),
    detail: rowDetail(row),
    value: row.p95,
    p50: row.p50,
    p75: row.p75,
    p95: row.p95,
    count: row.count,
    tone: row.tone,
    sampleState: row.sampleState ?? "ok"
  };
}

function dashboardDistribution(id: string, title: string, subtitle: string, counts: Map<string, number>) {
  const buckets = [...counts.entries()]
    .map(([label, count]) => ({ label, count, tone: distributionTone(label) }))
    .sort((left, right) => right.count - left.count || left.label.localeCompare(right.label, "zh-Hans-CN"));
  return { id, title, subtitle, unit: "count", buckets };
}

function dashboardTopN(id: string, title: string, subtitle: string, unit: string, rows: PerformanceSummaryRow[]) {
  const topRows: PerformanceDashboardTopRow[] = rows.slice(0, 8).map((row) => ({
    label: metricLabel(row),
    detail: rowDetail(row),
    value: row.p95,
    unit: row.unit === "score" ? "score" : unit,
    count: row.count,
    tone: row.tone
  }));
  return { id, title, subtitle, unit, rows: topRows };
}

function countRowsBy(rows: PerformanceSummaryRow[], getLabel: (row: PerformanceSummaryRow) => string) {
  const counts = new Map<string, number>();
  for (const row of rows) counts.set(getLabel(row), (counts.get(getLabel(row)) ?? 0) + Math.max(1, row.count));
  return counts;
}

function metricLabel(row: PerformanceSummaryRow) {
  const metric = row.metric || row.phase || row.kind || "unknown";
  const labels: Record<string, string> = {
    submit_to_first_visible: "提交到首个可见结果",
    submit_to_failure: "提交失败可见",
    backend_event_to_visible: "后端事件到可见",
    cache_hit_ratio: "缓存命中率",
    db_query_avoided: "DB 查询避免率",
    cache_unavailable: "缓存不可用率",
    sessions_api_p95: "会话 API P95",
    session_switch_first_visible: "切换会话首个可见",
    session_switch_full_load: "切换会话完整加载",
    workbench_open_first_visible: "打开工作台首个可见",
    workbench_open_full_load: "打开工作台完整加载",
    created_to_append: "创建到写入 Trace",
    append_to_sse: "Trace 写入到 SSE",
    sse_to_receive: "SSE 到浏览器接收",
    receive_to_project: "接收到状态投影",
    project_to_paint: "投影到画面可见",
    user_submit_to_api_accepted: "提交到 API 接收",
    api_accepted_to_backend_event: "API 接收到后端事件",
    api_request: "同源 API 请求",
    long_task: "主线程长任务",
    lcp: "最大内容绘制",
    inp: "交互响应",
    fid: "首次输入延迟",
    cls: "布局偏移",
    navigation_ttfb: "首字节时间",
    navigation_dom_content_loaded: "DOM 内容加载",
    navigation_load: "页面加载完成"
  };
  return labels[metric] ?? metric.replace(/_/gu, " ");
}

function rowDetail(row: PerformanceSummaryRow) {
  const route = dashboardRouteLabel(row.route);
  const parts = [
    route,
    row.eventType ? `事件：${eventTypeLabel(row.eventType)}` : "",
    row.phase ? `阶段：${metricLabel({ ...row, metric: row.phase })}` : "",
    row.backend ? `后端：${row.backend}` : "",
    row.transport ? `传输：${transportLabel(row.transport)}` : "",
    row.outcome ? `结果：${outcomeLabel(row.outcome)}` : ""
  ].filter(Boolean);
  return parts.join(" / ") || "当前窗口";
}

function dashboardRouteLabel(route: string | undefined) {
  const value = String(route ?? "").trim();
  if (!value || value === "-") return "";
  return value
    .replace(/:sessionId/gu, ":会话")
    .replace(/:traceId/gu, ":轨迹")
    .replace(/:runId/gu, ":运行")
    .replace(/:threadId/gu, ":线程")
    .replace(/:turnId/gu, ":轮次");
}

function toneLabel(tone: string | undefined) {
  if (tone === "ok") return "正常";
  if (tone === "warn") return "预警";
  if (tone === "blocked") return "阻塞";
  return "等待数据";
}

function sampleStateLabel(state: string | undefined) {
  if (state === "ok") return "正常样本";
  if (state === "low-sample") return "低样本";
  return "暂无数据";
}

function dashboardStatusLabel(status: string) {
  if (status === "fresh") return "采集正常";
  if (status === "low-sample") return "低样本";
  if (status === "stale") return "当前窗口无样本";
  if (status === "degraded") return "归因不完整";
  if (status === "ok") return "正常";
  if (status === "watch") return "预警";
  if (status === "warming") return "低样本";
  return "等待数据";
}

function dashboardTone(status: string) {
  if (status === "ok" || status === "fresh") return "ok";
  if (status === "watch" || status === "degraded" || status === "low-sample") return "warn";
  return "source";
}

function distributionTone(label: string) {
  if (label.includes("阻塞")) return "blocked";
  if (label.includes("预警") || label.includes("低样本")) return "warn";
  return "ok";
}

function problemLabel(problem: string | undefined) {
  const text = String(problem ?? "ok");
  if (text === "ok") return "正常";
  if (text === "low-sample") return "低样本";
  if (text === "timeout") return "超时";
  if (text === "network") return "网络失败";
  if (text === "server-error") return "服务端错误";
  if (text === "client-error") return "客户端错误";
  if (text === "layout-shift") return "布局偏移";
  if (text.startsWith("slow-")) return `慢路径：${metricLabel({ metric: text.slice(5), kind: "", route: "", method: "", statusClass: "", outcome: "", count: 0, average: 0, p50: 0, p75: 0, p95: 0, unit: "seconds", tone: "ok", problem: "" })}`;
  return text.replace(/[_-]/gu, " ");
}

function eventTypeLabel(value: string) {
  const labels: Record<string, string> = { assistant: "助手消息", tool_call: "工具调用", backend: "后端事件", terminal: "终态结果", error: "错误", status: "状态", request: "请求", result: "结果", unknown: "未知" };
  return labels[value] ?? value;
}

function transportLabel(value: string) {
  const labels: Record<string, string> = { sse: "实时流", rest_gap: "REST 补洞", poll: "轮询", none: "无", unknown: "未知" };
  return labels[value] ?? value;
}

function outcomeLabel(value: string) {
  const labels: Record<string, string> = { ok: "正常", timeout: "超时", error: "错误", dropped: "丢弃", stale: "滞后", partial: "部分", empty: "空结果", network: "网络", denied: "拒绝", unknown: "未知" };
  return labels[value] ?? value;
}

function numberField(value: unknown) {
  return Number.isFinite(Number(value)) ? Number(value) : 0;
}

function roundMetric(value: number) {
  if (!Number.isFinite(value)) return 0;
  return Math.round(value * 10000) / 10000;
}

function renderLabels(labels: Record<string, string>) {
  return Object.entries(labels)
    .map(([key, value]) => `${key}="${escapeLabelValue(value)}"`)
    .join(",");
}

function stableLabelKey(labels: Record<string, string>) {
  return Object.entries(labels).map(([key, value]) => `${key}=${value}`).join("\u0001");
}

function isLoopbackMetricsRequest(request) {
  const remoteAddress = String(request.socket?.remoteAddress ?? "");
  const host = String(request.headers?.host ?? "").trim().toLowerCase();
  const loopbackRemote = remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1";
  const loopbackHost = /^(?:127\.0\.0\.1|localhost)(?::\d+)?$/u.test(host);
  return loopbackRemote && loopbackHost;
}

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

function sanitizeMetricName(value: unknown, fallback: string) {
  const text = String(value ?? "").trim().toLowerCase().replace(/[^a-z0-9_:-]/gu, "_").slice(0, 64);
  return text || fallback;
}

function sanitizeLabelValue(value: unknown, fallback = "unknown") {
  const text = String(value ?? "").trim();
  const sanitized = text.replace(/[^A-Za-z0-9_.:/-]/gu, "_").slice(0, 120);
  return sanitized || fallback;
}

function requiredEnum(value: unknown, allowed: Set<string>) {
  const text = sanitizeMetricName(value, "");
  return allowed.has(text) ? text : null;
}

function optionalEnum(value: unknown, allowed: Set<string>, fallback: string) {
  const text = sanitizeMetricName(value, fallback);
  return allowed.has(text) ? text : fallback;
}

function normalizeEventType(value: unknown) {
  const text = sanitizeMetricName(value, "unknown");
  const aliased = text === "assistant_message" ? "assistant" : text === "tool" ? "tool_call" : text;
  return WORKBENCH_EVENT_TYPES.has(aliased) ? aliased : "unknown";
}

function normalizeBackendLabel(value: unknown) {
  const sanitized = sanitizeLabelValue(String(value ?? "unknown").trim().toLowerCase(), "unknown").slice(0, 64);
  if (!sanitized || looksHighCardinalityLabel(sanitized)) return "unknown";
  return sanitized;
}

function looksHighCardinalityLabel(value: string) {
  return /(?:^|[^a-z0-9])(?:trc|trace|thread|run|cmd|command|job|ses|session|cnv|conv|conversation|gws|box|res|pod|dp|usr|user)[_-][a-z0-9_-]{6,}/iu.test(value)
    || /^[0-9a-f]{8,}(?:-[0-9a-f]{4,}){2,}$/iu.test(value)
    || /^[A-Za-z0-9_-]{24,}$/u.test(value);
}

function durationValueSeconds(valueMs: unknown, maxSeconds: number) {
  const seconds = finiteNumber(valueMs, NaN) / 1000;
  if (!Number.isFinite(seconds) || seconds < 0) return null;
  return Math.min(seconds, maxSeconds);
}

function field(input: unknown, key: string) {
  if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
  return (input as Record<string, unknown>)[key];
}

function escapeLabelValue(value: unknown) {
  return String(value ?? "").replace(/\\/gu, "\\\\").replace(/\n/gu, "\\n").replace(/"/gu, "\\\"");
}

function normalizeMethod(value: unknown) {
  const method = String(value ?? "NONE").trim().toUpperCase();
  return /^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$/u.test(method) ? method : "NONE";
}

function normalizeStatusClass(value: unknown) {
  const text = String(value ?? "unknown").trim().toLowerCase();
  if (/^[1-5]xx$/u.test(text)) return text;
  if (text === "network" || text === "unknown") return text;
  return "unknown";
}

function statusClass(value: unknown) {
  const status = finiteNumber(value, 0);
  if (status >= 100 && status < 600) return `${Math.floor(status / 100)}xx`;
  if (status === 0) return "network";
  return "unknown";
}

function finiteNumber(value: unknown, fallback: number) {
  const number = typeof value === "number" ? value : Number(value);
  return Number.isFinite(number) ? number : fallback;
}

function sanitizeOtelAttribute(value: unknown, maxLength: number) {
  const text = String(value ?? "").trim();
  if (!text) return undefined;
  return text.replace(/[\u0000-\u001f\u007f]/gu, "_").slice(0, maxLength);
}

function sanitizeHex(value: unknown, length: number) {
  const text = String(value ?? "").trim().toLowerCase();
  return new RegExp(`^[0-9a-f]{${length}}$`, "u").test(text) && !/^0+$/u.test(text) ? text : undefined;
}

function validEpochMs(value: unknown) {
  const number = finiteNumber(value, NaN);
  return Number.isFinite(number) && number >= OTEL_MIN_VALID_EPOCH_MS ? number : null;
}

function parsePositiveInteger(value: unknown, fallback: number) {
  const number = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
  return Number.isInteger(number) && number > 0 ? number : fallback;
}

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

function readBody(request, limitBytes) {
  const limit = Number.isInteger(limitBytes) && limitBytes > 0 ? limitBytes : DEFAULT_WEB_PERFORMANCE_BODY_LIMIT_BYTES;
  return new Promise((resolve, reject) => {
    let body = "";
    request.setEncoding("utf8");
    request.on("data", (chunk) => {
      body += chunk;
      if (Buffer.byteLength(body, "utf8") > limit) {
        request.destroy(new Error(`request body exceeds ${limit} bytes`));
      }
    });
    request.on("end", () => resolve(body));
    request.on("error", reject);
  });
}

function sendJson(response, statusCode, body) {
  const payload = decorateErrorPayloadForHttp(body, { response, statusCode, layer: "api", category: "web_performance" });
  logErrorEnvelope(payload, { statusCode });
  response.writeHead(statusCode, {
    "content-type": "application/json; charset=utf-8",
    "cache-control": "no-store"
  });
  response.end(`${JSON.stringify(payload)}\n`);
}
