(function () {
  const form = document.querySelector("#upload-form");
  const dropZone = document.querySelector(".drop-zone");
  const fileInput = document.querySelector("#file-input");
  const fileSummary = document.querySelector("#file-summary");
  const progress = document.querySelector("#upload-progress");
  const uploadStatus = document.querySelector("#upload-status");
  const result = document.querySelector("#upload-result");
  const resultMeta = document.querySelector("#result-meta");
  const resultList = document.querySelector("#result-list");
  const uploadQueue = document.querySelector("#upload-queue");
  const totalProgressBar = document.querySelector("#total-progress-bar");
  const copyURL = document.querySelector("#copy-url");
  const openBox = document.querySelector("#open-box");
  const manageLink = document.querySelector("#manage-link");
  const newUpload = document.querySelector("#new-upload");
  const folderPicker = document.querySelector("[data-folder-picker]");
  const submitButton = form && form.querySelector("button[type='submit']");
  const idleUploadActions = form ? Array.from(form.querySelectorAll(".upload-idle-action")) : [];
  const activeUploadActions = document.querySelector("#upload-active-actions");
  const cancelUpload = document.querySelector("#cancel-upload");
  const pauseUpload = document.querySelector("#pause-upload");
  const RESUMABLE_SESSIONS_KEY = "warpbox-resumable-sessions";
  const SHARE_CACHE = "warpbox-share-target-v1";
  const SHARE_LATEST_KEY = "/__warpbox_share_target__/latest";
  const CELLULAR_WARNING_THRESHOLD_BYTES = 200 * 1024 * 1024;

  if (!form || !dropZone || !fileInput) {
    return;
  }

  // Remember the last-chosen expiry across uploads (per browser).
  const expirySelect = form.querySelector("[data-expiry-select]");
  if (expirySelect) {
    const EXPIRY_KEY = "warpbox-expiry";
    let saved = null;
    try {
      saved = localStorage.getItem(EXPIRY_KEY);
    } catch (e) {
      saved = null;
    }
    if (saved && expirySelect.querySelector('option[value="' + saved + '"]')) {
      expirySelect.value = saved;
    }
    expirySelect.addEventListener("change", () => {
      try {
        localStorage.setItem(EXPIRY_KEY, expirySelect.value);
      } catch (e) {
        /* ignore persistence failures */
      }
    });
  }

  let latestBoxURL = "";
  let selectedFiles = [];
  let uploadLocked = false;
  let recoveredDraft = null;
  let resumeMode = false;
  let sharedTargetDraft = null;
  let activeUploadRequest = null;
  let activeUploadSession = null;
  let uploadPaused = false;
  let uploadCancelled = false;
  let uploadFinalizing = false;
  let pauseWaiters = [];
  const maxUploadBytes = parseInt(form.dataset.maxUploadBytes || "-1", 10);
  const maxUploadLabel = form.dataset.maxUploadLabel || (maxUploadBytes > 0 && window.Warpbox.formatBytes ? window.Warpbox.formatBytes(maxUploadBytes) : "the configured limit");

  ["dragenter", "dragover"].forEach((eventName) => {
    dropZone.addEventListener(eventName, (event) => {
      event.preventDefault();
      dropZone.classList.add("is-dragging");
    });
  });

  ["dragleave", "drop"].forEach((eventName) => {
    dropZone.addEventListener(eventName, (event) => {
      event.preventDefault();
      dropZone.classList.remove("is-dragging");
    });
  });

  document.addEventListener("dragover", (event) => {
    if (event.dataTransfer && Array.from(event.dataTransfer.types || []).includes("Files")) {
      event.preventDefault();
    }
  });

  document.addEventListener("drop", (event) => {
    if (!hasTransferFiles(event.dataTransfer)) {
      return;
    }
    event.preventDefault();
    if (!dropZone.contains(event.target)) {
      addDroppedFiles(event.dataTransfer);
    }
  });

  dropZone.addEventListener("drop", (event) => {
    if (hasTransferFiles(event.dataTransfer)) {
      addDroppedFiles(event.dataTransfer);
    }
  });

  fileInput.addEventListener("change", () => {
    addSelectedFiles(fileInput.files);
    fileInput.value = "";
  });

  document.addEventListener("paste", (event) => {
    if (!event.clipboardData || !event.clipboardData.files || event.clipboardData.files.length === 0) {
      return;
    }
    if (isTextEditingTarget(event.target)) {
      return;
    }
    event.preventDefault();
    addSelectedFiles(event.clipboardData.files, { source: "pasted" });
  });

  if (folderPicker && typeof window.showDirectoryPicker === "function") {
    folderPicker.hidden = false;
    folderPicker.addEventListener("click", async () => {
      if (uploadLocked) {
        return;
      }
      try {
        updateStatus("Reading folder...");
        const directory = await window.showDirectoryPicker();
        const files = await filesFromDirectoryHandle(directory, directory.name || "");
        addSelectedFiles(files, { source: "folder" });
      } catch (error) {
        if (!error || error.name !== "AbortError") {
          updateStatus("Folder could not be read.");
        }
      }
    });
  }

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    if (selectedFiles.length === 0) {
      updateStatus("Choose at least one file first.");
      notify("warning", "Choose at least one file first.", {
        title: "No files selected",
      });
      return;
    }
    if (!validateSelectedFilesWithinLimit(selectedFiles)) {
      return;
    }
    if (isSlowOrMeteredConnection() && totalSelectedBytes(selectedFiles) >= CELLULAR_WARNING_THRESHOLD_BYTES) {
      const proceed = await confirmCellularUpload(selectedFiles);
      if (!proceed) {
        return;
      }
    }

    const formData = uploadFormData();
    await maybeRequestUploadNotificationPermission(selectedFiles);
    if (resumeMode && recoveredDraft) {
      renderResumeQueue(recoveredDraft.session, selectedFiles);
    } else {
      renderQueue(selectedFiles, "queued");
    }
    beginUpload();

    try {
      const payload = await uploadResumable(form.action, formData, selectedFiles);
      renderResult(payload);
      showUploadNotification("Warpbox upload complete", `${payload.files.length} file${payload.files.length === 1 ? "" : "s"} uploaded.`, payload.boxUrl);
      await clearSharedTargetPayload();
      form.reset();
      selectedFiles = [];
      sharedTargetDraft = null;
      resumeMode = false;
      recoveredDraft = null;
      fileInput.value = "";
      if (uploadQueue) {
        uploadQueue.hidden = true;
        uploadQueue.replaceChildren();
      }
      updateNewUploadVisibility();
      if (fileSummary) {
        fileSummary.textContent = "Upload complete.";
      }
    } catch (error) {
      if (isUploadCancelledError(error)) {
        await discardActiveUploadSession();
        await clearSharedTargetPayload();
        form.reset();
        resetFreshUploadState();
        return;
      }
      updateStatus(error.message || "Upload failed");
      notifyUploadError(error);
      showUploadNotification("Warpbox upload failed", error.message || "Upload failed");
    } finally {
      finishUpload();
    }
  });

  if (pauseUpload) {
    pauseUpload.addEventListener("click", () => {
      if (!uploadLocked || uploadCancelled) {
        return;
      }
      if (uploadPaused) {
        resumeActiveUpload();
      } else {
        pauseActiveUpload();
      }
    });
  }

  if (cancelUpload) {
    cancelUpload.addEventListener("click", async () => {
      if (!uploadLocked || uploadCancelled) {
        return;
      }
      const confirmed = await window.Warpbox.confirmDialog(
        "Cancel this upload? Uploaded chunks will be deleted and the form will be reset for a new upload.",
        {
          title: "Cancel upload?",
          variant: "warning",
          confirmLabel: "Cancel Upload",
          cancelLabel: "Keep Uploading",
        },
      );
      if (!confirmed || !uploadLocked || uploadFinalizing) {
        return;
      }
      requestUploadCancellation();
    });
  }

  if (copyURL) {
    copyURL.addEventListener("click", () => {
      window.Warpbox.copyText(latestBoxURL, copyURL, "Copied");
    });
  }

  if (newUpload) {
    newUpload.addEventListener("click", () => {
      if (sharedTargetDraft) {
        clearSharedTargetPayload().finally(() => resetFreshUploadState());
        return;
      }
      cancelRecoveredDraft().catch((error) => {
        updateStatus(error.message || "Upload draft could not be deleted");
      });
    });
  }

  if (isShareTargetLaunch()) {
    loadSharedTargetFiles();
  } else {
    recoverResumableSessions();
  }

  function addSelectedFiles(files, options) {
    if (uploadLocked) {
      return;
    }
    const rejected = [];
    Array.from(files || []).forEach((file) => {
      if (fileExceedsUploadLimit(file)) {
        rejected.push(file);
        return;
      }
      if (!selectedFiles.some((existing) => fileIdentity(existing) === fileIdentity(file))) {
        selectedFiles.push(file);
      }
    });
    if (rejected.length > 0) {
      notifyRejectedFiles(rejected);
    }
    if (options && options.source === "pasted" && files && files.length > 0) {
      updateStatus(`${files.length} pasted file${files.length === 1 ? "" : "s"} ready.`);
    }
    if (options && options.source === "folder" && files && files.length > 0) {
      updateStatus(`${files.length} folder file${files.length === 1 ? "" : "s"} ready.`);
    }
    updateSelectedState();
  }

  async function addDroppedFiles(dataTransfer) {
    if (uploadLocked) {
      return;
    }
    const files = await filesFromDataTransfer(dataTransfer);
    addSelectedFiles(files, { source: hasDirectoryItems(dataTransfer) ? "folder" : "dropped" });
  }

  async function filesFromDataTransfer(dataTransfer) {
    const items = Array.from(dataTransfer.items || []);
    const entries = items
      .map((item) => typeof item.webkitGetAsEntry === "function" ? item.webkitGetAsEntry() : null)
      .filter(Boolean);
    if (entries.length === 0) {
      return Array.from(dataTransfer.files || []);
    }
    const nested = await Promise.all(entries.map((entry) => filesFromEntry(entry, "")));
    return nested.flat();
  }

  function hasDirectoryItems(dataTransfer) {
    return Array.from(dataTransfer.items || []).some((item) => {
      const entry = typeof item.webkitGetAsEntry === "function" ? item.webkitGetAsEntry() : null;
      return entry && entry.isDirectory;
    });
  }

  function hasTransferFiles(dataTransfer) {
    if (!dataTransfer) {
      return false;
    }
    if (dataTransfer.files && dataTransfer.files.length > 0) {
      return true;
    }
    return Array.from(dataTransfer.items || []).some((item) => item.kind === "file");
  }

  function filesFromEntry(entry, parentPath) {
    if (!entry) {
      return Promise.resolve([]);
    }
    const relativePath = parentPath ? `${parentPath}/${entry.name}` : entry.name;
    if (entry.isFile) {
      return new Promise((resolve) => {
        entry.file((file) => resolve([withRelativePath(file, relativePath)]), () => resolve([]));
      });
    }
    if (!entry.isDirectory) {
      return Promise.resolve([]);
    }
    const reader = entry.createReader();
    const children = [];
    return new Promise((resolve) => {
      const readBatch = () => {
        reader.readEntries(async (entries) => {
          if (!entries.length) {
            const nested = await Promise.all(children.map((child) => filesFromEntry(child, relativePath)));
            resolve(nested.flat());
            return;
          }
          children.push(...entries);
          readBatch();
        }, () => resolve([]));
      };
      readBatch();
    });
  }

  async function filesFromDirectoryHandle(directory, parentPath) {
    const files = [];
    for await (const [name, handle] of directory.entries()) {
      const relativePath = parentPath ? `${parentPath}/${name}` : name;
      if (handle.kind === "file") {
        const file = await handle.getFile();
        files.push(withRelativePath(file, relativePath));
      } else if (handle.kind === "directory") {
        files.push(...await filesFromDirectoryHandle(handle, relativePath));
      }
    }
    return files;
  }

  function withRelativePath(file, relativePath) {
    if (!file || !relativePath) {
      return file;
    }
    try {
      Object.defineProperty(file, "warpboxRelativePath", {
        value: normalizeRelativePath(relativePath),
        configurable: true,
      });
    } catch (error) {
      file.warpboxRelativePath = normalizeRelativePath(relativePath);
    }
    return file;
  }

  function normalizeRelativePath(value) {
    return String(value || "")
      .replace(/\\/g, "/")
      .split("/")
      .filter((part) => part && part !== "." && part !== "..")
      .join("/");
  }

  function uploadName(file) {
    return normalizeRelativePath(file && (file.warpboxRelativePath || file.webkitRelativePath || file.name)) || (file && file.name) || "file";
  }

  function isTextEditingTarget(target) {
    if (!target) {
      return false;
    }
    const tag = (target.tagName || "").toLowerCase();
    return tag === "input" || tag === "textarea" || target.isContentEditable;
  }

  function fileExceedsUploadLimit(file) {
    return Number.isFinite(maxUploadBytes) && maxUploadBytes > 0 && file && file.size > maxUploadBytes;
  }

  function validateSelectedFilesWithinLimit(files) {
    const rejected = Array.from(files || []).filter(fileExceedsUploadLimit);
    if (rejected.length === 0) {
      return true;
    }
    selectedFiles = selectedFiles.filter((file) => !fileExceedsUploadLimit(file));
    notifyRejectedFiles(rejected);
    updateSelectedState();
    return false;
  }

  function notifyRejectedFiles(files) {
    const names = files.slice(0, 3).map((file) => `"${file.name}" (${window.Warpbox.formatBytes(file.size)})`).join(", ");
    const extra = files.length > 3 ? `, and ${files.length - 3} more` : "";
    const message = `${names}${extra} ${files.length === 1 ? "is" : "are"} over the ${maxUploadLabel} upload limit.`;
    updateStatus(message);
    notify("error", message, {
      title: "Upload limit exceeded",
      duration: 9000,
    });
  }

  function notifyUploadError(error) {
    const message = error && error.message ? error.message : "Upload failed";
    const lower = message.toLowerCase();
    const isLimit = lower.includes("limit") || lower.includes("quota") || lower.includes("too large") || lower.includes("exceeds");
    notify("error", message, {
      title: isLimit ? "Upload limit reached" : "Upload failed",
      duration: isLimit ? 9000 : 7200,
    });
  }

  async function maybeRequestUploadNotificationPermission(files) {
    if (!("Notification" in window) || Notification.permission !== "default" || totalSelectedBytes(files) < CELLULAR_WARNING_THRESHOLD_BYTES) {
      return;
    }
    try {
      await Notification.requestPermission();
    } catch (error) {
      /* notification permission is optional */
    }
  }

  async function showUploadNotification(title, body, url) {
    if (!("Notification" in window) || Notification.permission !== "granted") {
      return;
    }
    if (document.visibilityState === "visible") {
      return;
    }
    const options = {
      body,
      icon: "/static/android-chrome-192x192.png",
      badge: "/static/favicon-32x32.png",
      data: { url: window.Warpbox.absoluteURL(url || "/") },
    };
    try {
      const registration = navigator.serviceWorker ? await navigator.serviceWorker.ready : null;
      if (registration && registration.showNotification) {
        await registration.showNotification(title, options);
        return;
      }
    } catch (error) {
      /* fall through to page notification */
    }
    try {
      const notification = new Notification(title, options);
      notification.onclick = () => {
        window.focus();
        if (url) {
          window.location.href = window.Warpbox.absoluteURL(url);
        }
        notification.close();
      };
    } catch (error) {
      /* notifications are best-effort */
    }
  }

  function notify(variant, message, options) {
    if (window.Warpbox && typeof window.Warpbox.notify === "function") {
      window.Warpbox.notify({ ...(options || {}), variant, message });
    }
  }

  function isSlowOrMeteredConnection() {
    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    if (!connection) {
      return false;
    }
    if (connection.saveData === true) {
      return true;
    }
    return ["slow-2g", "2g", "3g"].includes(connection.effectiveType);
  }

  function totalSelectedBytes(files) {
    return files.reduce((sum, file) => sum + file.size, 0);
  }

  function confirmCellularUpload(files) {
    const list = document.createElement("div");
    list.className = "dialog-file-list";
    files.forEach((file) => {
      const icon = document.createElement("span");
      icon.className = "svg-icon svg-icon-document dialog-file-icon";
      icon.setAttribute("aria-hidden", "true");

      const name = document.createElement("span");
      name.className = "dialog-file-name";
      name.textContent = file.name;
      name.title = file.name;

      const size = document.createElement("span");
      size.className = "dialog-file-size";
      size.textContent = window.Warpbox.formatBytes(file.size);

      const row = document.createElement("div");
      row.className = "dialog-file-row";
      row.append(icon, name, size);
      list.append(row);
    });

    const totalLabel = window.Warpbox.formatBytes(totalSelectedBytes(files));
    const message = `You're on a slow or metered connection. You're about to upload ${files.length} file${files.length === 1 ? "" : "s"} (${totalLabel} total). This could take a while or use up your data plan.`;

    return window.Warpbox.confirmDialog(message, {
      title: "Slow connection detected",
      variant: "warning",
      body: list,
      confirmLabel: "Upload anyway",
      cancelLabel: "Cancel",
    });
  }

  function isShareTargetLaunch() {
    const params = new URLSearchParams(window.location.search || "");
    return params.has("share-target");
  }

  async function loadSharedTargetFiles() {
    if (!("caches" in window) || typeof File === "undefined") {
      updateStatus("Shared files could not be loaded in this browser.");
      recoverResumableSessions();
      return;
    }
    updateStatus("Loading shared files...");
    try {
      const cache = await caches.open(SHARE_CACHE);
      const metadataResponse = await cache.match(SHARE_LATEST_KEY);
      if (!metadataResponse) {
        updateStatus(new URLSearchParams(window.location.search).get("share-target") === "unsupported"
          ? "Install Warpbox as an app to share files into it from your device."
          : "No shared files were found.");
        recoverResumableSessions();
        return;
      }
      const metadata = await metadataResponse.json();
      if (metadata.error) {
        updateStatus(metadata.error);
        recoverResumableSessions();
        return;
      }
      const files = [];
      for (const item of metadata.files || []) {
        if (!item.key) {
          continue;
        }
        const response = await cache.match(item.key);
        if (!response) {
          continue;
        }
        const blob = await response.blob();
        files.push(new File([blob], item.name || "shared-file", {
          type: item.type || blob.type || "application/octet-stream",
          lastModified: item.lastModified || Date.now(),
        }));
      }
      sharedTargetDraft = metadata;
      selectedFiles = files;
      resumeMode = false;
      recoveredDraft = null;
      validateSelectedFilesWithinLimit(selectedFiles);
      if (selectedFiles.length > 0) {
        renderQueue(selectedFiles, "queued", { shared: true });
        updateStatus("Shared files ready.");
      } else {
        updateStatus("No files were included in this share.");
      }
      updateSelectedState();
    } catch (error) {
      updateStatus(error.message || "Shared files could not be loaded.");
      recoverResumableSessions();
    }
  }

  async function clearSharedTargetPayload() {
    const draft = sharedTargetDraft;
    sharedTargetDraft = null;
    if (!draft || !("caches" in window)) {
      sharedTargetDraft = null;
      return;
    }
    try {
      const cache = await caches.open(SHARE_CACHE);
      for (const item of draft.files || []) {
        if (item.key) {
          await cache.delete(item.key);
        }
      }
      if (draft.id) {
        await cache.delete("/__warpbox_share_target__/meta/" + encodeURIComponent(draft.id));
      }
      await cache.delete(SHARE_LATEST_KEY);
    } catch (error) {
      /* ignore cache cleanup failures */
    }
  }

  function removeSelectedFile(index) {
    if (uploadLocked) {
      return;
    }
    selectedFiles.splice(index, 1);
    updateSelectedState();
  }

  function updateSelectedState() {
    const count = selectedFiles.length || 0;
    const title = dropZone.querySelector(".drop-title");
    if (title) {
      title.textContent = count === 0 ? "Drop files to upload" : count === 1 ? "1 file selected" : `${count} files selected`;
    }
    if (fileSummary) {
      if (resumeMode && recoveredDraft) {
        fileSummary.textContent = count === 0
          ? "Reselect missing files to resume, or add extra files to this upload."
          : `${count} local file${count === 1 ? "" : "s"} ready for the recovered upload.`;
      } else if (sharedTargetDraft) {
        fileSummary.textContent = count === 0
          ? "No shared files were received."
          : `${count} shared file${count === 1 ? "" : "s"} ready. Review options, then upload.`;
      } else {
        fileSummary.textContent = count === 0 ? "Choose one or more files to begin." : `${count} file${count === 1 ? "" : "s"} ready.`;
      }
    }
    if (resumeMode && recoveredDraft) {
      renderResumeQueue(recoveredDraft.session, selectedFiles);
    } else if (sharedTargetDraft && count > 0) {
      renderQueue(selectedFiles, "queued", { shared: true });
    } else if (count > 0) {
      renderQueue(selectedFiles, "queued");
    } else if (uploadQueue) {
      uploadQueue.hidden = true;
      uploadQueue.replaceChildren();
    }
    updateNewUploadVisibility();
  }

  function updateNewUploadVisibility() {
    if (!newUpload) {
      return;
    }
    const visible = Boolean((resumeMode && recoveredDraft) || sharedTargetDraft);
    newUpload.hidden = !visible;
    newUpload.style.display = visible ? "" : "none";
  }

  function beginUpload() {
    uploadPaused = false;
    uploadCancelled = false;
    uploadFinalizing = false;
    activeUploadRequest = null;
    activeUploadSession = null;
    setLoading(true);
  }

  function finishUpload() {
    activeUploadRequest = null;
    activeUploadSession = null;
    uploadPaused = false;
    uploadCancelled = false;
    uploadFinalizing = false;
    releasePauseWaiters();
    setLoading(false);
  }

  function setLoading(isLoading) {
    uploadLocked = isLoading;
    if (progress) {
      progress.hidden = !isLoading;
    }
    idleUploadActions.forEach((button) => {
      button.style.display = isLoading ? "none" : "";
    });
    if (activeUploadActions) {
      activeUploadActions.hidden = !isLoading;
    }
    if (submitButton) {
      submitButton.disabled = isLoading;
      submitButton.textContent = "Upload files";
    }
    if (newUpload) {
      newUpload.disabled = isLoading;
    }
    if (cancelUpload) {
      cancelUpload.disabled = !isLoading;
      cancelUpload.textContent = "Cancel Upload";
    }
    updatePauseButton();
    updateStatus(isLoading ? "Transferring files..." : "");
    setTotalProgress(isLoading ? 0 : 100);
    if (!isLoading) {
      updateNewUploadVisibility();
    }
  }

  function pauseActiveUpload() {
    uploadPaused = true;
    updatePauseButton();
    updateStatus("Upload paused.");
    if (activeUploadRequest) {
      activeUploadRequest.abort();
    }
  }

  function resumeActiveUpload() {
    uploadPaused = false;
    updatePauseButton();
    updateStatus("Resuming upload...");
    releasePauseWaiters();
  }

  function requestUploadCancellation() {
    if (uploadFinalizing) {
      return;
    }
    uploadCancelled = true;
    uploadPaused = false;
    updateStatus("Cancelling upload...");
    if (cancelUpload) {
      cancelUpload.disabled = true;
      cancelUpload.textContent = "Cancelling...";
    }
    if (pauseUpload) {
      pauseUpload.disabled = true;
    }
    releasePauseWaiters();
    if (activeUploadRequest) {
      activeUploadRequest.abort();
    }
  }

  function updatePauseButton() {
    if (!pauseUpload) {
      return;
    }
    pauseUpload.disabled = !uploadLocked || uploadCancelled || uploadFinalizing;
    pauseUpload.textContent = uploadPaused ? "Resume Upload" : "Pause Upload";
    pauseUpload.classList.toggle("button-primary", uploadPaused);
    pauseUpload.classList.toggle("button-outline", !uploadPaused);
  }

  function releasePauseWaiters() {
    const waiters = pauseWaiters;
    pauseWaiters = [];
    waiters.forEach((resolve) => resolve());
  }

  async function waitForUploadReady() {
    while (uploadPaused && !uploadCancelled) {
      await new Promise((resolve) => pauseWaiters.push(resolve));
    }
    if (uploadCancelled) {
      throw uploadControlError("UploadCancelledError", "Upload cancelled");
    }
  }

  function uploadControlError(name, message) {
    const error = new Error(message);
    error.name = name;
    return error;
  }

  function isUploadPausedError(error) {
    return Boolean(error && error.name === "UploadPausedError");
  }

  function isUploadCancelledError(error) {
    return Boolean(error && error.name === "UploadCancelledError");
  }

  async function discardActiveUploadSession() {
    const session = activeUploadSession;
    activeUploadSession = null;
    if (!session || !session.sessionId) {
      return;
    }
    await cancelResumableSession(session.sessionId, session.resumeToken).catch(() => {});
    removeResumableSession(session.sessionId);
  }

  function beginUploadFinalization() {
    uploadFinalizing = true;
    updateStatus("Finalizing upload...");
    if (cancelUpload) {
      cancelUpload.disabled = true;
    }
    updatePauseButton();
  }

  function updateStatus(message) {
    if (uploadStatus) {
      uploadStatus.textContent = message;
    }
  }

  function updateUploadProgress(percent, bytesPerSecond) {
    const clamped = Math.max(0, Math.min(100, Math.round(percent || 0)));
    const rate = formatTransferRate(bytesPerSecond);
    updateStatus(rate ? `${clamped}% · ${rate}` : `${clamped}%`);
  }

  function createTransferRateTracker(initialBytes) {
    const startedAt = performance.now();
    const baseline = Math.max(0, initialBytes || 0);
    let lastRate = 0;
    return function track(currentBytes) {
      const elapsedSeconds = (performance.now() - startedAt) / 1000;
      const transferred = Math.max(0, (currentBytes || 0) - baseline);
      if (elapsedSeconds < 0.25 || transferred <= 0) {
        return lastRate;
      }
      lastRate = transferred / elapsedSeconds;
      return lastRate;
    };
  }

  function formatTransferRate(bytesPerSecond) {
    if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) {
      return "";
    }
    const units = ["b/s", "Kb/s", "Mb/s", "Gb/s"];
    let value = bytesPerSecond * 8;
    let unit = 0;
    while (value >= 1000 && unit < units.length - 1) {
      value /= 1000;
      unit += 1;
    }
    return `${value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unit]}`;
  }

  function renderResult(payload) {
    if (!result || !resultList || !resultMeta || !openBox) {
      return;
    }

    latestBoxURL = window.Warpbox.absoluteURL(payload.boxUrl);
    result.hidden = false;
    openBox.href = latestBoxURL;
    resultMeta.textContent = `${payload.files.length} file${payload.files.length === 1 ? "" : "s"} · expires ${window.Warpbox.formatDate(payload.expiresAt)}`;
    if (manageLink) {
      const anchor = manageLink.querySelector("a");
      manageLink.hidden = !payload.manageUrl;
      if (anchor && payload.manageUrl) {
        anchor.href = window.Warpbox.absoluteURL(payload.manageUrl);
      }
    }

    resultList.replaceChildren();
    payload.files.forEach((file) => {
      resultList.append(createFileRow({
        name: file.name,
        meta: `${file.size} · ${window.Warpbox.absoluteURL(file.url)}`,
        progress: 100,
        status: "complete",
      }));
    });
    result.scrollIntoView({ behavior: "smooth", block: "start" });
  }

  function uploadWithProgress(url, formData, files) {
    return uploadWithProgressAttempt(url, formData, files).catch(async (error) => {
      if (isUploadPausedError(error)) {
        await waitForUploadReady();
        return uploadWithProgress(url, formData, files);
      }
      throw error;
    });
  }

  async function uploadWithProgressAttempt(url, formData, files) {
    await waitForUploadReady();
    return new Promise((resolve, reject) => {
      const request = new XMLHttpRequest();
      activeUploadRequest = request;
      const rateTracker = createTransferRateTracker(0);
      request.open("POST", url);
      request.setRequestHeader("Accept", "application/json");

      request.upload.addEventListener("progress", (event) => {
        const rate = rateTracker(event.loaded || 0);
        if (!event.lengthComputable) {
          updateStatus(rate > 0 ? `Uploading · ${formatTransferRate(rate)}` : "Uploading...");
          return;
        }
        const percent = Math.round((event.loaded / event.total) * 100);
        updateUploadProgress(percent, rate);
        setTotalProgress(percent);
        setFileProgress(files, percent);
      });

      request.addEventListener("load", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        let payload = {};
        try {
          payload = JSON.parse(request.responseText || "{}");
        } catch (error) {
          reject(new Error("Upload response could not be read"));
          return;
        }
        if (request.status < 200 || request.status >= 300) {
          reject(new Error(payload.error || "Upload failed"));
          return;
        }
        setTotalProgress(100);
        setFileProgress(files, 100);
        resolve(payload);
      });

      request.addEventListener("error", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        reject(new Error("Network error during upload"));
      });
      request.addEventListener("abort", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        if (uploadCancelled) {
          reject(uploadControlError("UploadCancelledError", "Upload cancelled"));
        } else if (uploadPaused) {
          reject(uploadControlError("UploadPausedError", "Upload paused"));
        } else {
          reject(new Error("Upload aborted"));
        }
      });
      request.send(formData);
    });
  }

  async function uploadResumable(fallbackUrl, formData, files) {
    await waitForUploadReady();
    if (!window.fetch || typeof Blob === "undefined") {
      return uploadWithProgress(fallbackUrl, formData, files);
    }

    updateStatus("Fingerprinting files...");
    const fingerprints = await Promise.all(files.map((file) => fileFingerprint(file)));
    await waitForUploadReady();
    const createPayload = {
      files: files.map((file, index) => ({
        name: uploadName(file),
        size: file.size,
        contentType: file.type || "application/octet-stream",
        fingerprint: fingerprints[index],
      })),
      expiresMinutes: parseInt(formData.get("expires_minutes") || "0", 10) || 0,
      maxDownloads: parseInt(formData.get("max_downloads") || "0", 10) || 0,
      password: formData.get("password") || "",
      obfuscateMetadata: formData.get("obfuscate_metadata") === "on",
      collectionId: formData.get("collection_id") || "",
    };
    const persistable = !createPayload.password;
    let session = null;
    if (persistable && resumeMode && recoveredDraft) {
      session = await fetchResumableStatus(recoveredDraft.session.sessionId, recoveredDraft.session.resumeToken);
      session.resumeToken = recoveredDraft.session.resumeToken;
    } else if (persistable) {
      session = await findResumableSession(createPayload);
    }
    if (session) {
      activeUploadSession = session;
      validateResumeSelection(session, createPayload);
      session = await addMissingResumableFiles(session, createPayload);
      if (resumeMode && recoveredDraft && recoveredDraft.session.sessionId === session.sessionId) {
        recoveredDraft.session = session;
      }
      if (persistable) {
        saveResumableSession(session, createPayload);
      }
      activeUploadSession = session;
    }
    if (!session || session.status !== "uploading") {
      try {
        session = await createResumableSession(createPayload);
      } catch (error) {
        if ((error.message || "").toLowerCase().includes("resumable uploads are disabled")) {
          return uploadWithProgress(fallbackUrl, formData, files);
        }
        throw error;
      }
      if (persistable) {
        saveResumableSession(session, createPayload);
      }
      activeUploadSession = session;
    }
    await waitForUploadReady();
    const sessionFiles = files.map((file, index) => matchSessionFile(session, createPayload.files[index]));
    if (sessionFiles.some((file) => !file)) {
      throw new Error("Upload session could not match the selected files");
    }

    updateStatus("Uploading...");
    const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
    const completedByFile = new Array(files.length).fill(0);
    sessionFiles.forEach((sessionFile, index) => {
      completedByFile[index] = uploadedBytesForSessionFile(sessionFile, session.chunkSize);
      setSingleFileProgress(index, files[index], percentForBytes(completedByFile[index], files[index].size));
    });
    const initiallyUploadedBytes = completedByFile.reduce((sum, bytes) => sum + bytes, 0);
    const rateTracker = createTransferRateTracker(initiallyUploadedBytes);
    setTotalProgress(percentForBytes(initiallyUploadedBytes, totalBytes));

    for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
      const file = files[fileIndex];
      const sessionFile = sessionFiles[fileIndex];
      const uploaded = new Set(sessionFile.uploadedChunks || []);
      for (let chunkIndex = 0; chunkIndex < sessionFile.chunkCount; chunkIndex++) {
        await waitForUploadReady();
        if (uploaded.has(chunkIndex)) {
          continue;
        }
        const start = chunkIndex * session.chunkSize;
        const end = Math.min(file.size, start + session.chunkSize);
        await uploadChunkWithRetry(session, sessionFile, chunkIndex, file.slice(start, end), (loaded) => {
          const currentTotal = completedByFile.reduce((sum, bytes) => sum + bytes, 0) + loaded;
          const percent = percentForBytes(currentTotal, totalBytes);
          const rate = rateTracker(currentTotal);
          setTotalProgress(percent);
          setSingleFileProgress(fileIndex, file, percentForBytes(completedByFile[fileIndex] + loaded, file.size));
          updateUploadProgress(percent, rate);
        });
        completedByFile[fileIndex] += end - start;
        uploaded.add(chunkIndex);
        sessionFile.uploadedChunks = Array.from(uploaded).sort((a, b) => a - b);
        if (persistable) {
          saveResumableSession(session, createPayload);
        }
      }
      setSingleFileProgress(fileIndex, file, 100);
    }

    await waitForUploadReady();
    beginUploadFinalization();
    const resultPayload = await completeResumableSession(session.sessionId, session.resumeToken);
    const wasResumeMode = resumeMode;
    if (persistable) {
      removeResumableSession(session.sessionId);
    }
    activeUploadSession = null;
    if (resumeMode && recoveredDraft && recoveredDraft.session.sessionId === session.sessionId) {
      resumeMode = false;
      recoveredDraft = null;
    }
    setTotalProgress(100);
    if (!wasResumeMode) {
      setFileProgress(files, 100);
    }
    return resultPayload;
  }

  async function createResumableSession(payload) {
    const response = await fetch("/api/v1/uploads/resumable", {
      method: "POST",
      headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });
    return readUploadJSON(response, "Upload session could not be created");
  }

  async function fetchResumableStatus(sessionID, resumeToken) {
    const response = await fetch(`/api/v1/uploads/resumable/${encodeURIComponent(sessionID)}`, {
      headers: resumableHeaders(resumeToken),
    });
    return readUploadJSON(response, "Upload session could not be resumed");
  }

  async function addResumableFiles(sessionID, resumeToken, files) {
    const response = await fetch(`/api/v1/uploads/resumable/${encodeURIComponent(sessionID)}/files`, {
      method: "POST",
      headers: {
        ...resumableHeaders(resumeToken),
        "Accept": "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ files }),
    });
    return readUploadJSON(response, "Upload session files could not be added");
  }

  function uploadChunk(sessionID, resumeToken, fileID, chunkIndex, chunk, onProgress) {
    return new Promise((resolve, reject) => {
      const request = new XMLHttpRequest();
      activeUploadRequest = request;
      request.open("PUT", `/api/v1/uploads/resumable/${encodeURIComponent(sessionID)}/files/${encodeURIComponent(fileID)}/chunks/${chunkIndex}`);
      request.setRequestHeader("Accept", "application/json");
      request.setRequestHeader("X-Warpbox-Resume-Token", resumeToken || "");
      request.upload.addEventListener("progress", (event) => {
        if (event.lengthComputable && onProgress) {
          onProgress(event.loaded);
        }
      });
      request.addEventListener("load", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        if (request.status < 200 || request.status >= 300) {
          let payload = {};
          try {
            payload = JSON.parse(request.responseText || "{}");
          } catch (error) {
            payload = {};
          }
          reject(new Error(payload.error || "Chunk upload failed"));
          return;
        }
        resolve();
      });
      request.addEventListener("error", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        reject(new Error("Network error during chunk upload"));
      });
      request.addEventListener("abort", () => {
        if (activeUploadRequest === request) {
          activeUploadRequest = null;
        }
        if (uploadCancelled) {
          reject(uploadControlError("UploadCancelledError", "Upload cancelled"));
        } else if (uploadPaused) {
          reject(uploadControlError("UploadPausedError", "Upload paused"));
        } else {
          reject(new Error("Chunk upload aborted"));
        }
      });
      request.send(chunk);
    });
  }

  async function uploadChunkWithRetry(session, sessionFile, chunkIndex, chunk, onProgress) {
    const delays = [1000, 2000, 5000, 10000, 20000];
    let lastError = null;
    for (let attempt = 0; attempt <= delays.length; attempt++) {
      await waitForUploadReady();
      try {
        return await uploadChunk(session.sessionId, session.resumeToken, sessionFile.id, chunkIndex, chunk, onProgress);
      } catch (error) {
        if (isUploadCancelledError(error)) {
          throw error;
        }
        if (isUploadPausedError(error)) {
          await waitForUploadReady();
          await wait(150);
          attempt -= 1;
          continue;
        }
        lastError = error;
        if (attempt >= delays.length) {
          break;
        }
        const seconds = Math.round(delays[attempt] / 1000);
        updateStatus(`Connection interrupted, retrying chunk ${chunkIndex + 1} in ${seconds}s`);
        await waitForUploadDelay(delays[attempt]);
      }
    }
    throw lastError || new Error("Chunk upload failed");
  }

  async function completeResumableSession(sessionID, resumeToken) {
    const response = await fetch(`/api/v1/uploads/resumable/${encodeURIComponent(sessionID)}/complete`, {
      method: "POST",
      headers: resumableHeaders(resumeToken),
    });
    return readUploadJSON(response, "Upload could not be completed");
  }

  async function cancelResumableSession(sessionID, resumeToken) {
    const response = await fetch(`/api/v1/uploads/resumable/${encodeURIComponent(sessionID)}`, {
      method: "DELETE",
      headers: resumableHeaders(resumeToken),
    });
    if (!response.ok && response.status !== 404) {
      await readUploadJSON(response, "Upload draft could not be deleted");
    }
  }

  function resumableHeaders(resumeToken) {
    return {
      "Accept": "application/json",
      "X-Warpbox-Resume-Token": resumeToken || "",
    };
  }

  function wait(ms) {
    return new Promise((resolve) => window.setTimeout(resolve, ms));
  }

  async function waitForUploadDelay(ms) {
    const deadline = performance.now() + ms;
    while (performance.now() < deadline) {
      await wait(Math.min(100, Math.max(0, deadline - performance.now())));
      await waitForUploadReady();
    }
  }

  async function readUploadJSON(response, fallback) {
    let payload = {};
    try {
      payload = await response.json();
    } catch (error) {
      payload = {};
    }
    if (!response.ok) {
      throw new Error(payload.error || fallback);
    }
    return payload;
  }

  async function findResumableSession(payload) {
    const records = loadResumableSessions();
    const optionKey = resumableOptionKey(payload);
    const selectedKeys = new Set(payload.files.map((file) => resumableFileKey(file)));
    for (const record of records) {
      if (record.optionKey !== optionKey) {
        continue;
      }
      if (!record.files || !record.files.some((file) => selectedKeys.has(resumableFileKey(file)))) {
        continue;
      }
      const session = await fetchResumableStatus(record.sessionId, record.resumeToken).catch(() => null);
      if (!session || session.status !== "uploading") {
        removeResumableSession(record.sessionId);
        continue;
      }
      session.resumeToken = record.resumeToken;
      const sessionKeys = new Set(session.files.map((file) => resumableFileKey(file)));
      const selectedContainsSessionFile = Array.from(sessionKeys).some((key) => selectedKeys.has(key));
      if (selectedContainsSessionFile) {
        return session;
      }
    }
    return null;
  }

  async function addMissingResumableFiles(session, payload) {
    const existing = new Set(session.files.map((file) => resumableFileKey(file)));
    const missing = payload.files.filter((file) => !existing.has(resumableFileKey(file)));
    if (missing.length === 0) {
      return session;
    }
    const updated = await addResumableFiles(session.sessionId, session.resumeToken, missing);
    updated.resumeToken = session.resumeToken;
    return updated;
  }

  function validateResumeSelection(session, payload) {
    if (!resumeMode || !recoveredDraft || session.sessionId !== recoveredDraft.session.sessionId) {
      return;
    }
    const existingByNameSize = new Map();
    (session.files || []).forEach((file) => {
      existingByNameSize.set(`${file.name}:${file.size}`, resumableFileKey(file));
    });
    for (const file of payload.files || []) {
      const expectedKey = existingByNameSize.get(`${file.name}:${file.size}`);
      if (expectedKey && expectedKey !== resumableFileKey(file)) {
        throw new Error(`"${file.name}" does not match the pending upload. Select the exact original file.`);
      }
    }
  }

  function matchSessionFile(session, file) {
    const key = resumableFileKey(file);
    return session.files.find((sessionFile) => resumableFileKey(sessionFile) === key) || null;
  }

  function resumableOptionKey(payload) {
    return [
      payload.expiresMinutes,
      payload.maxDownloads,
      payload.obfuscateMetadata ? "1" : "0",
      payload.collectionId || "",
    ].join(":");
  }

  function resumableFileKey(file) {
    return [file.name, file.size, file.fingerprint || ""].join(":");
  }

  function loadResumableSessions() {
    try {
      const value = localStorage.getItem(RESUMABLE_SESSIONS_KEY);
      const records = value ? JSON.parse(value) : [];
      return Array.isArray(records) ? records : [];
    } catch (error) {
      return [];
    }
  }

  function saveResumableSession(session, payload) {
    try {
      const records = loadResumableSessions().filter((record) => record.sessionId !== session.sessionId);
      records.push({
        sessionId: session.sessionId,
        resumeToken: session.resumeToken || "",
        optionKey: resumableOptionKey(payload),
        options: {
          expiresMinutes: payload.expiresMinutes,
          maxDownloads: payload.maxDownloads,
          obfuscateMetadata: !!payload.obfuscateMetadata,
          collectionId: payload.collectionId || "",
        },
        files: session.files.map((file) => ({
          name: file.name,
          size: file.size,
          contentType: file.contentType || "application/octet-stream",
          fingerprint: file.fingerprint || "",
          uploadedChunks: file.uploadedChunks || [],
          chunkCount: file.chunkCount || 0,
        })),
        updatedAt: new Date().toISOString(),
      });
      localStorage.setItem(RESUMABLE_SESSIONS_KEY, JSON.stringify(records.slice(-25)));
    } catch (error) {
      /* ignore persistence failures */
    }
  }

  async function recoverResumableSessions() {
    const records = loadResumableSessions()
      .filter((record) => record.sessionId && record.resumeToken)
      .sort((a, b) => new Date(b.updatedAt || 0).getTime() - new Date(a.updatedAt || 0).getTime());
    if (records.length === 0) {
      return;
    }
    for (const record of records) {
      const session = await fetchResumableStatus(record.sessionId, record.resumeToken).catch(() => null);
      if (!session || session.status !== "uploading") {
        removeResumableSession(record.sessionId);
        continue;
      }
      session.resumeToken = record.resumeToken;
      recoveredDraft = { session, record };
      selectedFiles = [];
      renderRecoveredQueue([{ session, record }]);
      updateRecoveredSummary(session);
      showRecoveryModal(recoveredDraft);
      return;
    }
  }

  function updateRecoveredSummary(session) {
    updateStatus("Unfinished upload found. Choose how to continue.");
    if (fileSummary) {
      const totalFiles = (session.files || []).length;
      const completedFiles = completedSessionFiles(session).length;
      fileSummary.textContent = `Recovered ${totalFiles} pending file${totalFiles === 1 ? "" : "s"}; ${completedFiles} fully uploaded.`;
    }
  }

  function removeResumableSession(sessionID) {
    try {
      const records = loadResumableSessions().filter((record) => record.sessionId !== sessionID);
      localStorage.setItem(RESUMABLE_SESSIONS_KEY, JSON.stringify(records));
    } catch (error) {
      /* ignore persistence failures */
    }
  }

  function completedSessionFiles(session) {
    return (session.files || []).filter((file) => (file.uploadedChunks || []).length >= file.chunkCount);
  }

  function showRecoveryModal(draft) {
    const old = document.querySelector(".upload-recovery-overlay");
    if (old) {
      old.remove();
    }
    const completeCount = completedSessionFiles(draft.session).length;
    const totalCount = (draft.session.files || []).length;
    const overlay = document.createElement("div");
    overlay.className = "upload-recovery-overlay";
    overlay.setAttribute("role", "dialog");
    overlay.setAttribute("aria-modal", "true");
    overlay.setAttribute("aria-labelledby", "upload-recovery-title");

    const modal = document.createElement("div");
    modal.className = "upload-recovery-modal card";
    const content = document.createElement("div");
    content.className = "card-content";

    const title = document.createElement("h2");
    title.id = "upload-recovery-title";
    title.textContent = "Unfinished upload found";
    const copy = document.createElement("p");
    copy.textContent = `Warpbox found a private draft with ${totalCount} file${totalCount === 1 ? "" : "s"}. ${completeCount} file${completeCount === 1 ? " is" : "s are"} already fully uploaded.`;

    const actions = document.createElement("div");
    actions.className = "upload-recovery-actions";

    const startOver = document.createElement("button");
    startOver.type = "button";
    startOver.className = "button button-danger";
    startOver.textContent = "New Upload";
    startOver.addEventListener("click", async () => {
      startOver.disabled = true;
      try {
        await cancelRecoveredDraft();
        overlay.remove();
      } catch (error) {
        startOver.disabled = false;
        updateStatus(error.message || "Upload draft could not be deleted");
      }
    });

    const resume = document.createElement("button");
    resume.type = "button";
    resume.className = "button button-primary";
    resume.textContent = "Resume";
    resume.addEventListener("click", () => {
      resumeRecoveredDraft();
      overlay.remove();
    });

    actions.append(startOver, resume);
    content.append(title, copy, actions);
    modal.append(content);
    overlay.append(modal);
    document.body.append(overlay);
  }

  async function cancelRecoveredDraft() {
    if (!recoveredDraft) {
      resetFreshUploadState();
      return;
    }
    const draft = recoveredDraft;
    updateStatus("Deleting unfinished upload...");
    await cancelResumableSession(draft.session.sessionId, draft.session.resumeToken);
    removeResumableSession(draft.session.sessionId);
    resetFreshUploadState();
  }

  function resumeRecoveredDraft() {
    if (!recoveredDraft) {
      return;
    }
    resumeMode = true;
    selectedFiles = [];
    renderResumeQueue(recoveredDraft.session, selectedFiles);
    updateSelectedState();
    updateNewUploadVisibility();
    updateStatus("Drop or reselect missing files to continue. Extra files will be added to this upload.");
  }

  function resetFreshUploadState() {
    selectedFiles = [];
    resumeMode = false;
    recoveredDraft = null;
    sharedTargetDraft = null;
    fileInput.value = "";
    result.hidden = true;
    if (resultList) {
      resultList.replaceChildren();
    }
    setTotalProgress(0);
    updateStatus("");
    updateSelectedState();
  }

  function uploadedBytesForSessionFile(file, chunkSize) {
    return (file.uploadedChunks || []).reduce((sum, index) => {
      const start = index * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      return sum + Math.max(0, end - start);
    }, 0);
  }

  function renderRecoveredQueue(items) {
    if (!uploadQueue) {
      return;
    }
    const rows = [];
    items.forEach(({ session }) => {
      (session.files || []).forEach((file) => {
        const uploadedBytes = uploadedBytesForSessionFile(file, session.chunkSize);
        const complete = (file.uploadedChunks || []).length >= file.chunkCount;
        rows.push({
          name: file.name,
          size: file.size,
          uploadedBytes,
          meta: complete
            ? `${window.Warpbox.formatBytes(file.size)} · uploaded`
            : `${window.Warpbox.formatBytes(uploadedBytes)} of ${window.Warpbox.formatBytes(file.size)} · Drop/reselect this file to continue`,
          progress: percentForBytes(uploadedBytes, file.size),
          status: complete ? "complete" : "waiting",
          readonly: true,
        });
      });
    });
    uploadQueue.hidden = rows.length === 0;
    uploadQueue.replaceChildren();
    rows.forEach((row) => uploadQueue.append(createFileRow(row)));
    const totalBytes = rows.reduce((sum, row) => sum + (row.size || 0), 0);
    if (totalBytes > 0) {
      setTotalProgress(percentForBytes(rows.reduce((sum, row) => sum + (row.uploadedBytes || 0), 0), totalBytes));
    } else if (rows.length > 0) {
      const completed = rows.filter((row) => row.status === "complete").length;
      setTotalProgress(percentForBytes(completed, rows.length));
    }
  }

  function renderResumeQueue(session, localFiles) {
    if (!uploadQueue) {
      return;
    }
    const rows = [];
    const localByNameSize = new Map();
    (localFiles || []).forEach((file, index) => {
      localByNameSize.set(`${uploadName(file)}:${file.size}`, { file, index });
    });
    const usedLocalIndexes = new Set();
    (session.files || []).forEach((file) => {
      const uploadedBytes = uploadedBytesForSessionFile(file, session.chunkSize);
      const complete = (file.uploadedChunks || []).length >= file.chunkCount;
      const localMatch = localByNameSize.get(`${file.name}:${file.size}`) || null;
      if (localMatch) {
        usedLocalIndexes.add(localMatch.index);
      }
      rows.push({
        name: uploadName(file),
        size: file.size,
        uploadedBytes,
        meta: complete
          ? `${window.Warpbox.formatBytes(file.size)} · uploaded`
          : localMatch
            ? `${window.Warpbox.formatBytes(uploadedBytes)} of ${window.Warpbox.formatBytes(file.size)} · ready to resume`
            : `${window.Warpbox.formatBytes(uploadedBytes)} of ${window.Warpbox.formatBytes(file.size)} · waiting for local file`,
        progress: percentForBytes(uploadedBytes, file.size),
        status: complete ? "complete" : localMatch ? "queued" : "waiting",
        readonly: !localMatch,
        index: localMatch ? localMatch.index : undefined,
        removable: Boolean(localMatch && !complete),
      });
    });
    (localFiles || []).forEach((file, index) => {
      if (usedLocalIndexes.has(index)) {
        return;
      }
      rows.push({
        name: uploadName(file),
        meta: `${window.Warpbox.formatBytes(file.size)} · new file`,
        progress: 0,
        status: "queued",
        index,
        removable: true,
      });
    });
    uploadQueue.hidden = rows.length === 0;
    uploadQueue.replaceChildren();
    rows.forEach((row) => uploadQueue.append(createFileRow(row)));
  }

  function percentForBytes(bytes, total) {
    if (!total) {
      return 100;
    }
    return Math.max(0, Math.min(100, Math.round((bytes / total) * 100)));
  }

  function renderQueue(files, status, options) {
    if (!uploadQueue) {
      return;
    }
    const shared = Boolean(options && options.shared);
    uploadQueue.hidden = files.length === 0;
    uploadQueue.replaceChildren();
    files.forEach((file, index) => {
      uploadQueue.append(createFileRow({
        name: uploadName(file),
        meta: shared ? `${window.Warpbox.formatBytes(file.size)} · Shared from device` : window.Warpbox.formatBytes(file.size),
        progress: status === "queued" ? 0 : 100,
        status,
        index,
        removable: status === "queued",
        shared,
      }));
    });
  }

  function createFileRow(file) {
    const row = document.createElement("div");
    row.className = `result-item upload-file-row upload-file-${file.status || "queued"}`;
    row.dataset.fileName = file.name;
    if (typeof file.index === "number") {
      row.dataset.fileIndex = file.index;
    }

    const body = document.createElement("span");
    const name = document.createElement("strong");
    name.className = "file-name";
    name.textContent = file.name;
    name.title = file.name;
    const meta = document.createElement("code");
    meta.textContent = file.meta;
    body.append(name, meta);

    const side = document.createElement("div");
    side.className = "file-progress-side";
    const percent = document.createElement("span");
    percent.className = "file-progress-percent";
    percent.textContent = `${file.progress}%`;
    const bar = document.createElement("div");
    bar.className = "progress file-progress";
    const fill = document.createElement("span");
    fill.style.transform = `scaleX(${file.progress / 100})`;
    bar.append(fill);
    side.append(percent, bar);
    if (file.status === "waiting") {
      const badge = document.createElement("small");
      badge.className = "upload-file-state";
      badge.textContent = "Needs local file";
      side.append(badge);
    }
    if (file.shared) {
      const badge = document.createElement("small");
      badge.className = "upload-file-state upload-file-state-shared";
      badge.textContent = "Shared from device";
      side.append(badge);
    }
    if (file.removable) {
      const remove = document.createElement("button");
      remove.className = "upload-file-remove";
      remove.type = "button";
      remove.setAttribute("aria-label", `Remove ${file.name}`);
      remove.textContent = "×";
      remove.addEventListener("click", () => removeSelectedFile(file.index || 0));
      side.append(remove);
    }

    row.append(body, side);
    return row;
  }

  function uploadFormData() {
    const formData = new FormData(form);
    formData.delete("file");
    formData.delete("file_path");
    selectedFiles.forEach((file) => {
      formData.append("file", file, uploadName(file));
      formData.append("file_path", uploadName(file));
    });
    return formData;
  }

  function fileIdentity(file) {
    return [uploadName(file), file.size, file.lastModified || 0].join(":");
  }

  async function fileFingerprint(file) {
    if (!window.crypto || !window.crypto.subtle || !file.slice || typeof TextEncoder === "undefined") {
      return fileIdentity(file);
    }
    const sampleSize = Math.min(file.size, 1024 * 1024);
    const sample = await file.slice(0, sampleSize).arrayBuffer();
    const metadata = new TextEncoder().encode([uploadName(file), file.size, file.lastModified || 0, sampleSize].join(":"));
    const combined = new Uint8Array(metadata.byteLength + sample.byteLength);
    combined.set(metadata, 0);
    combined.set(new Uint8Array(sample), metadata.byteLength);
    const digest = await window.crypto.subtle.digest("SHA-256", combined);
    return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
  }

  function setTotalProgress(percent) {
    if (totalProgressBar) {
      totalProgressBar.style.transform = `scaleX(${Math.max(0, Math.min(100, percent)) / 100})`;
    }
  }

  function setFileProgress(files, totalPercent) {
    if (!uploadQueue) {
      return;
    }
    const count = files.length || 1;
    const completedFloat = (Math.max(0, Math.min(100, totalPercent)) / 100) * count;
    uploadQueue.querySelectorAll(".upload-file-row").forEach((row, index) => {
      const progress = Math.max(0, Math.min(100, Math.round((completedFloat - index) * 100)));
      const percent = row.querySelector(".file-progress-percent");
      const fill = row.querySelector(".file-progress span");
      if (percent) {
        percent.textContent = `${progress}%`;
      }
      if (fill) {
        fill.style.transform = `scaleX(${progress / 100})`;
      }
    });
  }

  function setSingleFileProgress(index, file, progress) {
    if (!uploadQueue) {
      return;
    }
    const row = uploadQueue.querySelector(`.upload-file-row[data-file-index="${index}"]`);
    if (!row) {
      return;
    }
    const percent = row.querySelector(".file-progress-percent");
    const fill = row.querySelector(".file-progress span");
    const normalized = Math.max(0, Math.min(100, progress));
    if (percent) {
      percent.textContent = `${normalized}%`;
    }
    if (fill) {
      fill.style.transform = `scaleX(${normalized / 100})`;
    }
  }
})();
