// rMaps Service Worker for Push Notifications, Background Location & Offline Support
const CACHE_VERSION = 'v2';
const STATIC_CACHE = `rmaps-static-${CACHE_VERSION}`;
const TILE_CACHE = `rmaps-tiles-${CACHE_VERSION}`;
const DATA_CACHE = `rmaps-data-${CACHE_VERSION}`;
const SYNC_TAG = 'rmaps-location-sync';

// Assets to precache for app shell
const PRECACHE_ASSETS = [
  '/',
  '/icon-192.png',
  '/icon-512.png',
  '/manifest.json',
];

// Tile URL patterns to cache
const TILE_PATTERNS = [
  /basemaps\.cartocdn\.com/,
  /tile\.openstreetmap\.org/,
];

// Max tiles to cache (prevent storage bloat)
const MAX_TILE_CACHE_SIZE = 500;

// ==================== INSTALL ====================
self.addEventListener('install', (event) => {
  console.log('[SW] Installing service worker...');

  event.waitUntil(
    caches.open(STATIC_CACHE)
      .then((cache) => {
        console.log('[SW] Precaching app shell');
        return cache.addAll(PRECACHE_ASSETS);
      })
      .then(() => self.skipWaiting())
      .catch((err) => {
        console.error('[SW] Precache failed:', err);
        // Don't fail installation if precache fails
        return self.skipWaiting();
      })
  );
});

// ==================== ACTIVATE ====================
self.addEventListener('activate', (event) => {
  console.log('[SW] Activating service worker...');

  event.waitUntil(
    caches.keys()
      .then((cacheNames) => {
        return Promise.all(
          cacheNames
            .filter((name) => {
              // Delete old versioned caches
              return name.startsWith('rmaps-') &&
                     name !== STATIC_CACHE &&
                     name !== TILE_CACHE &&
                     name !== DATA_CACHE;
            })
            .map((name) => {
              console.log('[SW] Deleting old cache:', name);
              return caches.delete(name);
            })
        );
      })
      .then(() => self.clients.claim())
  );
});

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

  // Skip non-GET requests
  if (event.request.method !== 'GET') return;

  // Skip chrome-extension and other non-http(s) requests
  if (!url.protocol.startsWith('http')) return;

  // Map tiles - cache-first strategy
  if (isTileRequest(url)) {
    event.respondWith(handleTileRequest(event.request));
    return;
  }

  // API requests - network-first strategy
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(handleApiRequest(event.request));
    return;
  }

  // Static assets and pages - stale-while-revalidate
  event.respondWith(handleStaticRequest(event.request));
});

// Check if URL is a map tile
function isTileRequest(url) {
  return TILE_PATTERNS.some(pattern => pattern.test(url.href));
}

// Handle map tile requests - cache-first for fast loading
async function handleTileRequest(request) {
  const cache = await caches.open(TILE_CACHE);
  const cached = await cache.match(request);

  if (cached) {
    // Return cached tile immediately, refresh in background
    refreshTileCache(request, cache);
    return cached;
  }

  try {
    const response = await fetch(request);
    if (response.ok) {
      // Clone and cache the response
      cache.put(request, response.clone());
      // Trim cache if too large
      trimCache(cache, MAX_TILE_CACHE_SIZE);
    }
    return response;
  } catch (error) {
    console.log('[SW] Tile fetch failed, no cache:', request.url);
    // Return a placeholder or transparent tile
    return new Response('', { status: 404 });
  }
}

// Refresh tile in background
async function refreshTileCache(request, cache) {
  try {
    const response = await fetch(request);
    if (response.ok) {
      cache.put(request, response);
    }
  } catch (e) {
    // Ignore background refresh failures
  }
}

// Handle API requests - network-first with cache fallback
async function handleApiRequest(request) {
  const cache = await caches.open(DATA_CACHE);

  try {
    const response = await fetch(request);
    if (response.ok) {
      cache.put(request, response.clone());
    }
    return response;
  } catch (error) {
    console.log('[SW] API fetch failed, trying cache:', request.url);
    const cached = await cache.match(request);
    if (cached) {
      return cached;
    }
    return new Response(JSON.stringify({ error: 'Offline' }), {
      status: 503,
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

// Handle static assets - stale-while-revalidate
async function handleStaticRequest(request) {
  const cache = await caches.open(STATIC_CACHE);
  const cached = await cache.match(request);

  const fetchPromise = fetch(request)
    .then((response) => {
      if (response.ok) {
        cache.put(request, response.clone());
      }
      return response;
    })
    .catch(() => null);

  // Return cached version immediately if available
  if (cached) {
    // Refresh in background
    fetchPromise;
    return cached;
  }

  // Wait for network if no cache
  const networkResponse = await fetchPromise;
  if (networkResponse) {
    return networkResponse;
  }

  // Fallback to root for navigation requests (SPA)
  if (request.mode === 'navigate') {
    const rootCached = await cache.match('/');
    if (rootCached) {
      return rootCached;
    }
  }

  return new Response('Offline', { status: 503 });
}

// Trim cache to max size (LRU-ish - just delete oldest)
async function trimCache(cache, maxSize) {
  const keys = await cache.keys();
  if (keys.length > maxSize) {
    const deleteCount = keys.length - maxSize;
    console.log(`[SW] Trimming ${deleteCount} tiles from cache`);
    for (let i = 0; i < deleteCount; i++) {
      await cache.delete(keys[i]);
    }
  }
}

// ==================== INDEXEDDB FOR ROOM STATE ====================
const DB_NAME = 'rmaps-offline';
const DB_VERSION = 1;
const ROOM_STORE = 'room-state';

function openDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      if (!db.objectStoreNames.contains(ROOM_STORE)) {
        db.createObjectStore(ROOM_STORE, { keyPath: 'slug' });
      }
    };
  });
}

async function saveRoomState(slug, state) {
  try {
    const db = await openDB();
    const tx = db.transaction(ROOM_STORE, 'readwrite');
    const store = tx.objectStore(ROOM_STORE);
    await store.put({ slug, state, timestamp: Date.now() });
    console.log('[SW] Room state saved:', slug);
  } catch (error) {
    console.error('[SW] Failed to save room state:', error);
  }
}

async function getRoomState(slug) {
  try {
    const db = await openDB();
    const tx = db.transaction(ROOM_STORE, 'readonly');
    const store = tx.objectStore(ROOM_STORE);
    const request = store.get(slug);

    return new Promise((resolve) => {
      request.onsuccess = () => resolve(request.result?.state || null);
      request.onerror = () => resolve(null);
    });
  } catch (error) {
    console.error('[SW] Failed to get room state:', error);
    return null;
  }
}

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

  if (event.tag === SYNC_TAG) {
    event.waitUntil(syncLocation());
  }
});

async function syncLocation() {
  try {
    const clients = await self.clients.matchAll({ type: 'window' });

    for (const client of clients) {
      client.postMessage({ type: 'REQUEST_LOCATION_SYNC' });
    }

    console.log('[SW] Location sync requested from clients');
  } catch (error) {
    console.error('[SW] Location sync failed:', error);
  }
}

async function requestLocationFromClient() {
  const clients = await self.clients.matchAll({
    type: 'window',
    includeUncontrolled: true
  });

  if (clients.length > 0) {
    clients[0].postMessage({ type: 'REQUEST_LOCATION_UPDATE' });
    return true;
  }
  return false;
}

// ==================== PUSH NOTIFICATIONS ====================
self.addEventListener('push', (event) => {
  console.log('[SW] Push received:', event);

  let data = {
    title: 'rMaps',
    body: 'You have a new notification',
    icon: '/icon-192.png',
    badge: '/icon-192.png',
    tag: 'rmaps-notification',
    data: {},
    silent: false,
  };

  if (event.data) {
    try {
      const payload = event.data.json();
      data = { ...data, ...payload };
    } catch (e) {
      data.body = event.data.text();
    }
  }

  // Handle silent push - request location update without showing notification
  if (data.silent || data.data?.type === 'location_request') {
    event.waitUntil(
      requestLocationFromClient().then((sent) => {
        console.log('[SW] Silent push - location request sent:', sent);
      })
    );
    return;
  }

  const options = {
    body: data.body,
    icon: data.icon || '/icon-192.png',
    badge: data.badge || '/icon-192.png',
    tag: data.tag || 'rmaps-notification',
    data: data.data || {},
    vibrate: [200, 100, 200, 100, 400],
    actions: data.actions || [],
    requireInteraction: data.requireInteraction || false,
    silent: false,
    renotify: true,
  };

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

// ==================== NOTIFICATION EVENTS ====================
self.addEventListener('notificationclick', (event) => {
  console.log('[SW] Notification clicked:', event);

  event.notification.close();

  const data = event.notification.data || {};
  let targetUrl = '/';

  if (data.roomSlug) {
    targetUrl = `/${data.roomSlug}`;
  } else if (data.url) {
    targetUrl = data.url;
  }

  if (event.action === 'view') {
    targetUrl = data.url || targetUrl;
  } else if (event.action === 'dismiss') {
    return;
  }

  event.waitUntil(
    clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
      for (const client of clientList) {
        if (client.url.includes(targetUrl) && 'focus' in client) {
          return client.focus();
        }
      }
      if (clients.openWindow) {
        return clients.openWindow(targetUrl);
      }
    })
  );
});

self.addEventListener('notificationclose', (event) => {
  console.log('[SW] Notification closed:', event);
});

// ==================== MESSAGE HANDLER ====================
self.addEventListener('message', async (event) => {
  console.log('[SW] Message received:', event.data);

  if (!event.data) return;

  switch (event.data.type) {
    case 'SKIP_WAITING':
      self.skipWaiting();
      break;

    case 'SAVE_ROOM_STATE':
      if (event.data.slug && event.data.state) {
        await saveRoomState(event.data.slug, event.data.state);
      }
      break;

    case 'GET_ROOM_STATE':
      if (event.data.slug) {
        const state = await getRoomState(event.data.slug);
        event.source.postMessage({
          type: 'ROOM_STATE',
          slug: event.data.slug,
          state
        });
      }
      break;

    case 'CLEAR_CACHES':
      await caches.delete(STATIC_CACHE);
      await caches.delete(TILE_CACHE);
      await caches.delete(DATA_CACHE);
      console.log('[SW] All caches cleared');
      break;
  }
});

console.log('[SW] Service worker loaded');
