const REDACTED = "[redacted]";
const SENSITIVE_KEYS = new Set([
  "accesstoken",
  "refreshtoken",
  "apikey",
  "authorization",
  "password",
  "passwd",
  "secret",
  "clientsecret",
  "privatekey",
  "cookie",
  "setcookie",
  "credential",
  "credentials",
  "cookieheader",
  "setcookieheader",
  "authtoken",
  "csrftoken",
  "devicetoken",
  "idtoken",
  "sessiontoken",
  "authfilename",
  "authfilepath",
  "authpath",
  "token"
]);
const PUBLIC_TOOL_ARGUMENT_KEYS = new Set([
  "code", "command", "query", "q", "path", "file", "filepath", "pattern", "url",
  "refid", "line", "lineno", "page", "pageno", "id", "name", "method", "text",
  "prompt", "location", "ticker", "type", "market", "datefrom", "dateto", "team",
  "league", "width", "height", "limit", "offset", "cursor", "searchquery", "imagequery",
  "open", "click", "find", "screenshot", "finance", "weather", "sports", "time", "recency",
  "domains", "detail", "numgames", "opponent", "utcoffset", "duration", "start", "end",
  "tool", "server"
]);
const MAX_PUBLIC_TOOL_ARGUMENT_BYTES = 4096;
const MAX_PUBLIC_TOOL_ARGUMENT_STRING_BYTES = 2048;

export function sanitizePublicText(value, config = {}) {
  return sanitizePublicTextWithContext(value, createSanitizerContext(config));
}

function sanitizePublicTextWithContext(value, context) {
  try {
    let text = String(value ?? "")
      .replace(/\bCODEX_HOME\b/gi, REDACTED)
      .replace(/\bdata:[^\s"'<>]+/gi, REDACTED)
      .replace(/\b(Set-Cookie|Cookie)\s*:\s*[^\r\n]*/gi, `$1: ${REDACTED}`)
      .replace(/("?(?:access[_-]?token|refresh[_-]?token|api[_-]?key|authorization|password|client[_-]?secret|session[_-]?token|csrf[_-]?token|id[_-]?token|cookie|credential)"?\s*[:=]\s*)(?:"[^"]*"|'[^']*'|(?:Basic|Bearer)\s+[A-Za-z0-9._~+/=-]+|[^\s,}]+)/gi, `$1"${REDACTED}"`)
      .replace(/Bearer\s+[A-Za-z0-9._~+\/-]+/gi, `Bearer ${REDACTED}`)
      .replace(/\bBasic\s+[A-Za-z0-9._~+/=-]+/gi, `Basic ${REDACTED}`)
      .replace(/\bsk-[A-Za-z0-9_-]+\b/gi, `sk-${REDACTED}`);

    if (context.privatePathPattern) text = text.replace(context.privatePathPattern, "[private-path]");
    return text
      .replace(/(["'])(?:[A-Za-z]:\\+|\\{2,}|[A-Za-z]:\/+|\/{2})[^"'\r\n]+\1/g, (_match, quote) => `${quote}[private-path]${quote}`)
      .replace(/\b[A-Za-z]:[\\/][^\r\n"',;|<>]*/g, "[private-path]")
      .replace(/\\{2,}[^\r\n"',;|<>]*/g, "[private-path]")
      .replace(/(?:[A-Za-z]:\\+|\\{2,}[^\\\s]+\\+)(?:[^\\\r\n"',;|<>]+\\+)*[^\\\r\n"',;|<>]*\.[A-Za-z0-9]{1,12}\b/g, "[private-path]")
      .replace(/(?:[A-Za-z]:\\+|\\{2,}[^\\\s]+\\+)[^\s"',}\]]+/g, "[private-path]")
      .replace(/\b[A-Za-z]:\/(?:[^\s"',;|<>}\]]+\/)*[^\s"',;|<>}\]]*/g, "[private-path]")
      .replace(/\\+(?:auth|credentials?|cookies?)\.(?:json|sqlite|db)\b/gi, REDACTED)
      .replace(/\b(?:auth|credentials?|cookies?)\.(?:json|sqlite|db)\b/gi, REDACTED);
  } catch {
    return REDACTED;
  }
}

export function sanitizePublicData(value, config = {}) {
  return sanitizePublicValue(value, createSanitizerContext(config), new WeakSet(), 0);
}

function sanitizePublicValue(value, context, active, depth) {
  if (typeof value === "string") return sanitizePublicTextWithContext(value, context);
  if (depth > 32) return REDACTED;
  if (Array.isArray(value)) {
    if (active.has(value)) return REDACTED;
    active.add(value);
    const result = value.map((item) => sanitizePublicValue(item, context, active, depth + 1));
    active.delete(value);
    return result;
  }
  if (!value || typeof value !== "object") return value;
  if (active.has(value)) return REDACTED;
  active.add(value);
  const result = {};
  let keys;
  try {
    keys = Object.keys(value);
  } catch {
    active.delete(value);
    return REDACTED;
  }
  for (const key of keys) {
    if (isSensitiveKey(key)) {
      result[key] = REDACTED;
      continue;
    }
    try {
      const item = value[key];
      const projected = normalizeKey(key) === "arguments" ? projectPublicToolArguments(item) : item;
      result[key] = sanitizePublicValue(projected, context, active, depth + 1);
    } catch {
      result[key] = REDACTED;
    }
  }
  active.delete(value);
  return result;
}

export function projectPublicToolArguments(value) {
  if (value == null) return undefined;
  const state = { tooLarge: false, entries: 0 };
  const projected = projectToolArgumentValue(value, 0, state);
  if (state.tooLarge) return { notice: "arguments_too_large" };
  if (projected && typeof projected === "object" && !Array.isArray(projected) && Object.keys(projected).length === 0) {
    return { notice: "arguments_hidden" };
  }
  if (Buffer.byteLength(JSON.stringify(projected ?? null), "utf8") > MAX_PUBLIC_TOOL_ARGUMENT_BYTES) {
    return { notice: "arguments_too_large" };
  }
  return projected;
}

function projectToolArgumentValue(value, depth, state) {
  if (depth > 4 || state.entries > 64) {
    state.tooLarge = true;
    return undefined;
  }
  if (typeof value === "string") {
    if (Buffer.byteLength(value, "utf8") > MAX_PUBLIC_TOOL_ARGUMENT_STRING_BYTES) state.tooLarge = true;
    return state.tooLarge ? undefined : value;
  }
  if (typeof value === "number" || typeof value === "boolean" || value == null) return value;
  if (Array.isArray(value)) {
    if (value.length > 20) {
      state.tooLarge = true;
      return undefined;
    }
    return value.map((item) => projectToolArgumentValue(item, depth + 1, state));
  }
  if (typeof value !== "object") return undefined;

  const result = {};
  for (const [key, item] of Object.entries(value)) {
    if (!PUBLIC_TOOL_ARGUMENT_KEYS.has(normalizeKey(key)) || isSensitiveKey(key)) continue;
    state.entries += 1;
    result[key] = projectToolArgumentValue(item, depth + 1, state);
    if (state.tooLarge) break;
  }
  return result;
}

function isSensitiveKey(key) {
  return SENSITIVE_KEYS.has(normalizeKey(key));
}

function normalizeKey(key) {
  return String(key).toLowerCase().replace(/[^a-z0-9]/g, "");
}

function configuredPrivatePaths(config) {
  return [...new Set([
    config.projectsRoot,
    ...(config.accounts ?? []).map((account) => account.codexHome),
    ...(config.workspaces ?? []).map((workspace) => workspace.path)
  ].filter((value) => typeof value === "string" && value.length > 0))]
    .sort((left, right) => right.length - left.length);
}

function createSanitizerContext(config) {
  const literals = [...new Set(configuredPrivatePaths(config)
    .flatMap((path) => [JSON.stringify(path).slice(1, -1), path])
    .filter(Boolean))]
    .sort((left, right) => right.length - left.length);
  return {
    privatePathPattern: literals.length > 0
      ? new RegExp(literals.map(escapeRegExp).join("|"), "gi")
      : null
  };
}

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
