/**
 * CapiFlow — Dashboard Charts & AJAX.
 *
 * Handles:
 *  - 4 AJAX data fetchers: KPI, Funnel, Table, Live Events (15s poll)
 *  - Chart.js instances: Funnel bar chart + Recovery doughnut
 *  - SVG Confidence Gauge animation
 *  - Smart polling (Visibility API — pauses when tab hidden)
 *  - Empty state + skeleton reveal logic
 *  - Meta Integration Health table rendering
 *  - Smart Insights generation from KPI data
 *
 * JS Object: capiflowDashboard (from wp_localize_script in Admin_Settings)
 *   .ajaxUrl       — wp-admin/admin-ajax.php
 *   .nonce         — capiflow_dashboard nonce
 *   .setupComplete — bool
 *   .currency      — WC currency symbol
 *   .i18n          — translation strings
 *
 * @package CapiFlow
 */
/* global capiflowDashboard, Chart */
(function ($) {
    'use strict';

    if (typeof capiflowDashboard === 'undefined') {
        return;
    }

    var cfg         = capiflowDashboard;
    var nonce       = cfg.nonce;
    var ajaxUrl     = cfg.ajaxUrl;
    var currency    = cfg.currency || '$';
    var liveInterval = null;
    var funnelChart  = null;
    var donutChart   = null;
    var destroyed    = false;

    // ── Helpers ──────────────────────────────────────────────────────────────

    function numFmt(n) {
        if (typeof n !== 'number' && typeof n !== 'string') return '—';
        var num = parseFloat(n);
        if (isNaN(num)) return '—';
        return num.toLocaleString(undefined, { maximumFractionDigits: 0 });
    }

    function moneyFmt(n) {
        if (typeof n === 'undefined' || n === null) return '—';
        var num = parseFloat(n);
        if (isNaN(num)) return '—';
        return currency + num.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    }

    function getPeriod() {
        var sel = document.getElementById('capiflow-period-selector');
        return sel ? parseInt(sel.value, 10) : 30;
    }

    function getEl(idOrEl) { return (typeof idOrEl === 'string') ? document.getElementById(idOrEl) : idOrEl; }
    function show(idOrEl)  { var el = getEl(idOrEl); if (el) el.style.display = ''; }
    function hide(idOrEl)  { var el = getEl(idOrEl); if (el) el.style.display = 'none'; }
    function block(idOrEl) { var el = getEl(idOrEl); if (el) el.style.display = 'block'; }
    function flex(idOrEl)  { var el = getEl(idOrEl); if (el) el.style.display = 'flex'; }

    function setText(id, value) {
        var el = document.getElementById(id);
        if (el) el.textContent = value;
    }

    /** Animates a number from 0 to target over ~600ms. */
    function animateNumber(el, target, formatter) {
        if (!el) return;
        var start = 0;
        var steps = 20;
        var step  = target / steps;
        var i     = 0;
        var t     = setInterval(function () {
            i++;
            start += step;
            el.textContent = formatter ? formatter(Math.round(start)) : Math.round(start).toLocaleString();
            if (i >= steps) {
                el.textContent = formatter ? formatter(target) : target.toLocaleString();
                clearInterval(t);
            }
        }, 30);
    }

    // ── Skeleton → Data reveal ────────────────────────────────────────────────

    function revealKPI(wrapId, skeletonId, dataId, emptyId, hasData) {
        hide(skeletonId);
        if (hasData) {
            show(dataId);
            hide(emptyId);
        } else {
            hide(dataId);
            block(emptyId);
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 1. KPI DATA
    // ─────────────────────────────────────────────────────────────────────────

    function loadKPIData() {
        $.post(ajaxUrl, { action: 'capiflow_get_kpi_data', nonce: nonce, days: getPeriod() })
            .done(function (res) {
                // Always hide all skeletons first.
                hide('capiflow-kpi-total-skeleton');
                hide('capiflow-kpi-rescued-skeleton');
                hide('capiflow-kpi-revenue-skeleton');
                hide('capiflow-kpi-emq-skeleton');

                if (!res.success || !res.data) {
                    block('capiflow-kpi-total-empty');
                    block('capiflow-kpi-rescued-empty');
                    block('capiflow-kpi-revenue-empty');
                    block('capiflow-kpi-emq-empty');
                    return;
                }

                var d = res.data;

                // ── Total Events ───────────────────────────────────────────
                if (d.total_events > 0) {
                    show(document.querySelector('#capiflow-kpi-total .capiflow-kpi-data'));
                    animateNumber(document.getElementById('kpi-total-events'), d.total_events);
                } else {
                    block('capiflow-kpi-total-empty');
                }

                // ── Rescued Events ─────────────────────────────────────────
                if (d.rescued_events > 0) {
                    show(document.querySelector('#capiflow-kpi-rescued .capiflow-kpi-data'));
                    animateNumber(document.getElementById('kpi-rescued-events'), d.rescued_events);
                    var rescuedPct = d.total_events > 0
                        ? ((d.rescued_events / d.total_events) * 100).toFixed(1) + '% of total'
                        : '';
                    setText('kpi-rescued-sub', rescuedPct);
                } else {
                    block('capiflow-kpi-rescued-empty');
                }

                // ── Rescued Revenue ────────────────────────────────────────
                var revNum = parseFloat(d.rescued_revenue || 0);
                if (revNum > 0) {
                    show(document.querySelector('#capiflow-kpi-revenue .capiflow-kpi-data'));
                    animateNumber(
                        document.getElementById('kpi-rescued-revenue'),
                        revNum,
                        function(v) { return currency + v.toLocaleString(); }
                    );
                    setText('kpi-revenue-sub', 'Last ' + (d.period_days || 30) + ' days');
                } else {
                    block('capiflow-kpi-revenue-empty');
                }

                // ── EMQ Score ──────────────────────────────────────────────
                if (d.emq !== null && d.emq !== undefined) {
                    show(document.querySelector('#capiflow-kpi-emq .capiflow-kpi-data'));
                    var emqEl    = document.getElementById('kpi-emq-score');
                    var emqColor = d.emq >= 7 ? '#10b981' : (d.emq >= 5 ? '#f59e0b' : '#ef4444');
                    if (emqEl) {
                        emqEl.textContent = d.emq + '/10';
                        emqEl.style.color = emqColor;
                    }
                    var emqLabel = d.emq >= 7 ? '✅ Excellent' : (d.emq >= 5 ? '🟡 Good' : '⚠️ Needs Work');
                    setText('kpi-emq-sub', emqLabel);
                } else {
                    block('capiflow-kpi-emq-empty');
                }

                // ── Side effects ───────────────────────────────────────────
                renderInsights(d);
                updateGauge(d);
                renderHealthTable(d.emq_health);
                renderBusinessProtection(d.business_protection);
                // Always call updateDonut — shows chart if data exists, empty state if not.
                if (typeof Chart !== 'undefined') {
                    updateDonut(d.rescued_events || 0, d.total_events || 0);
                } else {
                    hide('capiflow-donut-skeleton');
                    block('capiflow-donut-empty');
                }
            })
            .fail(function () {
                hide('capiflow-kpi-total-skeleton');
                hide('capiflow-kpi-rescued-skeleton');
                hide('capiflow-kpi-revenue-skeleton');
                hide('capiflow-kpi-emq-skeleton');
                hide('capiflow-donut-skeleton');
                block('capiflow-kpi-total-empty');
                block('capiflow-donut-empty');
                console.warn('[CapiFlow] KPI load failed.');
            });
    }


    // ─────────────────────────────────────────────────────────────────────────
    // 2. FUNNEL CHART
    // ─────────────────────────────────────────────────────────────────────────

    function loadFunnelData() {
        if (typeof Chart === 'undefined') {
            hide('capiflow-funnel-skeleton');
            block('capiflow-funnel-empty');
            return;
        }

        $.post(ajaxUrl, { action: 'capiflow_get_funnel_data', nonce: nonce, days: getPeriod() })
            .done(function (res) {
                hide('capiflow-funnel-skeleton');
                if (!res.success || !res.data || !res.data.funnel || res.data.funnel.length === 0) {
                    block('capiflow-funnel-empty');
                    return;
                }

                show('capiflow-funnel-chart-area');
                var funnel = res.data.funnel;
                var labels = funnel.map(function(f){ return f.event_name || f.label || ''; });
                var values = funnel.map(function(f){ return parseInt(f.event_count || f.count || 0, 10); });

                var canvas = document.getElementById('capiflow-funnel-chart');
                if (!canvas) return;

                if (funnelChart) { funnelChart.destroy(); }

                funnelChart = new Chart(canvas, {
                    type: 'bar',
                    data: {
                        labels: labels,
                        datasets: [{
                            label: 'Events',
                            data: values,
                            backgroundColor: [
                                'rgba(16,185,129,.7)',
                                'rgba(99,102,241,.7)',
                                'rgba(245,158,11,.7)',
                                'rgba(239,68,68,.7)',
                                'rgba(59,130,246,.7)',
                                'rgba(168,85,247,.7)',
                            ],
                            borderRadius: 6,
                        }]
                    },
                    options: {
                        indexAxis: 'y',
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: { legend: { display: false } },
                        scales: {
                            x: { grid: { color: 'rgba(255,255,255,.06)' }, ticks: { color: 'rgba(255,255,255,.5)' } },
                            y: { grid: { display: false }, ticks: { color: 'rgba(255,255,255,.7)', font: { size: 11 } } }
                        }
                    }
                });
            })
            .fail(function () {
                hide('capiflow-funnel-skeleton');
                block('capiflow-funnel-empty');
            });
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 3. RECOVERY DONUT
    // ─────────────────────────────────────────────────────────────────────────

    function updateDonut(rescued, total) {
        hide('capiflow-donut-skeleton');
        if (!total || total === 0) {
            block('capiflow-donut-empty');
            return;
        }

        show('capiflow-donut-chart-area');
        var server = rescued || 0;
        var both   = total - server;

        var canvas = document.getElementById('capiflow-donut-chart');
        if (!canvas) return;

        if (donutChart) { donutChart.destroy(); }

        donutChart = new Chart(canvas, {
            type: 'doughnut',
            data: {
                labels: ['Both (Pixel + CAPI)', 'CAPI Rescued'],
                datasets: [{
                    data: [both, server],
                    backgroundColor: ['rgba(99,102,241,.7)', 'rgba(16,185,129,.7)'],
                    borderWidth: 0,
                    hoverOffset: 4,
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                cutout: '65%',
                plugins: {
                    legend: {
                        position: 'bottom',
                        labels: { color: 'rgba(255,255,255,.6)', font: { size: 11 } }
                    },
                    tooltip: { callbacks: {
                        label: function(ctx) {
                            var pct = ((ctx.raw / total) * 100).toFixed(1);
                            return ctx.label + ': ' + ctx.raw.toLocaleString() + ' (' + pct + '%)';
                        }
                    }}
                }
            }
        });
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 4. EVENT BREAKDOWN TABLE
    // ─────────────────────────────────────────────────────────────────────────

    function loadTableData() {
        $.post(ajaxUrl, { action: 'capiflow_get_table_data', nonce: nonce, days: getPeriod() })
            .done(function (res) {
                hide('capiflow-table-skeleton');
                if (!res.success || !res.data || !res.data.rows || res.data.rows.length === 0) {
                    block('capiflow-table-empty');
                    return;
                }

                show('capiflow-table-area');
                var tbody = document.getElementById('capiflow-events-tbody');
                if (!tbody) return;

                tbody.innerHTML = '';
                res.data.rows.forEach(function (row) {
                    var rate = row.total_count > 0
                        ? Math.round((row.server_count / row.total_count) * 100) + '%'
                        : '—';
                    var tr = document.createElement('tr');
                    tr.style.borderBottom = '1px solid rgba(255,255,255,.05)';
                    tr.innerHTML = [
                        '<td style="padding:8px 4px;color:#e0e0e0;font-weight:500;">' + escHtml(row.event_name || '—') + '</td>',
                        '<td style="padding:8px 4px;text-align:center;color:rgba(255,255,255,.6);">' + numFmt(row.today_count || 0) + '</td>',
                        '<td style="padding:8px 4px;text-align:center;color:rgba(255,255,255,.6);">' + numFmt(row.total_count || 0) + '</td>',
                        '<td style="padding:8px 4px;text-align:center;color:#60a5fa;">' + numFmt(row.both_count || 0) + '</td>',
                        '<td style="padding:8px 4px;text-align:center;color:#10b981;">' + numFmt(row.server_count || 0) + '</td>',
                        '<td style="padding:8px 4px;text-align:center;"><span style="background:rgba(16,185,129,.15);color:#10b981;padding:2px 8px;border-radius:10px;font-size:11px;">' + rate + '</span></td>',
                    ].join('');
                    tbody.appendChild(tr);
                });
            })
            .fail(function () {
                hide('capiflow-table-skeleton');
                block('capiflow-table-empty');
            });
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 5. LIVE EVENT TERMINAL (15s poll)
    // ─────────────────────────────────────────────────────────────────────────

    function loadLiveEvents() {
        $.post(ajaxUrl, { action: 'capiflow_get_live_events', nonce: nonce })
            .done(function (res) {
                var terminal = document.getElementById('capiflow-terminal');
                if (!terminal || !res.success || !res.data || !res.data.events) return;

                var events = res.data.events;
                if (events.length === 0) return;

                // Remove init placeholder
                var initEl = document.getElementById('capiflow-terminal-init');
                if (initEl) { initEl.remove(); }

                // Prepend newest events (keep last 40 max).
                events.slice(0, 5).forEach(function (ev) {
                    var entry = document.createElement('div');
                    entry.className = 'capiflow-terminal-entry';
                    var statusClass = (ev.status === 'sent' || ev.http_code === '200') ? 't-ok' : 't-fail';
                    var statusIcon  = statusClass === 't-ok' ? '✅' : '❌';
                    var ms          = ev.processing_ms ? ev.processing_ms + 'ms' : '';
                    // created_at can be a MySQL datetime string ("2026-04-05 23:15:00")
                    // or a Unix timestamp (int). Handle both safely.
                    var raw = ev.created_at;
                    var d;
                    if (typeof raw === 'number') {
                        d = new Date(raw * 1000);
                    } else if (typeof raw === 'string' && raw.length > 0) {
                        // MySQL datetime: replace space with T for cross-browser compat
                        d = new Date(raw.replace(' ', 'T'));
                    } else {
                        d = new Date();
                    }
                    var ts = isNaN(d.getTime()) ? '--:--:--' : d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0') + ':' + d.getSeconds().toString().padStart(2,'0');
                    entry.innerHTML = '<span class="t-time">' + ts + '</span>'
                        + '<span class="t-event">' + escHtml(ev.event_name || ev.event_type || 'Event') + '</span>'
                        + (ev.order_id ? ' #' + escHtml(String(ev.order_id)) : '')
                        + ' <span class="' + statusClass + '">' + statusIcon + '</span>'
                        + (ms ? '<span class="t-ms">' + ms + '</span>' : '');

                    terminal.insertBefore(entry, terminal.firstChild);
                });

                // Trim to 40 entries max.
                while (terminal.children.length > 40) {
                    terminal.removeChild(terminal.lastChild);
                }
            });
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 6. CONFIDENCE GAUGE
    // Gauge converted to teaser (free) or rendered by Pro.
    // ─────────────────────────────────────────────────────────────────────────

    function updateGauge() {}

    // ─────────────────────────────────────────────────────────────────────────
    // 7. SMART INSIGHTS
    // ─────────────────────────────────────────────────────────────────────────

    function renderInsights(d) {
        hide('capiflow-insights-skeleton');

        var list    = document.getElementById('capiflow-insights-list');
        var empty   = document.getElementById('capiflow-insights-empty');
        var insights = [];

        // ── EMQ Score ──────────────────────────────────────────────
        if (d.emq >= 7) {
            insights.push('✅ Purchase EMQ is ' + d.emq + '/10 — Excellent match quality.');
        } else if (d.emq >= 5) {
            insights.push('🟡 EMQ is ' + d.emq + '/10 — Add phone & external_id to improve.');
        } else if (d.emq !== null && d.emq !== undefined) {
            insights.push('⚠️ EMQ is only ' + d.emq + '/10 — Check Pixel ID and PII settings.');
        }

        // ── Rescue Rate ────────────────────────────────────────────
        if (d.total_events > 0 && d.rescued_events > 0) {
            var pct = Math.round((d.rescued_events / d.total_events) * 100);
            if (pct >= 20) {
                insights.push('🛡️ ' + pct + '% of events rescued by CAPI — blocking protection active.');
            } else {
                insights.push('ℹ️ Only ' + pct + '% CAPI rescue rate — your pixel loads well on most browsers.');
            }
        }

        // ── Rescued Revenue ────────────────────────────────────────
        var rev = parseFloat(d.rescued_revenue || 0);
        if (rev > 0) {
            insights.push('💰 ' + currency + rev.toLocaleString() + ' revenue rescued that pixel alone would have missed.');
        }

        // ── Active Tracking ────────────────────────────────────────
        if (d.total_events > 0 && d.rescued_events === 0 && (d.emq === null || d.emq === undefined)) {
            insights.push('📊 Tracking active — EMQ score will appear after Meta processes your events (~24h).');
        }

        // ── No Events ─────────────────────────────────────────────
        if (d.total_events === 0) {
            insights.push('📡 No events yet — complete a test purchase to start tracking.');
            insights.push('💡 Tip: Check Settings → Pixel ID and Access Token are configured correctly.');
        }

        if (insights.length === 0) {
            hide('capiflow-insights-list');
            block('capiflow-insights-empty');
            return;
        }

        if (list) {
            list.innerHTML = '';
            insights.forEach(function (txt) {
                var li = document.createElement('li');
                li.style.cssText = 'padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05);color:rgba(255,255,255,.75);font-size:13px;';
                li.textContent = txt;
                list.appendChild(li);
            });
            list.style.display = '';
            hide('capiflow-insights-empty');
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 8. META INTEGRATION HEALTH
    // Health table converted to teaser (free) or rendered by Pro.
    // This stub just shows the "See details →" link on EMQ KPI card when data exists.
    // ─────────────────────────────────────────────────────────────────────────

    function renderHealthTable(emqData) {
        var link = document.getElementById('capiflow-emq-deep-link');
        if (link) {
            link.style.display = (emqData && emqData.web && emqData.web.length > 0)
                ? 'inline-block' : 'none';
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // BUSINESS PROTECTION SUMMARY (3-layer: Impact → Breakdown → Status)
    // Data from Pro plugin via apply_filters('capiflow_business_protection_data')
    // ─────────────────────────────────────────────────────────────────────────

    function renderBusinessProtection(bp) {
        hide('capiflow-bp-skeleton');

        var wrapper = document.getElementById('capiflow-bp-data');
        var empty   = document.getElementById('capiflow-bp-empty');
        var teaser  = document.getElementById('capiflow-bp-teaser');

        // Free user → teaser is already rendered server-side, nothing to do
        if (teaser && !wrapper) return;

        // Pro user but no data or no modules enabled
        if (!bp || !bp.has_data) {
            if (wrapper) hide(wrapper);
            if (empty)   block(empty);
            return;
        }

        if (wrapper) show(wrapper);
        if (empty)   hide(empty);

        // ── Layer 1: Big Impact Number (Revenue Recovered + Costs Saved) ─
        var totalEl = document.getElementById('capiflow-bp-total');
        if (totalEl) {
            var total = parseFloat(bp.total_impact || 0);
            animateNumber(totalEl, total, function(v) {
                return currency + v.toLocaleString();
            });
            totalEl.style.color = total > 0 ? '#22c55e' : 'rgba(255,255,255,.4)';
        }

        // ── Layer 2: Revenue Recovery (left column) ─────────────────────
        var recoverEl = document.getElementById('capiflow-bp-recover-value');
        if (recoverEl) {
            var recoverTotal = parseFloat(bp.revenue_recovery.total || 0);
            recoverEl.textContent = currency + recoverTotal.toLocaleString();
        }

        // Revenue Recovery breakdown items
        var recoverList = document.getElementById('capiflow-bp-recover-list');
        if (recoverList && bp.revenue_recovery.items) {
            recoverList.innerHTML = '';
            bp.revenue_recovery.items.forEach(function(item) {
                var li = document.createElement('div');
                li.style.cssText = 'font-size:11px;color:rgba(255,255,255,.5);margin-top:4px;';
                li.innerHTML = escHtml(item.icon || '•') + ' '
                    + escHtml(item.label) + ': '
                    + '<strong style="color:rgba(255,255,255,.7);">' + currency + parseFloat(item.amount || 0).toLocaleString() + '</strong>'
                    + (item.detail ? ' <span style="color:rgba(255,255,255,.3);">(' + escHtml(item.detail) + ')</span>' : '');
                recoverList.appendChild(li);
            });
        }

        // ── Layer 2: Cost Savings (right column) ────────────────────────
        var savingsEl = document.getElementById('capiflow-bp-savings-value');
        if (savingsEl) {
            var savingsTotal = parseFloat(bp.cost_savings.total || 0);
            savingsEl.textContent = currency + savingsTotal.toLocaleString();
        }

        var savingsList = document.getElementById('capiflow-bp-savings-list');
        if (savingsList && bp.cost_savings.items) {
            savingsList.innerHTML = '';
            bp.cost_savings.items.forEach(function(item) {
                var li = document.createElement('div');
                li.style.cssText = 'font-size:11px;color:rgba(255,255,255,.5);margin-top:4px;';
                li.innerHTML = escHtml(item.icon || '•') + ' '
                    + escHtml(item.label) + ': '
                    + '<strong style="color:rgba(255,255,255,.7);">' + currency + parseFloat(item.amount || 0).toLocaleString() + '</strong>'
                    + (item.detail ? ' <span style="color:rgba(255,255,255,.3);">(' + escHtml(item.detail) + ')</span>' : '');
                savingsList.appendChild(li);
            });
        }

        // ── Layer 2.5: Performance Rate Bars ─────────────────────────────
        var ratesRow = document.getElementById('capiflow-bp-rates');
        if (ratesRow && bp.rates) {
            var r = bp.rates;
            var hasRates = r.total_orders > 0 || r.total_abandoned > 0;

            if (hasRates) {
                ratesRow.style.display = 'flex';
                ratesRow.innerHTML = '';

                // Fraud Detection Rate
                if (r.total_orders > 0) {
                    var fraudColor = r.fraud_rate > 5 ? '#ef4444' : (r.fraud_rate > 2 ? '#f59e0b' : '#22c55e');
                    var fraudHtml = '<div class="capiflow-bp-rate">'
                        + '<div class="capiflow-bp-rate-header">'
                        + '<span class="capiflow-bp-rate-label">\uD83C\uDFAF Fraud Detection</span>'
                        + '<span class="capiflow-bp-rate-value" style="color:' + fraudColor + ';">' + r.fraud_rate + '%</span>'
                        + '</div>'
                        + '<div class="capiflow-bp-rate-bar">'
                        + '<div class="capiflow-bp-rate-fill capiflow-bp-rate-fill--fraud" id="bp-fraud-fill"></div>'
                        + '</div>'
                        + '<div class="capiflow-bp-rate-detail">'
                        + escHtml(String(r.fraud_blocked)) + ' blocked / ' + escHtml(String(r.total_orders)) + ' orders'
                        + '</div>'
                        + '</div>';
                    ratesRow.innerHTML += fraudHtml;
                }

                // Cart Recovery Rate
                if (r.total_abandoned > 0) {
                    var recColor = r.recovery_rate >= 15 ? '#22c55e' : (r.recovery_rate >= 5 ? '#f59e0b' : '#ef4444');
                    var recHtml = '<div class="capiflow-bp-rate">'
                        + '<div class="capiflow-bp-rate-header">'
                        + '<span class="capiflow-bp-rate-label">\uD83D\uDED2 Recovery Rate</span>'
                        + '<span class="capiflow-bp-rate-value" style="color:' + recColor + ';">' + r.recovery_rate + '%</span>'
                        + '</div>'
                        + '<div class="capiflow-bp-rate-bar">'
                        + '<div class="capiflow-bp-rate-fill capiflow-bp-rate-fill--recovery" id="bp-recovery-fill"></div>'
                        + '</div>'
                        + '<div class="capiflow-bp-rate-detail">'
                        + escHtml(String(r.carts_recovered)) + ' recovered / ' + escHtml(String(r.total_abandoned)) + ' abandoned'
                        + '</div>'
                        + '</div>';
                    ratesRow.innerHTML += recHtml;
                }

                // Animate fill bars after a tick (CSS transition needs element in DOM first)
                setTimeout(function() {
                    var fraudFill = document.getElementById('bp-fraud-fill');
                    if (fraudFill) fraudFill.style.width = Math.min(r.fraud_rate * 2, 100) + '%'; // ×2 scale for visibility
                    var recFill = document.getElementById('bp-recovery-fill');
                    if (recFill) recFill.style.width = Math.min(r.recovery_rate, 100) + '%';
                }, 50);
            }
        }

        // ── Layer 3: Module Status Badges ────────────────────────────────
        var statusRow = document.getElementById('capiflow-bp-status');
        if (statusRow && bp.modules) {
            statusRow.innerHTML = '';
            bp.modules.forEach(function(mod) {
                var badge = document.createElement('span');
                badge.className = 'capiflow-bp-module-badge';
                if (mod.status === 'active') {
                    badge.innerHTML = '✅ ' + escHtml(mod.name);
                    badge.classList.add('capiflow-bp-module--active');
                } else if (mod.status === 'warning') {
                    badge.innerHTML = '⚠️ ' + escHtml(mod.name);
                    badge.classList.add('capiflow-bp-module--warning');
                    if (mod.configure_url) {
                        var link = document.createElement('a');
                        link.href = mod.configure_url;
                        link.textContent = ' Configure →';
                        link.style.cssText = 'font-size:10px;color:#f59e0b;text-decoration:none;margin-left:2px;';
                        badge.appendChild(link);
                    }
                } else {
                    badge.innerHTML = '❌ ' + escHtml(mod.name);
                    badge.classList.add('capiflow-bp-module--disabled');
                }
                statusRow.appendChild(badge);
            });
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // 9. FULL RELOAD (period change or refresh button)
    // ─────────────────────────────────────────────────────────────────────────

    function reloadAll() {
        // Show skeletons again
        ['capiflow-kpi-total-skeleton','capiflow-kpi-rescued-skeleton',
         'capiflow-kpi-revenue-skeleton','capiflow-kpi-emq-skeleton',
         'capiflow-funnel-skeleton','capiflow-table-skeleton',
         'capiflow-insights-skeleton'].forEach(function (id) {
            block(id);
        });
        // Hide data areas
        document.querySelectorAll('.capiflow-kpi-data').forEach(function(el){ el.style.display = 'none'; });
        document.querySelectorAll('.capiflow-empty-state').forEach(function(el){ el.style.display = 'none'; });

        loadKPIData();
        loadFunnelData();
        loadTableData();
        loadLiveEvents();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // INIT
    // ─────────────────────────────────────────────────────────────────────────

    $(document).ready(function () {
        // Initial load.
        loadKPIData();
        loadFunnelData();
        loadTableData();
        loadLiveEvents();

        // Start live terminal poll (15s).
        liveInterval = setInterval(loadLiveEvents, 15000);

        // Visibility API — pause polling when tab is hidden (battery / server load).
        document.addEventListener('visibilitychange', function () {
            if (destroyed) return;
            if (document.hidden) {
                clearInterval(liveInterval);
                liveInterval = null;
            } else {
                loadLiveEvents(); // immediate refresh on tab focus
                liveInterval = setInterval(loadLiveEvents, 15000);
            }
        });

        // Period selector change.
        $('#capiflow-period-selector').on('change', reloadAll);

        // Refresh button.
        $('#capiflow-refresh-btn').on('click', reloadAll);

        // Cleanup on unload.
        $(window).on('unload', function () {
            destroyed = true;
            clearInterval(liveInterval);
        });
    });

    // ── Utility ──────────────────────────────────────────────────────────────

    function escHtml(str) {
        return String(str)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

})(jQuery);
