/**
 * BreakPilot PWA Service Worker
 *
 * Funktionen:
 * 1. Cacht statische Assets fuer Offline-Nutzung
 * 2. Cacht LLM-Modell fuer lokale Header-Extraktion
 * 3. Background Sync fuer Korrektur-Uploads
 */

const APP_CACHE = 'breakpilot-app-v1';
const LLM_CACHE = 'breakpilot-llm-v1';
const DATA_CACHE = 'breakpilot-data-v1';

// Statische Assets zum Cachen beim Install
const STATIC_ASSETS = [
    '/',
    '/app',
    '/static/css/main.css',
    '/static/js/main.js'
];

// LLM/OCR Assets (groessere Dateien, separater Cache)
const LLM_ASSETS = [
    // Tesseract.js (OCR)
    'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js',
    'https://cdn.jsdelivr.net/npm/tesseract.js-core@5.0.0/tesseract-core.wasm.js',

    // Tesseract Deutsch Sprachpaket
    'https://tessdata.projectnaptha.com/4.0.0/deu.traineddata.gz',

    // Transformers.js (fuer zukuenftige Vision-Modelle)
    'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.1/dist/transformers.min.js'
];

// ============================================================
// INSTALL EVENT
// ============================================================

self.addEventListener('install', (event) => {
    console.log('[ServiceWorker] Installing...');

    event.waitUntil(
        Promise.all([
            // App Cache
            caches.open(APP_CACHE).then((cache) => {
                console.log('[ServiceWorker] Caching app shell');
                return cache.addAll(STATIC_ASSETS);
            }),

            // LLM Cache (optional, bei Fehler ignorieren)
            caches.open(LLM_CACHE).then((cache) => {
                console.log('[ServiceWorker] Pre-caching LLM assets');
                return Promise.allSettled(
                    LLM_ASSETS.map(url =>
                        cache.add(url).catch(err => {
                            console.warn('[ServiceWorker] Failed to cache:', url, err);
                        })
                    )
                );
            })
        ]).then(() => {
            console.log('[ServiceWorker] Install complete');
            return self.skipWaiting();
        })
    );
});

// ============================================================
// ACTIVATE EVENT
// ============================================================

self.addEventListener('activate', (event) => {
    console.log('[ServiceWorker] Activating...');

    event.waitUntil(
        // Alte Caches aufräumen
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames
                    .filter(name => {
                        // Nur alte Versionen loeschen
                        return name.startsWith('breakpilot-') &&
                               ![APP_CACHE, LLM_CACHE, DATA_CACHE].includes(name);
                    })
                    .map(name => {
                        console.log('[ServiceWorker] Deleting old cache:', name);
                        return caches.delete(name);
                    })
            );
        }).then(() => {
            console.log('[ServiceWorker] Claiming clients');
            return self.clients.claim();
        })
    );
});

// ============================================================
// FETCH EVENT
// ============================================================

self.addEventListener('fetch', (event) => {
    const url = new URL(event.request.url);

    // API-Requests: Network-First (immer frische Daten)
    if (url.pathname.startsWith('/api/')) {
        event.respondWith(networkFirstStrategy(event.request));
        return;
    }

    // LLM/OCR Assets: Cache-First (grosse Dateien)
    if (isLLMAsset(url)) {
        event.respondWith(cacheFirstStrategy(event.request, LLM_CACHE));
        return;
    }

    // Statische Assets: Cache-First mit Network-Fallback
    if (isStaticAsset(url)) {
        event.respondWith(cacheFirstStrategy(event.request, APP_CACHE));
        return;
    }

    // Alles andere: Network-First
    event.respondWith(networkFirstStrategy(event.request));
});

// ============================================================
// CACHING STRATEGIES
// ============================================================

/**
 * Cache-First Strategy: Versucht Cache, dann Network.
 * Gut fuer statische Assets und LLM-Modelle.
 */
async function cacheFirstStrategy(request, cacheName) {
    const cache = await caches.open(cacheName);
    const cachedResponse = await cache.match(request);

    if (cachedResponse) {
        // Im Hintergrund aktualisieren (Stale-While-Revalidate)
        fetchAndCache(request, cache);
        return cachedResponse;
    }

    // Nicht im Cache: Von Network holen und cachen
    try {
        const networkResponse = await fetch(request);
        if (networkResponse.ok) {
            cache.put(request, networkResponse.clone());
        }
        return networkResponse;
    } catch (error) {
        console.error('[ServiceWorker] Fetch failed:', error);
        return new Response('Offline - Bitte Internetverbindung pruefen', {
            status: 503,
            statusText: 'Service Unavailable'
        });
    }
}

/**
 * Network-First Strategy: Versucht Network, dann Cache.
 * Gut fuer API-Calls und dynamische Inhalte.
 */
async function networkFirstStrategy(request) {
    const cache = await caches.open(DATA_CACHE);

    try {
        const networkResponse = await fetch(request);

        // Erfolgreiche GET-Requests cachen
        if (networkResponse.ok && request.method === 'GET') {
            cache.put(request, networkResponse.clone());
        }

        return networkResponse;

    } catch (error) {
        // Network failed: Versuche Cache
        const cachedResponse = await cache.match(request);
        if (cachedResponse) {
            return cachedResponse;
        }

        // Kein Cache: Offline-Fehler
        return new Response(JSON.stringify({
            error: 'offline',
            message: 'Keine Internetverbindung'
        }), {
            status: 503,
            headers: { 'Content-Type': 'application/json' }
        });
    }
}

/**
 * Holt Resource von Network und cached sie (im Hintergrund).
 */
async function fetchAndCache(request, cache) {
    try {
        const response = await fetch(request);
        if (response.ok) {
            cache.put(request, response.clone());
        }
    } catch (error) {
        // Ignorieren - Cache ist noch gueltig
    }
}

// ============================================================
// HELPERS
// ============================================================

function isStaticAsset(url) {
    return url.pathname.startsWith('/static/') ||
           url.pathname.endsWith('.css') ||
           url.pathname.endsWith('.js') ||
           url.pathname.endsWith('.png') ||
           url.pathname.endsWith('.jpg') ||
           url.pathname.endsWith('.svg') ||
           url.pathname.endsWith('.woff2');
}

function isLLMAsset(url) {
    const llmDomains = [
        'cdn.jsdelivr.net',
        'huggingface.co',
        'tessdata.projectnaptha.com'
    ];

    const llmExtensions = [
        '.onnx',
        '.wasm',
        '.traineddata',
        '.traineddata.gz',
        '.bin'
    ];

    return llmDomains.some(d => url.hostname.includes(d)) ||
           llmExtensions.some(e => url.pathname.endsWith(e));
}

// ============================================================
// BACKGROUND SYNC (fuer Offline-Uploads)
// ============================================================

self.addEventListener('sync', (event) => {
    console.log('[ServiceWorker] Sync event:', event.tag);

    if (event.tag === 'upload-exams') {
        event.waitUntil(uploadPendingExams());
    }
});

async function uploadPendingExams() {
    // Hole ausstehende Uploads aus IndexedDB
    const db = await openDB('breakpilot-pending', 1);
    const pendingUploads = await db.getAll('uploads');

    for (const upload of pendingUploads) {
        try {
            const response = await fetch(upload.url, {
                method: 'POST',
                body: upload.formData
            });

            if (response.ok) {
                await db.delete('uploads', upload.id);

                // Benachrichtigung an Client
                self.clients.matchAll().then(clients => {
                    clients.forEach(client => {
                        client.postMessage({
                            type: 'upload-complete',
                            id: upload.id
                        });
                    });
                });
            }
        } catch (error) {
            console.error('[ServiceWorker] Upload failed:', error);
        }
    }
}

// Simple IndexedDB wrapper
function openDB(name, version) {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(name, version);
        request.onerror = () => reject(request.error);
        request.onsuccess = () => resolve(request.result);
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            if (!db.objectStoreNames.contains('uploads')) {
                db.createObjectStore('uploads', { keyPath: 'id', autoIncrement: true });
            }
        };
    });
}

// ============================================================
// PUSH NOTIFICATIONS (fuer Korrektur-Fortschritt)
// ============================================================

self.addEventListener('push', (event) => {
    const data = event.data?.json() || {};

    const options = {
        body: data.body || 'Neue Benachrichtigung',
        icon: '/static/icons/icon-192.png',
        badge: '/static/icons/badge-72.png',
        tag: data.tag || 'default',
        data: data.url || '/',
        actions: data.actions || []
    };

    event.waitUntil(
        self.registration.showNotification(
            data.title || 'BreakPilot',
            options
        )
    );
});

self.addEventListener('notificationclick', (event) => {
    event.notification.close();

    event.waitUntil(
        clients.matchAll({ type: 'window' }).then(clientList => {
            // Existierendes Fenster fokussieren
            for (const client of clientList) {
                if (client.url.includes('/app') && 'focus' in client) {
                    return client.focus();
                }
            }
            // Neues Fenster oeffnen
            if (clients.openWindow) {
                return clients.openWindow(event.notification.data || '/app');
            }
        })
    );
});

console.log('[ServiceWorker] Loaded');
