// packages/core-shared/src/instrumentation/sentry/scrub.ts
// Use structural types matching Sentry's beforeSend/beforeSendTransaction signatures
// to avoid importing @sentry/core types directly (they're not re-exported by @sentry/nextjs).

import {
  IPV4_REGEX,
  IPV6_REGEX,
  REDACTED_IP,
  REDACTED_VALUE,
  keyContainsPii,
  queryParamContainsPii,
} from "./pii-fields";

// Minimal structural types matching Sentry's event shape used in scrubbers.
// Using index signatures broad enough to satisfy both ErrorEvent and TransactionEvent.
type SentryRequest = {
  url?: string;
  headers?: Record<string, string | undefined>;
  [key: string]: unknown;
};

type SentryEvent = {
  extra?: Record<string, unknown> | null;
  contexts?: Record<string, Record<string, unknown> | undefined>;
  request?: SentryRequest;
  transaction?: string;
  [key: string]: unknown;
};

type SentryEventHint = Record<string, unknown>;

function redactString(s: string): string {
  // Create new regexes each call since regexes with /g are stateful
  const ipv4 = new RegExp(IPV4_REGEX.source, "g");
  const ipv6 = new RegExp(IPV6_REGEX.source, "g");
  return s.replace(ipv4, REDACTED_IP).replace(ipv6, REDACTED_IP);
}

function deepScrub(value: unknown, parentKey = ""): unknown {
  if (value === null || value === undefined) return value;
  if (typeof value === "string") {
    return parentKey && keyContainsPii(parentKey) ? REDACTED_VALUE : redactString(value);
  }
  if (typeof value === "number" || typeof value === "boolean") {
    return parentKey && keyContainsPii(parentKey) ? REDACTED_VALUE : value;
  }
  if (Array.isArray(value)) {
    return value.map((v) => deepScrub(v, parentKey));
  }
  if (typeof value === "object") {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
      out[k] = keyContainsPii(k) ? REDACTED_VALUE : deepScrub(v, k);
    }
    return out;
  }
  return value;
}

export function beforeSend(event: SentryEvent, _hint: SentryEventHint): SentryEvent | null {
  return deepScrub(event) as SentryEvent;
}

function scrubUrl(url: string): string {
  try {
    const u = new URL(url, "http://placeholder.local");
    for (const [k] of Array.from(u.searchParams.entries())) {
      if (queryParamContainsPii(k)) {
        u.searchParams.set(k, REDACTED_VALUE);
      }
    }
    return url.startsWith("/") ? `${u.pathname}${u.search}` : u.toString();
  } catch {
    return url;
  }
}

export function beforeSendTransaction(
  event: SentryEvent,
  _hint: SentryEventHint,
): SentryEvent | null {
  const out: SentryEvent = { ...event };
  if (out.request?.url) {
    out.request = { ...out.request, url: scrubUrl(out.request.url) };
  }
  if (out.transaction && (out.transaction.includes("?") || out.transaction.includes("="))) {
    out.transaction = scrubUrl(out.transaction);
  }
  return out;
}
