/**
 * Shared log-sanitization helpers.
 *
 * Centralises truncate + secret-redaction patterns so every provider stays
 * consistent and any regex improvements only need one change.
 *
 * Coverage:
 *   - `Authorization: Bearer <token>` (with required whitespace)
 *   - `Authorization: Token <token>` (Replicate uses this, not Bearer)
 *   - `Authorization: Basic <base64>` (D-ID and similar)
 *   - Bare tokens by known provider prefix:
 *     sk-/pk- (OpenAI, Anthropic, Stability),
 *     r8_ (Replicate), gsk_ (Groq), xai- (xAI), tgp_ (Together),
 *     fw_ (Fireworks), pplx- (Perplexity), pa- (Voyage),
 *     jina_ (Jina), fish- (Fish Audio)
 *   - Generic key=value pairs: api_key=…, access_token: …, secret_key=…
 */
const TOKEN_PREFIXES = [
    "sk",
    "pk",
    "r8",
    "gsk",
    "xai",
    "tgp",
    "fw",
    "pplx",
    "pa",
    "jina",
    "fish",
];
const PREFIX_PATTERN = TOKEN_PREFIXES.join("|");
/**
 * Pattern matching common bearer/API-key tokens in plain text.
 *
 * Case-insensitive (`i` flag) since header names ("Authorization") and scheme
 * names ("Bearer", "Token", "Basic") are sometimes lower-cased in error
 * bodies. `g` flag for replace-all.
 */
const SECRET_PATTERN = new RegExp(
// Authorization schemes — required whitespace between scheme and value
"\\bBearer\\s+[A-Za-z0-9_\\-\\.]{8,}\\b" +
    "|\\bToken\\s+[A-Za-z0-9_\\-\\.]{8,}\\b" +
    "|\\bBasic\\s+[A-Za-z0-9+/=]{12,}\\b" +
    // Bare tokens by known prefix (e.g. `sk-abc…`, `r8_xyz…`)
    `|\\b(?:${PREFIX_PATTERN})[_\\-][A-Za-z0-9_\\-\\.]{8,}\\b` +
    // Generic key=value pairs (URL params, JSON bodies, header dumps)
    "|\\b(?:api[_-]?key|access[_-]?token|secret[_-]?key|refresh[_-]?token)\\s*[:=]\\s*['\"]?[^\\s,;'\"&]+", "gi");
/** Header names that should always be redacted regardless of value shape. */
const SENSITIVE_HEADER_NAMES = [
    "authorization",
    "cookie",
    "set-cookie",
    "x-api-key",
    "api-key",
    "apikey",
    "x-auth-token",
    "x-csrf-token",
];
/** Object keys that should always be redacted regardless of value shape. */
const SENSITIVE_OBJECT_KEYS = [
    "apikey",
    "api_key",
    "apiKey",
    "access_token",
    "accessToken",
    "refresh_token",
    "refreshToken",
    "secret",
    "secretkey",
    "secret_key",
    "secretKey",
    "password",
    "authorization",
    "oauth",
    "oauthToken",
    "credentials",
];
/**
 * Truncate `text` to `maxLen` chars then replace embedded secrets with `***`.
 *
 * Use this for free-form text logged from response/request bodies. For
 * structured data (records, headers) prefer {@link sanitizeRecord} and
 * {@link sanitizeHeaders} which know to redact by key name as well.
 *
 * @param text   - Raw text to sanitize (typically an HTTP response body).
 * @param maxLen - Maximum number of characters to keep (default 500).
 */
export function sanitizeForLog(text, maxLen = 500) {
    if (!text) {
        return text;
    }
    return text.slice(0, maxLen).replace(SECRET_PATTERN, "***");
}
/**
 * Recursively sanitize a record/array, returning a structurally identical
 * value with sensitive keys redacted and string values run through
 * {@link sanitizeForLog}.
 *
 * Safe to call on any JSON-shaped data. Cycles are detected and replaced
 * with the string `"[Circular]"` to avoid infinite recursion when logging
 * mid-stream objects that reference themselves.
 *
 * @param value - The value to sanitize.
 * @param maxStringLen - Per-string truncation cap (default 1000).
 */
export function sanitizeRecord(value, maxStringLen = 1000) {
    const seen = new WeakSet();
    const walk = (v) => {
        if (v === null || v === undefined) {
            return v;
        }
        if (typeof v === "string") {
            return sanitizeForLog(v, maxStringLen);
        }
        if (typeof v !== "object") {
            return v;
        }
        if (seen.has(v)) {
            return "[Circular]";
        }
        seen.add(v);
        if (Array.isArray(v)) {
            return v.map(walk);
        }
        const out = {};
        for (const [k, val] of Object.entries(v)) {
            if (SENSITIVE_OBJECT_KEYS.some((name) => name.toLowerCase() === k.toLowerCase())) {
                out[k] = "***";
            }
            else {
                out[k] = walk(val);
            }
        }
        return out;
    };
    return walk(value);
}
/**
 * Sanitize an HTTP headers object — redacts sensitive header names entirely
 * (`***`) and applies {@link sanitizeForLog} to remaining values.
 *
 * Accepts both `Headers` instances and plain-object header maps so providers
 * can log either shape uniformly.
 */
export function sanitizeHeaders(headers) {
    if (!headers) {
        return {};
    }
    const out = {};
    const set = (name, value) => {
        if (value === undefined || value === null) {
            return;
        }
        const lower = name.toLowerCase();
        if (SENSITIVE_HEADER_NAMES.includes(lower)) {
            out[name] = "***";
            return;
        }
        out[name] = sanitizeForLog(value, 500);
    };
    if (headers instanceof Headers) {
        headers.forEach((value, key) => set(key, value));
        return out;
    }
    for (const [key, value] of Object.entries(headers)) {
        set(key, value);
    }
    return out;
}
