/**
 * ignoreFields — strips non-deterministic fields before assertion comparison.
 *
 * For tool outputs that contain timestamps, UUIDs, temp paths, or other
 * non-deterministic values, assertions need a way to exclude specific
 * fields from comparison. This module provides field stripping using
 * dot-notation paths.
 *
 * Usage in task definitions:
 * ```typescript
 * assertions: [
 *   {
 *     type: "tool-output-matches",
 *     value: { title: "Hello" },
 *     ignoreFields: ["metadata.createdAt", "result.id", "_rev"],
 *   },
 * ]
 * ```
 *
 * @see docs/archive/exec-plans/architecture-overhaul/phase-4-agent-harness.md
 */
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Strip specified fields from an object using dot-notation paths.
 *
 * @param obj - The object to strip fields from (not mutated)
 * @param fields - Dot-notation field paths to remove
 * @returns A new object with the specified fields removed
 *
 * @example
 * ```typescript
 * stripFields(
 *   { title: "Hello", metadata: { createdAt: "2024-01-01", author: "Alice" } },
 *   ["metadata.createdAt"]
 * )
 * // => { title: "Hello", metadata: { author: "Alice" } }
 * ```
 */
export function stripFields(obj, fields) {
    if (!fields || fields.length === 0)
        return obj;
    if (obj === null || obj === undefined)
        return obj;
    if (typeof obj !== "object")
        return obj;
    // Deep clone to avoid mutation
    const clone = JSON.parse(JSON.stringify(obj));
    for (const field of fields) {
        removeFieldByPath(clone, field.split("."));
    }
    return clone;
}
/**
 * Strip specified fields from both actual and expected values,
 * then compare them.
 *
 * @returns true if the objects are equal after stripping
 */
export function compareWithIgnoredFields(actual, expected, ignoreFields) {
    const strippedActual = stripFields(actual, ignoreFields);
    const strippedExpected = stripFields(expected, ignoreFields);
    return JSON.stringify(strippedActual) === JSON.stringify(strippedExpected);
}
/**
 * Generate a Promptfoo-compatible JavaScript assertion that applies
 * ignoreFields stripping before comparison.
 *
 * This wraps a comparison assertion with field stripping logic,
 * producing a self-contained JS assertion string.
 */
export function buildIgnoreFieldsWrapper(comparisonCode, ignoreFields) {
    if (ignoreFields.length === 0)
        return comparisonCode;
    const stripFn = `function stripFields(obj, fields) {\n` +
        `  if (!obj || typeof obj !== 'object') return obj;\n` +
        `  const clone = JSON.parse(JSON.stringify(obj));\n` +
        `  for (const field of fields) {\n` +
        `    const parts = field.split('.');\n` +
        `    let current = clone;\n` +
        `    for (let i = 0; i < parts.length - 1; i++) {\n` +
        `      if (!current || typeof current !== 'object') break;\n` +
        `      current = current[parts[i]];\n` +
        `    }\n` +
        `    if (current && typeof current === 'object') {\n` +
        `      delete current[parts[parts.length - 1]];\n` +
        `    }\n` +
        `  }\n` +
        `  return clone;\n` +
        `}\n`;
    return (`${stripFn}\n` +
        `const __ignoreFields = ${JSON.stringify(ignoreFields)};\n` +
        `${comparisonCode}`);
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
function removeFieldByPath(obj, path) {
    if (path.length === 0)
        return;
    const [head, ...rest] = path;
    if (rest.length === 0) {
        // Base case: delete the field
        delete obj[head];
        return;
    }
    // Recursive case: traverse into nested object
    const child = obj[head];
    if (child !== null && child !== undefined && typeof child === "object") {
        removeFieldByPath(child, rest);
    }
}
