importScripts('https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js');
const CACHE_NAME = 'vdo-ninja-notifications-v1';
const urlsToCache = [
  '/notifications/media/tone.mp3',
  '/notifications/media/chime.wav',
  '/notifications/media/bell.wav',
  '/notifications/media/logo2.png',
  '/notifications/media/icon.png',
];
let sseConnection = null;
let pollingInterval = null;
let reconnectTimeout = null;
let currentTopic = null;
let reconnectAttempts = 0;
let pushSubscription = null;
const MAX_RECONNECT_ATTEMPTS = 10;
const MAX_RECONNECT_DELAY = 300000;
const pendingResponses = new Map();
const recentNotifications = new Set();
const processedNotificationIds = new Set();
const PROCESSED_ID_EXPIRY = 30000;

self.addEventListener('pushsubscriptionchange', (event) => {
  console.log('[SW-DEBUG] Push subscription change event detected.');
  event.waitUntil(handleSubscriptionChange(event));
});

async function handleSubscriptionChange(event) {
  try {
    console.log('[SW-DEBUG] Handling subscription change');
    // Get the old subscription data (may be null)
    const oldSubscription = event.oldSubscription;
    let topic = null;
    
    // Get the current topic from IndexedDB
    try {
      topic = await getStoreItem('topic');
      if (!topic) {
        console.error('[SW-DEBUG] No topic found for resubscription');
        return;
      }
    } catch (error) {
      console.error('[SW-DEBUG] Error retrieving topic:', error);
      return;
    }
    
    // Try to get or create a new subscription
    let newSubscription;
    try {
      // Try to get new subscription from event if provided
      if (event.newSubscription) {
        newSubscription = event.newSubscription;
      } else {
        // Get VAPID key from server
        const keyResponse = await fetch('https://notify.vdo.ninja/vapidPublicKey');
        if (!keyResponse.ok) {
          throw new Error('Failed to fetch VAPID key');
        }
        
        const keyData = await keyResponse.json();
        const vapidKey = keyData.key;
        
        if (!vapidKey) {
          throw new Error('Invalid VAPID key format');
        }
        
        // Convert the VAPID key to the required format
        const convertedVapidKey = urlBase64ToUint8Array(vapidKey);
        
        // Create a new subscription
        newSubscription = await self.registration.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: convertedVapidKey
        });
      }
      
      // Send the new subscription to the server
      await fetch('https://notify.vdo.ninja/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          subscription: newSubscription,
          topic: topic
        })
      });
      
      console.log('[SW-DEBUG] Successfully updated push subscription');
      
      // Notify any open clients
      self.clients.matchAll().then(clients => {
        clients.forEach(client => {
          client.postMessage({
            action: 'subscriptionUpdated',
            topic: topic
          });
        });
      });
      
    } catch (error) {
      console.error('[SW-DEBUG] Error resubscribing:', error);
    }
  } catch (error) {
    console.error('[SW-DEBUG] Error in handleSubscriptionChange:', error);
  }
}

self.addEventListener('push', event => {
  event.waitUntil((async () => {
    try {
      let notification = {};
      if (event.data) {
        try {
          notification = JSON.parse(event.data.text());
        } catch (e) {
          console.error('[SW] Failed to parse push data:', e);
          notification = {
            title: 'VDO.Ninja Notification',
            body: 'Someone joined your room',
            timestamp: Date.now(),
            url: self.registration.scope
          };
        }
      } else {
        notification = {
          title: 'VDO.Ninja Notification',
          body: 'Someone joined your room',
          timestamp: Date.now(),
          url: self.registration.scope
        };
      }
      
      notification.id = notification.id || `push-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
      notification.timestamp = notification.timestamp || Date.now();
      notification.source = notification.source || 'web_push';
      
      const isDuplicate = await isNotificationDuplicate(notification);
      if (!isDuplicate) {
        await storeNotification(notification);
        
        // Check permission before showing
        if (Notification.permission === 'granted') {
          await showNotification(notification).catch(err => {
            console.error('[SW] Show notification error:', err);
          });
        } else {
          console.warn('[SW] Notification permission not granted');
        }
        
        broadcastToClients('newNotification', { notification });
      }
    } catch (error) {
      console.error('[SW] Error handling push notification:', error);
    }
  })());
});

async function decryptPushMessage(rawData, subscription) {
  try {
    try {
      return JSON.parse(rawData);
    } catch (e) {
      console.log('[SW] Push message not valid JSON, attempting decryption');
    }
    if (!subscription.keys || !subscription.keys.p256dh || !subscription.keys.auth) {
      console.error('[SW] Cannot decrypt push: missing subscription keys');
      throw new Error('Missing subscription keys');
    }
    console.log('[SW] Push message received for endpoint:', subscription.endpoint.slice(-10));
    try {
      const decodedData = atob(rawData);
      return JSON.parse(decodedData);
    } catch (e) {
      return {
        title: 'VDO.Ninja Notification',
        body: 'New notification received',
        timestamp: Date.now(),
        url: self.registration.scope
      };
    }
  } catch (error) {
    console.error('[SW] Error decrypting push message:', error);
    return {
      title: 'VDO.Ninja Notification',
      body: 'New notification received',
      timestamp: Date.now(),
      url: self.registration.scope
    };
  }
}
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
      .then(() => self.skipWaiting())
  );
});
self.addEventListener('activate', event => {
  event.waitUntil(
    Promise.all([
      self.clients.claim(),
      initStore().then(() => {
        return getStoreItem('topic').then(topic => {
          if (topic) {
            console.log('[SW] Restoring connection for topic:', topic);
            connectToSSE(topic);
          }
        });
      })
    ])
  );
});
self.addEventListener('fetch', event => {
  if (event.request.method !== 'GET') {
    return;
  }
  const url = new URL(event.request.url);
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    return;
  }
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        if (response) {
          return response;
        }
        return fetch(event.request)
          .catch(error => {
            console.error('[SW] Fetch error:', error);
            if (event.request.url.includes('index.html')) {
              return new Response('Offline Mode: Please reconnect to the internet', {
                headers: { 'Content-Type': 'text/html' }
              });
            }
            return new Response('Offline resource unavailable');
          });
      })
  );
});
self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(
    self.clients.matchAll({ type: 'window' })
      .then(clients => {
        for (const client of clients) {
          if (client.url.includes(self.registration.scope) && 'focus' in client) {
            return client.focus();
          }
        }
        const url = event.notification.data?.url || self.registration.scope;
        return self.clients.openWindow(url);
      })
  );
});
self.addEventListener('message', event => {
  const data = event.data;
  if (!data || !data.action) return;
  const messageId = data.messageId || `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
  const port = event.ports && event.ports[0];
  if (!port) {
    console.error('[SW] No message port provided');
    return;
  }
  if (data.requiresResponse !== false) {
    pendingResponses.set(messageId, {
      port,
      timestamp: Date.now()
    });
    setTimeout(() => {
      const pendingResponse = pendingResponses.get(messageId);
      if (pendingResponse) {
        try {
          pendingResponse.port.postMessage({
            error: 'Response timeout in service worker'
          });
        } catch (e) {
        }
        pendingResponses.delete(messageId);
      }
    }, 9000);
  }
  const sendResponse = (response) => {
    const pendingResponse = pendingResponses.get(messageId);
    if (pendingResponse && pendingResponse.port) {
      try {
        pendingResponse.port.postMessage(response);
      } catch (e) {
        console.error('[SW] Error sending response:', e);
      }
      pendingResponses.delete(messageId);
    }
  };
  Promise.resolve().then(async () => {
    switch (data.action) {
      case 'connect':
        if (data.topic) {
          const success = await connectToSSE(data.topic);
          sendResponse({
            connected: success,
            topic: data.topic
          });
        }
        break;
      case 'disconnect':
        await disconnectAll();
        sendResponse({
          connected: false
        });
        break;
      case 'syncNotifications':
        const notifications = await getAllNotifications();
        sendResponse({
          notifications: notifications
        });
        break;
      case 'checkStatus':
        const [topic, sseStatus, pollingStatus] = await Promise.all([
          getStoreItem('topic'),
          getStoreItem('sseStatus'),
          getStoreItem('pollingStatus')
        ]);
        sendResponse({
          action: 'statusUpdate',
          config: topic ? { topic } : null,
          sse: sseStatus || { connected: false },
          polling: pollingStatus || { isPolling: false }
        });
        break;
      case 'startPolling':
        if (data.topic) {
          startPolling(data.topic);
          sendResponse({ success: true });
        }
        break;
      case 'testNotification':
        if (data.notification) {
          await showNotification(data.notification);
          await storeNotification(data.notification);
          broadcastToClients('newNotification', {
            notification: data.notification
          });
          sendResponse({ success: true });
        }
        break;
      default:
        sendResponse({ error: 'Unknown action' });
    }
  }).catch(error => {
    console.error(`[SW] Error processing ${data.action}:`, error);
    sendResponse({
      error: error.message || 'Error processing request'
    });
  });
});
self.addEventListener('periodicsync', event => {
  if (event.tag === 'notification-poll') {
    Promise.all([
      getStoreItem('topic'),
      getStoreItem('sseStatus')
    ]).then(([topic, sseStatus]) => {
      if (topic && (!sseStatus || !sseStatus.connected)) {
        console.log('[SW] Running periodic background poll');
        pollForNotifications(topic);
      }
    });
  }
});
const DB_NAME = 'VDONinjaNotifications';
const DB_VERSION = 1;
let db = null;
function initStore() {
  if (db) return Promise.resolve(db);
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onupgradeneeded = event => {
      const db = event.target.result;
      if (!db.objectStoreNames.contains('notifications')) {
        const store = db.createObjectStore('notifications', { keyPath: 'id' });
        store.createIndex('timestamp', 'timestamp', { unique: false });
      }
      if (!db.objectStoreNames.contains('store')) {
        db.createObjectStore('store', { keyPath: 'key' });
      }
    };
    request.onsuccess = event => {
      db = event.target.result;
      resolve(db);
    };
    request.onerror = event => {
      console.error('[SW] IndexedDB error:', event.target.error);
      reject(event.target.error);
    };
  });
}
function storeNotification(notification) {
  return initStore().then(() => {
    return new Promise((resolve, reject) => {
      try {
        notification.id = notification.id || `notification-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
        notification.timestamp = notification.timestamp || Date.now();
        notification.isNew = true;
        const tx = db.transaction('notifications', 'readwrite');
        const store = tx.objectStore('notifications');
        console.log('[SW-DEBUG] Storing notification in IndexedDB:', notification.id);
        const request = store.put(notification);
        request.onsuccess = () => {
          console.log('[SW-DEBUG] Successfully stored notification in DB:', notification.id);
          cleanupOldNotifications();
          resolve(notification);
        };
        request.onerror = event => {
          const error = event.target.error;
          console.error('[SW-DEBUG] Error storing notification in DB:', error);
          reject(error);
        };
      } catch (error) {
        console.error('[SW-DEBUG] Exception in storeNotification:', error);
        reject(error);
      }
    });
  });
}
function getAllNotifications() {
  return initStore().then(() => {
    return new Promise((resolve, reject) => {
      const tx = db.transaction('notifications', 'readonly');
      const store = tx.objectStore('notifications');
      const index = store.index('timestamp');
      const request = index.getAll();
      request.onsuccess = () => {
        const notifications = request.result || [];
        resolve(notifications.sort((a, b) => b.timestamp - a.timestamp));
      };
      request.onerror = event => {
        console.error('[SW] Error getting notifications:', event.target.error);
        reject(event.target.error);
      };
    });
  });
}
function cleanupOldNotifications() {
  initStore().then(() => {
    const tx = db.transaction('notifications', 'readwrite');
    const store = tx.objectStore('notifications');
    const countRequest = store.count();
    countRequest.onsuccess = () => {
      if (countRequest.result <= 50) return;
      const excess = countRequest.result - 50;
      const index = store.index('timestamp');
      index.openCursor(null, 'next').onsuccess = event => {
        const cursor = event.target.result;
        if (!cursor) return;
        let deleted = 0;
        function deleteNext() {
          if (deleted >= excess) return;
          store.delete(cursor.value.id);
          deleted++;
          cursor.continue();
        }
        deleteNext();
      };
    };
  });
}
function setStoreItem(key, value) {
  return initStore().then(() => {
    return new Promise((resolve, reject) => {
      const tx = db.transaction('store', 'readwrite');
      const store = tx.objectStore('store');
      const request = store.put({
        key: key,
        value: value,
        updated: Date.now()
      });
      request.onsuccess = () => resolve(value);
      request.onerror = event => reject(event.target.error);
    });
  });
}
function getStoreItem(key) {
  return initStore().then(() => {
    return new Promise((resolve, reject) => {
      const tx = db.transaction('store', 'readonly');
      const store = tx.objectStore('store');
      const request = store.get(key);
      request.onsuccess = () => {
        resolve(request.result ? request.result.value : null);
      };
      request.onerror = event => reject(event.target.error);
    });
  });
}
function connectToSSE(topic) {
  if (!topic) return Promise.resolve(false);
  disconnectSSE();
  currentTopic = topic;
  return setStoreItem('topic', topic).then(() => {
    try {
      const url = `https://notify.vdo.ninja/events?topic=${encodeURIComponent(topic)}`;
      console.log('[SW] Connecting to SSE:', url);
      setStoreItem('sseStatus', {
        connected: false,
        connecting: true,
        lastConnectAttempt: Date.now()
      });
      
      // Broadcast connection attempt
      broadcastToClients('sseStatus', {
        status: 'reconnecting',
        attempt: reconnectAttempts
      });
      
      // Create new EventSource with longer timeout
      sseConnection = new EventSource(url);
      
      // Increase connection timeout from 10s to 30s
      const connectionTimeout = setTimeout(() => {
        if (sseConnection) {
          console.warn('[SW] SSE connection timed out, will retry');
          sseConnection.close();
          reconnectSSE();
        }
      }, 30000);
      
      // Add an open event listener to handle immediate connection
      sseConnection.addEventListener('open', () => {
        console.log('[SW] SSE connection opened');
        // Don't clear timeout yet - wait for 'connect' event with data
      });
      
      sseConnection.addEventListener('connect', event => {
        clearTimeout(connectionTimeout);
        console.log('[SW] SSE connection established');
        reconnectAttempts = 0;
        let connectionData = { connected: true };
        try {
          connectionData = JSON.parse(event.data);
        } catch (e) {
          console.warn('[SW] Could not parse connection data:', e);
        }
        
        setStoreItem('sseStatus', {
          connected: true,
          lastConnected: Date.now(),
          data: connectionData
        });
        
        broadcastToClients('connectionStatus', {
          connected: true,
          topic: topic
        });
        
        broadcastToClients('sseStatus', { status: 'connected' });
      });
	  
      sseConnection.addEventListener('notification', event => {
		  console.log('[SW-DEBUG] SSE notification event received');
		  try {
			let notification = {};
			let eventData = event.data;
			console.log('[SW-DEBUG] SSE event data:', eventData);
			try {
			  notification = JSON.parse(eventData);
			} catch (e) {
			  console.log('[SW-DEBUG] Failed to parse SSE data, using default notification');
			  notification = {
				title: 'VDO.Ninja Notification',
				body: 'New notification received',
				timestamp: Date.now()
			  };
			}
			notification.source = 'sse';
			notification.id = notification.id || `sse-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
			notification.timestamp = notification.timestamp || Date.now();
			console.log('[SW-DEBUG] Processing SSE notification:', notification);
			(async () => {
			  try {
				console.log('[SW-DEBUG] Checking if SSE notification is a duplicate');
				const isDuplicate = await isNotificationDuplicate(notification);
				if (!isDuplicate) {
				  console.log('[SW-DEBUG] Not a duplicate, proceeding with SSE notification:', notification.id);
				  try {
					await storeNotification(notification);
					console.log('[SW-DEBUG] Notification stored successfully');
				  } catch (storeError) {
					console.error('[SW-DEBUG] Error storing notification:', storeError);
				  }
				  try {
					console.log('[SW-DEBUG] Showing notification');
					await showNotification(notification);
					console.log('[SW-DEBUG] Notification shown successfully');
				  } catch (showError) {
					console.error('[SW-DEBUG] Error showing notification:', showError ? showError.toString() : 'Unknown error');
				  }
				  try {
					console.log('[SW-DEBUG] Broadcasting notification');
					broadcastToClients('newNotification', { notification });
					console.log('[SW-DEBUG] Notification broadcast complete');
				  } catch (broadcastError) {
					console.error('[SW-DEBUG] Error broadcasting notification:', broadcastError);
				  }
				  console.log('[SW-DEBUG] SSE notification processing complete');
				} else {
				  console.log('[SW-DEBUG] SSE notification is a duplicate, ignoring');
				}
			  } catch (error) {
				console.error('[SW-DEBUG] Error processing SSE notification:', error);
			  }
			})();
		  } catch (e) {
			console.error('[SW-DEBUG] Error handling SSE notification:', e);
		  }
		});
		
      sseConnection.addEventListener('reconnect', () => {
        console.log('[SW] Received reconnect instruction');
        reconnectSSE();
      });
	  
      sseConnection.onerror = error => {
        console.error('[SW] SSE connection error', error);
        // Don't immediately reconnect - check readyState first
        if (sseConnection && sseConnection.readyState === 2) { // CLOSED
          reconnectSSE();
        }
      };
	  
      return true;
    } catch (e) {
      console.error('[SW] Error setting up SSE:', e);
      reconnectSSE();
      return false;
    }
  });
}
function disconnectSSE() {
  if (sseConnection) {
    console.log('[SW] Closing SSE connection');
    sseConnection.close();
    sseConnection = null;
  }
  if (reconnectTimeout) {
    clearTimeout(reconnectTimeout);
    reconnectTimeout = null;
  }
}

function reconnectSSE() {
  disconnectSSE();
  if (!currentTopic) return;
  
  reconnectAttempts++;
  
  // Implement exponential backoff with jitter
  const baseDelay = Math.min(
    MAX_RECONNECT_DELAY,
    Math.pow(1.5, Math.min(reconnectAttempts, 8)) * 1000
  );
  
  // Add jitter to prevent thundering herd problem
  const jitter = Math.random() * 1000;
  const delay = baseDelay + jitter;
  
  console.log(`[SW] Reconnecting SSE in ${Math.round(delay)}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
  
  setStoreItem('sseStatus', {
    connected: false,
    reconnecting: true,
    lastDisconnected: Date.now(),
    reconnectAttempt: reconnectAttempts,
    nextReconnect: Date.now() + delay
  }).then(() => {
    broadcastToClients('sseStatus', {
      status: 'reconnecting',
      delay: delay,
      attempt: reconnectAttempts
    });
    
    // Switch to polling after MAX_RECONNECT_ATTEMPTS
    if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
      console.log('[SW] Max reconnect attempts reached, switching to polling');
      startPolling(currentTopic);
      
      // After a longer delay (5 minutes), try SSE again
      setTimeout(() => {
        reconnectAttempts = 0;
        connectToSSE(currentTopic);
      }, 300000);
      
      return;
    }
    
    // Set reconnect timeout
    reconnectTimeout = setTimeout(() => {
      connectToSSE(currentTopic);
    }, delay);
  });
}

function startPolling(topic) {
  if (!topic) return Promise.resolve(false);
  if (pollingInterval) {
    clearInterval(pollingInterval);
    pollingInterval = null;
  }
  return setStoreItem('pollingStatus', {
    isPolling: true,
    topic: topic,
    lastPoll: Date.now(),
    interval: 60000
  }).then(() => {
    broadcastToClients('sseStatus', { status: 'polling' });
    pollForNotifications(topic);
    pollingInterval = setInterval(() => {
      pollForNotifications(topic);
    }, 60000);
    if ('periodicSync' in self.registration) {
      self.registration.periodicSync.register('notification-poll', {
        minInterval: 15 * 60 * 1000
      }).catch(err => {
        console.warn('[SW] Failed to register periodic sync:', err);
      });
    }
    return true;
  });
}
async function isNotificationDuplicate(notification) {
  if (!notification) {
    console.log('[SW-DEBUG] Empty notification received');
    return true;
  }
  console.log('[SW-DEBUG] Checking notification:', {
    id: notification.id,
    title: notification.title,
    body: notification.body,
    source: notification.source,
    timestamp: notification.timestamp
  });
  notification.id = notification.id || `notification-${Date.now()}-${Math.random().toString(36).substring(2, 12)}`;
  if (processedNotificationIds.has(notification.id)) {
    console.log('[SW-DEBUG] Already processed this notification ID:', notification.id);
    return true;
  }
  const contentFingerprint = `${notification.title || ''}|${notification.body || ''}`;
  const shortWindowKey = `${contentFingerprint}|${Math.floor(Date.now()/5000)}`;
  console.log('[SW-DEBUG] Checking short-term cache with key:', shortWindowKey);
  console.log('[SW-DEBUG] Current cache size:', recentNotifications.size);
  console.log('[SW-DEBUG] Current cache entries:', Array.from(recentNotifications));
  if (recentNotifications.has(shortWindowKey)) {
    console.log('[SW-DEBUG] DUPLICATE: Found in short-term cache:', shortWindowKey);
    return true;
  }
  console.log('[SW-DEBUG] Not a duplicate (so far), adding to caches');
  recentNotifications.add(shortWindowKey);
  setTimeout(() => {
    recentNotifications.delete(shortWindowKey);
    console.log('[SW-DEBUG] Removed from short-term cache:', shortWindowKey);
  }, 5000);
  processedNotificationIds.add(notification.id);
  setTimeout(() => {
    processedNotificationIds.delete(notification.id);
    console.log('[SW-DEBUG] Removed from processed IDs cache:', notification.id);
  }, PROCESSED_ID_EXPIRY);
  console.log('[SW-DEBUG] Unique notification identified:', notification.id);
  return false;
}
function pollForNotifications(topic) {
  if (!topic) return Promise.resolve([]);
  return getStoreItem('pollingStatus').then(status => {
    const lastPoll = status?.lastPoll || (Date.now() - 3600000);
    return fetch(`https://notify.vdo.ninja/poll?topic=${encodeURIComponent(topic)}&since=${lastPoll}`)
      .then(response => response.json())
      .then(async data => {
        setStoreItem('pollingStatus', {
          ...(status || {}),
          lastPoll: Date.now(),
          isPolling: true,
          topic: topic
        });
        if (!data.notifications || !Array.isArray(data.notifications)) {
          return [];
        }
        const promises = [];
		for (const notification of data.notifications) {
		  const isDuplicate = await isNotificationDuplicate(notification);
		  if (!isDuplicate) {
			promises.push(
			  storeNotification(notification)
				.then(() => showNotification(notification))
				.then(() => broadcastToClients('newNotification', {
				  notification,
				  fromPoll: true
				}))
			);
		  } else {
			console.log('[SW] Duplicate notification from poll ignored:', notification);
		  }
		}
        return Promise.all(promises);
      })
      .catch(error => {
        console.error('[SW] Error polling for notifications:', error);
        return [];
      });
  });
}
function showNotification(notification) {
  if (!self.registration) {
    return Promise.reject(new Error('No service worker registration available'));
  }
  
	function testImage(url) {
	  return fetch(url)
		.then(response => {
		  if (response.ok) {
			return url;
		  }
		  throw new Error('Image failed to load: '+url);
		});
	}
  
  return Promise.all([
    testImage('./media/logo2.png'),
    testImage('./media/icon.png')
  ]).then(([logoExists, iconExists]) => {
    const options = {
      body: notification.body || 'Someone joined your room',
      icon: logoExists ? './media/logo2.png' : undefined,
      badge: iconExists ? './media/icon.png' : undefined,
      vibrate: [100, 50, 100],
      data: {
        url: notification.url || notification.originalUrl || self.registration.scope,
        timestamp: notification.timestamp || Date.now(),
        topic: notification.topic,
        id: notification.id
      }
    };
    
    return self.registration.showNotification(
      notification.title || 'VDO.Ninja',
      options
    );
  });
}

function broadcastToClients(action, data) {
  return self.clients.matchAll().then(clients => {
    clients.forEach(client => {
      client.postMessage({
        action: action,
        ...data
      });
    });
  });
}
function disconnectAll() {
  disconnectSSE();
  if (pollingInterval) {
    clearInterval(pollingInterval);
    pollingInterval = null;
  }
  currentTopic = null;
  return Promise.all([
    setStoreItem('topic', null),
    setStoreItem('sseStatus', { connected: false }),
    setStoreItem('pollingStatus', { isPolling: false })
  ]);
}