type WebPerformanceKind = "navigation" | "web_vital" | "api" | "long_task";

interface WebPerformanceEvent {
  kind: WebPerformanceKind;
  metric: string;
  route: string;
  observedAt: string;
  valueMs?: number;
  value?: number;
  method?: string;
  status?: number;
  statusClass?: string;
  outcome?: string;
}

interface ApiTimingInput {
  path: string;
  method?: string;
  status?: number;
  durationMs: number;
  outcome?: string;
}

type ExtendedPerformanceEntry = PerformanceEntry & {
  value?: number;
  hadRecentInput?: boolean;
  processingStart?: number;
};

type ExtendedPerformanceObserverInit = PerformanceObserverInit & {
  durationThreshold?: number;
};

const TELEMETRY_ENDPOINT = "/v1/web-performance";
const PAYLOAD_SCHEMA_VERSION = "hwlab-web-performance-v1";
const MAX_QUEUE_EVENTS = 120;
const MAX_BATCH_EVENTS = 48;
const DEFAULT_FLUSH_DELAY_MS = 1500;

let installed = false;
let flushTimer: number | null = null;
let queue: WebPerformanceEvent[] = [];
let latestLcpMs = 0;
let cumulativeLayoutShift = 0;
let maxInteractionMs = 0;

export function installWebPerformanceTelemetry(): void {
  if (installed || !browserRuntimeAvailable()) return;
  installed = true;

  window.addEventListener("load", () => {
    window.setTimeout(recordNavigationTiming, 0);
    window.setTimeout(recordNavigationTiming, 1000);
  }, { once: true });

  observeLargestContentfulPaint();
  observeLayoutShift();
  observeInteractionLatency();
  observeLongTasks();

  window.setTimeout(() => {
    recordFinalVitals();
    flushWebPerformanceQueue(false);
  }, 3500);

  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState !== "hidden") return;
    recordFinalVitals();
    flushWebPerformanceQueue(true);
  });
  window.addEventListener("pagehide", () => {
    recordFinalVitals();
    flushWebPerformanceQueue(true);
  }, { capture: true });
}

export function recordApiTiming(input: ApiTimingInput): void {
  if (!browserRuntimeAvailable()) return;
  const route = webPerformanceRouteTemplate(input.path);
  if (isObservabilityRoute(route) || currentPageRoute() === "/performance") return;
  const durationMs = finitePositive(input.durationMs, 0);
  if (durationMs <= 0) return;
  enqueueWebPerformanceEvent({
    kind: "api",
    metric: "api_request",
    route,
    method: normalizeMethod(input.method),
    status: normalizeStatus(input.status),
    statusClass: webPerformanceStatusClass(input.status),
    outcome: normalizeOutcome(input.outcome),
    valueMs: durationMs,
    observedAt: new Date().toISOString()
  });
}

export function webPerformanceRouteTemplate(input: string): string {
  const raw = String(input ?? "").trim() || "/";
  const normalizedRaw = raw.startsWith("#") ? raw.slice(1) || "/" : raw;
  let pathname = normalizedRaw.split(/[?#]/u, 1)[0] || "/";
  try {
    const base = typeof window !== "undefined" && window.location?.origin ? window.location.origin : "http://hwlab.local";
    pathname = new URL(normalizedRaw, base).pathname || "/";
  } catch {
    // Keep the conservative path derived above.
  }

  const parts = pathname.split("/").filter(Boolean).map((part) => routeSegmentTemplate(safeDecode(part)));
  const path = `/${parts.join("/")}`;
  return path.length > 120 ? `${path.slice(0, 117)}...` : path;
}

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

export function __resetWebPerformanceTelemetryForTest(): void {
  installed = false;
  flushTimer = null;
  queue = [];
  latestLcpMs = 0;
  cumulativeLayoutShift = 0;
  maxInteractionMs = 0;
}

function recordNavigationTiming(): void {
  const navigation = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming | undefined;
  if (!navigation) return;
  const route = currentPageRoute();
  enqueueDuration("navigation", "navigation_ttfb", route, navigation.responseStart - navigation.requestStart);
  enqueueDuration("navigation", "navigation_dom_content_loaded", route, navigation.domContentLoadedEventEnd - navigation.startTime);
  enqueueDuration("navigation", "navigation_load", route, navigation.loadEventEnd - navigation.startTime);
}

function observeLargestContentfulPaint(): void {
  observePerformanceEntries("largest-contentful-paint", (entry) => {
    latestLcpMs = Math.max(latestLcpMs, finitePositive(entry.startTime, 0));
  });
}

function observeLayoutShift(): void {
  observePerformanceEntries("layout-shift", (entry) => {
    if (entry.hadRecentInput) return;
    cumulativeLayoutShift += finitePositive(entry.value, 0);
  });
}

function observeInteractionLatency(): void {
  observePerformanceEntries("event", (entry) => {
    maxInteractionMs = Math.max(maxInteractionMs, finitePositive(entry.duration, 0));
  }, { durationThreshold: 40 });
  observePerformanceEntries("first-input", (entry) => {
    const processingStart = finitePositive(entry.processingStart, 0);
    const firstInputDelayMs = processingStart > 0 ? processingStart - entry.startTime : entry.duration;
    maxInteractionMs = Math.max(maxInteractionMs, finitePositive(firstInputDelayMs, 0));
  });
}

function observeLongTasks(): void {
  observePerformanceEntries("longtask", (entry) => {
    enqueueDuration("long_task", "long_task", currentPageRoute(), entry.duration);
  });
}

function observePerformanceEntries(type: string, onEntry: (entry: ExtendedPerformanceEntry) => void, options: ExtendedPerformanceObserverInit = {}): void {
  if (typeof PerformanceObserver === "undefined" || !supportsEntryType(type)) return;
  try {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries() as ExtendedPerformanceEntry[]) onEntry(entry);
    });
    observer.observe({ type, buffered: true, ...options });
  } catch {
    // Older browsers may list a type but reject buffered/duration options.
  }
}

function recordFinalVitals(): void {
  const route = currentPageRoute();
  if (latestLcpMs > 0) enqueueDuration("web_vital", "lcp", route, latestLcpMs);
  if (maxInteractionMs > 0) enqueueDuration("web_vital", "inp", route, maxInteractionMs);
  if (cumulativeLayoutShift > 0) {
    enqueueWebPerformanceEvent({
      kind: "web_vital",
      metric: "cls",
      route,
      value: Number(cumulativeLayoutShift.toFixed(4)),
      observedAt: new Date().toISOString()
    });
  }
}

function enqueueDuration(kind: WebPerformanceKind, metric: string, route: string, valueMs: number): void {
  const durationMs = finitePositive(valueMs, 0);
  if (durationMs <= 0) return;
  enqueueWebPerformanceEvent({
    kind,
    metric,
    route,
    valueMs: durationMs,
    observedAt: new Date().toISOString()
  });
}

function enqueueWebPerformanceEvent(event: WebPerformanceEvent): void {
  if (!browserRuntimeAvailable()) return;
  if (isObservabilityPageRoute(currentPageRoute())) return;
  queue.push(event);
  if (queue.length > MAX_QUEUE_EVENTS) queue = queue.slice(queue.length - MAX_QUEUE_EVENTS);
  scheduleFlush();
}

function scheduleFlush(delayMs = DEFAULT_FLUSH_DELAY_MS): void {
  if (flushTimer !== null) return;
  flushTimer = window.setTimeout(() => flushWebPerformanceQueue(false), delayMs);
}

function flushWebPerformanceQueue(useBeacon: boolean): void {
  if (!browserRuntimeAvailable()) return;
  if (flushTimer !== null) {
    window.clearTimeout(flushTimer);
    flushTimer = null;
  }
  if (queue.length === 0) return;
  if (isObservabilityPageRoute(currentPageRoute())) {
    queue = [];
    return;
  }
  const events = queue.splice(0, MAX_BATCH_EVENTS);
  const payload = JSON.stringify({
    schemaVersion: PAYLOAD_SCHEMA_VERSION,
    serviceId: "hwlab-cloud-web",
    page: currentPageRoute(),
    observedAt: new Date().toISOString(),
    events
  });
  if (useBeacon && typeof navigator.sendBeacon === "function") {
    const sent = navigator.sendBeacon(TELEMETRY_ENDPOINT, new Blob([payload], { type: "application/json" }));
    if (sent) return;
  }
  void fetch(TELEMETRY_ENDPOINT, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: payload,
    credentials: "same-origin",
    keepalive: true
  }).catch(() => undefined);
}

function currentPageRoute(): string {
  const hash = typeof window !== "undefined" ? window.location.hash.replace(/^#/u, "") : "/workspace";
  return webPerformanceRouteTemplate(hash || "/workspace");
}

function isObservabilityRoute(route: string): boolean {
  return route === TELEMETRY_ENDPOINT || route.startsWith(`${TELEMETRY_ENDPOINT}/`);
}

function isObservabilityPageRoute(route: string): boolean {
  return route === "/performance";
}

function supportsEntryType(type: string): boolean {
  const supported = PerformanceObserver.supportedEntryTypes;
  return Array.isArray(supported) && supported.includes(type);
}

function browserRuntimeAvailable(): boolean {
  return typeof window !== "undefined" && typeof document !== "undefined" && typeof performance !== "undefined";
}

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 sanitizeRouteSegment(segment);
}

function sanitizeRouteSegment(segment: string): string {
  const value = segment.replace(/[^A-Za-z0-9_.:-]/gu, "_").slice(0, 48);
  return value || "_";
}

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

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

function normalizeStatus(status: unknown): number {
  const value = typeof status === "number" && Number.isFinite(status) ? Math.trunc(status) : 0;
  return value >= 100 && value < 600 ? value : 0;
}

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

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