/**
 * Bot Blocker - Live Monitor
 *
 * Polls the REST endpoint and updates the Plotly geo-map and live feed.
 */
(function ($) {
    'use strict';

    /* ------------------------------------------------------------------ */
    /* Helpers                                                              */
    /* ------------------------------------------------------------------ */

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

    function colorFor(action, verification) {
        if (verification === 'failed')                                    { return '#ff8a00'; }
        if (action === 'block' || action === 'temporary_block')           { return '#ff3b30'; }
        if (action === 'rate_limit')                                      { return '#ffd60a'; }
        if (verification === 'verified')                                  { return '#3b82f6'; }
        return '#22c55e';
    }

    function sizeFor(point) {
        var base  = 10 + ((point.count || 1) - 1) * 2;
        var bonus = (point.action === 'block' || point.action === 'temporary_block') ? 4 : 0;
        return Math.min(28, base + bonus);
    }

    function relativeTime(utcStr) {
        if (!utcStr) { return ''; }
        var diff = Math.round((Date.now() - new Date(utcStr + ' UTC').getTime()) / 1000);
        if (diff < 2)    { return 'just now'; }
        if (diff < 60)   { return diff + 's ago'; }
        if (diff < 3600) { return Math.floor(diff / 60) + 'm ago'; }
        return Math.floor(diff / 3600) + 'h ago';
    }

    function actionLabel(action) {
        var map = {
            allow:           'Allowed',
            log:             'Logged',
            rate_limit:      'Rate limited',
            temporary_block: 'Temp blocked',
            block:           'Blocked'
        };
        return map[action] || action;
    }

    /* ------------------------------------------------------------------ */
    /* State                                                                */
    /* ------------------------------------------------------------------ */

    var lastData      = null;
    var mapHasPoints  = false;
    var prevPointKeys = {};   // tracks which point keys were in last render
    var prevCounts    = {};   // tracks previous stat values for pulse

    /* ------------------------------------------------------------------ */
    /* Dot arrival overlay                                                  */
    /* ------------------------------------------------------------------ */

    function pointKey(p) {
        return (p.lat || 0).toFixed(2) + ',' + (p.lon || 0).toFixed(2) + ',' + (p.action || '');
    }

    function triggerDotArrival(points, newKeys) {
        var $map = $('#bb-monitor-map');
        if (!$map.length) { return; }
        var mapW = $map.outerWidth()  || 600;
        var mapH = $map.outerHeight() || 400;

        points.forEach(function (p) {
            var key = pointKey(p);
            if (!newKeys[key]) { return; }

            var pxX = ((p.lon + 180) / 360) * mapW;
            var pxY = ((90 - p.lat) / 180) * mapH;

            var color = colorFor(p.action, p.verification);
            var $pulse = $('<div class="bb-dot-pulse"></div>').css({
                left:           pxX,
                top:            pxY,
                'border-color': color
            });
            $map.append($pulse);
            setTimeout(function () { $pulse.remove(); }, 1400);
        });
    }

    /* ------------------------------------------------------------------ */
    /* Rendering                                                            */
    /* ------------------------------------------------------------------ */

    function renderMap(points) {
        if (!points || points.length === 0) { return; }
        mapHasPoints = true;

        // Determine which keys are new since last render
        var newKeys = {};
        points.forEach(function (p) {
            var k = pointKey(p);
            if (!prevPointKeys[k]) { newKeys[k] = true; }
        });

        var trace = {
            type: 'scattergeo',
            mode: 'markers',
            lon:  points.map(function (p) { return p.lon; }),
            lat:  points.map(function (p) { return p.lat; }),
            text: points.map(function (p) {
                var lines = [
                    '<b>' + esc(p.bot || 'Unknown bot') + '</b>',
                    esc(p.location || 'Unknown location'),
                    esc(p.path),
                    actionLabel(p.action) + (p.verification ? ' \u00b7 ' + esc(p.verification) : '')
                ];
                if (p.count > 1) { lines.push('Hits: ' + p.count); }
                return lines.join('<br>');
            }),
            customdata:    points,
            hovertemplate: '%{text}<extra></extra>',
            marker: {
                size:    points.map(sizeFor),
                color:   points.map(function (p) { return colorFor(p.action, p.verification); }),
                line:    { color: '#0b1220', width: 1 },
                opacity: 0.88
            }
        };

        var layout = {
            dragmode:      'pan',
            paper_bgcolor: '#0b1220',
            plot_bgcolor:  '#0b1220',
            margin:        { l: 0, r: 0, t: 0, b: 0 },
            geo: {
                scope:          'world',
                projection:     { type: 'equirectangular' },
                bgcolor:        '#0b1220',
                showframe:      false,
                showcoastlines: false,
                showcountries:  true,
                countrycolor:   '#243447',
                showland:       true,
                landcolor:      '#101826',
                showocean:      true,
                oceancolor:     '#0b1220',
                lataxis:        { showgrid: false },
                lonaxis:        { showgrid: false }
            }
        };

        Plotly.react('bb-monitor-map', [trace], layout, { displayModeBar: false, responsive: true });

        // Trigger arrival pulse for new points
        if (Object.keys(newKeys).length > 0) {
            triggerDotArrival(points, newKeys);
        }

        // Update prevPointKeys for next render
        prevPointKeys = {};
        points.forEach(function (p) { prevPointKeys[pointKey(p)] = true; });
    }

    function renderFeed(feed) {
        var $list = $('#bb-feed-list').empty();

        if (!feed || feed.length === 0) {
            $list.html('<div class="bb-feed-empty">No events in the last 5 minutes.</div>');
            return;
        }

        feed.forEach(function (item) {
            var color = colorFor(item.action, item.verification);
            var burst = item.burst > 1 ? ' <span class="bb-burst">\u00d7' + item.burst + '</span>' : '';
            var verif = item.verification ? ' <span class="bb-tag bb-tag--verif">' + esc(item.verification) + '</span>' : '';
            var row   = $(
                '<div class="bb-feed-item">' +
                    '<div class="bb-feed-main">' + esc(item.bot || 'Unknown bot') + burst + '</div>' +
                    '<div class="bb-feed-sub">' + esc(item.location || 'Unmapped') + ' &middot; ' + esc(item.path) + '</div>' +
                    '<div class="bb-feed-meta">' +
                        '<span class="bb-tag">' + esc(actionLabel(item.action)) + '</span>' +
                        verif +
                        '<span class="bb-feed-time">' + esc(relativeTime(item.time)) + '</span>' +
                    '</div>' +
                '</div>'
            );
            row.css('--bb-accent', color);
            $list.append(row);
        });
    }

    function renderCounts(counts) {
        $('[data-key]').each(function () {
            var key    = $(this).data('key');
            var newVal = counts[key] || 0;
            var oldVal = prevCounts[key];
            $('strong', this).text(newVal);

            // Pulse the pill if the value changed
            if (oldVal !== undefined && oldVal !== newVal) {
                var $pill = $(this);
                $pill.addClass('bb-count--pulse');
                setTimeout(function () { $pill.removeClass('bb-count--pulse'); }, 700);
            }
            prevCounts[key] = newVal;
        });
    }

    function updateTimestamp() {
        var now = new Date();
        var h   = String(now.getHours()).padStart(2, '0');
        var m   = String(now.getMinutes()).padStart(2, '0');
        var s   = String(now.getSeconds()).padStart(2, '0');
        $('#bb-last-updated').text('Updated ' + h + ':' + m + ':' + s);
    }

    /* ------------------------------------------------------------------ */
    /* Data update                                                          */
    /* ------------------------------------------------------------------ */

    function applyData(data) {
        lastData = data;
        renderCounts(data.counts || {});
        renderMap(data.points || []);
        renderFeed(data.feed || []);
        updateTimestamp();
        $('#bb-monitor-map').removeClass('bb-map--error');
    }

    /* ------------------------------------------------------------------ */
    /* Polling                                                              */
    /* ------------------------------------------------------------------ */

    function poll() {
        $.ajax({
            url:    BotBlockerMonitor.rest,
            method: 'GET',
            beforeSend: function (xhr) {
                xhr.setRequestHeader('X-WP-Nonce', BotBlockerMonitor.nonce);
            }
        })
        .done(function (data) { applyData(data); })
        .fail(function () {
            $('#bb-last-updated').text('Connection error - retrying...');
            $('#bb-monitor-map').addClass('bb-map--error');
        });
    }

    /* ------------------------------------------------------------------ */
    /* Boot                                                                 */
    /* ------------------------------------------------------------------ */

    $(function () {
        if (!$('#bb-monitor-map').length) { return; }
        poll();
        setInterval(poll, Math.max(2000, (parseInt(BotBlockerMonitor.refresh, 10) || 3) * 1000));
    });

})(jQuery);
