(function () {
  if (!window.MDHubCache) {
    return;
  }

  const syncQueue = document.querySelector("[data-sync-queue]");
  const syncQueueTitle = document.querySelector("[data-sync-queue-title]");
  const syncQueueSummary = document.querySelector("[data-sync-queue-summary]");
  const syncQueueList = document.querySelector("[data-sync-queue-list]");
  const syncNow = document.querySelector("[data-sync-now]");

  function normalizeDocPath(path) {
    if (!path) return "";
    return path.replace(/^\/+/, "");
  }

  function formatPath(path) {
    return (path || "").replace(/^\/docs\//, "") || "/";
  }

  function pluralize(count, singular, plural) {
    return count === 1 ? singular : plural;
  }

  function emitSyncState(detail) {
    window.dispatchEvent(new CustomEvent("mdhub:sync-state", { detail: detail }));
  }

  function renderPendingEdits(pending, state) {
    if (!syncQueue || !syncQueueTitle || !syncQueueSummary || !syncQueueList) {
      return;
    }

    const conflicts = pending.filter(function (edit) {
      return Boolean(edit.conflict);
    });
    const clean = pending.filter(function (edit) {
      return !edit.conflict;
    });

    syncQueue.hidden = pending.length === 0;
    if (pending.length === 0) {
      syncQueueList.replaceChildren();
      syncQueueTitle.textContent = "Synced";
      syncQueueSummary.textContent = "No pending changes.";
      if (syncNow) {
        syncNow.disabled = true;
      }
      return;
    }

    const countLabel = pending.length + " pending " + pluralize(pending.length, "change", "changes");
    syncQueueTitle.textContent = conflicts.length > 0 ? countLabel + " with conflicts" : countLabel;
    if (!navigator.onLine) {
      syncQueueSummary.textContent = "Offline. Changes will sync when the connection returns.";
    } else if (state === "syncing") {
      syncQueueSummary.textContent = "Syncing queued edits...";
    } else if (conflicts.length > 0) {
      syncQueueSummary.textContent = conflicts.length + " " + pluralize(conflicts.length, "file needs", "files need") + " conflict resolution.";
    } else {
      syncQueueSummary.textContent = "Queued edits are ready to sync.";
    }

    syncQueueList.replaceChildren();
    pending.slice(0, 5).forEach(function (edit) {
      const item = document.createElement("li");
      item.dataset.state = edit.conflict ? "conflict" : "queued";

      const path = document.createElement("span");
      path.textContent = formatPath(edit.path);
      item.appendChild(path);

      const status = document.createElement("span");
      status.textContent = edit.conflict ? "Conflict" : "Queued";
      item.appendChild(status);

      syncQueueList.appendChild(item);
    });

    if (pending.length > 5) {
      const overflow = document.createElement("li");
      overflow.textContent = "+" + (pending.length - 5) + " more";
      syncQueueList.appendChild(overflow);
    }

    if (syncNow) {
      syncNow.disabled = !navigator.onLine || clean.length === 0 || state === "syncing";
    }
  }

  async function refreshSyncQueue(state) {
    const pending = await window.MDHubCache.getPendingEdits();
    pending.sort(function (a, b) {
      return (b.updatedAt || 0) - (a.updatedAt || 0);
    });
    renderPendingEdits(pending, state || "idle");
    emitSyncState({
      state: state || "idle",
      pending: pending.length,
      conflicts: pending.filter(function (edit) {
        return Boolean(edit.conflict);
      }).length,
    });
    return pending;
  }

  async function postDocument(path, content, baseHash) {
    const response = await fetch("/api/documents/" + normalizeDocPath(path), {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ content: content, baseHash: baseHash || "" }),
    });

    const payload = await response.json().catch(function () {
      return {};
    });

    if (!response.ok) {
      if (response.status === 409) {
        const conflict = new Error("document conflict");
        conflict.name = "DocumentConflictError";
        conflict.details = payload;
        throw conflict;
      }
      throw new Error("save failed");
    }

    return payload;
  }

  async function saveDocument(path, content, baseHash) {
    const normalizedPath = window.MDHubCache.normalizePath("/docs/" + normalizeDocPath(path));

    try {
      const result = await postDocument(path, content, baseHash);
      await window.MDHubCache.deletePendingEdit(normalizedPath);
      await refreshSyncQueue("saved");
      return {
        status: "saved",
        result: result,
      };
    } catch (error) {
      if (error.name === "DocumentConflictError") {
        await window.MDHubCache.putPendingEdit(normalizedPath, content, baseHash);
        await window.MDHubCache.markPendingEditConflict(normalizedPath, error.details);
        await refreshSyncQueue("conflict");
        return {
          status: "conflict",
          conflict: error.details,
        };
      }
      await window.MDHubCache.putPendingEdit(normalizedPath, content, baseHash);
      await refreshSyncQueue("queued");
      return {
        status: "queued",
      };
    }
  }

  async function syncPending() {
    if (!navigator.onLine) {
      await refreshSyncQueue("offline");
      return;
    }

    const pending = await refreshSyncQueue("syncing");
    pending.sort(function (a, b) {
      return (a.updatedAt || 0) - (b.updatedAt || 0);
    });

    for (const edit of pending) {
      if (edit.conflict) {
        continue;
      }
      try {
        await postDocument(edit.path.replace(/^\/docs\//, ""), edit.content, edit.baseHash);
        await window.MDHubCache.deletePendingEdit(edit.path);
      } catch (error) {
        if (error.name === "DocumentConflictError") {
          await window.MDHubCache.markPendingEditConflict(edit.path, error.details);
        }
        break;
      }
    }
    await refreshSyncQueue("idle");
  }

  window.addEventListener("online", function () {
    syncPending().catch(function () {});
  });

  window.addEventListener("offline", function () {
    refreshSyncQueue("offline").catch(function () {});
  });

  if (syncNow) {
    syncNow.addEventListener("click", function () {
      syncPending().catch(function () {
        refreshSyncQueue("queued").catch(function () {});
      });
    });
  }

  syncPending().catch(function () {});
  refreshSyncQueue("idle").catch(function () {});

  window.MDHubSync = {
    saveDocument: saveDocument,
    syncPending: syncPending,
    refreshSyncQueue: refreshSyncQueue,
  };
})();
