const CACHE_VERSION = 'melonlite-pwa-v171-queue-grid-cleanup-20260706';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const COVER_CACHE = `${CACHE_VERSION}-covers`;
const OFFLINE_URL = './offline.php';
const COVER_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 14;
const COVER_MAX_ITEMS = 180;

const APP_SHELL = [
  OFFLINE_URL,
  './manifest.json',
  './assets/css/style.css',
  './assets/css/floating-player.css',
  './assets/js/api-client.js',
  './assets/js/sidebar.js',
  './assets/js/playlist.js',
  './assets/js/tags.js',
  './assets/js/notes.js',
  './assets/js/search.js',
  './assets/js/player.js',
  './assets/js/download.js',
  './assets/js/announcements.js',
  './assets/js/feedback.js',
  './assets/js/metadata-request.js',
  './assets/js/user-settings.js',
  './assets/js/home-customizer.js',
  './assets/js/onboarding.js',
  './assets/js/app-navigation.js',
  './assets/js/accessibility.js',
  './assets/js/pwa.js',
  './assets/img/default-cover.svg',
  './assets/img/icons/icon-192.png',
  './assets/img/icons/icon-512.png'
];

const NEVER_CACHE_PATTERNS = [
  /\/stream\.php/i,
  /\/download\.php/i,
  /\/api\//i,
  /\/ajax\//i,
  /\/lyrics_save\.php/i,
  /\/lyrics_delete\.php/i,
  /\/album_description_save\.php/i,
  /\/feedback_submit\.php/i,
  /\/metadata_request_submit\.php/i,
  /\/policies\.php/i,
  /\/playback_failure_report\.php/i,
  /\/song_update\.php/i,
  /\/playlist_delete\.php/i,
  /\/admin\//i,
  /\/admin\/feedback\.php/i,
  /\/admin\/playback_failures\.php/i,
  /\/admin\/metadata_requests\.php/i,
  /\/admin\/patch_notes\.php/i,
  /\/admin\/service_status\.php/i,
  /\/login\.php/i,
  /\/logout\.php/i,
  /\/auth_status\.php/i,
  /\/session/i,
  /\/csrf/i,
  /\/permission/i,
  /\/auto_playlist_save\.php/i,
  /\/scan\.php/i,
  /\/settings_save\.php/i,
  /\/missing_action\.php/i,
  /\/metadata_action\.php/i,
  /\/tag_song\.php/i,
  /\/save_note\.php/i,
  /\/save_rating\.php/i,
  /\/toggle_like\.php/i,
  /\/play_count\.php/i,
  /\/backup(_|\.)/i,
  /\/restore\.php/i,
  /\/import\.php/i,
  /\/export\.php/i,
  /\/backup_download\.php/i,
  /\/admin\/backup/i,
  /\/admin\/export/i,
  /\/admin\/import/i,
  /\/admin\/restore/i,
  /\/admin\/settings_import/i,
  /\/admin\/metadata_action/i
];

const NETWORK_FIRST_HTML_PATTERNS = [
  /\/$/i,
  /\/index\.php/i,
  /\/song\.php/i,
  /\/search\.php/i,
  /\/user_settings\.php/i,
  /\/help\.php/i,
  /\/updates\.php/i,
  /\/status\.php/i,
  /\/playlist_view\.php/i,
  /\/recent\.php/i,
  /\/recommend\.php/i,
  /\/auto_playlist/i,
  /\/lyrics_edit\.php/i,
  /\/offline\.php/i
];

function isSameOrigin(url) {
  return url.origin === self.location.origin;
}

function shouldNeverCache(url) {
  const target = `${url.pathname}${url.search}`;
  return NEVER_CACHE_PATTERNS.some((pattern) => pattern.test(target));
}

function isCoverRequest(url) {
  return /\/cover\.php$/i.test(url.pathname);
}

function isManifest(url, request) {
  return request.destination === 'manifest' || /\/manifest\.json$/i.test(url.pathname);
}

function isStaticAsset(request, url) {
  return request.destination === 'style'
    || request.destination === 'script'
    || /\.css$/i.test(url.pathname)
    || /\.js$/i.test(url.pathname);
}

function isIconAsset(request, url) {
  return request.destination === 'image'
    && (/\/assets\/img\/icons\//i.test(url.pathname) || /\/assets\/img\/default-cover\.svg$/i.test(url.pathname));
}

function isNetworkFirstHtml(request, url) {
  if (request.mode === 'navigate') return true;
  if (request.destination === 'document') return true;
  if (isPjaxRequest(request)) return true;
  if (/\.php$/i.test(url.pathname)) return true;
  return NETWORK_FIRST_HTML_PATTERNS.some((pattern) => pattern.test(url.pathname));
}

function isPjaxRequest(request) {
  return request.headers.get('x-melonlite-pjax') === '1';
}

function isHtmlLikeRequest(request, url) {
  return request.mode === 'navigate'
    || request.destination === 'document'
    || isPjaxRequest(request)
    || /\.php$/i.test(url.pathname);
}

async function putIfCacheable(cacheName, request, response) {
  if (!response || response.status !== 200 || response.type === 'opaque') return response;
  const url = new URL(request.url);
  if (isHtmlLikeRequest(request, url)) return response;
  const cacheControl = response.headers.get('Cache-Control') || '';
  if (/no-store|no-cache|private/i.test(cacheControl)) return response;
  const cache = await caches.open(cacheName);
  await cache.put(request, response.clone());
  return response;
}

async function networkFirst(request, fallbackUrl = OFFLINE_URL, options = {}) {
  const cacheName = options.cacheName || STATIC_CACHE;
  const writeCache = options.writeCache !== false;
  const allowCachedFallback = options.allowCachedFallback !== false;
  try {
    const response = await fetch(request);
    if (writeCache) await putIfCacheable(cacheName, request, response);
    return response;
  } catch (err) {
    if (allowCachedFallback) {
      const cached = await caches.match(request);
      if (cached) return cached;
    }
    if (fallbackUrl) return (await caches.match(fallbackUrl)) || Response.error();
    return Response.error();
  }
}

async function networkOnly(request) {
  return fetch(request);
}

async function staleWhileRevalidate(request) {
  const cached = await caches.match(request);
  const fetchPromise = fetch(request)
    .then((response) => putIfCacheable(STATIC_CACHE, request, response))
    .catch(() => undefined);
  if (cached) return cached;
  return (await fetchPromise) || Response.error();
}

async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;
  const response = await fetch(request);
  return putIfCacheable(STATIC_CACHE, request, response);
}

async function trimCoverCache(cache) {
  const requests = await cache.keys();
  if (requests.length <= COVER_MAX_ITEMS) return;
  const removeCount = requests.length - COVER_MAX_ITEMS;
  await Promise.all(requests.slice(0, removeCount).map((request) => cache.delete(request)));
}

async function coverCacheFirst(request) {
  const cache = await caches.open(COVER_CACHE);
  const cached = await cache.match(request);
  if (cached) {
    const cachedAt = Number(cached.headers.get('sw-cached-at') || 0);
    if (cachedAt && Date.now() - cachedAt < COVER_MAX_AGE_MS) return cached;
    cache.delete(request).catch(() => undefined);
  }

  try {
    const response = await fetch(request);
    if (!response || response.status !== 200 || response.type === 'opaque') return response;
    const headers = new Headers(response.headers);
    headers.set('sw-cached-at', String(Date.now()));
    const body = await response.clone().blob();
    const cachedResponse = new Response(body, {
      status: response.status,
      statusText: response.statusText,
      headers
    });
    await cache.put(request, cachedResponse.clone());
    await trimCoverCache(cache);
    return response;
  } catch (err) {
    if (cached) return cached;
    return (await caches.match('./assets/img/default-cover.svg')) || Response.error();
  }
}

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(STATIC_CACHE)
      .then((cache) => Promise.allSettled(APP_SHELL.map((url) => cache.add(new Request(url, { cache: 'reload' })))))
      .catch(() => undefined)
  );
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    Promise.all([
      caches.keys().then((keys) => Promise.all(
        keys
          .filter((key) => key.startsWith('melonlite-pwa-') && key !== STATIC_CACHE && key !== COVER_CACHE)
          .map((key) => caches.delete(key))
      )),
      self.registration.navigationPreload ? self.registration.navigationPreload.enable().catch(() => undefined) : Promise.resolve()
    ])
  );
  self.clients.claim();
});

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
  if (event.data && event.data.type === 'CLEAR_RUNTIME_CACHES') {
    event.waitUntil(caches.delete(COVER_CACHE));
  }
  if (event.data && event.data.type === 'CLEAR_ALL_CACHES') {
    event.waitUntil(
      caches.keys().then((keys) => Promise.all(
        keys
          .filter((key) => key.startsWith('melonlite-pwa-'))
          .map((key) => caches.delete(key))
      ))
    );
  }
});

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

  const url = new URL(request.url);
  if (!isSameOrigin(url)) return;
  if (shouldNeverCache(url)) return;
  if (isPjaxRequest(request)) {
    event.respondWith(networkFirst(request, null, {
      writeCache: false,
      allowCachedFallback: false
    }));
    return;
  }
  if (request.cache === 'reload' || request.cache === 'no-store') {
    event.respondWith(networkOnly(request));
    return;
  }

  if (isCoverRequest(url)) {
    if (!url.searchParams.has('v')) {
      event.respondWith(networkFirst(request, './assets/img/default-cover.svg', {
        writeCache: false,
        allowCachedFallback: false
      }));
      return;
    }
    event.respondWith(coverCacheFirst(request));
    return;
  }

  if (isManifest(url, request)) {
    event.respondWith(networkFirst(request, './manifest.json'));
    return;
  }

  if (isNetworkFirstHtml(request, url)) {
    event.respondWith(networkFirst(request, OFFLINE_URL, {
      writeCache: false,
      allowCachedFallback: false
    }));
    return;
  }

  if (isStaticAsset(request, url)) {
    if (url.searchParams.has('v')) {
      event.respondWith(networkFirst(request, null, {
        writeCache: true,
        allowCachedFallback: true
      }));
      return;
    }
    event.respondWith(staleWhileRevalidate(request));
    return;
  }

  if (isIconAsset(request, url)) {
    event.respondWith(cacheFirst(request));
  }
});
