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 () => {
    let communityListData; const communitiesKey = "communities";
    let name = await ConfigEntry.get(communitiesKey);
    communityListData = name ? name : "[]";
    try { communityListData = JSON.parse(communityListData);
    } catch (e){ lib.error(e, communityListData); }
    communityListData = Array.from(communityListData.filter(c => c !== ""));
    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 (_) {}
            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()">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 - immer auch in Konsole loggen
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}`);
};
window.onunhandledrejection = (e) => {
    console.error('[PROMISE ERROR]', e.reason);
    lib.showError('Promise Error', e.reason);
};

// 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 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 (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; cursor:pointer;">Go to Settings</button>
            <button onclick="this.closest('dialog').close()" style="padding:6px 16px; 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' && 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._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
lib._fetchPhases = [
    { 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)' },
    { 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._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
    const area = document.getElementById('eco-content-area');
    if (area) {
        let phaseListHtml = '';
        for (const p of lib._fetchPhases) {
            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;">
                Grab a drink and a snack — come back in 20 minutes.
            </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();

        // Update progress bar
        const fill = document.getElementById('eco-progress-fill');
        if (fill) {
            // Overall progress: treat phases 0..11 (12 phases total)
            // Completed phases + fraction of current phase
            const totalPhases = lib._fetchPhases.length;
            let completedPhases = 0;
            for (const p of lib._fetchPhases) {
                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
        for (const p of lib._fetchPhases) {
            const li = document.getElementById('eco-phase-' + p.phase);
            if (!li) continue;

            if (s.status === 'idle' && s.phase === -1) {
                // All done
                li.className = 'eco-phase-done';
                li.querySelector('.eco-phase-label').textContent = p.label;
            } else 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 finished, stop polling and show done state
        if (s.status === 'idle' && s.phase === -1) {
            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;
    }
};

// --- 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
    }
};

