import { sanitizePublicData } from "./public-sanitizer.js";

const LEVELS = new Set(["info", "warn", "error"]);
const LOG_CODE = /^[a-z][a-z0-9_]*$/;

export function writeSafeLog(logger = console, level = "info", code = "log_event", data = {}, config = {}) {
  const safeLevel = LEVELS.has(level) ? level : "error";
  try {
    const safeCode = LOG_CODE.test(String(code)) ? String(code) : "invalid_log_code";
    const normalized = normalizeLogValue(data, new WeakSet());
    const sanitized = sanitizePublicData({ level: safeLevel, code: safeCode, ...normalized }, config);
    const line = JSON.stringify(sanitized);
    if (!line) throw new Error("log_serialization_failed");
    writeLine(logger, safeLevel, line);
  } catch {
    writeLine(logger, safeLevel, JSON.stringify({ level: safeLevel, code: "log_sanitization_failed" }));
  }
}

function normalizeLogValue(value, active) {
  if (value instanceof Error) {
    if (active.has(value)) return "[redacted]";
    active.add(value);
    const details = {
      name: value.name,
      message: value.message,
      stack: value.stack,
      status: value.status,
      safeLogCode: value.safeLogCode
    };
    for (const [key, item] of Object.entries(value)) details[key] = normalizeLogValue(item, active);
    active.delete(value);
    return details;
  }
  if (Array.isArray(value)) {
    if (active.has(value)) return "[redacted]";
    active.add(value);
    const result = value.map((item) => normalizeLogValue(item, active));
    active.delete(value);
    return result;
  }
  if (!value || typeof value !== "object") return value;
  if (active.has(value)) return "[redacted]";
  active.add(value);
  const result = {};
  for (const [key, item] of Object.entries(value)) result[key] = normalizeLogValue(item, active);
  active.delete(value);
  return result;
}

function writeLine(logger, level, line) {
  try {
    const sink = typeof logger?.[level] === "function" ? logger[level] : logger?.log;
    if (typeof sink !== "function") return;
    sink.call(logger, line);
  } catch {
    // Logging must never expose unsanitized fallback data or break application work.
  }
}
