// AIS Map Service Worker — кеширование тайлов и ассетов.
// Цели: моментальная отрисовка карты после первой загрузки, офлайн-доступ к ранее
// просмотренным тайлам, минимум сетевых запросов в slow-networks и при pan/zoom.

const CACHE_VERSION = 'v2';
const TILE_CACHE = 'aismap-tiles-' + CACHE_VERSION;
const ASSET_CACHE = 'aismap-assets-' + CACHE_VERSION;
const APP_CACHE = 'aismap-app-' + CACHE_VERSION;

// Ограничиваем, сколько тайлов держим на устройстве, чтобы не забить storage на телефоне.
const TILE_CACHE_MAX = 4000;

self.addEventListener('install', (event) => {
    self.skipWaiting();
});

self.addEventListener('activate', (event) => {
    event.waitUntil((async () => {
        const keys = await caches.keys();
        await Promise.all(
            keys
                .filter((k) => ![TILE_CACHE, ASSET_CACHE, APP_CACHE].includes(k))
                .map((k) => caches.delete(k))
        );
        await self.clients.claim();
    })());
});

function isTileRequest(url) {
    return url.pathname.startsWith('/tiles/') || url.pathname.startsWith('/vtiles/');
}

function isLongLivedAsset(url) {
    if (url.pathname.startsWith('/static/leaflet/')) return true;
    if (url.pathname.startsWith('/static/xterm/')) return true;
    if (url.pathname.startsWith('/svg/')) return true;
    return false;
}

function isAppShell(url) {
    if (url.pathname === '/' || url.pathname === '/cert') return true;
    if (url.pathname.startsWith('/static/js/')) return true;
    if (url.pathname.startsWith('/static/css/')) return true;
    return false;
}

function isApiRequest(url) {
    return url.pathname.startsWith('/api/') || url.pathname === '/ws' || url.pathname.startsWith('/ws/');
}

async function trimCache(cacheName, max) {
    try {
        const cache = await caches.open(cacheName);
        const keys = await cache.keys();
        if (keys.length <= max) return;
        const excess = keys.length - max;
        for (let i = 0; i < excess; i += 1) {
            await cache.delete(keys[i]);
        }
    } catch (e) {}
}

// Cache-first с фоновым обновлением: отдаём из кеша если есть, в фоне освежаем.
async function cacheFirstWithRefresh(request, cacheName) {
    const cache = await caches.open(cacheName);
    const cached = await cache.match(request);
    const fetchAndUpdate = fetch(request)
        .then((resp) => {
            if (resp && resp.ok) {
                cache.put(request, resp.clone()).catch(() => {});
                if (cacheName === TILE_CACHE) {
                    trimCache(TILE_CACHE, TILE_CACHE_MAX);
                }
            }
            return resp;
        })
        .catch(() => null);
    if (cached) {
        // Обновление в фоне, клиенту отдаём кеш немедленно.
        fetchAndUpdate.catch(() => {});
        return cached;
    }
    const fresh = await fetchAndUpdate;
    if (fresh) return fresh;
    return new Response('', { status: 504, statusText: 'Gateway Timeout (offline)' });
}

// Stale-while-revalidate: мгновенно из кеша, параллельно обновляем кеш.
async function staleWhileRevalidate(request, cacheName) {
    const cache = await caches.open(cacheName);
    const cached = await cache.match(request);
    const networkFetch = fetch(request)
        .then((resp) => {
            if (resp && resp.ok) {
                cache.put(request, resp.clone()).catch(() => {});
            }
            return resp;
        })
        .catch(() => null);
    if (cached) {
        networkFetch.catch(() => {});
        return cached;
    }
    const fresh = await networkFetch;
    if (fresh) return fresh;
    return new Response('', { status: 504, statusText: 'Gateway Timeout (offline)' });
}

self.addEventListener('fetch', (event) => {
    const req = event.request;
    if (req.method !== 'GET') return;

    let url;
    try {
        url = new URL(req.url);
    } catch (e) {
        return;
    }

    // Не трогаем кросс-оригинальные запросы (OSM, CARTO и т.п.) — пусть идут напрямую.
    if (url.origin !== self.location.origin) return;

    // API и WebSocket — без кеша. WebSocket вообще не проходит через fetch, но на всякий случай.
    if (isApiRequest(url)) return;

    if (isTileRequest(url)) {
        event.respondWith(cacheFirstWithRefresh(req, TILE_CACHE));
        return;
    }

    if (isLongLivedAsset(url)) {
        event.respondWith(cacheFirstWithRefresh(req, ASSET_CACHE));
        return;
    }

    if (isAppShell(url)) {
        event.respondWith(staleWhileRevalidate(req, APP_CACHE));
        return;
    }
});

// Принудительный сброс кешей по команде со страницы.
self.addEventListener('message', (event) => {
    if (!event.data || typeof event.data !== 'object') return;
    if (event.data.type === 'aismap:clear-caches') {
        event.waitUntil((async () => {
            const keys = await caches.keys();
            await Promise.all(keys.map((k) => caches.delete(k)));
        })());
    }
});
