// SPEC: pikasTech/unidesk#1436 WEB-PROBE performance probe.
// Responsibility: Source string for page PerformanceObserver and CPU profile capture helpers.

export function nodeWebObserveRunnerPerformanceSource(): string {
  return String.raw`
async function installPagePerformanceProbe(targetPage, pageRole = "control", targetPageId = pageId) {
  const installer = (input) => {
    const win = window;
    const existing = win.__UNIDESK_WEB_PROBE_PERFORMANCE__;
    if (existing && existing.version === 1) {
      existing.pageRole = input.pageRole;
      existing.pageId = input.pageId;
      existing.pageEpoch = input.pageEpoch;
      existing.installedAgainAt = Date.now();
      return { ok: true, alreadyInstalled: true, supportedTypes: existing.supportedTypes || [], valuesRedacted: true };
    }
    const buffer = [];
    const supportedTypes = [];
    const maxBufferSize = Math.max(1, Number(input.maxBufferSize || 1));
    const push = (event) => {
      buffer.push({
        probeVersion: 1,
        pageRole: input.pageRole,
        pageId: input.pageId,
        pageEpoch: input.pageEpoch,
        url: location.href,
        path: location.pathname,
        timeOrigin: Math.round(performance.timeOrigin || 0),
        now: Math.round(performance.now()),
        observedAt: Date.now(),
        ...event,
        valuesRedacted: true
      });
      while (buffer.length > maxBufferSize) buffer.shift();
    };
    const num = (value) => Number.isFinite(Number(value)) ? Number(value) : null;
    const text = (value, limit = 240) => String(value || "").slice(0, limit);
    const compactAttribution = (attribution) => Array.from(attribution || []).slice(0, 12).map((item) => ({
      name: text(item?.name, 120),
      entryType: text(item?.entryType, 80),
      startTime: num(item?.startTime),
      duration: num(item?.duration),
      containerType: text(item?.containerType, 80),
      containerName: text(item?.containerName, 160),
      containerId: text(item?.containerId, 120),
      containerSrc: text(item?.containerSrc, 260),
      valuesRedacted: true
    }));
    const compactScript = (script) => ({
      invoker: text(script?.invoker, 180),
      invokerType: text(script?.invokerType, 80),
      sourceURL: text(script?.sourceURL, 360),
      sourceFunctionName: text(script?.sourceFunctionName, 180),
      sourceCharPosition: num(script?.sourceCharPosition),
      lineNumber: num(script?.lineNumber),
      columnNumber: num(script?.columnNumber),
      startTime: num(script?.startTime),
      duration: num(script?.duration),
      executionStart: num(script?.executionStart),
      forcedStyleAndLayoutDuration: num(script?.forcedStyleAndLayoutDuration),
      pauseDuration: num(script?.pauseDuration),
      valuesRedacted: true
    });
    const observe = (type, handler) => {
      try {
        const observer = new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            try { handler(entry); } catch (error) { push({ kind: "observer-error", observedEntryType: type, message: text(error && error.message, 240) }); }
          }
        });
        observer.observe({ type, buffered: true });
        supportedTypes.push(type);
        return observer;
      } catch {
        return null;
      }
    };
    const observers = [];
    const longTask = observe("longtask", (entry) => push({
      kind: "longtask",
      name: text(entry.name, 120),
      entryType: text(entry.entryType, 80),
      startTime: num(entry.startTime),
      duration: num(entry.duration),
      attribution: compactAttribution(entry.attribution),
    }));
    if (longTask) observers.push(longTask);
    const loaf = observe("long-animation-frame", (entry) => push({
      kind: "long-animation-frame",
      name: text(entry.name, 120),
      entryType: text(entry.entryType, 80),
      startTime: num(entry.startTime),
      duration: num(entry.duration),
      renderStart: num(entry.renderStart),
      styleAndLayoutStart: num(entry.styleAndLayoutStart),
      blockingDuration: num(entry.blockingDuration),
      firstUIEventTimestamp: num(entry.firstUIEventTimestamp),
      scripts: Array.from(entry.scripts || []).slice(0, 24).map(compactScript),
    }));
    if (loaf) observers.push(loaf);
    let lastTick = performance.now();
    const tickIntervalMs = Math.max(50, Number(input.eventLoopProbeIntervalMs || 250));
    const eventLoopGapRedMs = Math.max(100, Number(input.eventLoopGapRedMs || 1000));
    const timer = setInterval(() => {
      const now = performance.now();
      const gap = now - lastTick - tickIntervalMs;
      lastTick = now;
      if (gap >= eventLoopGapRedMs) {
        push({ kind: "event-loop-gap", startTime: Math.max(0, now - gap), duration: Math.round(gap), thresholdMs: eventLoopGapRedMs });
      }
    }, tickIntervalMs);
    win.__UNIDESK_WEB_PROBE_PERFORMANCE__ = {
      version: 1,
      pageRole: input.pageRole,
      pageId: input.pageId,
      pageEpoch: input.pageEpoch,
      installedAt: Date.now(),
      supportedTypes,
      buffer,
      observers,
      timer,
      drain() {
        const items = buffer.splice(0, buffer.length);
        return { ok: true, count: items.length, supportedTypes, items, valuesRedacted: true };
      }
    };
    return { ok: true, alreadyInstalled: false, supportedTypes, valuesRedacted: true };
  };
  await targetPage.addInitScript(installer, { pageRole, pageId: targetPageId, pageEpoch: pageRole === "observer" ? observerPageEpoch : controlPageEpoch, eventLoopGapRedMs: alertThresholds.eventLoopGapRedMs, maxBufferSize: maxPerformanceEntries });
  if (!targetPage.isClosed()) {
    await withHardTimeout(targetPage.evaluate(installer, { pageRole, pageId: targetPageId, pageEpoch: pageRole === "observer" ? observerPageEpoch : controlPageEpoch, eventLoopGapRedMs: alertThresholds.eventLoopGapRedMs, maxBufferSize: maxPerformanceEntries }), 3000, "installPagePerformanceProbe evaluate exceeded 3000ms");
  }
}

async function drainPagePerformanceEvents(targetPage, { reason, groupSeq, pageRole, targetPageId, pageEpoch }) {
  if (!targetPage || targetPage.isClosed()) return { ok: false, reason: "page-closed", count: 0, valuesRedacted: true };
  const timeoutMs = Math.max(1000, Math.min(3000, Number(alertThresholds.playwrightResponsivenessRedMs) || 3000));
  const drained = await withHardTimeout(targetPage.evaluate(() => {
    const probe = window.__UNIDESK_WEB_PROBE_PERFORMANCE__;
    if (!probe || typeof probe.drain !== "function") return { ok: false, reason: "performance-probe-not-installed", count: 0, valuesRedacted: true };
    return probe.drain();
  }), timeoutMs, "drainPagePerformanceEvents exceeded " + timeoutMs + "ms").catch((error) => ({ ok: false, reason: isTimeoutErrorMessage(error?.message) ? "drain-timeout" : "drain-error", error: errorSummary(error), count: 0, valuesRedacted: true }));
  const items = Array.isArray(drained?.items) ? drained.items : [];
  for (const item of items) {
    await appendJsonl(files.performanceEvents, eventRecord("performance-event", {
      reason,
      sampleGroupSeq: groupSeq,
      pageRole,
      pageId: targetPageId,
      pageEpoch: Number.isFinite(Number(pageEpoch)) ? Number(pageEpoch) : 0,
      performance: compactPerformanceEvent(item),
      valuesRedacted: true,
    }));
  }
  if (drained?.ok === false && drained.reason !== "performance-probe-not-installed") {
    await appendJsonl(files.performanceEvents, eventRecord("performance-drain-error", {
      reason,
      sampleGroupSeq: groupSeq,
      pageRole,
      pageId: targetPageId,
      pageEpoch: Number.isFinite(Number(pageEpoch)) ? Number(pageEpoch) : 0,
      drain: drained,
      valuesRedacted: true,
    }));
  }
  return { ok: drained?.ok === true, count: items.length, reason: drained?.reason ?? null, supportedTypes: Array.isArray(drained?.supportedTypes) ? drained.supportedTypes.slice(0, 8) : [], valuesRedacted: true };
}

function compactPerformanceEvent(item) {
  const value = item && typeof item === "object" ? item : {};
  const scripts = Array.isArray(value.scripts) ? value.scripts.slice(0, 24).map(compactPerformanceScript) : [];
  const attribution = Array.isArray(value.attribution) ? value.attribution.slice(0, 12).map(compactPerformanceAttribution) : [];
  return {
    kind: truncate(value.kind, 80),
    name: truncate(value.name, 160),
    entryType: truncate(value.entryType, 80),
    startTime: numberOrNull(value.startTime),
    duration: numberOrNull(value.duration),
    blockingDuration: numberOrNull(value.blockingDuration),
    renderStart: numberOrNull(value.renderStart),
    styleAndLayoutStart: numberOrNull(value.styleAndLayoutStart),
    firstUIEventTimestamp: numberOrNull(value.firstUIEventTimestamp),
    thresholdMs: numberOrNull(value.thresholdMs),
    timeOrigin: numberOrNull(value.timeOrigin),
    observedAt: numberOrNull(value.observedAt),
    now: numberOrNull(value.now),
    path: truncate(value.path, 220),
    url: safeUrl(value.url),
    scripts,
    attribution,
    valuesRedacted: true,
  };
}

function compactPerformanceScript(script) {
  const value = script && typeof script === "object" ? script : {};
  return {
    invoker: truncate(value.invoker, 180),
    invokerType: truncate(value.invokerType, 80),
    sourceURL: safeUrl(value.sourceURL),
    sourceFunctionName: truncate(value.sourceFunctionName, 180),
    sourceCharPosition: numberOrNull(value.sourceCharPosition),
    lineNumber: numberOrNull(value.lineNumber),
    columnNumber: numberOrNull(value.columnNumber),
    startTime: numberOrNull(value.startTime),
    duration: numberOrNull(value.duration),
    executionStart: numberOrNull(value.executionStart),
    forcedStyleAndLayoutDuration: numberOrNull(value.forcedStyleAndLayoutDuration),
    pauseDuration: numberOrNull(value.pauseDuration),
    valuesRedacted: true,
  };
}

function compactPerformanceAttribution(item) {
  const value = item && typeof item === "object" ? item : {};
  return {
    name: truncate(value.name, 120),
    entryType: truncate(value.entryType, 80),
    startTime: numberOrNull(value.startTime),
    duration: numberOrNull(value.duration),
    containerType: truncate(value.containerType, 80),
    containerName: truncate(value.containerName, 160),
    containerId: truncate(value.containerId, 120),
    containerSrc: safeUrl(value.containerSrc),
    valuesRedacted: true,
  };
}

async function capturePerformanceProfile(command) {
  const durationMs = boundedInteger(command.durationMs, Math.min(5000, maxPerformanceCaptureMs), 100, maxPerformanceCaptureMs);
  const timeoutMs = Math.max(3000, Math.min(15000, Number(alertThresholds.playwrightResponsivenessRedMs) || 5000));
  const captureId = safeId(command.label || command.id || ("perf-" + Date.now().toString(36))) || ("perf-" + Date.now().toString(36));
  const targetPage = page && !page.isClosed() ? page : observerPage && !observerPage.isClosed() ? observerPage : null;
  if (!targetPage) throw new Error("performanceCapture requires an open page");
  const targetPageRole = targetPage === observerPage ? "observer" : "control";
  const targetPageId = targetPageRole === "observer" ? observerPageId : pageId;
  const targetPageEpoch = targetPageRole === "observer" ? observerPageEpoch : controlPageEpoch;
  const captureDir = path.join(dirs.performanceCaptures, captureId);
  await mkdir(captureDir, { recursive: true, mode: 0o700 });
  await installPagePerformanceProbe(targetPage, targetPageRole, targetPageId)
    .catch((error) => appendJsonl(files.errors, eventRecord("performance-capture-probe-install-error", { commandId: command.id, pageRole: targetPageRole, pageId: targetPageId, error: errorSummary(error), valuesRedacted: true })));
  const beforeDrain = await drainPagePerformanceEvents(targetPage, { reason: "performanceCapture-before", groupSeq: sampleSeq, pageRole: targetPageRole, targetPageId, pageEpoch: targetPageEpoch })
    .catch((error) => ({ ok: false, error: errorSummary(error), count: 0, valuesRedacted: true }));
  let session = null;
  const startedAtMs = Date.now();
  const startedAt = new Date(startedAtMs).toISOString();
  let pageClockStart = null;
  let stopped = null;
  let captureError = null;
  try {
    pageClockStart = await withHardTimeout(targetPage.evaluate(() => ({ timeOrigin: Math.round(performance.timeOrigin || 0), now: Math.round(performance.now()), url: location.href, path: location.pathname, title: document.title, valuesRedacted: true })), timeoutMs, "performanceCapture page clock exceeded " + timeoutMs + "ms")
      .catch((error) => ({ error: errorSummary(error), valuesRedacted: true }));
    session = await withHardTimeout(targetPage.context().newCDPSession(targetPage), timeoutMs, "performanceCapture newCDPSession exceeded " + timeoutMs + "ms");
    await withHardTimeout(session.send("Profiler.enable"), timeoutMs, "Profiler.enable exceeded " + timeoutMs + "ms");
    await withHardTimeout(session.send("Profiler.start"), timeoutMs, "Profiler.start exceeded " + timeoutMs + "ms");
    await appendJsonl(files.performanceEvents, eventRecord("performance-capture-started", {
      captureId,
      durationMs,
      pageRole: targetPageRole,
      pageId: targetPageId,
      pageEpoch: targetPageEpoch,
      pageClock: pageClockStart,
      currentUrl: pageUrl(targetPage),
      beforeDrain,
      valuesRedacted: true,
    }));
    await interruptibleRunnerSleep(durationMs);
    stopped = await withHardTimeout(session.send("Profiler.stop"), Math.max(timeoutMs, 5000), "Profiler.stop exceeded " + Math.max(timeoutMs, 5000) + "ms");
  } catch (error) {
    captureError = error;
  } finally {
    if (session) {
      await withHardTimeout(session.send("Profiler.disable"), 1000, "Profiler.disable exceeded 1000ms").catch(() => {});
      await withHardTimeout(session.detach(), 1000, "performanceCapture CDP session detach exceeded 1000ms").catch(() => {});
    }
  }
  const completedAtMs = Date.now();
  const afterDrain = await drainPagePerformanceEvents(targetPage, { reason: "performanceCapture-after", groupSeq: sampleSeq, pageRole: targetPageRole, targetPageId, pageEpoch: targetPageEpoch })
    .catch((error) => ({ ok: false, error: errorSummary(error), count: 0, valuesRedacted: true }));
  const summaryFile = path.join(captureDir, "summary.json");
  if (captureError) {
    const failureKind = isTimeoutErrorMessage(captureError?.message) ? "performance-capture-cdp-timeout" : "performance-capture-failed";
    const summaryPayload = {
      ok: false,
      captureId,
      type: "performance-cpu-profile",
      commandId: command.id,
      label: truncate(command.label || "", 200),
      startedAt,
      completedAt: new Date(completedAtMs).toISOString(),
      durationMs: completedAtMs - startedAtMs,
      requestedDurationMs: durationMs,
      pageRole: targetPageRole,
      pageId: targetPageId,
      pageEpoch: targetPageEpoch,
      pageClockStart,
      currentUrl: pageUrl(targetPage),
      beforeDrain,
      afterDrain,
      failureKind,
      error: errorSummary(captureError),
      valuesRedacted: true,
    };
    await writeFile(summaryFile, JSON.stringify(summaryPayload, null, 2) + "\n", { mode: 0o600 });
    const summaryMeta = await fileMeta(summaryFile);
    artifactSeq += 1;
    const artifact = {
      seq: artifactSeq,
      sampleSeq,
      ts: new Date().toISOString(),
      kind: "performance-cpu-profile-failed",
      captureId,
      commandId: command.id,
      summaryPath: summaryFile,
      summaryByteCount: summaryMeta.byteCount,
      summarySha256: summaryMeta.sha256,
      pageRole: targetPageRole,
      pageId: targetPageId,
      durationMs: summaryPayload.durationMs,
      failureKind,
      valuesRedacted: true,
    };
    await appendJsonl(files.artifacts, artifact);
    await appendJsonl(files.performanceEvents, eventRecord("performance-capture-failed", {
      captureId,
      pageRole: targetPageRole,
      pageId: targetPageId,
      pageEpoch: targetPageEpoch,
      artifact,
      failureKind,
      error: summaryPayload.error,
      valuesRedacted: true,
    }));
    const wrapped = captureError instanceof Error ? captureError : new Error(String(captureError));
    wrapped.details = summaryPayload;
    throw wrapped;
  }
  const profile = stopped?.profile || null;
  const summary = summarizeCpuProfile(profile);
  const profileFile = path.join(captureDir, "profile.cpuprofile");
  const summaryPayload = {
    ok: true,
    captureId,
    type: "performance-cpu-profile",
    commandId: command.id,
    label: truncate(command.label || "", 200),
    startedAt,
    completedAt: new Date(completedAtMs).toISOString(),
    durationMs: completedAtMs - startedAtMs,
    requestedDurationMs: durationMs,
    pageRole: targetPageRole,
    pageId: targetPageId,
    pageEpoch: targetPageEpoch,
    pageClockStart,
    currentUrl: pageUrl(targetPage),
    beforeDrain,
    afterDrain,
    profile: summary,
    valuesRedacted: true,
  };
  await writeFile(profileFile, JSON.stringify(profile || {}, null, 2) + "\n", { mode: 0o600 });
  await writeFile(summaryFile, JSON.stringify(summaryPayload, null, 2) + "\n", { mode: 0o600 });
  const [profileMeta, summaryMeta] = await Promise.all([fileMeta(profileFile), fileMeta(summaryFile)]);
  artifactSeq += 1;
  const artifact = {
    seq: artifactSeq,
    sampleSeq,
    ts: new Date().toISOString(),
    kind: "performance-cpu-profile",
    captureId,
    commandId: command.id,
    path: profileFile,
    summaryPath: summaryFile,
    byteCount: profileMeta.byteCount,
    sha256: profileMeta.sha256,
    summaryByteCount: summaryMeta.byteCount,
    summarySha256: summaryMeta.sha256,
    pageRole: targetPageRole,
    pageId: targetPageId,
    durationMs: summaryPayload.durationMs,
    topFunctions: summary.topFunctions.slice(0, 8),
    topStacks: summary.topStacks.slice(0, 5),
    valuesRedacted: true,
  };
  await appendJsonl(files.artifacts, artifact);
  await appendJsonl(files.performanceEvents, eventRecord("performance-capture-completed", {
    captureId,
    pageRole: targetPageRole,
    pageId: targetPageId,
    pageEpoch: targetPageEpoch,
    artifact,
    summary: { ...summary, topFunctions: summary.topFunctions.slice(0, 12), topStacks: summary.topStacks.slice(0, 8), valuesRedacted: true },
    valuesRedacted: true,
  }));
  return { ...summaryPayload, artifact, valuesRedacted: true };
}

function summarizeCpuProfile(profile) {
  const nodes = Array.isArray(profile?.nodes) ? profile.nodes : [];
  const samples = Array.isArray(profile?.samples) ? profile.samples : [];
  const timeDeltas = Array.isArray(profile?.timeDeltas) ? profile.timeDeltas : [];
  const byId = new Map();
  const parentById = new Map();
  for (const node of nodes) {
    const id = Number(node?.id);
    if (!Number.isFinite(id)) continue;
    byId.set(id, node);
    for (const childIdRaw of Array.isArray(node.children) ? node.children : []) {
      const childId = Number(childIdRaw);
      if (Number.isFinite(childId)) parentById.set(childId, id);
    }
  }
  const functionRows = new Map();
  const stackRows = new Map();
  const addFunction = (frame, deltaMs, field) => {
    const compact = compactCpuCallFrame(frame);
    const key = cpuFrameKey(compact);
    const row = functionRows.get(key) || { ...compact, key, sampleCount: 0, selfTimeMs: 0, totalTimeMs: 0, valuesRedacted: true };
    if (field === "selfTimeMs") row.sampleCount += 1;
    row[field] += deltaMs;
    functionRows.set(key, row);
  };
  const nodePath = (id) => {
    const out = [];
    const seen = new Set();
    let current = id;
    while (Number.isFinite(Number(current)) && byId.has(Number(current)) && !seen.has(Number(current)) && out.length < 64) {
      seen.add(Number(current));
      out.push(byId.get(Number(current)));
      current = parentById.get(Number(current));
    }
    return out.reverse();
  };
  let totalTimeMs = 0;
  for (let index = 0; index < samples.length; index += 1) {
    const nodeId = Number(samples[index]);
    if (!byId.has(nodeId)) continue;
    const deltaUs = Number(timeDeltas[index]);
    const deltaMs = Number.isFinite(deltaUs) && deltaUs > 0 ? deltaUs / 1000 : 0;
    totalTimeMs += deltaMs;
    const pathNodes = nodePath(nodeId);
    const leaf = pathNodes[pathNodes.length - 1] || byId.get(nodeId);
    addFunction(leaf?.callFrame, deltaMs, "selfTimeMs");
    for (const item of pathNodes) addFunction(item?.callFrame, deltaMs, "totalTimeMs");
    const stackFrames = pathNodes.map((item) => compactCpuCallFrame(item?.callFrame)).filter((item) => item.functionName || item.url).slice(-10);
    const stackKey = stackFrames.map(cpuFrameLabel).join(" <- ");
    if (stackKey) {
      const row = stackRows.get(stackKey) || { key: stackKey, sampleCount: 0, selfTimeMs: 0, leaf: stackFrames[stackFrames.length - 1] || null, frames: stackFrames, valuesRedacted: true };
      row.sampleCount += 1;
      row.selfTimeMs += deltaMs;
      stackRows.set(stackKey, row);
    }
  }
  const rounded = (value) => Number((Number(value || 0)).toFixed(2));
  const topFunctions = Array.from(functionRows.values())
    .map((item) => ({ ...item, selfTimeMs: rounded(item.selfTimeMs), totalTimeMs: rounded(item.totalTimeMs), selfPercent: totalTimeMs > 0 ? Number(((item.selfTimeMs / totalTimeMs) * 100).toFixed(2)) : 0, totalPercent: totalTimeMs > 0 ? Number(((item.totalTimeMs / totalTimeMs) * 100).toFixed(2)) : 0 }))
    .sort((left, right) => Number(right.selfTimeMs || 0) - Number(left.selfTimeMs || 0) || Number(right.totalTimeMs || 0) - Number(left.totalTimeMs || 0))
    .slice(0, 50);
  const topStacks = Array.from(stackRows.values())
    .map((item) => ({ ...item, selfTimeMs: rounded(item.selfTimeMs), selfPercent: totalTimeMs > 0 ? Number(((item.selfTimeMs / totalTimeMs) * 100).toFixed(2)) : 0 }))
    .sort((left, right) => Number(right.selfTimeMs || 0) - Number(left.selfTimeMs || 0))
    .slice(0, 40);
  return {
    nodeCount: nodes.length,
    sampleCount: samples.length,
    timeDeltaCount: timeDeltas.length,
    totalTimeMs: rounded(totalTimeMs),
    topFunctions,
    topStacks,
    valuesRedacted: true,
  };
}

function compactCpuCallFrame(frame) {
  const value = frame && typeof frame === "object" ? frame : {};
  return {
    functionName: truncate(value.functionName || "(anonymous)", 180),
    url: safeUrl(value.url || ""),
    scriptId: value.scriptId === undefined || value.scriptId === null ? null : String(value.scriptId).slice(0, 80),
    lineNumber: numberOrNull(value.lineNumber),
    columnNumber: numberOrNull(value.columnNumber),
    valuesRedacted: true,
  };
}

function cpuFrameKey(frame) {
  return [frame.functionName || "(anonymous)", frame.url || frame.scriptId || "", frame.lineNumber ?? "", frame.columnNumber ?? ""].join("@");
}

function cpuFrameLabel(frame) {
  const location = frame.url || frame.scriptId || "-";
  const line = frame.lineNumber === null || frame.lineNumber === undefined ? "" : ":" + String(Number(frame.lineNumber) + 1);
  return String(frame.functionName || "(anonymous)") + "@" + location + line;
}
`;
}
