// Shivao Service Worker — offline real
// Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto.
// Versão usada nos cache names — bumpa essa string pra invalidar caches antigos em deploys.
const VERSION = 'shivao-v1.10.13';
const SHELL_CACHE = `shivao-shell-${VERSION}`;
const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell
const WINDY_CACHE = `shivao-windy-${VERSION}`;

// Recursos do "shell" (UI essencial). Pré-cacheados no install.
const SHELL_URLS = [
  '/',
  '/manifest.json',
  '/icon.svg',
  // Leaflet (cdnjs)
  'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css',
  'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js',
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(SHELL_CACHE).then(async (cache) => {
      // Tenta cachear cada um individualmente — falha de 1 não derruba install
      await Promise.all(SHELL_URLS.map(url =>
        cache.add(url).catch(e => console.warn('[SW] precache miss:', url, e.message))
      ));
    }).then(() => self.skipWaiting())
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      // Remove caches antigos do shell e windy (não tiles — que tem própria versão)
      keys.filter(k => k.startsWith('shivao-shell-') && k !== SHELL_CACHE)
        .concat(keys.filter(k => k.startsWith('shivao-windy-') && k !== WINDY_CACHE))
        .map(k => caches.delete(k))
    )).then(() => self.clients.claim())
  );
});

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  if (event.request.method !== 'GET') return; // POST/PUT/DELETE passam direto

  // /api do Shivão: passa direto, não interferir em sync/heartbeat/auth
  if (url.pathname.startsWith('/api/')) return;

  // Map tiles (OSM): cache-first, com network fallback
  if (url.host === 'tile.openstreetmap.org' || url.pathname.match(/\.(png|jpg|jpeg)$/) && url.pathname.match(/\/\d+\/\d+\/\d+\./)) {
    event.respondWith(cacheFirst(event.request, TILES_CACHE));
    return;
  }

  // Windy API: network-first, fallback cache (mostra última previsão quando offline)
  if (url.host === 'api.windy.com') {
    event.respondWith(networkFirst(event.request, WINDY_CACHE, 3600 * 1000)); // TTL 1h
    return;
  }

  // Open-Meteo (fallback meteo): mesmo padrão
  if (url.host === 'api.open-meteo.com' || url.host === 'marine-api.open-meteo.com') {
    event.respondWith(networkFirst(event.request, WINDY_CACHE, 3600 * 1000));
    return;
  }

  // Fonts Google (fontes carregam no startup): cache-first
  if (url.host === 'fonts.googleapis.com' || url.host === 'fonts.gstatic.com') {
    event.respondWith(cacheFirst(event.request, SHELL_CACHE));
    return;
  }

  // CDN Leaflet e similares
  if (url.host === 'cdnjs.cloudflare.com') {
    event.respondWith(cacheFirst(event.request, SHELL_CACHE));
    return;
  }

  // Mesma origem (HTML/JS/CSS do app): stale-while-revalidate
  if (url.origin === self.location.origin) {
    event.respondWith(staleWhileRevalidate(event.request, SHELL_CACHE));
    return;
  }
  // Resto: deixa rede tratar
});

async function cacheFirst(request, cacheName) {
  const cache = await caches.open(cacheName);
  const cached = await cache.match(request);
  if (cached) return cached;
  try {
    const fresh = await fetch(request);
    if (fresh.ok) cache.put(request, fresh.clone()).catch(() => {});
    return fresh;
  } catch (e) {
    return new Response('offline-no-cache', { status: 503, statusText: 'Offline' });
  }
}

async function networkFirst(request, cacheName, ttlMs) {
  const cache = await caches.open(cacheName);
  try {
    const fresh = await fetch(request);
    if (fresh.ok) cache.put(request, fresh.clone()).catch(() => {});
    return fresh;
  } catch (e) {
    const cached = await cache.match(request);
    if (cached) return cached;
    throw e;
  }
}

async function staleWhileRevalidate(request, cacheName) {
  const cache = await caches.open(cacheName);
  const cached = await cache.match(request);
  const fetchPromise = fetch(request).then(res => {
    if (res.ok) cache.put(request, res.clone()).catch(() => {});
    return res;
  }).catch(() => cached || new Response('offline', { status: 503 }));
  return cached || fetchPromise;
}

// Mensagens da página: pré-cachear tiles de uma área
self.addEventListener('message', (event) => {
  if (!event.data || !event.data.type) return;

  if (event.data.type === 'PRECACHE_TILES') {
    const { bounds, minZoom, maxZoom } = event.data;
    precacheTiles(bounds, minZoom, maxZoom).then(stats => {
      event.source && event.source.postMessage({ type: 'PRECACHE_DONE', ...stats });
    }).catch(err => {
      event.source && event.source.postMessage({ type: 'PRECACHE_ERROR', error: err.message });
    });
  }

  if (event.data.type === 'CACHE_SIZE') {
    cacheSizes().then(sizes => {
      event.source && event.source.postMessage({ type: 'CACHE_SIZE_REPORT', sizes });
    });
  }

  if (event.data.type === 'CLEAR_TILES') {
    caches.delete(TILES_CACHE).then(ok => {
      event.source && event.source.postMessage({ type: 'TILES_CLEARED', ok });
    });
  }
});

// Calcula tiles necessários e baixa em paralelo (max 6 simultâneos)
async function precacheTiles(bounds, minZoom, maxZoom) {
  const cache = await caches.open(TILES_CACHE);
  const tiles = [];
  for (let z = minZoom; z <= maxZoom; z++) {
    const min = lngLatToTile(bounds.west, bounds.north, z);
    const max = lngLatToTile(bounds.east, bounds.south, z);
    for (let x = min.x; x <= max.x; x++) {
      for (let y = min.y; y <= max.y; y++) {
        tiles.push({ z, x, y });
      }
    }
  }

  let done = 0, failed = 0;
  const total = tiles.length;
  // Limita a 6 conexões paralelas pra respeitar OSM tile policy
  const queue = tiles.slice();
  await Promise.all([...Array(6)].map(async () => {
    while (queue.length) {
      const t = queue.shift();
      const url = `https://tile.openstreetmap.org/${t.z}/${t.x}/${t.y}.png`;
      try {
        const res = await fetch(url, { headers: { 'User-Agent': 'Shivao-PWA' } });
        if (res.ok) await cache.put(url, res);
        else failed++;
      } catch { failed++; }
      done++;
    }
  }));

  return { total, done, failed };
}

function lngLatToTile(lng, lat, z) {
  const x = Math.floor((lng + 180) / 360 * Math.pow(2, z));
  const y = Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, z));
  return { x, y };
}

async function cacheSizes() {
  const result = {};
  for (const name of [SHELL_CACHE, TILES_CACHE, WINDY_CACHE]) {
    const cache = await caches.open(name);
    const keys = await cache.keys();
    result[name] = keys.length;
  }
  return result;
}
