lib = {}
lib.primaryColorRGB = [32, 178, 170]; // default: lightseagreen

// ============================================================================
// Console Logger - sends console output to server for logging (batched)
// Logs are collected and flushed every 2s in a single request to avoid
// flooding the browser's 6-connection-per-host limit (critical on Windows).
// ============================================================================
(function() {
    const originalLog = console.log;
    const originalWarn = console.warn;
    const originalError = console.error;

    const MAX_MSG_LENGTH = 512; // truncate large payloads (API responses etc.)
    const FLUSH_INTERVAL_MS = 2000;

    // Generate session ID once per page load (format: yyyyMMdd_HHmmss)
    const now = new Date();
    const sessionId = now.getFullYear().toString() +
        String(now.getMonth() + 1).padStart(2, '0') +
        String(now.getDate()).padStart(2, '0') + '_' +
        String(now.getHours()).padStart(2, '0') +
        String(now.getMinutes()).padStart(2, '0') +
        String(now.getSeconds()).padStart(2, '0');

    let buffer = [];
    let flushTimer = null;

    const formatArgs = (args) => {
        let message = args.map(a => {
            if (typeof a === 'object') {
                try { return JSON.stringify(a); }
                catch { return String(a); }
            }
            return String(a);
        }).join(' ');
        if (message.length > MAX_MSG_LENGTH) {
            message = message.slice(0, MAX_MSG_LENGTH) + '...(truncated)';
        }
        return message;
    };

    const enqueue = (level, args) => {
        buffer.push({
            level: level,
            message: formatArgs(args),
            timestamp: new Date().toISOString(),
            sessionId: sessionId
        });
        // Schedule flush if not already pending
        if (!flushTimer) {
            flushTimer = setTimeout(flush, FLUSH_INTERVAL_MS);
        }
    };

    const flush = () => {
        flushTimer = null;
        if (buffer.length === 0) return;
        const batch = buffer;
        buffer = [];
        fetch('/api/logs/frontend-batch', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ logs: batch })
        }).catch(() => {});
    };

    // Flush on page unload so we don't lose the last batch
    window.addEventListener('beforeunload', flush);

    console.log = function(...args) {
        originalLog.apply(console, args);
        enqueue('log', args);
    };

    console.warn = function(...args) {
        originalWarn.apply(console, args);
        enqueue('warn', args);
    };

    console.error = function(...args) {
        originalError.apply(console, args);
        enqueue('error', args);
    };
})();
lib.setTheme = async function (themeName){
    ConfigEntry.set('theme', themeName).then()
    const themes = {
        blue:   { color: "lightseagreen", rgb: [32, 178, 170] },
        red:    { color: "crimson",       rgb: [220, 20, 60] },
        green:  { color: "lightgreen",    rgb: [144, 238, 144] },
        purple: { color: "#a855f7",       rgb: [168, 85, 247] },
        orange: { color: "#f97316",       rgb: [249, 115, 22] },
        pink:   { color: "#ec4899",       rgb: [236, 72, 153] },
        cyan:   { color: "#06b6d4",       rgb: [6, 182, 212] },
        gold:   { color: "#eab308",       rgb: [234, 179, 8] }
    };
    const theme = themes[themeName] || themes.blue;
    let color = theme.color;
    lib.primaryColorRGB = theme.rgb;
    let css = `
        :root { --theme-color: ${color}; }
        button, a, select, b, h1, h2, h3, h4, h5, h6{ color: ${color} !important; }
        input[type="checkbox"]{ accent-color: ${color} !important;}
    `;
    let style = document.getElementById("dynamicThemeStyle");
    if(!style){
        style = document.createElement('style');
        style.id = "dynamicThemeStyle";
        document.head.appendChild(style);
    }
    style.innerHTML = css;
    window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme: themeName, rgb: theme.rgb } }));
};
lib.setupThemeOnLoad = async function (){
    let themeName = await ConfigEntry.get('theme');
    if(!themeName) {await ConfigEntry.set('theme', 'red'); themeName = 'red';}
    lib.setTheme(themeName).then();
    const themeSelection = document.getElementById("themeSelection");
    if(themeSelection)
        for(let o of themeSelection.options)
            if(o.value === themeName) o.selected = "true";
};
lib.error = function(message, data){ console.error(message); console.error(data); } // todo

lib.loadAllCommunities = async () => {
    const communitiesKey = "communities";
    let raw = await ConfigEntry.get(communitiesKey);
    let communityListData = [];
    try { communityListData = JSON.parse(raw || "[]"); } catch (e) { lib.error(e, raw); }
    communityListData = Array.from(communityListData.filter(c => c !== ""));

    // Auto-populate from discovered communities on first launch (empty config)
    if (communityListData.length === 0) {
        try {
            const res = await fetch('/api/skool/my-communities');
            if (res.ok) {
                const data = await res.json();
                const discovered = (data.communities || []).map(c => c.slug).filter(s => s);
                if (discovered.length > 0) {
                    communityListData = discovered;
                    await ConfigEntry.set(communitiesKey, JSON.stringify(communityListData));
                    // Auto-select first community if none is set
                    const current = await ConfigEntry.get("current_community");
                    if (!current) {
                        await ConfigEntry.set("current_community", communityListData[0]);
                    }
                    console.log("auto-populated communities from discovery:", communityListData);
                }
            }
        } catch (e) {
            console.warn("Could not auto-populate communities:", e);
        }
    }

    console.log("loaded communities:", communityListData);
    return communityListData;
}
// Populate the community selector header dropdown with discovered communities.
// Falls back to plain slugs if the discovery API is unavailable.
lib.populateCommunitySelector = async (selectorId, currentCommunity, communityListData) => {
    const selector = document.getElementById(selectorId);
    if (!selector) return;
    selector.innerHTML = '';
    selector.disabled = false;
    selector.title = '';

    let discoveredMap = {};
    try {
        const res = await fetch('/api/skool/my-communities');
        if (res.ok) {
            const data = await res.json();
            for (let c of (data.communities || [])) {
                discoveredMap[c.slug] = c;
            }
        }
    } catch (e) {
        console.warn("Could not load discovered communities for selector:", e);
    }

    for (let slug of communityListData) {
        const option = document.createElement("option");
        option.value = slug;
        const discovered = discoveredMap[slug];
        if (discovered) {
            let label = discovered.displayName || slug;
            if (label.length > 35) label = label.substring(0, 35) + '...';
            const members = discovered.totalMembers ? ` (${discovered.totalMembers})` : '';
            option.innerText = label + members;
        } else {
            option.innerText = slug.length > 35 ? slug.substring(0, 35) + '...' : slug;
        }
        if (slug === currentCommunity) option.selected = true;
        selector.appendChild(option);
    }

    selector.onchange = async () => {
        const newSlug = selector.value;
        if (newSlug && newSlug !== currentCommunity) {
            // Stop fetcher before switching community so it doesn't
            // continue processing tasks for the old community
            try { await fetch('/api/fetcher/stop', { method: 'POST' }); } catch (_) {}
            // Clean up quick fetch state (cancels pending full fetch schedule)
            sessionStorage.removeItem('showQuickFetchDoneBar');
            await ConfigEntry.set('current_community', newSlug);
            window.location.reload();
        }
    };
};

// todo: breaks currently if we use select...
lib.addEventHandlerToCloseDialogByClickingOutside = (dialogElement) => {
    dialogElement.addEventListener("click", (event) => {
        const rect = dialogElement.getBoundingClientRect();
        const clickedInDialog =
            event.clientX >= rect.left &&
            event.clientX <= rect.right &&
            event.clientY >= rect.top &&
            event.clientY <= rect.bottom;
        if (!clickedInDialog) dialogElement.close();
    });
}

// Error Dialog
lib.errorDialog = null;
lib.showError = function(title, message) {
    console.error(`[ERROR] ${title}:`, message);
    if (!lib.errorDialog) {
        lib.errorDialog = document.createElement('dialog');
        lib.errorDialog.innerHTML = `
            <h3 id="errTitle" style="color:crimson"></h3>
            <pre id="errMsg" style="max-width:500px;max-height:300px;overflow:auto"></pre>
            <iframe id="errIframe" style="width:600px;height:400px;border:1px solid #333;display:none"></iframe>
            <br><button onclick="this.closest('dialog').close()" style="padding:6px 16px; background:#e94560; color:white; border:none; border-radius:4px; cursor:pointer;">OK</button>
        `;
        document.body.appendChild(lib.errorDialog);
    }
    const msgStr = String(message);
    const isHtml = msgStr.trim().startsWith('<!') || msgStr.trim().startsWith('<html');
    const preEl = lib.errorDialog.querySelector('#errMsg');
    const iframeEl = lib.errorDialog.querySelector('#errIframe');

    lib.errorDialog.querySelector('#errTitle').textContent = title;
    if (isHtml) {
        preEl.style.display = 'none';
        iframeEl.style.display = 'block';
        iframeEl.srcdoc = msgStr;
    } else {
        preEl.style.display = 'block';
        iframeEl.style.display = 'none';
        preEl.textContent = msgStr;
    }
    lib.errorDialog.showModal();
};

// =============================================================================
// Inline Section Error Helper - shows inline errors with retry button
// Section loaders call this instead of showing a modal on API failure.
// =============================================================================

lib.retryCounters = {};  // { sectionKey: count }
lib.MAX_RETRIES = 3;

lib.showSectionError = function(containerId, sectionName, retryFnName, sectionKey) {
    if (!lib.retryCounters[sectionKey]) lib.retryCounters[sectionKey] = 0;
    lib.retryCounters[sectionKey]++;
    const count = lib.retryCounters[sectionKey];
    const container = document.getElementById(containerId);
    if (!container) return;

    const canRetry = count < lib.MAX_RETRIES;
    const errorDiv = document.createElement('div');
    errorDiv.style.cssText = 'padding:12px;color:#c44;border:1px solid #c44;border-radius:4px;margin:8px 0;font-size:0.9em;';

    if (canRetry) {
        errorDiv.innerHTML = `
            <span>Failed to load ${sectionName}. Try again.</span>
            <button id="retry-btn-${sectionKey}" style="margin-left:8px;padding:2px 8px;"
                onclick="(async () => {
                    const btn = document.getElementById('retry-btn-${sectionKey}');
                    if(btn){btn.disabled=true;btn.textContent='Retrying...';}
                    if(typeof ${retryFnName}==='function') await ${retryFnName}();
                })()">Retry</button>`;
    } else {
        errorDiv.innerHTML = `<span>Failed to load ${sectionName}. Check the logs or restart.</span>`;
    }

    container.innerHTML = '';
    container.appendChild(errorDiv);
};

// Global error handlers - log to console + show dialog + auto-report if enabled
window.onerror = (msg, src, line, col, error) => {
    console.error('[JS ERROR]', msg, `\n  at ${src}:${line}:${col}`, error);
    lib.showError('JS Error', `${msg}\n${src}:${line}`);
    if (lib._maybeAutoReportFrontendError) {
        lib._maybeAutoReportFrontendError(msg, `${src}:${line}:${col}`);
    }
};
window.onunhandledrejection = (e) => {
    console.error('[PROMISE ERROR]', e.reason);
    lib.showError('Promise Error', e.reason);
    if (lib._maybeAutoReportFrontendError) {
        lib._maybeAutoReportFrontendError(String(e.reason), 'unhandled_promise');
    }
};

// Cat Loading Animation
lib.loadingOverlay = null;
lib.loadingInterval = null;
lib.catEmojis = ['🐱', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾', '🐈', '🐈‍⬛'];
lib.loadingTexts = ['thinking', 'doodling', 'miauing', 'purring', 'napping', 'stretching', 'hunting', 'grooming', 'exploring', 'sneaking'];

lib.showLoading = function(customText, options = {}) {
    if (lib.loadingOverlay) return;

    const { onCancel, warning, showProgress } = options;

    lib.loadingOverlay = document.createElement('div');
    lib.loadingOverlay.id = 'catLoadingOverlay';
    lib.loadingOverlay.innerHTML = `
        <style>
            #catLoadingOverlay {
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background: rgba(0,0,0,0.7); z-index: 9999;
                display: flex; flex-direction: column; align-items: center; justify-content: center;
            }
            #catLoadingOverlay .cat-container {
                position: relative; width: 200px; height: 150px; overflow: hidden;
            }
            #catLoadingOverlay .floating-cat {
                position: absolute; font-size: 2rem;
                animation: floatUp 2s ease-out forwards;
            }
            @keyframes floatUp {
                0% { transform: translateY(100px) scale(0.5); opacity: 0; }
                20% { opacity: 1; }
                80% { opacity: 1; }
                100% { transform: translateY(-100px) scale(1.2); opacity: 0; }
            }
            #catLoadingOverlay .loading-text {
                font-size: 1.5rem; color: white; margin-top: 20px;
                font-family: monospace;
            }
            #catLoadingOverlay .dots::after {
                content: ''; animation: dots 1.5s infinite;
            }
            @keyframes dots {
                0%, 20% { content: '.'; }
                40% { content: '..'; }
                60%, 100% { content: '...'; }
            }
            #catLoadingOverlay .loading-progress {
                color: #888; font-size: 1rem; margin-top: 10px;
            }
            #catLoadingOverlay .loading-warning {
                color: #f85149; font-size: 1rem; font-weight: bold; margin-top: 15px;
            }
            #catLoadingOverlay .loading-cancel {
                margin-top: 20px; background: #f85149; color: #fff; border: none;
                padding: 10px 25px; font-size: 1rem; cursor: pointer; border-radius: 5px;
            }
            #catLoadingOverlay progress {
                width: 250px; height: 12px; margin-top: 10px;
            }
        </style>
        <div class="cat-container" id="catContainer"></div>
        <div class="loading-text"><span id="loadingTextContent">thinking</span><span class="dots"></span></div>
        ${showProgress ? '<div class="loading-progress" id="loadingProgress">0 / 0</div><progress id="loadingProgressBar" value="0" max="100"></progress>' : ''}
        ${warning ? `<div class="loading-warning">${warning}</div>` : ''}
        ${onCancel ? '<button class="loading-cancel" id="loadingCancelBtn">Abbrechen</button>' : ''}
    `;
    document.body.appendChild(lib.loadingOverlay);

    if (onCancel) {
        lib.loadingOverlay.querySelector('#loadingCancelBtn').onclick = onCancel;
    }

    // Spawn cats
    const container = lib.loadingOverlay.querySelector('#catContainer');
    const spawnCat = () => {
        const cat = document.createElement('span');
        cat.className = 'floating-cat';
        cat.textContent = lib.catEmojis[Math.floor(Math.random() * lib.catEmojis.length)];
        cat.style.left = (Math.random() * 160 + 20) + 'px';
        container.appendChild(cat);
        setTimeout(() => cat.remove(), 2000);
    };
    spawnCat();
    lib.loadingInterval = setInterval(spawnCat, 400);

    // Change text
    const textEl = lib.loadingOverlay.querySelector('#loadingTextContent');
    if (customText) {
        textEl.textContent = customText;
    } else {
        const changeText = () => {
            textEl.textContent = lib.loadingTexts[Math.floor(Math.random() * lib.loadingTexts.length)];
        };
        lib.textInterval = setInterval(changeText, 2000);
    }
};

lib.updateLoadingProgress = function(current, total) {
    if (!lib.loadingOverlay) return;
    const progressEl = lib.loadingOverlay.querySelector('#loadingProgress');
    const barEl = lib.loadingOverlay.querySelector('#loadingProgressBar');
    if (progressEl) progressEl.textContent = `${current} / ${total}`;
    if (barEl) barEl.value = total > 0 ? Math.round((current / total) * 100) : 0;
};

lib.hideLoading = function() {
    if (lib.loadingInterval) { clearInterval(lib.loadingInterval); lib.loadingInterval = null; }
    if (lib.textInterval) { clearInterval(lib.textInterval); lib.textInterval = null; }
    if (lib.loadingOverlay) { lib.loadingOverlay.remove(); lib.loadingOverlay = null; }
};

// see the desktop implementation for reference
lib.setToggleDevToolsShortcutEventListener = async () => {   // does not work...!!!!!
        document.addEventListener('keydown', async (event) => {
            if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
                event.preventDefault();

                // Use webview binding to call native function
                if (window.__TAURI__) {
                    try {
                        await window.__TAURI__.core.invoke('toggle_devtools');
                    } catch (e) {
                        console.log('DevTools toggle not available:', e);
                    }
                }else{
                    alert("DevTools toggle is only available in the desktop application.");
                }
            }
        });
    };

// =============================================================================
// Fetcher Progress Bar - Shows background fetcher status in header
// =============================================================================

lib.fetcherPollingInterval = null;
lib.fetcherLastStatus = null;

// Create and inject the progress bar HTML into the page
lib.initFetcherProgressBar = function() {
    if (document.getElementById('fetcher-progress-bar')) return; // Already exists

    const bar = document.createElement('div');
    bar.id = 'fetcher-progress-bar';
    bar.style.cssText = `
        display: none;
        height: 18px;
        background: #2d2d2d;
        cursor: pointer;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        z-index: 1001;
        font-size: 11px;
        line-height: 18px;
        overflow: hidden;
    `;

    bar.innerHTML = `
        <div id="fetcher-progress-fill" style="
            height: 100%;
            transition: width 0.3s ease;
            width: 0%;
        "></div>
        <span id="fetcher-progress-text" style="
            position: absolute;
            left: 50%;
            top: 0;
            transform: translateX(-50%);
            color: whitesmoke;
            white-space: nowrap;
        "></span>
    `;

    document.body.insertBefore(bar, document.body.firstChild);

    // Adjust header and body padding when bar is visible
    lib._updateFetcherBarLayout();
};

lib._updateFetcherBarLayout = function() {
    const fetcherBar = document.getElementById('fetcher-progress-bar');
    const quickFetchDoneBar = document.getElementById('quick-fetch-done-bar');
    const updateBar = document.getElementById('update-available-bar');
    const header = document.querySelector('body > header');
    if (!header) return;

    let topOffset = 0;
    if (fetcherBar && fetcherBar.style.display !== 'none') topOffset += 18;
    if (quickFetchDoneBar && quickFetchDoneBar.style.display !== 'none') {
        quickFetchDoneBar.style.top = topOffset + 'px';
        topOffset += 24;
    }
    if (updateBar && updateBar.style.display !== 'none') {
        updateBar.style.top = topOffset + 'px';
        topOffset += 28;
    }
    header.style.top = topOffset + 'px';
    document.body.style.paddingTop = (55 + topOffset) + 'px';
};

lib._getFetcherColor = function() {
    // Use the current theme color
    const rgb = lib.primaryColorRGB || [32, 178, 170];
    return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
};

lib._lastDbTaskCheckTime = 0;
lib._lastDbTaskCount = 0;
lib._rateLimitDialogShown = false;

lib._showRateLimitDialog = function() {
    if (lib._rateLimitDialogShown) return;
    lib._rateLimitDialogShown = true;
    const dlg = document.createElement('dialog');
    dlg.style.cssText = 'max-width:480px; padding:24px; border-radius:8px;';
    dlg.innerHTML = `
        <h3 style="color:#d29922; margin-top:0;">Session expired</h3>
        <p style="line-height:1.6;">
            Skool is rejecting requests (HTTP 202). This usually means your session cookies have expired.
        </p>
        <p style="line-height:1.6;"><b>To fix this:</b></p>
        <ol style="line-height:1.8;">
            <li>Open the Skool website in your browser</li>
            <li>Log out of your account</li>
            <li>Log back in</li>
            <li>Re-import your cookies in Settings</li>
        </ol>
        <p style="color:#8b949e; font-size:0.9em;">
            Paused phases will automatically retry after 1 hour, or you can restart the fetcher after updating your cookies.
        </p>
        <div style="text-align:right; margin-top:16px;">
            <button onclick="window.location.href='/settings.html'" style="margin-right:8px; padding:6px 16px; background:#0f3460; color:white; border:none; border-radius:4px; cursor:pointer;">Go to Settings</button>
            <button onclick="this.closest('dialog').close()" style="padding:6px 16px; background:#e94560; color:white; border:none; border-radius:4px; cursor:pointer;">OK</button>
        </div>
    `;
    document.body.appendChild(dlg);
    lib.addEventHandlerToCloseDialogByClickingOutside(dlg);
    dlg.addEventListener('close', () => dlg.remove());
    dlg.showModal();
};

lib.pollFetcherStatus = async function() {
    try {
        const res = await fetch('/api/fetcher/status');
        const status = await res.json();

        // Detect running -> idle transition for rollback notice BEFORE updating cache
        const wasRunning = lib.fetcherLastStatus && lib.fetcherLastStatus.status === 'running';
        lib.fetcherLastStatus = status;

        // Show rollback notice when fetcher transitions from running to idle with a rollback error
        if (wasRunning && status.status === 'idle' && status.lastRollbackError) {
            const noticeEl = document.getElementById('bgfetcher-rollback-notice');
            if (noticeEl) {
                noticeEl.innerHTML = '<span style="color:#d29922;font-size:0.9em">Refresh failed. Showing last loaded data.</span>';
                noticeEl.style.display = 'block';
            }
        } else if (status.status === 'running') {
            // Clear rollback notice when a new fetch starts
            const noticeEl = document.getElementById('bgfetcher-rollback-notice');
            if (noticeEl) {
                noticeEl.innerHTML = '';
                noticeEl.style.display = 'none';
            }
        }

        const bar = document.getElementById('fetcher-progress-bar');
        const fill = document.getElementById('fetcher-progress-fill');
        const text = document.getElementById('fetcher-progress-text');
        if (!bar || !fill || !text) return;

        const color = lib._getFetcherColor();
        fill.style.background = color;

        // Use remainingInPhase from fetcher status (in-memory, no DB queries needed)
        const remainingTasks = status.remainingInPhase || 0;
        const totalTasks = status.totalTasks || 0;

        if (status.status === 'running') {
            // Fetcher is running - show progress
            bar.style.display = 'block';
            bar.onclick = () => window.location.href = '/data.html';
            const progress = totalTasks > 0
                ? (status.completedTasks / totalTasks * 100)
                : 0;
            fill.style.width = progress + '%';
            fill.style.opacity = '1';
            text.innerText = `${status.completedTasks}/${totalTasks} ${status.phaseName || 'fetching'}`;
        } else if (status.status === 'idle' && status.fullFetchScheduledAt > 0) {
            // Quick fetch done, waiting for full fetch auto-start
            bar.style.display = 'block';
            bar.onclick = null;
            fill.style.width = '100%';
            fill.style.opacity = '0.3';
            const now = Math.floor(Date.now() / 1000);
            const remaining = status.fullFetchScheduledAt - now;
            if (remaining > 0) {
                const mins = Math.floor(remaining / 60);
                const secs = remaining % 60;
                text.innerText = `Full fetch in ${mins}:${String(secs).padStart(2, '0')}...`;
            } else {
                text.innerText = 'Full fetch starting...';
            }
        } else if (status.status === 'idle' && remainingTasks > 0) {
            // Fetcher is idle but has remaining tasks from last run
            bar.style.display = 'block';
            bar.onclick = lib.startFetcher;
            fill.style.width = '100%';
            fill.style.opacity = '0.3';
            text.innerText = `Tasks available - click to start`;
        } else if (status.status === 'idle') {
            // Fetcher is idle with no in-memory tasks - check DB for pending tasks (throttled)
            const now = Date.now();
            if (now - lib._lastDbTaskCheckTime > 30000) {
                lib._lastDbTaskCheckTime = now;
                try {
                    const taskRes = await fetch('/api/fetch-tasks');
                    const taskData = await taskRes.json();
                    lib._lastDbTaskCount = (taskData.tasks && taskData.tasks.length) || 0;
                } catch (e) {
                    lib._lastDbTaskCount = 0;
                }
            }
            if (lib._lastDbTaskCount > 0) {
                bar.style.display = 'block';
                bar.onclick = lib.startFetcher;
                fill.style.width = '100%';
                fill.style.opacity = '0.3';
                text.innerText = `${lib._lastDbTaskCount} tasks available - click to start`;
            } else {
                bar.style.display = 'none';
                fill.style.opacity = '1';
            }
        } else {
            // Not running and not idle - hide bar
            bar.style.display = 'none';
            fill.style.opacity = '1';
        }

        lib._updateFetcherBarLayout();
    } catch (e) {
        // Silently ignore fetch errors
    }
};

lib.startFetcher = async function() {
    try {
        const res = await fetch('/api/fetcher/start', { method: 'POST' });
        const data = await res.json();
        if (!data.started && data.error) {
            alert('Could not start fetcher: ' + data.error);
        }
        // Poll immediately to update UI
        lib.pollFetcherStatus();
    } catch (e) {
        console.error('Failed to start fetcher:', e);
    }
};

lib.startFetcherPolling = function() {
    lib.initFetcherProgressBar();
    lib.initQuickFetchDoneBar();
    lib._fetcherPollingActive = true;
    lib._pollFetcherLoop();
    lib.checkForUpdates();
};

lib._pollFetcherLoop = async function() {
    if (!lib._fetcherPollingActive) return;
    await lib.pollFetcherStatus();
    if (lib._fetcherPollingActive) {
        lib.fetcherPollingTimeout = setTimeout(lib._pollFetcherLoop, 2000);
    }
};

lib.stopFetcherPolling = function() {
    lib._fetcherPollingActive = false;
    if (lib.fetcherPollingTimeout) {
        clearTimeout(lib.fetcherPollingTimeout);
        lib.fetcherPollingTimeout = null;
    }
};

// --- Update available bar ---

lib.initUpdateBar = function() {
    if (document.getElementById('update-available-bar')) return;

    const bar = document.createElement('div');
    bar.id = 'update-available-bar';
    bar.style.cssText = `
        display: none;
        height: 28px;
        background: #3a2a1a;
        border-bottom: 1px solid #ff9800;
        cursor: pointer;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        z-index: 1000;
        font-size: 12px;
        line-height: 28px;
        text-align: center;
        color: #ff9800;
    `;
    bar.onclick = () => window.location.href = '/upgrade.html';

    document.body.insertBefore(bar, document.body.firstChild);
};

// =============================================================================
// Empty Community Overlay - shown when community has no fetched data yet
// =============================================================================

lib._emptyCommunityOverlay = null;

lib.checkEmptyCommunity = async function() {
    try {
        const res = await fetch('/api/dashboard/summary');
        if (!res.ok) return; // Can't determine state, skip

        const data = await res.json();
        if (data.total_members > 0) return; // Community has data, nothing to do

        // Check if fetcher is already running
        const fetcherRes = await fetch('/api/fetcher/status');
        const fetcherStatus = await fetcherRes.json();
        if (fetcherStatus.status === 'running') return; // Already fetching, no overlay needed

        lib._showEmptyCommunityOverlay();
    } catch (e) {
        console.warn('Empty community check failed:', e);
    }
};

// Phase definitions for the progress list (split into quick + full)
lib._quickFetchPhases = [
    { phase: 0, label: 'Members & Posts (page 1)' },
    { phase: 1, label: 'Members & Posts (remaining pages)' },
    { phase: 2, label: 'Profiles (quick)' },
    { phase: 3, label: 'Comments (quick)' },
    { phase: 4, label: 'Likes (quick)' },
    { phase: 5, label: 'Other Communities (quick)' },
];
lib._fullFetchPhases = [
    { phase: 6, label: 'Chat Channels' },
    { phase: 7, label: 'Chat Messages' },
    { phase: 8, label: 'Profiles (full)' },
    { phase: 9, label: 'Comments (full)' },
    { phase: 10, label: 'Likes (full)' },
    { phase: 11, label: 'Other Communities (full)' },
];
lib._fetchPhases = [...lib._quickFetchPhases, ...lib._fullFetchPhases];

lib._ecoPollingInterval = null;

lib._showEmptyCommunityOverlay = function() {
    if (lib._emptyCommunityOverlay) return;

    const overlay = document.createElement('div');
    overlay.id = 'empty-community-overlay';
    overlay.innerHTML = `
        <style>
            #empty-community-overlay {
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background: rgba(0,0,0,0.85); z-index: 10000;
                display: flex; align-items: center; justify-content: center;
            }
            #empty-community-overlay .eco-box {
                background: #1c1c1c; border: 1px solid #333; border-radius: 12px;
                padding: 40px 48px; max-width: 520px; text-align: center;
            }
            #empty-community-overlay .eco-icon {
                font-size: 3.5rem; margin-bottom: 16px;
            }
            #empty-community-overlay h2 {
                color: #e0e0e0 !important; margin: 0 0 12px 0; font-size: 1.4rem;
            }
            #empty-community-overlay p {
                color: #999; line-height: 1.6; margin: 0 0 24px 0; font-size: 0.95rem;
            }
            #empty-community-overlay .eco-btn-start {
                padding: 12px 32px; font-size: 1.05rem; cursor: pointer;
                border: none; border-radius: 6px; font-weight: bold;
                background: var(--theme-color, lightseagreen); color: white !important;
                transition: opacity 0.2s;
            }
            #empty-community-overlay .eco-btn-start:hover { opacity: 0.85; }
            #empty-community-overlay .eco-btn-start:disabled {
                opacity: 0.5; cursor: default;
            }
            #empty-community-overlay .eco-dismiss {
                display: block; margin: 16px auto 0; font-size: 0.8rem;
                color: #666; cursor: pointer; background: none; border: none;
            }
            #empty-community-overlay .eco-dismiss:hover { color: #999 !important; }
            #empty-community-overlay .eco-progress-bar {
                width: 100%; height: 8px; background: #333; border-radius: 4px;
                overflow: hidden; margin: 16px 0 20px;
            }
            #empty-community-overlay .eco-progress-fill {
                height: 100%; width: 0%; border-radius: 4px;
                background: var(--theme-color, lightseagreen);
                transition: width 0.5s ease;
            }
            #empty-community-overlay .eco-phase-list {
                text-align: left; list-style: none; padding: 0; margin: 0 0 8px;
                font-size: 0.85rem; line-height: 2;
            }
            #empty-community-overlay .eco-phase-list li {
                display: flex; align-items: center; gap: 8px;
            }
            #empty-community-overlay .eco-phase-dot {
                width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
            }
            #empty-community-overlay .eco-phase-pending .eco-phase-dot { background: #444; }
            #empty-community-overlay .eco-phase-pending { color: #555; }
            #empty-community-overlay .eco-phase-active .eco-phase-dot {
                background: var(--theme-color, lightseagreen);
                box-shadow: 0 0 6px var(--theme-color, lightseagreen);
            }
            #empty-community-overlay .eco-phase-active { color: #e0e0e0; font-weight: bold; }
            #empty-community-overlay .eco-phase-done .eco-phase-dot { background: #2ea043; }
            #empty-community-overlay .eco-phase-done { color: #888; }
            #empty-community-overlay .eco-task-info {
                color: #666; font-size: 0.78rem; font-weight: normal; margin-left: 4px;
            }
        </style>
        <div class="eco-box">
            <div class="eco-icon">📦</div>
            <h2>Your community database is empty</h2>
            <div id="eco-content-area">
                <p>
                    No data has been fetched for this community yet.<br>
                    Start fetching now — the most important data (members &amp; posts)
                    will be ready in about <b>20 minutes</b>.
                </p>
                <button class="eco-btn-start" id="eco-start-btn" onclick="lib._startFetchFromOverlay()">
                    Start Fetching Now
                </button>
            </div>
            <button class="eco-dismiss" onclick="lib._dismissEmptyCommunityOverlay()">dismiss</button>
        </div>
    `;
    document.body.appendChild(overlay);
    lib._emptyCommunityOverlay = overlay;
};

lib._startFetchFromOverlay = async function() {
    const btn = document.getElementById('eco-start-btn');
    if (btn) { btn.disabled = true; btn.textContent = 'Starting...'; }

    try {
        const res = await fetch('/api/fetcher/start', { method: 'POST' });
        const data = await res.json();
        if (!data.started && data.error) {
            if (btn) { btn.disabled = false; btn.textContent = 'Start Fetching Now'; }
            alert('Could not start fetcher: ' + data.error);
            return;
        }
    } catch (e) {
        if (btn) { btn.disabled = false; btn.textContent = 'Start Fetching Now'; }
        alert('Failed to start fetcher');
        return;
    }

    // Replace content with progress view (only quick fetch phases 0-5)
    const area = document.getElementById('eco-content-area');
    if (area) {
        let phaseListHtml = '';
        for (const p of lib._quickFetchPhases) {
            phaseListHtml += `<li id="eco-phase-${p.phase}" class="eco-phase-pending"><span class="eco-phase-dot"></span><span class="eco-phase-label">${p.label}</span></li>`;
        }
        area.innerHTML = `
            <p style="color:#aaa; margin-bottom: 12px; font-size: 0.9rem;">
                Fetching your community's core data...
            </p>
            <div class="eco-progress-bar"><div class="eco-progress-fill" id="eco-progress-fill"></div></div>
            <ul class="eco-phase-list" id="eco-phase-list">${phaseListHtml}</ul>
        `;
    }

    // Start polling fetcher status to update the progress UI
    lib._ecoUpdateProgress(); // immediate first tick
    lib._ecoPollingInterval = setInterval(lib._ecoUpdateProgress, 2000);
};

lib._ecoUpdateProgress = async function() {
    try {
        const res = await fetch('/api/fetcher/status');
        const s = await res.json();

        const fill = document.getElementById('eco-progress-fill');

        // Quick Fetch completion: overlay auto-close + page reload
        if (s.quickFetchComplete || s.phaseName === 'quick_done') {
            // Stop polling
            if (lib._ecoPollingInterval) {
                clearInterval(lib._ecoPollingInterval);
                lib._ecoPollingInterval = null;
            }
            // Mark all quick phases as done, bar to 100%
            for (const p of lib._quickFetchPhases) {
                const li = document.getElementById('eco-phase-' + p.phase);
                if (li) {
                    li.className = 'eco-phase-done';
                    li.querySelector('.eco-phase-label').textContent = p.label;
                }
            }
            if (fill) fill.style.width = '100%';
            // Show "Done!" message
            const msgP = document.querySelector('#eco-content-area > p');
            if (msgP) msgP.textContent = 'Done! Loading your data...';
            // Store info for post-reload notification bar
            sessionStorage.setItem('showQuickFetchDoneBar', JSON.stringify({
                fullFetchScheduledAt: s.fullFetchScheduledAt || 0,
                ts: Math.floor(Date.now() / 1000)
            }));
            // Auto-close overlay and reload after 2s
            setTimeout(() => {
                lib._dismissEmptyCommunityOverlay();
                window.location.reload();
            }, 2000);
            return;
        }

        // Update progress bar (based on quick phases only in overlay)
        if (fill) {
            const totalPhases = lib._quickFetchPhases.length;
            let completedPhases = 0;
            for (const p of lib._quickFetchPhases) {
                if (s.phase > p.phase) completedPhases++;
            }
            const currentFraction = (s.totalTasks > 0)
                ? (s.completedTasks / s.totalTasks)
                : 0;
            const pct = ((completedPhases + currentFraction) / totalPhases) * 100;
            fill.style.width = Math.min(pct, 100) + '%';
        }

        // Update phase list items (quick phases only in overlay)
        for (const p of lib._quickFetchPhases) {
            const li = document.getElementById('eco-phase-' + p.phase);
            if (!li) continue;

            if (s.phase > p.phase) {
                li.className = 'eco-phase-done';
                li.querySelector('.eco-phase-label').innerHTML = p.label;
            } else if (s.phase === p.phase && s.status === 'running') {
                li.className = 'eco-phase-active';
                const info = s.currentTaskInfo ? ` <span class="eco-task-info">${s.completedTasks}/${s.totalTasks} — ${s.currentTaskInfo}</span>` : '';
                li.querySelector('.eco-phase-label').innerHTML = p.label + info;
            } else {
                li.className = 'eco-phase-pending';
                li.querySelector('.eco-phase-label').textContent = p.label;
            }
        }

        // If fetcher stopped/idle without quick_done (e.g. manual stop), stop polling
        if (s.status === 'idle' && s.phase === -1 && !s.quickFetchComplete) {
            if (lib._ecoPollingInterval) {
                clearInterval(lib._ecoPollingInterval);
                lib._ecoPollingInterval = null;
            }
            if (fill) fill.style.width = '100%';
        }
    } catch (e) {
        // Silently ignore polling errors
    }
};

lib._dismissEmptyCommunityOverlay = function() {
    if (lib._ecoPollingInterval) {
        clearInterval(lib._ecoPollingInterval);
        lib._ecoPollingInterval = null;
    }
    if (lib._emptyCommunityOverlay) {
        lib._emptyCommunityOverlay.remove();
        lib._emptyCommunityOverlay = null;
    }
};

// --- Quick Fetch Done notification bar ---

lib.initQuickFetchDoneBar = function() {
    const stored = sessionStorage.getItem('showQuickFetchDoneBar');
    if (!stored) return;

    let info;
    try { info = JSON.parse(stored); } catch (e) { return; }
    sessionStorage.removeItem('showQuickFetchDoneBar');

    const scheduledAt = info.fullFetchScheduledAt || 0;
    if (scheduledAt <= 0) return;

    // Create the green notification bar
    const bar = document.createElement('div');
    bar.id = 'quick-fetch-done-bar';
    bar.style.cssText = `
        position: fixed; top: 0; left: 0; width: 100%; z-index: 9999;
        background: #2ea043; color: white; text-align: center;
        font-size: 0.82rem; padding: 4px 0; cursor: pointer;
        height: 24px; line-height: 24px;
    `;
    bar.onclick = () => { bar.style.display = 'none'; lib._updateFetcherBarLayout(); };
    document.body.insertBefore(bar, document.body.firstChild);

    const updateCountdown = () => {
        const now = Math.floor(Date.now() / 1000);
        const remaining = scheduledAt - now;
        if (remaining <= 0) {
            bar.textContent = 'Basic data loaded! Full fetch starting now...';
            // Auto-hide 10s after full fetch starts
            setTimeout(() => {
                bar.style.display = 'none';
                lib._updateFetcherBarLayout();
            }, 10000);
            return;
        }
        const mins = Math.floor(remaining / 60);
        const secs = remaining % 60;
        const timeStr = mins > 0 ? `${mins}:${String(secs).padStart(2, '0')}` : `${secs}s`;
        bar.textContent = `Basic data loaded! Full details fetching in ${timeStr}...`;
    };

    updateCountdown();
    const countdownInterval = setInterval(() => {
        const now = Math.floor(Date.now() / 1000);
        if (now >= scheduledAt) {
            clearInterval(countdownInterval);
            updateCountdown(); // show "starting now"
        } else {
            updateCountdown();
        }
    }, 1000);

    lib._updateFetcherBarLayout();
};

// --- Update available bar ---

lib.checkForUpdates = async function() {
    try {
        lib.initUpdateBar();
        const resp = await fetch('/api/version');
        const data = await resp.json();
        const bar = document.getElementById('update-available-bar');
        if (!bar) return;

        // Server is still fetching remote version in background - retry after a few seconds
        if (data.checking) {
            setTimeout(() => lib.checkForUpdates(), 5000);
            return;
        }

        if (data.updateAvailable) {
            bar.textContent = 'New version v' + data.latest + ' available — click here to update';
            bar.style.display = 'block';
        } else {
            bar.style.display = 'none';
        }
        lib._updateFetcherBarLayout();
    } catch (e) {
        // Silently ignore
    }
};

// =============================================================================
// Bug Report Dialog — accessible from every page via floating button
// =============================================================================

lib._reportDialog = null;

lib.initBugReportButton = function() {
    if (document.getElementById('bug-report-fab')) return;

    const fab = document.createElement('button');
    fab.id = 'bug-report-fab';
    fab.title = 'Report a bug or give feedback';
    fab.innerHTML = '&#x1F41E;';
    fab.style.cssText = `
        position:fixed; bottom:20px; right:20px; z-index:9998;
        width:40px; height:40px; border-radius:50%;
        background:#2d2d2d; border:1px solid #444;
        font-size:1.2rem; cursor:pointer;
        display:flex; align-items:center; justify-content:center;
        transition:background 0.2s; line-height:1;
    `;
    fab.onmouseenter = () => fab.style.background = '#3f3d3d';
    fab.onmouseleave = () => fab.style.background = '#2d2d2d';
    fab.onclick = lib.showBugReportDialog;
    document.body.appendChild(fab);
};

// =============================================================================
// Toast notifications
// =============================================================================

lib.showToast = function(message, durationMs = 4000) {
    const toast = document.createElement('div');
    toast.style.cssText = `
        position:fixed; bottom:70px; right:20px; z-index:10000;
        background:#1a1a2e; color:#e0e0e0; border:1px solid #444;
        padding:10px 16px; border-radius:8px; font-size:0.85rem;
        max-width:400px; word-break:break-all;
        box-shadow:0 4px 12px rgba(0,0,0,0.4);
        opacity:0; transition:opacity 0.3s;
    `;
    toast.textContent = message;
    document.body.appendChild(toast);
    requestAnimationFrame(() => toast.style.opacity = '1');
    setTimeout(() => {
        toast.style.opacity = '0';
        setTimeout(() => toast.remove(), 300);
    }, durationMs);
};

// =============================================================================
// Screenshot FAB (requires snapdom loaded globally)
// =============================================================================

lib.initScreenshotButton = function() {
    if (document.getElementById('screenshot-fab')) return;
    if (typeof snapdom === 'undefined') {
        console.warn('snapdom not loaded, screenshot button disabled');
        return;
    }

    const fab = document.createElement('button');
    fab.id = 'screenshot-fab';
    fab.title = 'Take screenshot of this page';
    fab.innerHTML = '&#x1F4F7;';
    fab.style.cssText = `
        position:fixed; bottom:20px; right:70px; z-index:9998;
        width:40px; height:40px; border-radius:50%;
        background:#2d2d2d; border:1px solid #444;
        font-size:1.2rem; cursor:pointer;
        display:flex; align-items:center; justify-content:center;
        transition:background 0.2s; line-height:1;
    `;
    fab.onmouseenter = () => fab.style.background = '#3f3d3d';
    fab.onmouseleave = () => fab.style.background = '#2d2d2d';
    fab.onclick = lib.takeScreenshot;
    document.body.appendChild(fab);
};

lib.takeScreenshot = async function() {
    const fab = document.getElementById('screenshot-fab');
    if (fab) { fab.disabled = true; fab.style.opacity = '0.5'; }

    try {
        const result = await snapdom(document.body);
        const blob = await result.toBlob({ type: 'image/png' });

        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        const page = location.pathname.split('/').pop().replace('.html', '') || 'page';
        const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
        const filename = `catknows-${page}-${ts}.png`;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);

        const savePath = `Downloads/${filename}`;
        try { await navigator.clipboard.writeText(savePath); } catch (_) {}
        lib.showToast(`Screenshot saved to ${savePath} (path copied)`);
    } catch (e) {
        console.error('Screenshot failed:', e);
        lib.showToast('Screenshot failed: ' + e.message);
    } finally {
        if (fab) { fab.disabled = false; fab.style.opacity = '1'; }
    }
};

lib.showBugReportDialog = function() {
    if (lib._reportDialog) {
        // Reset form state when re-opening
        const desc = document.getElementById('report-description');
        if (desc) desc.value = '';
        const img1 = document.getElementById('report-image-1');
        const img2 = document.getElementById('report-image-2');
        if (img1) img1.value = '';
        if (img2) img2.value = '';
        const preview = document.getElementById('report-image-preview');
        if (preview) preview.innerHTML = '';
        const status = document.getElementById('report-status');
        if (status) status.textContent = '';
        const btn = document.getElementById('report-submit-btn');
        if (btn) { btn.disabled = false; btn.textContent = 'Send Report'; }
        lib._reportDialog.showModal();
        return;
    }

    const dialog = document.createElement('dialog');
    dialog.id = 'bug-report-dialog';
    dialog.style.cssText = `
        max-width:520px; width:90vw; border:1px solid #444;
        border-radius:10px; background:#1c1c1c; color:#e0e0e0;
        padding:24px;
    `;
    dialog.innerHTML = `
        <h3 style="margin:0 0 16px;">Report a Bug / Give Feedback</h3>
        <p style="font-size:0.85em; color:#888; margin-bottom:16px;">
            Describe what happened. System info and recent logs will be attached automatically.
        </p>
        <textarea id="report-description" rows="5" placeholder="What went wrong? What did you expect to happen?"
            style="width:100%; box-sizing:border-box; resize:vertical;
                   background:#2d2d2d; color:#e0e0e0; border:1px solid #374151;
                   border-radius:4px; padding:8px; font-family:inherit;
                   font-size:0.95em;"></textarea>
        <div style="margin-top:12px;">
            <label style="font-size:0.9em;">Attach screenshots (optional, up to 2):</label>
            <div style="display:flex;gap:8px;margin-top:6px;align-items:center;">
                <input type="file" id="report-image-1" accept="image/*"
                       style="font-size:0.85em; flex:1;"
                       onchange="lib._updateReportImagePreviews()">
                <input type="file" id="report-image-2" accept="image/*"
                       style="font-size:0.85em; flex:1;"
                       onchange="lib._updateReportImagePreviews()">
            </div>
            <div id="report-image-preview" style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap;"></div>
        </div>
        <div id="report-status" style="margin-top:12px; font-size:0.85em; min-height:1.2em;"></div>
        <div style="display:flex; gap:10px; justify-content:flex-end; margin-top:16px;">
            <button onclick="this.closest('dialog').close()"
                style="padding:6px 16px; background:#e94560; color:white; border:none; border-radius:4px; cursor:pointer;">Cancel</button>
            <button id="report-submit-btn" onclick="lib._submitBugReport()"
                style="padding:6px 16px; background:var(--theme-color, lightseagreen);
                       color:white; border:none; border-radius:4px; font-weight:bold; cursor:pointer;">
                Send Report
            </button>
        </div>
    `;
    document.body.appendChild(dialog);
    lib.addEventHandlerToCloseDialogByClickingOutside(dialog);
    lib._reportDialog = dialog;
    dialog.showModal();
};

lib._updateReportImagePreviews = function() {
    const preview = document.getElementById('report-image-preview');
    preview.innerHTML = '';
    const inputs = [document.getElementById('report-image-1'), document.getElementById('report-image-2')];
    for (const input of inputs) {
        if (!input || !input.files || input.files.length === 0) continue;
        const file = input.files[0];
        if (file.size > 2 * 1024 * 1024) {
            input.value = '';
            preview.innerHTML = '<span style="color:#c44;">Each image must be under 2 MB</span>';
            return;
        }
        const img = document.createElement('img');
        img.style.cssText = 'max-width:80px;max-height:60px;border-radius:4px;border:1px solid #444;';
        const reader = new FileReader();
        reader.onload = (e) => { img.src = e.target.result; };
        reader.readAsDataURL(file);
        preview.appendChild(img);
    }
};

lib._fileToBase64 = function(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsDataURL(file);
    });
};

lib._submitBugReport = async function() {
    const btn = document.getElementById('report-submit-btn');
    const status = document.getElementById('report-status');
    const description = document.getElementById('report-description').value.trim();

    if (!description) {
        status.innerHTML = '<span style="color:#c44;">Please describe the issue.</span>';
        return;
    }

    btn.disabled = true;
    btn.textContent = 'Sending...';
    status.textContent = 'Collecting diagnostics and sending...';

    const images = [];
    const imgInputs = [document.getElementById('report-image-1'), document.getElementById('report-image-2')];
    for (const inp of imgInputs) {
        if (inp && inp.files && inp.files.length > 0) {
            const base64 = await lib._fileToBase64(inp.files[0]);
            images.push(base64);
        }
    }

    try {
        const res = await fetch('/api/report/bug', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ description, images })
        });
        const data = await res.json();

        if (res.ok && data.status === 'ok') {
            status.innerHTML = '<span style="color:#2ea043;">Report sent successfully. Thank you!</span>';
            btn.textContent = 'Sent!';
            setTimeout(() => {
                const dlg = document.getElementById('bug-report-dialog');
                if (dlg && dlg.open) dlg.close();
            }, 2000);
        } else {
            status.innerHTML = `<span style="color:#c44;">${data.message || 'Upload failed'}</span>`;
            btn.disabled = false;
            btn.textContent = 'Send Report';
        }
    } catch (e) {
        status.innerHTML = '<span style="color:#c44;">Network error -- please try again.</span>';
        btn.disabled = false;
        btn.textContent = 'Send Report';
    }
};

// =============================================================================
// Auto Error Reporting (frontend) — opt-in, rate-limited
// =============================================================================

lib._autoErrorReportEnabled = null;   // cached consent value
lib._autoErrorReportCacheTime = 0;    // when consent was last checked
lib._lastAutoErrorReportTime = 0;     // rate limiting

lib._maybeAutoReportFrontendError = function(errorMsg, location) {
    const now = Date.now();

    // Refresh cached consent every 30 seconds
    if (lib._autoErrorReportEnabled === null || (now - lib._autoErrorReportCacheTime) > 30000) {
        lib._autoErrorReportCacheTime = now;
        // Fire-and-forget config check — use cached value until resolved
        ConfigEntry.get('telemetry_auto_error_report').then(val => {
            lib._autoErrorReportEnabled = (val === '1');
        }).catch(() => {
            lib._autoErrorReportEnabled = false;
        });
    }

    if (!lib._autoErrorReportEnabled) return;

    // Rate limit: max 1 report per 5 minutes
    if (now - lib._lastAutoErrorReportTime < 5 * 60 * 1000) return;
    lib._lastAutoErrorReportTime = now;

    // Fire-and-forget POST to backend
    fetch('/api/report/error', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            error_message: String(errorMsg).substring(0, 2000),
            stack_trace: String(location).substring(0, 1000),
            source: 'frontend'
        })
    }).catch(() => {}); // silently ignore send failures
};

// =============================================================================
// Health Warning Banner — checks for system health issues on page load
// =============================================================================

lib._healthBannerDismissed = false;

lib.checkHealthWarnings = async function() {
    if (lib._healthBannerDismissed) return;
    try {
        const res = await fetch('/api/health/warnings');
        if (!res.ok) return;
        const data = await res.json();
        if (!data.warnings || data.warnings.length === 0) return;

        // Remove existing banner if any
        const existing = document.getElementById('health-warning-banner');
        if (existing) existing.remove();

        const hasCritical = data.warnings.some(w => w.level === 'critical');
        const banner = document.createElement('div');
        banner.id = 'health-warning-banner';
        banner.style.cssText = `
            position:fixed; top:50px; left:0; right:0; z-index:9997;
            padding:8px 16px; font-size:0.85em;
            background:${hasCritical ? '#7f1d1d' : '#78350f'};
            color:${hasCritical ? '#fca5a5' : '#fde68a'};
            display:flex; align-items:center; justify-content:space-between;
        `;
        const msgs = data.warnings.map(w => w.message).join(' | ');
        banner.innerHTML = `
            <span>${msgs}</span>
            <button onclick="this.parentElement.remove(); lib._healthBannerDismissed = true;"
                style="background:none; border:none; color:inherit; cursor:pointer; font-size:1.1em; padding:0 4px;">X</button>
        `;
        document.body.appendChild(banner);
    } catch (e) {
        // Silently ignore
    }
};

// =============================================================================
// Auto-init on DOMContentLoaded
// =============================================================================

document.addEventListener('DOMContentLoaded', () => {
    lib.initBugReportButton();
    lib.initScreenshotButton();
    lib.checkHealthWarnings();
});

