// src/redact/index.ts
var DEFAULT_REDACTION_PATTERNS = [
  { kind: "ssn", pattern: /\d{3}-\d{2}-\d{4}/ },
  { kind: "ein", pattern: /\d{2}-\d{7}/ }
];
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
  "ssn",
  "ein",
  "password",
  "apikey",
  "token",
  "secret",
  "authorization",
  "email",
  "phone"
]);
function redactString(value, patterns) {
  for (const { kind, pattern, validate } of patterns) {
    if (!validate) {
      if (pattern.test(value)) return `[REDACTED:${kind}]`;
      continue;
    }
    const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
    for (const m of value.matchAll(g)) {
      if (m[0].length > 0 && validate(m[0])) return `[REDACTED:${kind}]`;
    }
  }
  return value;
}
function maskSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
  const spans = detectSpans(text, patterns);
  if (spans.length === 0) return text;
  let out = "";
  let pos = 0;
  for (const s of spans) {
    if (s.start > pos) out += text.slice(pos, s.start);
    out += `[REDACTED:${s.kind}]`;
    pos = s.end;
  }
  if (pos < text.length) out += text.slice(pos);
  return out;
}
function isPlainObject(value) {
  if (value === null || typeof value !== "object") return false;
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}
function redactForIngestion(value, options = {}) {
  const patterns = options.extraPatterns ? [...DEFAULT_REDACTION_PATTERNS, ...options.extraPatterns] : DEFAULT_REDACTION_PATTERNS;
  const sensitiveKeys = options.extraSensitiveKeys ? /* @__PURE__ */ new Set([...SENSITIVE_KEYS, ...options.extraSensitiveKeys.map((k) => k.toLowerCase())]) : SENSITIVE_KEYS;
  const maskString = options.stringMode === "mask-spans" ? (s) => maskSpans(s, patterns) : (s) => redactString(s, patterns);
  const seen = /* @__PURE__ */ new WeakSet();
  const walk = (v) => {
    if (typeof v === "string") return maskString(v);
    if (Array.isArray(v)) {
      if (seen.has(v)) return v;
      seen.add(v);
      return v.map(walk);
    }
    if (isPlainObject(v)) {
      if (seen.has(v)) return v;
      seen.add(v);
      const out = {};
      for (const [k, val] of Object.entries(v)) {
        out[k] = sensitiveKeys.has(k.toLowerCase()) ? "[REDACTED:field]" : walk(val);
      }
      return out;
    }
    return v;
  };
  return walk(value);
}
function detectSpans(text, patterns = DEFAULT_REDACTION_PATTERNS) {
  const raw = [];
  for (const { kind, pattern, validate } of patterns) {
    const g = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
    for (const m of text.matchAll(g)) {
      if (m.index === void 0 || m[0].length === 0) continue;
      if (validate && !validate(m[0])) continue;
      raw.push({ kind, start: m.index, end: m.index + m[0].length, text: m[0] });
    }
  }
  raw.sort((a, b) => a.start - b.start || b.end - a.end);
  const spans = [];
  let cursor = -1;
  let i = 0;
  for (const s of raw) {
    if (s.start < cursor) continue;
    spans.push({ id: `span-${i++}`, ...s });
    cursor = s.end;
  }
  return spans;
}
async function buildRedactedDocument(text, options) {
  const spans = detectSpans(text, options.patterns);
  const segments = [];
  let pos = 0;
  for (const span of spans) {
    if (span.start > pos) segments.push({ type: "text", text: text.slice(pos, span.start) });
    segments.push({ type: "redacted", id: span.id, kind: span.kind, cipher: await options.encrypt(span.text) });
    pos = span.end;
  }
  if (pos < text.length) segments.push({ type: "text", text: text.slice(pos) });
  return { segments };
}
async function revealSpan(doc, spanId, options) {
  const seg = doc.segments.find(
    (s) => s.type === "redacted" && s.id === spanId
  );
  if (!seg) return { ok: false, reason: "not_found" };
  const allowed = await options.canReveal({ id: seg.id, kind: seg.kind });
  if (!allowed) return { ok: false, reason: "forbidden" };
  const value = await options.decrypt(seg.cipher);
  if (options.onReveal) await options.onReveal({ id: seg.id, kind: seg.kind });
  return { ok: true, value };
}

export {
  DEFAULT_REDACTION_PATTERNS,
  maskSpans,
  redactForIngestion,
  detectSpans,
  buildRedactedDocument,
  revealSpan
};
//# sourceMappingURL=chunk-5RMIUJDI.js.map