// ==========================================
// Debug Engine — Preact-compatible data layer
// ==========================================
// Console interceptor, fetch logger, error tracker, diagnostics.
// Singleton — init() must run before SDK boot to capture early errors.
// UI-agnostic: Preact components subscribe via .subscribe().
//
// v0.37.18: Extracted from debug.js (v0.37.14).
//
// Exports: debugEngine (singleton)

export const debugEngine = {
    entries: [],
    maxEntries: 500,
    networkLog: [],
    maxNetwork: 200,
    errorCount: 0,

    _origConsole: {},
    _origFetch: null,
    _initTime: Date.now(),
    _subscribers: new Set(),

    // ── Bootstrap ────────────────────────────

    init() {
        this._interceptConsole();
        this._interceptFetch();
        this._interceptErrors();
        this._registerShortcut();
        this.log('DEBUG', 'Debug engine initialized');
        this.log('DEBUG', `Page: ${location.href}`);
        this.log('DEBUG', `UA: ${navigator.userAgent.slice(0, 120)}`);
    },

    // ── Subscription (reactive updates) ─────

    subscribe(fn) {
        this._subscribers.add(fn);
        return () => this._subscribers.delete(fn);
    },

    _notify() {
        for (const fn of this._subscribers) {
            try { fn(); } catch (_) { /* subscriber error */ }
        }
    },

    // ── Console Interception ────────────────

    _interceptConsole() {
        ['log', 'warn', 'error', 'info', 'debug'].forEach(method => {
            this._origConsole[method] = console[method].bind(console);
            console[method] = (...args) => {
                const type = method.toUpperCase();
                const message = args.map(a => this._serialize(a)).join(' ');
                this._addEntry(type, message);
                this._origConsole[method](...args);
            };
        });
    },

    // ── Fetch Interception ──────────────────

    _interceptFetch() {
        this._origFetch = window.fetch.bind(window);
        const self = this;

        window.fetch = async function(input, init) {
            const url = typeof input === 'string' ? input : input?.url || String(input);
            const method = init?.method || 'GET';
            const entry = {
                id: Date.now(),
                ts: new Date().toISOString(),
                method,
                url: url.slice(0, 300),
                status: null,
                statusText: '',
                contentType: null,
                duration: null,
                error: null,
                responsePreview: null,
                requestBodyPreview: null,
                proxyDetected: false
            };

            // Capture request body preview (redact keys)
            if (init?.body) {
                try {
                    const bodyStr = typeof init.body === 'string' ? init.body : '';
                    const parsed = bodyStr ? JSON.parse(bodyStr) : {};
                    const safe = { ...parsed };
                    ['password', 'api_key', 'apiKey', 'refresh_token', 'access_token'].forEach(k => {
                        if (safe[k]) safe[k] = '***';
                    });
                    entry.requestBodyPreview = JSON.stringify(safe).slice(0, 300);
                } catch (_) {
                    entry.requestBodyPreview = '(non-JSON body)';
                }
            }

            self.networkLog.push(entry);
            if (self.networkLog.length > self.maxNetwork) {
                self.networkLog.shift();
            }

            const start = performance.now();
            try {
                const resp = await self._origFetch(input, init);
                entry.status = resp.status;
                entry.statusText = resp.statusText;
                entry.contentType = resp.headers.get('content-type') || null;
                entry.duration = Math.round(performance.now() - start);

                const isApiCall = url.includes('/api/') || url.includes('/health');
                if (isApiCall && entry.contentType && entry.contentType.includes('text/html')) {
                    entry.proxyDetected = true;
                    self._addEntry('NET:PROXY', `${method} ${url.slice(0, 80)} \u2192 PROXY HTML (${entry.contentType}) (${entry.duration}ms)`);
                    try {
                        const clone = resp.clone();
                        const text = await clone.text();
                        entry.responsePreview = text.slice(0, 500);
                    } catch (_) { /* */ }
                } else {
                    const logType = resp.ok ? 'NET' : 'NET:ERR';
                    self._addEntry(logType, `${method} ${url.slice(0, 80)} \u2192 ${resp.status} (${entry.duration}ms)`);
                }

                if (!resp.ok && !entry.proxyDetected) {
                    try {
                        const clone = resp.clone();
                        const text = await clone.text();
                        entry.responsePreview = text.slice(0, 500);
                    } catch (_) { /* can't peek */ }
                }

                return resp;
            } catch (err) {
                entry.duration = Math.round(performance.now() - start);
                entry.error = err.message || String(err);
                self._addEntry('NET:ERR', `${method} ${url.slice(0, 80)} \u2192 FAILED: ${entry.error} (${entry.duration}ms)`);
                throw err;
            }
        };
    },

    // ── Global Error Handlers ───────────────

    _interceptErrors() {
        window.addEventListener('error', (e) => {
            this._addEntry('EXCEPTION', `${e.message} at ${e.filename || '?'}:${e.lineno || 0}:${e.colno || 0}`);
        });

        window.addEventListener('unhandledrejection', (e) => {
            const msg = e.reason?.message || e.reason || 'Unknown rejection';
            this._addEntry('REJECTION', String(msg));
        });
    },

    // ── Keyboard Shortcut ───────────────────

    _registerShortcut() {
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.shiftKey && e.key === 'L') {
                e.preventDefault();
                // Dispatched as custom event; DebugModal listens for it.
                window.dispatchEvent(new CustomEvent('debug:toggle'));
            }
        });
    },

    // ── Entry Management ────────────────────

    _addEntry(type, message) {
        const entry = {
            ts: new Date().toISOString(),
            elapsed: Date.now() - this._initTime,
            type,
            message
        };
        this.entries.push(entry);
        if (this.entries.length > this.maxEntries) {
            this.entries.shift();
        }

        if (type === 'ERROR' || type === 'EXCEPTION' || type === 'REJECTION' || type === 'NET:ERR' || type === 'NET:PROXY') {
            this.errorCount++;
        }

        this._notify();
    },

    log(type, message) {
        this._addEntry(type, message);
    },

    clear() {
        this.entries = [];
        this.networkLog = [];
        this.errorCount = 0;
        this._notify();
    },

    // ── Serialization ───────────────────────

    _serialize(arg) {
        if (arg === null) return 'null';
        if (arg === undefined) return 'undefined';
        if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
        if (typeof arg === 'object') {
            try { return JSON.stringify(arg, null, 0); }
            catch (_) { return String(arg); }
        }
        return String(arg);
    },

    // ── State Snapshot ──────────────────────

    getStateSnapshot() {
        const snap = {
            env: window.__ENV__ || 'unknown',
            version: window.__VERSION__ || 'unknown',
            basePath: window.__BASE__ || '(root)',
            url: location.href,
        };

        if (window.sw) {
            snap.sdk = {
                user: window.sw.auth?.user ? {
                    username: window.sw.auth.user.username,
                    role: window.sw.auth.user.role,
                    display_name: window.sw.auth.user.display_name
                } : null,
                isAuthed: !!window.sw.auth?.token,
                wsConnected: window.sw.events?.connected ?? false,
                theme: window.sw.theme?.resolved ?? 'unknown',
            };
        }

        try {
            snap.storageKeys = Object.keys(localStorage).filter(k =>
                k.startsWith('chatSwitchboard') || k.startsWith('switchboard') || k.startsWith('sb_')
            );
        } catch (_) {
            snap.storageKeys = '(error reading)';
        }

        return snap;
    },

    // ── Connection Diagnostics ──────────────

    async runDiagnostics() {
        this.log('DIAG', '\u2500\u2500 Starting connection diagnostics \u2500\u2500');
        this.log('DIAG', `Environment: ${window.__ENV__ || 'unknown'} | Version: ${window.__VERSION__ || 'unknown'} | Base: ${window.__BASE__ || '(root)'}`);

        const baseUrl = window.location.origin + (window.__BASE__ || '');

        // Test 1: Health check
        this.log('DIAG', `Test 1: Health check \u2192 ${baseUrl}/api/v1/health`);
        try {
            const start = performance.now();
            const resp = await this._origFetch(baseUrl + '/api/v1/health', {
                signal: AbortSignal.timeout(10000)
            });
            const dur = Math.round(performance.now() - start);
            const contentType = resp.headers.get('content-type') || '(none)';
            const text = await resp.text();
            this.log('DIAG', `  Status: ${resp.status} (${dur}ms)`);
            this.log('DIAG', `  Content-Type: ${contentType}`);
            this.log('DIAG', `  Body: ${text.slice(0, 300)}`);

            if (contentType.includes('text/html')) {
                this.log('DIAG', '  \u26a0\ufe0f Got HTML instead of JSON \u2014 likely a PROXY interception page');
            }
            if (!resp.ok) {
                this.log('DIAG', `  \u26a0\ufe0f Non-200 status: ${resp.status} ${resp.statusText}`);
            }
            try {
                const json = JSON.parse(text);
                this.log('DIAG', `  \u2705 Valid JSON: database=${json.database}, registration=${json.registration_enabled}`);
            } catch (_) {
                this.log('DIAG', '  \u274c NOT valid JSON \u2014 proxy or wrong endpoint');
            }
        } catch (err) {
            this.log('DIAG', `  \u274c Fetch failed: ${err.message}`);
            if (err.name === 'TimeoutError' || err.name === 'AbortError') {
                this.log('DIAG', '  \u26a0\ufe0f Request timed out \u2014 proxy may be blocking');
            }
        }

        // Test 2: CORS preflight
        this.log('DIAG', 'Test 2: OPTIONS preflight check');
        try {
            const resp = await this._origFetch(baseUrl + '/api/v1/health', {
                method: 'OPTIONS',
                signal: AbortSignal.timeout(5000)
            });
            this.log('DIAG', `  Status: ${resp.status}`);
            this.log('DIAG', `  CORS headers: ${resp.headers.get('access-control-allow-origin') || '(none)'}`);
        } catch (err) {
            this.log('DIAG', `  \u274c OPTIONS failed: ${err.message}`);
        }

        // Test 3: Token validation
        if (window.sw?.auth?.token) {
            this.log('DIAG', 'Test 3: Token validation');
            try {
                const resp = await this._origFetch(baseUrl + '/api/v1/profile', {
                    headers: {
                        'Authorization': `Bearer ${window.sw.auth.token}`,
                        'Content-Type': 'application/json'
                    },
                    signal: AbortSignal.timeout(5000)
                });
                this.log('DIAG', `  Profile endpoint: ${resp.status}`);
                if (resp.status === 401) {
                    this.log('DIAG', '  \u26a0\ufe0f Token expired or invalid');
                }
            } catch (err) {
                this.log('DIAG', `  \u274c Token check failed: ${err.message}`);
            }
        } else {
            this.log('DIAG', 'Test 3: Skipped (no token)');
        }

        // Test 4: WebSocket
        this.log('DIAG', `Test 4: SDK WebSocket = ${window.sw?.events?.connected ? 'connected' : 'disconnected'}`);

        // Test 5: Service Worker & Cache
        this.log('DIAG', 'Test 5: Service Worker cache');
        try {
            if ('serviceWorker' in navigator) {
                const reg = await navigator.serviceWorker.getRegistration();
                this.log('DIAG', `  SW registered: ${reg ? 'yes' : 'no'}${reg ? ` (scope: ${reg.scope})` : ''}`);
                if (reg?.active) {
                    this.log('DIAG', `  SW state: ${reg.active.state}`);
                }
            } else {
                this.log('DIAG', '  SW not supported');
            }
            const keys = await caches.keys();
            this.log('DIAG', `  Caches (${keys.length}): ${keys.join(', ') || '(none)'}`);
            for (const key of keys) {
                const cache = await caches.open(key);
                const cacheEntries = await cache.keys();
                this.log('DIAG', `    ${key}: ${cacheEntries.length} entries`);
            }
        } catch (e) {
            this.log('DIAG', `  Cache check error: ${e.message}`);
        }

        this.log('DIAG', '\u2500\u2500 Diagnostics complete \u2500\u2500');
    },

    // ── Export ───────────────────────────────

    exportText() {
        let text = `=== Switchboard Core Debug Export ===\n`;
        text += `Exported: ${new Date().toISOString()}\n`;
        text += `URL: ${location.href}\n`;
        text += `ENV: ${window.__ENV__ || 'unknown'} | VERSION: ${window.__VERSION__ || 'unknown'} | BASE: ${window.__BASE__ || '(root)'}\n`;
        text += `UA: ${navigator.userAgent}\n\n`;

        text += `--- STATE ---\n`;
        text += JSON.stringify(this.getStateSnapshot(), null, 2) + '\n\n';

        text += `--- CONSOLE LOG (${this.entries.length}) ---\n`;
        for (const e of this.entries) {
            text += `[${e.ts}] [${e.type}] ${e.message}\n`;
        }
        text += '\n';

        text += `--- NETWORK LOG (${this.networkLog.length}) ---\n`;
        for (const r of this.networkLog) {
            const proxy = r.proxyDetected ? ' [PROXY]' : '';
            text += `[${r.ts}] ${r.method} ${r.url} \u2192 ${r.status || 'FAILED'}${proxy} ${r.error || ''} (${r.duration || '?'}ms)`;
            if (r.contentType) text += ` [${r.contentType}]`;
            text += '\n';
            if (r.responsePreview) text += `  Response: ${r.responsePreview.slice(0, 200)}\n`;
        }

        return text;
    },

    copyLog() {
        const text = this.exportText();
        navigator.clipboard.writeText(text)
            .then(() => this.log('DEBUG', 'Debug log copied to clipboard'))
            .catch(() => this.log('ERROR', 'Copy to clipboard failed'));
    },

    downloadLog() {
        const text = this.exportText();
        const blob = new Blob([text], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `switchboard-debug-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
        a.click();
        URL.revokeObjectURL(url);
    },

    async purgeCache() {
        this.log('DIAG', 'Purging Service Worker caches...');

        try {
            const keys = await caches.keys();
            const deleted = await Promise.all(keys.map(k => caches.delete(k)));
            this.log('DIAG', `  Deleted ${deleted.filter(Boolean).length} cache(s): ${keys.join(', ') || '(none)'}`);

            if ('serviceWorker' in navigator) {
                const registrations = await navigator.serviceWorker.getRegistrations();
                for (const reg of registrations) {
                    await reg.unregister();
                    this.log('DIAG', `  Unregistered SW: ${reg.scope}`);
                }
            }

            this.log('DIAG', '  Cache purged \u2014 reloading...');
            setTimeout(() => location.reload(), 500);
        } catch (e) {
            this.log('DIAG', `  Purge failed: ${e.message}`);
        }
    }
};
