const CACHE_NAME = "dada-public-assets-v1";
const DATABASE_NAME = "dada-public-asset-cache-v1";
const LRU_STORE = "public_asset_lru";
const POLICY_STORE = "client_cache_policy";
const PUBLIC_ROUTE = /^\/api\/v1\/assets\/public\/([a-z0-9][a-z0-9._-]{0,79})\/([0-9a-f-]{36})$/i;

function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DATABASE_NAME, 1);
    request.onupgradeneeded = () => {
      const database = request.result;
      if (!database.objectStoreNames.contains(LRU_STORE)) database.createObjectStore(LRU_STORE, { keyPath: "resource_id" });
      if (!database.objectStoreNames.contains(POLICY_STORE)) database.createObjectStore(POLICY_STORE, { keyPath: "policy_id" });
    };
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error ?? new Error("public_cache_database_open_failed"));
  });
}

async function readMetadata(resourceId) {
  const database = await openDatabase();
  try {
    return await new Promise((resolve, reject) => {
      const request = database.transaction(LRU_STORE, "readonly").objectStore(LRU_STORE).get(resourceId);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error ?? new Error("public_cache_metadata_read_failed"));
    });
  } finally {
    database.close();
  }
}

async function touchMetadata(resourceId, releaseVersion) {
  const database = await openDatabase();
  try {
    await new Promise((resolve, reject) => {
      const transaction = database.transaction(LRU_STORE, "readwrite");
      const store = transaction.objectStore(LRU_STORE);
      const request = store.get(resourceId);
      request.onsuccess = () => {
        const entry = request.result;
        if (entry?.release_version === releaseVersion) store.put({ ...entry, last_accessed_at: Date.now() });
      };
      transaction.oncomplete = () => resolve();
      transaction.onabort = () => reject(transaction.error ?? new Error("public_cache_touch_aborted"));
      transaction.onerror = () => reject(transaction.error ?? new Error("public_cache_touch_failed"));
    });
  } finally {
    database.close();
  }
}

self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
self.addEventListener("fetch", (event) => {
  if (event.request.method !== "GET") return;
  const url = new URL(event.request.url);
  if (url.origin !== self.location.origin || url.search || url.hash) return;
  const route = PUBLIC_ROUTE.exec(url.pathname);
  if (!route) return;

  event.respondWith((async () => {
    const releaseVersion = route[1];
    const resourceId = route[2];
    const metadata = await readMetadata(resourceId);
    if (!metadata || metadata.release_version !== releaseVersion) return fetch(event.request);
    const cached = await (await caches.open(CACHE_NAME)).match(event.request);
    if (!cached) return fetch(event.request);
    event.waitUntil(touchMetadata(resourceId, releaseVersion));
    return cached;
  })());
});
