"use strict";

/**
 * Dev-only log bus used by the testbed `Logs` tab. Stores a bounded
 * ring-buffer of entries that arrive from:
 *   - `console.log/info/warn/error` (patched in `installDevLogCapture`)
 *   - `axios` request/response interceptors (HTTP)
 *   - `XmppClient.attachEventListeners` (XMPP stanzas in/out)
 *
 * Pure module-level singleton — no React, callable from anywhere
 * (reducers, services, helpers). React side reads via
 * `useDevLogs()` which uses `useSyncExternalStore`.
 */

const MAX_ENTRIES = 500;
let entries = [];
let listeners = new Set();
let nextId = 1;
export function pushLog(kind, message, details) {
  const detailsStr = details === undefined ? undefined : typeof details === 'string' ? details : safeStringify(details);
  entries = [...entries, {
    id: nextId++,
    kind,
    ts: Date.now(),
    message,
    details: detailsStr
  }];
  if (entries.length > MAX_ENTRIES) {
    entries = entries.slice(-MAX_ENTRIES);
  }
  // Defer subscriber notifications via microtask so a `pushLog` called
  // during a React render (e.g. via the captured `console.log`) doesn't
  // trigger a setState-during-render warning in subscribers using
  // `useSyncExternalStore`.
  const notify = () => listeners.forEach(fn => {
    try {
      fn();
    } catch {
      /* noop */
    }
  });
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(notify);
  } else {
    Promise.resolve().then(notify);
  }
}
export function clearLogs() {
  entries = [];
  listeners.forEach(fn => fn());
}
export function getLogs() {
  return entries;
}
export function subscribeLogs(fn) {
  listeners.add(fn);
  return () => {
    listeners.delete(fn);
  };
}
function safeStringify(value) {
  try {
    return JSON.stringify(value, (_key, v) => {
      if (typeof v === 'bigint') {
        return v.toString();
      }
      if (v instanceof Error) {
        return {
          name: v.name,
          message: v.message,
          stack: v.stack
        };
      }
      return v;
    }, 2);
  } catch {
    try {
      return String(value);
    } catch {
      return '[unstringifiable]';
    }
  }
}

// ---------------------------------------------------------------------
// console.* capture
// ---------------------------------------------------------------------
let consolePatched = false;
export function installConsoleCapture() {
  // Under Jest, patching `console.*` to fan out through `pushLog` causes
  // a render-time feedback loop: React's dev build emits `console.error`
  // during act() for various reasons (deprecated APIs, act-warnings),
  // each call schedules a microtask notify → `useSyncExternalStore`
  // subscribers re-render → next render may log again → "Maximum
  // update depth exceeded" → the suite hangs. Skipping the patch in
  // test environments eliminates the loop without changing dev/prod
  // behaviour. Mirrors how the web build gates DEV-only diagnostics.
  if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') {
    return;
  }
  if (consolePatched) {
    return;
  }
  consolePatched = true;
  ['log', 'info', 'warn', 'error'].forEach(level => {
    const original = console[level].bind(console);
    console[level] = (...args) => {
      try {
        const msg = args.map(a => typeof a === 'string' ? a : safeStringify(a)).join(' ');
        pushLog(level === 'log' ? 'log' : level, msg);
      } catch {
        /* swallow */
      }
      original(...args);
    };
  });
}

// ---------------------------------------------------------------------
// axios interceptor
// ---------------------------------------------------------------------
let axiosPatched = false;
export function installAxiosCapture(axiosInstance) {
  if (axiosPatched || !axiosInstance?.interceptors) {
    return;
  }
  axiosPatched = true;
  axiosInstance.interceptors.request.use(config => {
    const method = (config.method || 'get').toUpperCase();
    const url = (config.baseURL || '') + (config.url || '');
    pushLog('http', `→ ${method} ${url}`, {
      headers: redactHeaders(config.headers),
      data: config.data
    });
    return config;
  }, error => {
    pushLog('http', '→ request error', error?.message || String(error));
    return Promise.reject(error);
  });
  axiosInstance.interceptors.response.use(response => {
    const method = (response.config?.method || 'get').toUpperCase();
    const url = (response.config?.baseURL || '') + (response.config?.url || '');
    pushLog('http', `← ${response.status} ${method} ${url}`, truncateBody(response.data));
    return response;
  }, error => {
    const method = (error.config?.method || '?').toUpperCase();
    const url = (error.config?.baseURL || '') + (error.config?.url || '');
    const status = error.response?.status || 'no-status';
    pushLog('http', `← ERR ${status} ${method} ${url}`, {
      data: error.response?.data,
      message: error.message
    });
    return Promise.reject(error);
  });
}
function redactHeaders(headers) {
  if (!headers || typeof headers !== 'object') {
    return headers;
  }
  const out = {};
  for (const k of Object.keys(headers)) {
    const v = headers[k];
    if (/^(authorization|x-custom-token|cookie)$/i.test(k)) {
      out[k] = typeof v === 'string' ? v.slice(0, 16) + '…' : '[redacted]';
    } else {
      out[k] = v;
    }
  }
  return out;
}
function truncateBody(body) {
  if (body == null) {
    return body;
  }
  const str = typeof body === 'string' ? body : safeStringify(body);
  if (str.length <= 2000) {
    return str;
  }
  return str.slice(0, 2000) + `… [+${str.length - 2000} chars]`;
}
//# sourceMappingURL=devLogger.js.map