/**
 * Stream Chunk Redaction Utilities
 * Sanitizes sensitive data from stream chunks before client delivery
 *
 * NOTE: Redaction is DISABLED by default. Enable via config.enabled = true
 */
/**
 * Default fields to redact from stream chunks
 */
const DEFAULT_REDACTED_FIELDS = [
    "request",
    "args",
    "result",
    "apiKey",
    "token",
    "authorization",
    "credentials",
    "password",
    "secret",
];
/**
 * Default redaction placeholder
 */
const DEFAULT_PLACEHOLDER = "[REDACTED]";
/**
 * Maximum recursion depth for redactObject
 */
const MAX_REDACTION_DEPTH = 10;
/**
 * Redact sensitive data from a stream chunk.
 *
 * IMPORTANT: Redaction is DISABLED by default. You must set `config.enabled = true`
 * to enable redaction. This is a security feature that requires explicit opt-in.
 *
 * @param chunk - The stream chunk to redact
 * @param config - Redaction configuration (enabled: false by default)
 * @returns The redacted chunk with sensitive data removed, or original if disabled
 *
 * @example
 * ```typescript
 * // Redaction disabled by default - returns chunk unchanged
 * const unchanged = redactStreamChunk(chunk);
 *
 * // Enable redaction explicitly
 * const redacted = redactStreamChunk(chunk, { enabled: true });
 * // Sensitive fields replaced with "[REDACTED]"
 * ```
 *
 * @example
 * ```typescript
 * // With custom configuration (must enable first)
 * const redacted = redactStreamChunk(chunk, {
 *   enabled: true,
 *   additionalFields: ["customSecret"],
 *   preserveFields: ["result"], // Don't redact result
 * });
 * ```
 */
export function redactStreamChunk(chunk, config) {
    // CRITICAL: Redaction is DISABLED by default
    // Must explicitly enable with config.enabled = true
    if (!config?.enabled) {
        return chunk;
    }
    if (!chunk || typeof chunk !== "object") {
        return chunk;
    }
    const placeholder = config?.placeholder ?? DEFAULT_PLACEHOLDER;
    const redactedFields = new Set([
        ...DEFAULT_REDACTED_FIELDS,
        ...(config?.additionalFields ?? []),
    ]);
    // Remove preserved fields from redaction set
    if (config?.preserveFields) {
        for (const field of config.preserveFields) {
            redactedFields.delete(field);
        }
    }
    return redactChunkByType(chunk, redactedFields, placeholder, config);
}
/**
 * Redact chunk based on its type
 */
function redactChunkByType(chunk, redactedFields, placeholder, config) {
    const typedChunk = chunk;
    const chunkType = typedChunk.type;
    switch (chunkType) {
        case "step-start":
            return redactStepStart(typedChunk, redactedFields, placeholder);
        case "tool-call":
            return redactToolCall(typedChunk, redactedFields, placeholder, config);
        case "tool-result":
            return redactToolResult(typedChunk, redactedFields, placeholder, config);
        case "error":
            return redactError(typedChunk, redactedFields, placeholder);
        default:
            return redactGenericChunk(typedChunk, redactedFields, placeholder);
    }
}
/**
 * Redact step-start chunks
 */
function redactStepStart(chunk, redactedFields, placeholder) {
    if (!hasPayload(chunk)) {
        return chunk;
    }
    const { payload, ...rest } = chunk;
    const payloadObj = payload;
    const redactedPayload = redactObject(payloadObj, redactedFields, placeholder, 0);
    return {
        ...rest,
        type: "step-start",
        payload: redactedPayload,
    };
}
/**
 * Redact tool-call chunks
 */
function redactToolCall(chunk, redactedFields, placeholder, config) {
    // Handle chunks with payload structure
    if (hasPayload(chunk)) {
        const { payload, ...rest } = chunk;
        const payloadObj = payload;
        // Redact tool arguments if configured
        if (payloadObj.toolCall && typeof payloadObj.toolCall === "object") {
            const toolCall = payloadObj.toolCall;
            const redactArgs = config?.redactToolArgs !== false && redactedFields.has("args");
            return {
                ...rest,
                type: "tool-call",
                payload: {
                    ...payloadObj,
                    toolCall: {
                        ...toolCall,
                        args: redactArgs ? placeholder : toolCall.args,
                    },
                },
            };
        }
        return {
            ...rest,
            type: "tool-call",
            payload: redactObject(payloadObj, redactedFields, placeholder, 0),
        };
    }
    // Handle chunks with data structure (DataStreamEvent format)
    if (hasData(chunk)) {
        const { data, ...rest } = chunk;
        const dataObj = data;
        const redactArgs = config?.redactToolArgs !== false && redactedFields.has("args");
        // Check for arguments field in data
        if ("arguments" in dataObj && redactArgs) {
            return {
                ...rest,
                type: "tool-call",
                data: {
                    ...dataObj,
                    arguments: placeholder,
                },
            };
        }
        return {
            ...rest,
            type: "tool-call",
            data: redactObject(dataObj, redactedFields, placeholder, 0),
        };
    }
    return chunk;
}
/**
 * Redact tool-result chunks
 */
function redactToolResult(chunk, redactedFields, placeholder, config) {
    const redactResult = config?.redactToolResults !== false && redactedFields.has("result");
    // Handle chunks with payload structure
    if (hasPayload(chunk)) {
        const { payload, ...rest } = chunk;
        const payloadObj = payload;
        return {
            ...rest,
            type: "tool-result",
            payload: {
                ...payloadObj,
                result: redactResult ? placeholder : payloadObj.result,
            },
        };
    }
    // Handle chunks with data structure (DataStreamEvent format)
    if (hasData(chunk)) {
        const { data, ...rest } = chunk;
        const dataObj = data;
        return {
            ...rest,
            type: "tool-result",
            data: {
                ...dataObj,
                result: redactResult ? placeholder : dataObj.result,
            },
        };
    }
    return chunk;
}
/**
 * Redact error chunks (remove stack traces in production)
 */
function redactError(chunk, redactedFields, placeholder) {
    const isProduction = process.env.NODE_ENV === "production";
    // Handle chunks with data structure (DataStreamEvent format)
    if (hasData(chunk)) {
        const { data, ...rest } = chunk;
        const dataObj = data;
        const { stack, ...dataRest } = dataObj;
        return {
            ...rest,
            type: "error",
            data: {
                ...redactObject(dataRest, redactedFields, placeholder, 0),
                ...(isProduction ? {} : { stack }),
            },
        };
    }
    // Handle direct chunk with stack
    const { stack, ...restChunk } = chunk;
    const redactedRest = redactObject(restChunk, redactedFields, placeholder, 0);
    return {
        ...redactedRest,
        type: "error",
        ...(isProduction ? {} : { stack }),
    };
}
/**
 * Redact generic chunks by scanning all fields
 */
function redactGenericChunk(chunk, redactedFields, placeholder) {
    return redactObject(chunk, redactedFields, placeholder, 0);
}
/**
 * Recursively redact sensitive fields from an object.
 *
 * @param obj - Object to redact
 * @param redactedFields - Set of field names to redact (case-insensitive matching)
 * @param placeholder - Replacement value for redacted fields
 * @param depth - Current recursion depth
 * @returns Object with sensitive fields redacted
 */
function redactObject(obj, redactedFields, placeholder, depth) {
    // Prevent infinite recursion
    if (depth > MAX_REDACTION_DEPTH) {
        return obj;
    }
    const result = {};
    for (const [key, value] of Object.entries(obj)) {
        // Case-insensitive field name matching
        const shouldRedact = redactedFields.has(key.toLowerCase());
        if (shouldRedact) {
            result[key] = placeholder;
        }
        else if (value && typeof value === "object" && !Array.isArray(value)) {
            result[key] = redactObject(value, redactedFields, placeholder, depth + 1);
        }
        else if (Array.isArray(value)) {
            result[key] = value.map((item) => {
                if (item && typeof item === "object") {
                    return redactObject(item, redactedFields, placeholder, depth + 1);
                }
                return item;
            });
        }
        else {
            result[key] = value;
        }
    }
    return result;
}
/**
 * Type guard for chunks with payload
 */
function hasPayload(chunk) {
    return ("payload" in chunk &&
        chunk.payload !== null &&
        typeof chunk.payload === "object");
}
/**
 * Type guard for chunks with data (DataStreamEvent format)
 */
function hasData(chunk) {
    return ("data" in chunk && chunk.data !== null && typeof chunk.data === "object");
}
/**
 * Create a redaction transform function for streams.
 *
 * IMPORTANT: Redaction is DISABLED by default. You must set `config.enabled = true`
 * to enable redaction. If not enabled, the returned function passes chunks through unchanged.
 *
 * @param config - Redaction configuration
 * @returns A transform function for stream chunks
 *
 * @example
 * ```typescript
 * // Redaction disabled - chunks pass through unchanged
 * const redactor = createStreamRedactor();
 * const unchanged = redactor(chunk); // Returns original chunk
 *
 * // Enable redaction
 * const redactor = createStreamRedactor({ enabled: true });
 * const redactedStream = stream.pipeThrough(new TransformStream({
 *   transform: (chunk, controller) => {
 *     controller.enqueue(redactor(chunk));
 *   }
 * }));
 * ```
 *
 * @example
 * ```typescript
 * // With custom options
 * const redactor = createStreamRedactor({
 *   enabled: true,
 *   redactToolArgs: true,
 *   redactToolResults: true,
 *   additionalFields: ["internalId"],
 * });
 * ```
 */
export function createStreamRedactor(config) {
    return (chunk) => {
        // When redaction is disabled (default), return chunk unchanged
        // This allows the redactor to be used with any chunk type as a no-op
        if (!config?.enabled) {
            return chunk;
        }
        // When enabled, apply redaction (works with any object-based chunk)
        return redactStreamChunk(chunk, config);
    };
}
