// src/diagnostics.ts
var RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION = "riddle-proof.capture-diagnostic.v1";
var DEFAULT_DIAGNOSTIC_STRING_LIMIT = 2e3;
var DEFAULT_DIAGNOSTIC_ARRAY_LIMIT = 50;
var DEFAULT_DIAGNOSTIC_HISTORY_LIMIT = 20;
var DEFAULT_REDACTED_VALUE = "[redacted]";
var DEFAULT_SENSITIVE_KEY_PATTERN = /authorization|api_?key|apikey|cookie|header|localstorage|password|secret|token/i;
function isRecord(value) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function normalizeSensitiveKey(key) {
  return key.toLowerCase().replace(/[^a-z0-9_]/g, "").replace(/_/g, "");
}
function isSensitiveKey(key, pattern) {
  pattern.lastIndex = 0;
  const rawMatch = pattern.test(key);
  pattern.lastIndex = 0;
  return rawMatch || pattern.test(normalizeSensitiveKey(key));
}
function truncateString(value, limit) {
  if (value.length <= limit) return value;
  return `${value.slice(0, limit)}... [truncated]`;
}
function sortedKeys(value) {
  return isRecord(value) ? Object.keys(value).sort() : [];
}
function stringValue(value) {
  if (typeof value !== "string") return void 0;
  const trimmed = value.trim();
  return trimmed ? trimmed : void 0;
}
function numberValue(value) {
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
}
function artifactItems(value) {
  if (!Array.isArray(value)) return [];
  return value.filter(isRecord);
}
function artifactSummary(item, source) {
  const artifact = item;
  const metadata = isRecord(artifact.metadata) ? artifact.metadata : void 0;
  return {
    name: stringValue(artifact.name) || "",
    kind: stringValue(artifact.kind),
    role: stringValue(artifact.role),
    url: stringValue(artifact.url),
    path: stringValue(artifact.path),
    content_type: stringValue(artifact.content_type),
    size_bytes: numberValue(artifact.size_bytes),
    metadata_keys: metadata ? Object.keys(metadata).sort() : void 0,
    source
  };
}
function artifactErrorMap(value, redaction) {
  if (!isRecord(value)) return {};
  const entries = Object.entries(value).map(([key, child]) => {
    const redacted = redactForProofDiagnostics(child, redaction);
    return [key, typeof redacted === "string" ? redacted : JSON.stringify(redacted)];
  }).sort(([left], [right]) => left.localeCompare(right));
  return Object.fromEntries(entries);
}
function redactForProofDiagnostics(value, options = {}) {
  const stringLimit = Math.max(1, options.string_limit ?? DEFAULT_DIAGNOSTIC_STRING_LIMIT);
  const arrayLimit = Math.max(0, options.array_limit ?? DEFAULT_DIAGNOSTIC_ARRAY_LIMIT);
  const redactedValue = options.redacted_value || DEFAULT_REDACTED_VALUE;
  const sensitiveKeyPattern = options.sensitive_key_pattern || DEFAULT_SENSITIVE_KEY_PATTERN;
  const seen = /* @__PURE__ */ new WeakSet();
  function redact(child) {
    if (child === null || child === void 0) return null;
    if (typeof child === "string") return truncateString(child, stringLimit);
    if (typeof child === "number") return Number.isFinite(child) ? child : null;
    if (typeof child === "boolean") return child;
    if (typeof child === "bigint" || typeof child === "symbol" || typeof child === "function") {
      return truncateString(String(child), stringLimit);
    }
    if (Array.isArray(child)) {
      return child.slice(0, arrayLimit).map((item) => redact(item));
    }
    if (isRecord(child)) {
      if (seen.has(child)) return "[circular]";
      seen.add(child);
      const entries = [];
      for (const [key, nested] of Object.entries(child)) {
        entries.push([
          key,
          isSensitiveKey(key, sensitiveKeyPattern) ? redactedValue : redact(nested)
        ]);
      }
      return Object.fromEntries(entries);
    }
    return truncateString(String(child), stringLimit);
  }
  return redact(value);
}
function summarizeCaptureArtifacts(payload) {
  const record = isRecord(payload) ? payload : {};
  const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
  const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
  const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
  const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
  const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
  const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
  const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
  return {
    outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
    screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
    artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
    result_keys: Array.from(resultKeys).sort(),
    artifact_json: sortedKeys(artifactJson),
    artifact_errors: artifactErrorMap(record._artifact_errors),
    proof_script_error: Boolean(proofJson.script_error),
    console_summary: consoleSummary
  };
}
function createCaptureDiagnostic(input) {
  const payload = isRecord(input.payload) ? input.payload : {};
  const redaction = input.redaction;
  const error = input.error ?? payload.error ?? payload.stderr ?? payload.message ?? "";
  return {
    version: RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION,
    label: input.label,
    tool: input.tool,
    captured_at: input.captured_at || (/* @__PURE__ */ new Date()).toISOString(),
    ok: typeof input.ok === "boolean" ? input.ok : typeof payload.ok === "boolean" ? payload.ok : void 0,
    timeout: typeof input.timeout === "boolean" ? input.timeout : Boolean(payload.timeout),
    error: truncateString(String(error ?? ""), redaction?.string_limit ?? DEFAULT_DIAGNOSTIC_STRING_LIMIT),
    route: input.route,
    preview_url: input.preview_url,
    wait_for_selector: input.wait_for_selector,
    args: redactForProofDiagnostics(input.args === void 0 ? {} : input.args, redaction),
    artifact_summary: summarizeCaptureArtifacts(input.payload),
    evidence: input.evidence === void 0 ? void 0 : redactForProofDiagnostics(input.evidence, redaction),
    notes: input.notes?.map((note) => note.trim()).filter(Boolean)
  };
}
function appendCaptureDiagnostic(state, input, historyLimit = DEFAULT_DIAGNOSTIC_HISTORY_LIMIT) {
  const diagnostic = createCaptureDiagnostic(input);
  const existing = Array.isArray(state.capture_diagnostics) ? state.capture_diagnostics : [];
  state.capture_diagnostics = [...existing, diagnostic].slice(-Math.max(1, historyLimit));
  return diagnostic;
}

export {
  RIDDLE_PROOF_CAPTURE_DIAGNOSTIC_VERSION,
  DEFAULT_DIAGNOSTIC_STRING_LIMIT,
  DEFAULT_DIAGNOSTIC_ARRAY_LIMIT,
  DEFAULT_DIAGNOSTIC_HISTORY_LIMIT,
  redactForProofDiagnostics,
  summarizeCaptureArtifacts,
  createCaptureDiagnostic,
  appendCaptureDiagnostic
};
