/**
 * AIEO Web Push — Service Worker.
 *
 * Served by WordPress at the site root (/aieo-push-sw.js) via a rewrite rule
 * so the SW scope can claim the entire origin. The PHP handler emits
 * `Content-Type: application/javascript` and `Service-Worker-Allowed: /`.
 *
 * Phase 3 ships with empty-payload pushes: when a `push` event arrives with
 * no data, we show a hardcoded "Test push" notification. Phase 4+ will
 * include encrypted payloads with structured data (title, body, icon, url).
 */

self.addEventListener('install', function (event) {
    // Take effect immediately on first install — no waiting for clients to
    // refresh. The opt-in flow always registers + subscribes back-to-back.
    self.skipWaiting();
});

self.addEventListener('activate', function (event) {
    event.waitUntil(self.clients.claim());
});

self.addEventListener('push', function (event) {
    var title = 'Femme Fatale';
    var options = {
        body: 'Test push received.',
        icon: '/wp-content/uploads/2024/05/cropped-fav-32x32.png',
        badge: '/wp-content/uploads/2024/05/cropped-fav-32x32.png',
        tag:  'aieo-push',
        renotify: true,
        data: { url: '/' }
    };

    // If a payload is provided (Phase 4+), parse it and override the
    // hardcoded fallbacks. Empty payloads (Phase 3 test) keep the defaults.
    if (event.data) {
        try {
            var payload = event.data.json();
            if (payload && typeof payload === 'object') {
                if (payload.title) { title = String(payload.title); }
                if (payload.body)  { options.body = String(payload.body); }
                if (payload.icon)  { options.icon = String(payload.icon); }
                if (payload.badge) { options.badge = String(payload.badge); }
                if (payload.tag)   { options.tag = String(payload.tag); }
                if (payload.url)   { options.data = { url: String(payload.url), dispatchId: payload.dispatchId || 0 }; }
                if (payload.actions && Array.isArray(payload.actions)) {
                    options.actions = payload.actions.slice(0, 2);
                }
            }
        } catch (e) {
            // Fall through to defaults on parse error.
        }
    }

    event.waitUntil(self.registration.showNotification(title, options));
});

/**
 * Click handler — opens / focuses a tab on the configured URL and pings the
 * REST `event` endpoint so we can record the click in `wp_aieo_push_dispatch`.
 */
self.addEventListener('notificationclick', function (event) {
    event.notification.close();

    var data = event.notification.data || {};
    var url  = data.url || '/';
    var dispatchId = parseInt(data.dispatchId, 10) || 0;

    // Best-effort beacon to record the click. Don't await — the open is more
    // important UX than the analytics ping.
    if (dispatchId > 0) {
        try {
            fetch('/wp-json/aieo/v1/push/event', {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ dispatch_id: dispatchId, kind: 'click' }),
                keepalive: true
            });
        } catch (e) { /* ignore */ }
    }

    event.waitUntil(
        self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) {
            // Re-use an existing tab on the same origin if there is one.
            for (var i = 0; i < clientList.length; i++) {
                var client = clientList[i];
                if ('focus' in client) {
                    if (client.url === url) { return client.focus(); }
                }
            }
            if (self.clients.openWindow) {
                return self.clients.openWindow(url);
            }
        })
    );
});

self.addEventListener('notificationclose', function (event) {
    var data = event.notification.data || {};
    var dispatchId = parseInt(data.dispatchId, 10) || 0;
    if (dispatchId <= 0) { return; }
    try {
        fetch('/wp-json/aieo/v1/push/event', {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ dispatch_id: dispatchId, kind: 'dismiss' }),
            keepalive: true
        });
    } catch (e) { /* ignore */ }
});

/**
 * Re-subscribe automatically when the push subscription is invalidated by
 * the browser (Mozilla / Chromium do this periodically). We POST the new
 * subscription back to /push/subscribe so the server's record stays fresh.
 */
self.addEventListener('pushsubscriptionchange', function (event) {
    event.waitUntil((async function () {
        try {
            var oldEndpoint = (event.oldSubscription && event.oldSubscription.endpoint) || '';

            // The browser may already have a fresh subscription; if not, try
            // to subscribe with whatever applicationServerKey was last stored
            // by the page-side code (cached via IndexedDB would be cleaner,
            // but cookies + localStorage aren't accessible from a SW).
            var newSub = event.newSubscription;
            if (!newSub && self.registration && self.registration.pushManager) {
                // Without the applicationServerKey we cannot subscribe. Bail
                // and let the next page-load opt-in flow recover us.
                return;
            }
            if (newSub) {
                await fetch('/wp-json/aieo/v1/push/subscribe', {
                    method: 'POST',
                    credentials: 'same-origin',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        endpoint:   newSub.endpoint,
                        p256dh:     arrayBufferToBase64Url(newSub.getKey ? newSub.getKey('p256dh') : null),
                        auth:       arrayBufferToBase64Url(newSub.getKey ? newSub.getKey('auth')   : null),
                        previous_endpoint: oldEndpoint
                    })
                });
            }
        } catch (e) { /* ignore */ }
    })());
});

function arrayBufferToBase64Url(buf) {
    if (!buf) { return ''; }
    var bytes = new Uint8Array(buf);
    var binary = '';
    for (var i = 0; i < bytes.byteLength; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
