import * as plugins from './plugins.js';
import * as interfaces from '../dist_ts_interfaces/index.js';
import { logger } from './logging.js';
import { getMetricsCollector } from './classes.metrics.js';
import { getEventBus, ServiceWorkerEvent } from './classes.eventbus.js';
import { getPersistentStore } from './classes.persistentstore.js';
import { getRequestLogStore } from './classes.requestlogstore.js';
/**
 * This class is meant to be used only on the backend side
 */
export class ServiceworkerBackend {
    deesComms = new plugins.deesComms.DeesComms();
    swSelf;
    clientUpdateInterval = null;
    // Throttling for metrics updates (max 1 per 500ms)
    metricsUpdateThrottle = null;
    pendingMetricsUpdate = false;
    METRICS_THROTTLE_MS = 500;
    /**
     * Helper to create properly formatted TypedRequest messages for DeesComms
     */
    createMessage(method, request) {
        const id = `${method}_${Date.now()}`;
        return {
            method,
            request,
            messageId: id,
            correlation: {
                id,
                phase: 'request'
            }
        };
    }
    constructor(optionsArg) {
        this.swSelf = optionsArg.self;
        const metrics = getMetricsCollector();
        // lets handle wakestuff
        optionsArg.self.addEventListener('message', (event) => {
            if (event.data && event.data.type === 'wakeUpCall') {
                console.log('sw-backend: got wake up call');
            }
        });
        this.deesComms.createTypedHandler('broadcastConnectionPolling', async (reqArg) => {
            // Record connection attempt
            metrics.recordConnectionAttempt();
            metrics.recordConnectionSuccess();
            // Update connected clients count
            await this.updateConnectedClientsCount();
            return {
                serviceworkerId: '123'
            };
        });
        this.deesComms.createTypedHandler('purgeServiceWorkerCache', async (reqArg) => {
            console.log(`Executing purge cache in serviceworker backend.`);
            return await optionsArg.purgeCache?.(reqArg);
        });
        // Handler for getting current SW status
        this.deesComms.createTypedHandler('serviceworker_getStatus', async () => {
            const metrics = getMetricsCollector();
            const metricsData = metrics.getMetrics();
            return {
                isActive: true,
                isOnline: metricsData.speedtest.isOnline,
                cacheHitRate: metrics.getCacheHitRate(),
                resourceCount: metrics.getResourceCount(),
                connectedClients: metricsData.connection.connectedClients,
                lastUpdateCheck: metricsData.update.lastCheckTimestamp,
            };
        });
        // Handler for getting event log
        this.deesComms.createTypedHandler('serviceworker_getEventLog', async (reqArg) => {
            const persistentStore = getPersistentStore();
            return await persistentStore.getEventLog({
                limit: reqArg.limit,
                type: reqArg.type,
                since: reqArg.since,
                before: reqArg.before,
            });
        });
        // Handler for getting cumulative metrics
        this.deesComms.createTypedHandler('serviceworker_getCumulativeMetrics', async () => {
            const persistentStore = getPersistentStore();
            return persistentStore.getCumulativeMetrics();
        });
        // Handler for clearing event log
        this.deesComms.createTypedHandler('serviceworker_clearEventLog', async () => {
            const persistentStore = getPersistentStore();
            const success = await persistentStore.clearEventLog();
            return { success };
        });
        // Handler for getting event count since timestamp
        this.deesComms.createTypedHandler('serviceworker_getEventCount', async (reqArg) => {
            const persistentStore = getPersistentStore();
            const count = await persistentStore.getEventCount(reqArg.since);
            return { count };
        });
        // ================================
        // TypedRequest Traffic Monitoring
        // ================================
        // Handler for receiving TypedRequest logs from clients
        this.deesComms.createTypedHandler('serviceworker_typedRequestLog', async (reqArg) => {
            const requestLogStore = getRequestLogStore();
            requestLogStore.addEntry(reqArg);
            // Broadcast to sw-dash viewers
            await this.broadcastTypedRequestLogged(reqArg);
            return {};
        });
        // Handler for getting TypedRequest logs
        this.deesComms.createTypedHandler('serviceworker_getTypedRequestLogs', async (reqArg) => {
            const requestLogStore = getRequestLogStore();
            const logs = requestLogStore.getEntries({
                limit: reqArg.limit,
                method: reqArg.method,
                since: reqArg.since,
                before: reqArg.before,
            });
            const totalCount = requestLogStore.getTotalCount({
                method: reqArg.method,
                since: reqArg.since,
            });
            return { logs, totalCount };
        });
        // Handler for getting TypedRequest statistics
        this.deesComms.createTypedHandler('serviceworker_getTypedRequestStats', async () => {
            const requestLogStore = getRequestLogStore();
            return requestLogStore.getStats();
        });
        // Handler for clearing TypedRequest logs
        this.deesComms.createTypedHandler('serviceworker_clearTypedRequestLogs', async () => {
            const requestLogStore = getRequestLogStore();
            requestLogStore.clear();
            return { success: true };
        });
        // Periodically update connected clients count
        this.startClientCountUpdates();
        // Subscribe to EventBus and broadcast status updates
        this.setupEventBusSubscriptions();
    }
    /**
     * Sets up subscriptions to EventBus events and broadcasts them to clients
     */
    setupEventBusSubscriptions() {
        const eventBus = getEventBus();
        const persistentStore = getPersistentStore();
        // Network status changes
        eventBus.on(ServiceWorkerEvent.NETWORK_ONLINE, async () => {
            this.broadcastStatusUpdate({
                source: 'network',
                type: 'online',
                message: 'Connection restored',
                persist: false,
                timestamp: Date.now(),
            });
            // Log to persistent store
            await persistentStore.logEvent('network_online', 'Network connection restored');
        });
        eventBus.on(ServiceWorkerEvent.NETWORK_OFFLINE, async () => {
            this.broadcastStatusUpdate({
                source: 'network',
                type: 'offline',
                message: 'Connection lost - offline mode',
                persist: true,
                timestamp: Date.now(),
            });
            // Log to persistent store
            await persistentStore.logEvent('network_offline', 'Network connection lost');
        });
        // Update events
        eventBus.on(ServiceWorkerEvent.UPDATE_AVAILABLE, async (_event, payload) => {
            this.broadcastStatusUpdate({
                source: 'serviceworker',
                type: 'update',
                message: 'Update available',
                details: {
                    version: payload.newVersion,
                },
                persist: false,
                timestamp: Date.now(),
            });
            // Log to persistent store
            await persistentStore.logEvent('update_check', `Update available: ${payload.newVersion}`, {
                newVersion: payload.newVersion,
            });
        });
        eventBus.on(ServiceWorkerEvent.UPDATE_APPLIED, async (_event, payload) => {
            this.broadcastStatusUpdate({
                source: 'serviceworker',
                type: 'update',
                message: 'Update applied',
                details: {
                    version: payload.newVersion,
                },
                persist: false,
                timestamp: Date.now(),
            });
            // Log to persistent store
            await persistentStore.logEvent('sw_updated', `Service worker updated to ${payload.newVersion}`, {
                newVersion: payload.newVersion,
            });
        });
        eventBus.on(ServiceWorkerEvent.UPDATE_ERROR, async (_event, payload) => {
            this.broadcastStatusUpdate({
                source: 'serviceworker',
                type: 'error',
                message: `Update error: ${payload.error || 'Unknown error'}`,
                persist: true,
                timestamp: Date.now(),
            });
            // Log to persistent store
            await persistentStore.logEvent('error', `Update error: ${payload.error || 'Unknown error'}`, {
                error: payload.error,
            });
        });
        // Cache invalidation
        eventBus.on(ServiceWorkerEvent.CACHE_INVALIDATE_ALL, () => {
            this.broadcastStatusUpdate({
                source: 'serviceworker',
                type: 'cache',
                message: 'Clearing cache...',
                persist: false,
                timestamp: Date.now(),
            });
            // Note: cache_invalidated event is logged in the ServiceWorker class
        });
        // Lifecycle events
        eventBus.on(ServiceWorkerEvent.ACTIVATE, () => {
            this.broadcastStatusUpdate({
                source: 'serviceworker',
                type: 'connected',
                message: 'Service worker activated',
                persist: false,
                timestamp: Date.now(),
            });
            // Note: sw_activated event is logged in the ServiceWorker class
        });
    }
    /**
     * Broadcasts a status update to all connected clients
     */
    async broadcastStatusUpdate(status) {
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_statusUpdate', status));
            logger.log('info', `Status update broadcast: ${status.source}:${status.type} - ${status.message}`);
        }
        catch (error) {
            logger.log('warn', `Failed to broadcast status update: ${error}`);
        }
    }
    /**
     * Broadcasts a TypedRequest log entry to all connected clients (for sw-dash)
     */
    async broadcastTypedRequestLogged(entry) {
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_typedRequestLogged', entry));
        }
        catch (error) {
            logger.log('warn', `Failed to broadcast TypedRequest log: ${error}`);
        }
    }
    /**
     * Start periodic updates of connected client count
     */
    startClientCountUpdates() {
        // Update immediately
        this.updateConnectedClientsCount();
        // Then update every 5 seconds
        this.clientUpdateInterval = setInterval(() => {
            this.updateConnectedClientsCount();
        }, 5000);
    }
    /**
     * Update the connected clients count using the Clients API
     */
    async updateConnectedClientsCount() {
        try {
            const clients = await this.swSelf.clients.matchAll({ type: 'window' });
            const metrics = getMetricsCollector();
            metrics.setConnectedClients(clients.length);
        }
        catch (error) {
            logger.log('warn', `Failed to update connected clients count: ${error}`);
        }
    }
    /**
     * reloads all clients
     */
    async triggerReloadAll() {
        try {
            logger.log('info', 'Triggering reload for all clients due to new version');
            // Send update message via DeesComms
            // This will be picked up by clients that have registered a handler for 'serviceworker_newVersion'
            await this.deesComms.postMessage(this.createMessage('serviceworker_newVersion', {}));
            // As a fallback, also use the clients API to reload clients that might not catch the broadcast
            const clients = await this.swSelf.clients.matchAll({ type: 'window' });
            logger.log('info', `Found ${clients.length} clients to reload`);
            // Update metrics with current client count
            const metrics = getMetricsCollector();
            metrics.setConnectedClients(clients.length);
            for (const client of clients) {
                if ('navigate' in client) {
                    // For modern browsers, navigate to the same URL to trigger reload
                    client.navigate(client.url);
                    logger.log('info', `Navigated client to: ${client.url}`);
                }
            }
        }
        catch (error) {
            logger.log('error', `Failed to reload clients: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    /**
     * display notification
     */
    async addNotification(notificationArg) {
        try {
            // Check if we have permission to show notifications
            const permission = self.Notification?.permission;
            if (permission !== 'granted') {
                logger.log('warn', `Cannot show notification: permission is ${permission}`);
                return;
            }
            // Type-cast self to ServiceWorkerGlobalScope
            const swSelf = self;
            // Use type assertion for notification options to include vibrate
            const options = {
                body: notificationArg.body,
                icon: '/favicon.ico', // Assuming there's a favicon
                badge: '/favicon.ico',
                vibrate: [200, 100, 200]
            };
            await swSelf.registration.showNotification(notificationArg.title, options);
            logger.log('info', `Notification shown: ${notificationArg.title}`);
        }
        catch (error) {
            logger.log('error', `Failed to show notification: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async alert(alertText) {
        // Since we can't directly show alerts from service worker context,
        // we'll use notifications as a fallback
        await this.addNotification({
            title: 'Alert',
            body: alertText
        });
        // Send message to clients who might be able to show an actual alert
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_alert', { message: alertText }));
            logger.log('info', `Alert message sent to clients: ${alertText}`);
        }
        catch (error) {
            logger.log('error', `Failed to send alert to clients: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    // ===============
    // Push methods for real-time updates
    // ===============
    /**
     * Pushes a new event log entry to all connected clients
     * Called immediately when an event is logged
     */
    async pushEvent(entry) {
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_eventLogged', entry));
            logger.log('note', `Pushed event to clients: ${entry.type}`);
        }
        catch (error) {
            logger.log('warn', `Failed to push event: ${error}`);
        }
    }
    /**
     * Pushes a metrics update to all connected clients
     * Throttled to max 1 update per 500ms to prevent spam
     */
    pushMetricsUpdate() {
        // Mark that we have a pending update
        this.pendingMetricsUpdate = true;
        // If we're already throttling, just wait for the next window
        if (this.metricsUpdateThrottle) {
            return;
        }
        // Send the update and start throttle window
        this.sendMetricsUpdate();
        this.metricsUpdateThrottle = setTimeout(() => {
            this.metricsUpdateThrottle = null;
            // If there was a pending update during the throttle window, send it now
            if (this.pendingMetricsUpdate) {
                this.sendMetricsUpdate();
            }
        }, this.METRICS_THROTTLE_MS);
    }
    /**
     * Actually sends the metrics update via DeesComms
     */
    async sendMetricsUpdate() {
        this.pendingMetricsUpdate = false;
        const metrics = getMetricsCollector();
        const metricsData = metrics.getMetrics();
        const snapshot = {
            cache: {
                hits: metricsData.cache.hits,
                misses: metricsData.cache.misses,
                errors: metricsData.cache.errors,
                bytesServedFromCache: metricsData.cache.bytesServedFromCache,
                bytesFetched: metricsData.cache.bytesFetched,
            },
            network: {
                totalRequests: metricsData.network.totalRequests,
                successfulRequests: metricsData.network.successfulRequests,
                failedRequests: metricsData.network.failedRequests,
            },
            cacheHitRate: metrics.getCacheHitRate(),
            networkSuccessRate: metrics.getNetworkSuccessRate(),
            resourceCount: metrics.getResourceCount(),
            uptime: metricsData.uptime,
            timestamp: Date.now(),
        };
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_metricsUpdate', snapshot));
        }
        catch (error) {
            logger.log('warn', `Failed to push metrics update: ${error}`);
        }
    }
    /**
     * Pushes notification when a resource is cached
     */
    async pushResourceCached(url, contentType, size, cached) {
        try {
            await this.deesComms.postMessage(this.createMessage('serviceworker_resourceCached', { url, contentType, size, cached }));
        }
        catch (error) {
            logger.log('warn', `Failed to push resource cached: ${error}`);
        }
    }
}
//# sourceMappingURL=data:application/json;base64, 