self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (event.request.method === "POST" && url.origin === self.location.origin && url.pathname === "/share-target") {
    event.respondWith(handleShareTarget(event.request));
  }
});

self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const url = event.notification.data && event.notification.data.url ? event.notification.data.url : "/";
  event.waitUntil((async () => {
    const windows = await clients.matchAll({ type: "window", includeUncontrolled: true });
    for (const client of windows) {
      if ("focus" in client) {
        await client.focus();
        if ("navigate" in client) {
          await client.navigate(url);
        }
        return;
      }
    }
    if (clients.openWindow) {
      await clients.openWindow(url);
    }
  })());
});

const SHARE_CACHE = "warpbox-share-target-v1";
const SHARE_PREFIX = "/__warpbox_share_target__/";
const LATEST_KEY = SHARE_PREFIX + "latest";

async function handleShareTarget(request) {
  const id = Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
  try {
    const formData = await request.formData();
    const files = collectSharedFiles(formData);
    const cache = await caches.open(SHARE_CACHE);
    const metadata = {
      id,
      title: stringValue(formData.get("title")),
      text: stringValue(formData.get("text")),
      url: stringValue(formData.get("url")),
      createdAt: new Date().toISOString(),
      files: [],
    };

    await deletePreviousShare(cache);
    for (let index = 0; index < files.length; index += 1) {
      const file = files[index];
      const key = SHARE_PREFIX + "file/" + encodeURIComponent(id) + "/" + index;
      metadata.files.push({
        key,
        name: file.name || "shared-file",
        type: file.type || "application/octet-stream",
        size: file.size || 0,
        lastModified: file.lastModified || Date.now(),
      });
      await cache.put(key, new Response(file, {
        headers: {
          "Content-Type": file.type || "application/octet-stream",
          "Cache-Control": "no-store",
        },
      }));
    }

    await cache.put(LATEST_KEY, jsonResponse(metadata));
    await cache.put(SHARE_PREFIX + "meta/" + encodeURIComponent(id), jsonResponse(metadata));
  } catch (error) {
    await storeShareError(id, error);
  }

  return Response.redirect("/?share-target=1&share-id=" + encodeURIComponent(id), 303);
}

function collectSharedFiles(formData) {
  const files = [];
  ["files", "file", "sharex"].forEach((name) => {
    formData.getAll(name).forEach((value) => {
      if (value instanceof File && value.size > 0) {
        files.push(value);
      }
    });
  });
  return files;
}

function stringValue(value) {
  return typeof value === "string" ? value : "";
}

function jsonResponse(payload) {
  return new Response(JSON.stringify(payload), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "no-store",
    },
  });
}

async function storeShareError(id, error) {
  const cache = await caches.open(SHARE_CACHE);
  await cache.put(LATEST_KEY, jsonResponse({
    id,
    error: error && error.message ? error.message : "Shared files could not be staged.",
    createdAt: new Date().toISOString(),
    files: [],
  }));
}

async function deletePreviousShare(cache) {
  const previous = await cache.match(LATEST_KEY);
  if (!previous) {
    return;
  }
  let metadata = null;
  try {
    metadata = await previous.json();
  } catch (error) {
    metadata = null;
  }
  for (const file of metadata && metadata.files ? metadata.files : []) {
    if (file.key) {
      await cache.delete(file.key);
    }
  }
  if (metadata && metadata.id) {
    await cache.delete(SHARE_PREFIX + "meta/" + encodeURIComponent(metadata.id));
  }
  await cache.delete(LATEST_KEY);
}
