/**
 * IT Glue redaction.
 *
 * SECURITY-CRITICAL. This module recursively walks an arbitrary value
 * (object | array | scalar) and replaces any value whose KEY name looks like
 * a credential with the string "[REDACTED]". The original input is not
 * mutated — a deep-cloned copy is returned.
 *
 * Why it exists: IT Glue documents and configurations frequently embed plain-
 * text passwords, API keys, and tokens. None of those values may EVER reach
 * the LLM context, log lines, or any database row — only doc IDs and names.
 * See docs/wulf-pulse-ticket-analyzer-prompt.md → "Critical correctness notes".
 *
 * The match pattern is intentionally broad. False positives (a benign field
 * happens to be named "key") are acceptable; false negatives are not.
 */

const SENSITIVE_KEY_PATTERN = /password|secret|key|token|credential|api[_-]?key/i;

export const REDACTED_VALUE = '[REDACTED]';

/**
 * Recursively redact values whose keys match the sensitive-key pattern.
 * Returns a new value; does not mutate the input.
 */
export function redact<T>(value: T): T {
  return redactInternal(value, new WeakSet()) as T;
}

function redactInternal(value: unknown, seen: WeakSet<object>): unknown {
  if (value === null || value === undefined) return value;

  // Primitives — return as-is.
  if (typeof value !== 'object') return value;

  // Cycle guard — if we've already seen this object on this branch, return a
  // marker rather than recursing infinitely. (IT Glue payloads shouldn't have
  // cycles in practice, but defensive against malformed input.)
  if (seen.has(value as object)) return '[CIRCULAR]';
  seen.add(value as object);

  if (Array.isArray(value)) {
    return value.map((item) => redactInternal(item, seen));
  }

  const out: Record<string, unknown> = {};
  for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {
    if (isSensitiveKey(key)) {
      // Even if the value is an object/array, replace the whole subtree —
      // a credentials block named "auth" should not leak its leaves.
      out[key] = nested === null || nested === undefined ? nested : REDACTED_VALUE;
    } else {
      out[key] = redactInternal(nested, seen);
    }
  }
  return out;
}

/**
 * Exported for tests. True if the key name looks like a credential field.
 */
export function isSensitiveKey(key: string): boolean {
  return SENSITIVE_KEY_PATTERN.test(key);
}
