/**
 * remoteDevlog.js — клиент удалённого dev-логгера.
 *
 * Перехватывает: console.error/warn, window.onerror, unhandledrejection,
 * все fetch/XHR ошибки и не-2xx ответы; батчит и шлёт на бэкенд.
 *
 * Запускается только в localhost (dev), на проде no-op.
 */

const IS_DEV = typeof window !== 'undefined'
    && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');

const ENDPOINT = '/api-rbxl/devlog';
const FLUSH_INTERVAL_MS = 1500;
const MAX_BATCH = 50;

const queue = [];
let flushTimer = null;

function push(ev) {
    if (!IS_DEV) return;
    ev.ts = Date.now();
    ev.page = location.pathname + location.search;
    queue.push(ev);
    if (queue.length >= MAX_BATCH) flush();
    else scheduleFlush();
}

function scheduleFlush() {
    if (flushTimer) return;
    flushTimer = setTimeout(flush, FLUSH_INTERVAL_MS);
}

function flush() {
    flushTimer = null;
    if (queue.length === 0) return;
    const events = queue.splice(0, MAX_BATCH);
    try {
        const blob = new Blob([JSON.stringify({ events })], { type: 'application/json' });
        // sendBeacon — не блокирует, переживёт unload
        if (navigator.sendBeacon) {
            navigator.sendBeacon(ENDPOINT, blob);
        } else {
            fetch(ENDPOINT, { method: 'POST', body: blob, headers: { 'Content-Type': 'application/json' }, keepalive: true })
                .catch(() => {});
        }
    } catch (e) { /* swallow */ }
}

function truncate(s, max = 4000) {
    if (typeof s !== 'string') {
        try { s = JSON.stringify(s); } catch { s = String(s); }
    }
    return s.length > max ? s.slice(0, max) + '...[truncated]' : s;
}

export function installRemoteDevlog() {
    if (!IS_DEV) return;

    // 1. console.error / console.warn (но НЕ console.log — слишком шумно)
    const origError = console.error.bind(console);
    const origWarn = console.warn.bind(console);
    console.error = (...args) => {
        try { push({ kind: 'console.error', message: truncate(args.map(formatArg).join(' ')) }); } catch {}
        origError(...args);
    };
    console.warn = (...args) => {
        try { push({ kind: 'console.warn', message: truncate(args.map(formatArg).join(' ')) }); } catch {}
        origWarn(...args);
    };

    // 2. window.onerror
    window.addEventListener('error', (ev) => {
        push({
            kind: 'window.error',
            message: ev.message,
            filename: ev.filename,
            lineno: ev.lineno,
            colno: ev.colno,
            stack: ev.error?.stack ? truncate(ev.error.stack) : null,
        });
    });

    // 3. Unhandled promise rejection
    window.addEventListener('unhandledrejection', (ev) => {
        const reason = ev.reason;
        push({
            kind: 'unhandledrejection',
            message: truncate(reason?.message || String(reason)),
            stack: reason?.stack ? truncate(reason.stack) : null,
        });
    });

    // 4. fetch wrapper — логируем все не-2xx и failed
    const origFetch = window.fetch.bind(window);
    window.fetch = async (input, init) => {
        const url = typeof input === 'string' ? input : input?.url || '';
        const method = (init?.method || 'GET').toUpperCase();
        const t0 = performance.now();
        try {
            const resp = await origFetch(input, init);
            if (!resp.ok) {
                let body = '';
                try {
                    const cloned = resp.clone();
                    body = truncate(await cloned.text(), 2000);
                } catch {}
                push({
                    kind: 'fetch.bad',
                    url,
                    method,
                    status: resp.status,
                    duration_ms: Math.round(performance.now() - t0),
                    body,
                });
            }
            return resp;
        } catch (e) {
            push({
                kind: 'fetch.fail',
                url,
                method,
                duration_ms: Math.round(performance.now() - t0),
                message: e?.message || String(e),
            });
            throw e;
        }
    };

    // 5. XHR wrapper — для axios и т.п.
    const XhrOpen = XMLHttpRequest.prototype.open;
    const XhrSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function (method, url, ...rest) {
        this.__rdl = { method, url, t0: performance.now() };
        return XhrOpen.call(this, method, url, ...rest);
    };
    XMLHttpRequest.prototype.send = function (body) {
        this.addEventListener('loadend', () => {
            const meta = this.__rdl || {};
            const status = this.status;
            if (status === 0 || status >= 400) {
                push({
                    kind: 'xhr.bad',
                    url: meta.url,
                    method: meta.method,
                    status,
                    duration_ms: Math.round(performance.now() - (meta.t0 || performance.now())),
                    body: truncate(this.responseText || '', 2000),
                });
            }
        });
        return XhrSend.call(this, body);
    };

    // Периодически флушим (для долгоживущих логов)
    setInterval(() => { if (queue.length) flush(); }, 5000);
    window.addEventListener('beforeunload', flush);

    // Стартовая отметка чтобы в логе было видно начало сессии
    push({ kind: 'session.start', ua: navigator.userAgent });
}

function formatArg(a) {
    if (a == null) return String(a);
    if (typeof a === 'string') return a;
    if (a instanceof Error) return a.message + (a.stack ? '\n' + a.stack : '');
    try { return JSON.stringify(a); } catch { return String(a); }
}
