(function () {
  var form = document.querySelector("[data-editor-form]");
  var textarea = document.querySelector("[data-document-editor]");
  var statusNodes = document.querySelectorAll("[data-sync-status], [data-sync-status-secondary]");
  var hashEl = document.querySelector("[data-editor-hash]");
  var documentShell = document.querySelector("[data-document-path][data-document-hash]");
  var conflictNotice = document.querySelector("[data-conflict-notice]");
  var conflictHash = document.querySelector("[data-conflict-hash]");
  var conflictStatus = document.querySelector("[data-conflict-status]");
  var conflictDiff = document.querySelector("[data-conflict-diff]");
  var conflictApply = document.querySelector("[data-conflict-apply]");
  var conflictDismiss = document.querySelector("[data-conflict-dismiss]");
  var conflictResolution = document.querySelector("[data-conflict-resolution]");
  var conflictResolutionMount = document.querySelector("[data-conflict-codemirror-mount]");
  var conflictUseServer = document.querySelector("[data-conflict-use='server']");
  var conflictUseLocal = document.querySelector("[data-conflict-use='local']");
  var conflictUseBoth = document.querySelector("[data-conflict-use='both']");
  var codeMirrorContainer = document.querySelector("[data-codemirror-mount]");
  var codeMirrorReadyPromise = null;
  var codeMirrorModules = null;
  var documentCompletionOptionsPromise = null;

  if (!form || !textarea || statusNodes.length === 0) {
    return;
  }

  var path = form.getAttribute("data-document-path");
  var baseHash = documentShell ? documentShell.getAttribute("data-document-hash") : "";
  var saveTimer = null;
  var lastSavedValue = textarea.value;
  var editor = null;
  var currentConflict = null;
  var currentConflictLocalContent = "";
  var resolvedConflictContent = "";
  var conflictDiffView = null;
  var resolutionEditor = null;
  var suppressResolutionEvents = false;
  var doneLink = document.querySelector("[data-done-link]");
  var isSaving = false;
  var suppressBeforeUnload = false;

  function setStatus(nextStatus) {
    statusNodes.forEach(function (node) {
      node.textContent = nextStatus;
      node.dataset.state = nextStatus.toLowerCase();
    });
  }

  function getEditorValue() {
    return editor ? editor.getValue() : textarea.value;
  }

  function setEditorValue(value) {
    textarea.value = value;
    if (editor && editor.getValue() !== value) {
      editor.setValue(value);
    }
  }

  function getResolutionValue() {
    return resolutionEditor ? resolutionEditor.getValue() : resolvedConflictContent;
  }

  function setResolutionEditorValue(value) {
    resolvedConflictContent = value;
    if (conflictResolution) {
      conflictResolution.value = value;
    }
    if (resolutionEditor && resolutionEditor.getValue() !== value) {
      suppressResolutionEvents = true;
      resolutionEditor.setValue(value);
      suppressResolutionEvents = false;
    }
  }

  function ensureTrailingNewline(value) {
    return value.endsWith("\n") ? value : value + "\n";
  }

  function buildCombinedResolution(serverContent, localContent) {
    return ensureTrailingNewline(serverContent) + "\n" + ensureTrailingNewline(localContent);
  }

  function setConflictMessage(message) {
    if (conflictStatus) {
      conflictStatus.textContent = message;
    }
  }

  function setResolutionContent(content, message) {
    setResolutionEditorValue(content);
    if (conflictApply) {
      conflictApply.disabled = false;
    }
    if (message) {
      setConflictMessage(message);
    }
  }

  async function loadDiffs() {
    var modules = await Promise.all([
      import("https://cdn.jsdelivr.net/npm/@pierre/diffs@1.1.22/+esm"),
      import("https://cdn.jsdelivr.net/npm/@pierre/diffs@1.1.22/dist/style.js"),
    ]);
    return {
      renderer: modules[0],
      stylesheet: modules[1].default || "",
    };
  }

  function styleDiffContainer(diffContainer) {
    diffContainer.style.setProperty("--diffs-light-bg", "#fcfaf5");
    diffContainer.style.setProperty("--diffs-light", "#1c2430");
    diffContainer.style.setProperty("--diffs-dark-bg", "#1e1e1e");
    diffContainer.style.setProperty("--diffs-dark", "#e8ecf0");
    diffContainer.style.setProperty("--diffs-addition-color", "#3f8f59");
    diffContainer.style.setProperty("--diffs-deletion-color", "#c8553d");
    diffContainer.style.setProperty("--diffs-modified-color", "#c67a2a");
    diffContainer.style.setProperty("--diffs-font-family", "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace");
    diffContainer.style.setProperty("--diffs-header-font-family", "'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif");
  }

  function injectDiffStyles(diffContainer, stylesheet) {
    var root = diffContainer.shadowRoot;
    if (!root || !stylesheet || root.querySelector("[data-cairnquire-diffs-style]")) {
      return;
    }

    var style = document.createElement("style");
    style.setAttribute("data-cairnquire-diffs-style", "");
    style.textContent = stylesheet;
    root.prepend(style);
  }

  function renderConflictResolver(conflict, localContent) {
    if (!conflictDiff) {
      return;
    }

    if (conflictDiffView) {
      conflictDiffView.cleanUp();
      conflictDiffView = null;
    }
    conflictDiff.replaceChildren();
    currentConflictLocalContent = localContent;
    setResolutionEditorValue(localContent);
    if (conflictApply) {
      conflictApply.disabled = false;
    }
    ensureResolutionEditor();
    setConflictMessage("Loading conflict resolver...");

    loadDiffs().then(function (diffsModule) {
      var diffs = diffsModule.renderer;
      var diffContainer = document.createElement("div");
      styleDiffContainer(diffContainer);
      conflictDiff.appendChild(diffContainer);
      var serverFile = {
        name: path,
        contents: conflict.currentContent || "",
        lang: "markdown",
        cacheKey: "server:" + (conflict.currentHash || ""),
      };
      var localFile = {
        name: path,
        contents: localContent,
        lang: "markdown",
        cacheKey: "local:" + (conflict.baseHash || "") + ":" + localContent.length,
      };

      conflictDiffView = new diffs.FileDiff({
        disableFileHeader: false,
        diffStyle: "unified",
        diffIndicators: "classic",
        lineDiffType: "word",
        overflow: "scroll",
        parseDiffOptions: { context: 4 },
      });
      conflictDiffView.render({
        oldFile: serverFile,
        newFile: localFile,
        fileContainer: diffContainer,
      });
      injectDiffStyles(diffContainer, diffsModule.stylesheet);
      setConflictMessage("Red deletions are from the server copy. Green additions are from your queued edit.");
    }).catch(function () {
      setConflictMessage("Could not load the diff renderer. Your edit is still queued locally.");
    });
  }

  function showConflict(conflict, localContent) {
    currentConflict = conflict;
    setStatus("Conflict");
    form.hidden = true;
    if (conflictHash && conflict && conflict.currentHash) {
      conflictHash.textContent = conflict.currentHash;
    }
    if (conflictNotice) {
      conflictNotice.hidden = false;
    }
    renderConflictResolver(conflict, localContent || getEditorValue());
  }

  function hideConflict() {
    form.hidden = false;
    if (conflictNotice) {
      conflictNotice.hidden = true;
    }
    if (conflictDiff) {
      conflictDiff.replaceChildren();
    }
    if (conflictDiffView) {
      conflictDiffView.cleanUp();
      conflictDiffView = null;
    }
    if (conflictApply) {
      conflictApply.disabled = true;
    }
    setResolutionEditorValue("");
    currentConflict = null;
    currentConflictLocalContent = "";
  }

  async function saveNow() {
    var value = getEditorValue();
    if (value === lastSavedValue) {
      setStatus("Saved");
      return "unchanged";
    }

    isSaving = true;
    hideConflict();
    setStatus("Saving");

    if (!window.MDHubSync) {
      isSaving = false;
      setStatus("Queued");
      return "queued";
    }

    try {
      var result = await window.MDHubSync.saveDocument(path, value, baseHash);
      if (result.status === "saved") {
        lastSavedValue = value;
        if (hashEl && result.result && result.result.hash) {
          hashEl.textContent = result.result.hash;
          baseHash = result.result.hash;
          if (documentShell) {
            documentShell.setAttribute("data-document-hash", result.result.hash);
          }
        }
        setStatus("Saved");
        isSaving = false;
        return "saved";
      }

      if (result.status === "conflict") {
        showConflict(result.conflict, value);
        isSaving = false;
        return "conflict";
      }

      setStatus("Queued");
      return "queued";
    } catch (err) {
      setStatus("Queued");
      throw err;
    } finally {
      isSaving = false;
    }
  }

  async function applyResolvedConflict() {
    if (!window.MDHubSync || !currentConflict) {
      return;
    }

    var resolutionContent = getResolutionValue();
    resolvedConflictContent = resolutionContent;
    setConflictMessage("Saving resolved document...");
    if (conflictApply) {
      conflictApply.disabled = true;
    }

    var result = await window.MDHubSync.saveDocument(path, resolutionContent, currentConflict.currentHash);
    if (result.status === "saved") {
      setEditorValue(resolutionContent);
      lastSavedValue = resolutionContent;
      if (hashEl && result.result && result.result.hash) {
        hashEl.textContent = result.result.hash;
        baseHash = result.result.hash;
        if (documentShell) {
          documentShell.setAttribute("data-document-hash", result.result.hash);
        }
      }
      hideConflict();
      setStatus("Saved");
      return;
    }

    if (result.status === "conflict") {
      showConflict(result.conflict, resolutionContent);
      return;
    }

    setStatus("Queued");
    setConflictMessage("Resolved document is queued and will retry when the server is reachable.");
  }

  function restorePendingConflict() {
    if (!window.MDHubCache || !window.MDHubCache.getPendingEdit) {
      return;
    }
    var normalizedPath = window.MDHubCache.normalizePath("/docs/" + path.replace(/^\/+/, ""));
    window.MDHubCache.getPendingEdit(normalizedPath).then(function (pending) {
      if (pending && pending.conflict) {
        showConflict(pending.conflict, pending.content || getEditorValue());
      }
    }).catch(function () {});
  }

  function scheduleSave() {
    setStatus("Unsaved");
    if (saveTimer) {
      clearTimeout(saveTimer);
    }
    saveTimer = setTimeout(function () {
      saveNow().catch(function () {
        setStatus("Queued");
      });
    }, 2000);
  }

  function doneHref() {
    return doneLink ? doneLink.getAttribute("href") : "/docs/" + path.replace(/\.md$/, "");
  }

  function clearScheduledSave() {
    if (saveTimer) {
      clearTimeout(saveTimer);
      saveTimer = null;
    }
  }

  function leaveEditorNow() {
    clearScheduledSave();
    suppressBeforeUnload = true;
    window.location.href = doneHref();
  }

  function saveThenLeaveEditor() {
    var href = doneHref();

    if (currentConflict) {
      setStatus("Conflict");
      if (conflictNotice) {
        conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
      }
      return;
    }

    clearScheduledSave();

    if (isSaving) {
      waitForSaveThenNavigate(href);
      return;
    }

    setStatus("Saving");
    saveNow()
      .then(function (result) {
        if (result === "saved" || result === "unchanged") {
          suppressBeforeUnload = true;
          window.location.href = href;
          return;
        }

        if (result === "conflict") {
          setStatus("Conflict");
          return;
        }

        setStatus("Queued");
        if (window.confirm("Could not save. Leave without saving?")) {
          suppressBeforeUnload = true;
          window.location.href = href;
        }
      })
      .catch(function () {
        setStatus("Queued");
        if (window.confirm("Could not save. Leave without saving?")) {
          suppressBeforeUnload = true;
          window.location.href = href;
        }
      });
  }

  function loadCodeMirror() {
    if (codeMirrorReadyPromise) {
      return codeMirrorReadyPromise;
    }

    codeMirrorReadyPromise = Promise.all([
      import("/static/codemirror.bundle.js"),
    ]).then(function (modules) {
      var bundle = modules[0];
      codeMirrorModules = {
        cm: bundle,
        markdown: bundle,
        vim: bundle,
      };
      registerVimCommands();
      return codeMirrorModules;
    });

    return codeMirrorReadyPromise;
  }

  function registerVimCommands() {
    var Vim = codeMirrorModules && codeMirrorModules.vim && codeMirrorModules.vim.Vim;
    if (!Vim) {
      return;
    }

    Vim.defineEx("write", "w", function () {
      saveNow().catch(function () {
        setStatus("Queued");
      });
    });
    Vim.defineEx("quit", "q", function () {
      leaveEditorNow();
    });
    Vim.defineEx("wq", "wq", function () {
      saveThenLeaveEditor();
    });
    Vim.defineEx("xit", "x", function () {
      saveThenLeaveEditor();
    });
    Vim.map("<Esc>", ":q<CR>");
  }

  function codeMirrorTheme() {
    var EditorView = codeMirrorModules.cm.EditorView;
    var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

    return EditorView.theme({
      "&": {
        height: "100%",
        minHeight: "60vh",
        backgroundColor: "var(--panel)",
        color: "var(--text)",
        fontSize: "14px",
      },
      "&.cm-focused": {
        outline: "none",
      },
      ".cm-scroller": {
        fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
        lineHeight: "22px",
      },
      ".cm-content": {
        padding: "12px 0",
        caretColor: "var(--accent)",
      },
      ".cm-line": {
        padding: "0 14px",
      },
      ".cm-gutters": {
        backgroundColor: "var(--panel-strong)",
        borderRight: "1px solid var(--border)",
        color: "var(--muted)",
      },
      ".cm-activeLine, .cm-activeLineGutter": {
        backgroundColor: isDark ? "oklch(0.24 0.012 55)" : "#f0ede5",
      },
      ".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
        backgroundColor: "color-mix(in srgb, var(--accent) 22%, transparent)",
      },
      ".cm-cursor": {
        borderLeftColor: "var(--accent)",
      },
      ".cm-panel": {
        backgroundColor: "var(--panel-strong)",
        borderTop: "1px solid var(--border)",
        color: "var(--text)",
        fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
      },
      ".cm-vim-panel input": {
        color: "var(--text)",
      },
    }, { dark: isDark });
  }

  function codeMirrorHighlighting() {
    var cm = codeMirrorModules.cm;
    var tags = codeMirrorModules.cm.tags;
    var urlColor = window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "oklch(0.78 0.14 190)"
      : "#047c78";

    return cm.syntaxHighlighting(cm.HighlightStyle.define([
      {
        tag: [tags.url, tags.link],
        color: urlColor,
        textDecoration: "underline",
        textUnderlineOffset: "2px",
      },
    ]));
  }

  function normalizeCompletionPath(doc) {
    var docPath = doc && doc.path ? String(doc.path) : "";
    return docPath.replace(/\.md$/, "");
  }

  function buildDocumentCompletionOptions(documents) {
    var seen = new Set();

    return (documents || [])
      .map(function (doc) {
        var label = normalizeCompletionPath(doc);
        if (!label || seen.has(label) || label === path.replace(/\.md$/, "")) {
          return null;
        }
        seen.add(label);

        return {
          label: label,
          detail: doc.title && doc.title !== label ? doc.title : doc.path,
          type: "file",
          apply: function (view, completion, from, to) {
            var insert = completion.label;
            var after = view.state.doc.sliceString(to, Math.min(view.state.doc.length, to + 2));
            if (after !== "]]") {
              insert += "]]";
            }
            view.dispatch({
              changes: { from: from, to: to, insert: insert },
              selection: { anchor: from + insert.length },
            });
          },
        };
      })
      .filter(Boolean)
      .sort(function (a, b) {
        return a.label.localeCompare(b.label);
      });
  }

  function fetchDocumentCompletionOptions() {
    return fetch("/api/documents", { credentials: "same-origin" })
      .then(function (response) {
        if (!response.ok) {
          throw new Error("Could not load documents");
        }
        return response.json();
      })
      .then(function (data) {
        var documents = data.documents || [];
        if (window.MDHubCache && window.MDHubCache.cacheDocuments) {
          window.MDHubCache.cacheDocuments(documents).catch(function () {});
        }
        return buildDocumentCompletionOptions(documents);
      });
  }

  function loadDocumentCompletionOptions() {
    if (documentCompletionOptionsPromise) {
      return documentCompletionOptionsPromise;
    }

    documentCompletionOptionsPromise = Promise.resolve()
      .then(function () {
        if (window.MDHubCache && window.MDHubCache.getCachedDocuments) {
          return window.MDHubCache.getCachedDocuments().then(function (documents) {
            if (documents && documents.length > 0) {
              return buildDocumentCompletionOptions(documents);
            }
            return fetchDocumentCompletionOptions();
          });
        }
        return fetchDocumentCompletionOptions();
      })
      .catch(function () {
        return [];
      });

    return documentCompletionOptionsPromise;
  }

  function wikiLinkCompletions(context) {
    var before = context.matchBefore(/\[\[([^\]\n]*)$/);
    if (!before) {
      return null;
    }

    return loadDocumentCompletionOptions().then(function (options) {
      if (!options.length) {
        return null;
      }
      return {
        from: before.from + 2,
        to: before.to,
        options: options,
        validFor: /^[^\]\n]*$/,
      };
    });
  }

  function createCodeMirrorEditor(mount, initialValue, options) {
    var cm = codeMirrorModules.cm;
    var markdownModule = codeMirrorModules.markdown;
    var vimModule = codeMirrorModules.vim;
    var onChange = options && options.onChange ? options.onChange : function () {};
    var extensions = [
      vimModule.vim({ status: true }),
      cm.basicSetup,
      markdownModule.markdown({
        base: markdownModule.markdownLanguage,
      }),
      cm.EditorView.lineWrapping,
      codeMirrorTheme(),
      codeMirrorHighlighting(),
      cm.autocompletion({
        override: [wikiLinkCompletions],
        activateOnTyping: true,
      }),
      cm.EditorView.updateListener.of(function (update) {
        if (update.docChanged) {
          onChange(update.state.doc.toString());
        }
      }),
    ];

    mount.replaceChildren();
    mount.classList.add("codemirror-mounted");

    var view = new cm.EditorView({
      doc: initialValue,
      extensions: extensions,
      parent: mount,
    });

    return {
      view: view,
      getValue: function () {
        return view.state.doc.toString();
      },
      setValue: function (value) {
        if (view.state.doc.toString() === value) {
          return;
        }
        view.dispatch({
          changes: { from: 0, to: view.state.doc.length, insert: value },
        });
      },
      focus: function () {
        view.focus();
      },
      hasTextFocus: function () {
        return view.hasFocus;
      },
      destroy: function () {
        view.destroy();
      },
    };
  }

  function ensureResolutionEditor() {
    if (!conflictResolution || !conflictResolutionMount) {
      return;
    }

    loadCodeMirror().then(function () {
      if (resolutionEditor) {
        return;
      }

      resolutionEditor = createCodeMirrorEditor(conflictResolutionMount, conflictResolution.value || resolvedConflictContent, {
        onChange: function (value) {
          if (suppressResolutionEvents) {
            return;
          }
          resolvedConflictContent = value;
          conflictResolution.value = value;
          if (conflictApply) {
            conflictApply.disabled = false;
          }
          setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
        },
      });
      conflictResolution.hidden = true;
      resolutionEditor.focus();
    }).catch(function () {
      conflictResolution.hidden = false;
    });
  }

  if (codeMirrorContainer) {
    loadCodeMirror().then(function () {
      editor = createCodeMirrorEditor(codeMirrorContainer, textarea.value, {
        onChange: function (value) {
          textarea.value = value;
          scheduleSave();
        },
      });
      textarea.hidden = true;
      editor.focus();
    }).catch(function () {
      textarea.addEventListener("input", scheduleSave);
    });
  } else {
    textarea.addEventListener("input", scheduleSave);
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    saveNow().catch(function () {
      setStatus("Queued");
    });
  });

  if (conflictApply) {
    conflictApply.addEventListener("click", function () {
      applyResolvedConflict().catch(function () {
        setStatus("Queued");
        setConflictMessage("Could not save the resolved document. It remains queued locally.");
      });
    });
  }

  if (conflictDismiss) {
    conflictDismiss.addEventListener("click", function () {
      hideConflict();
    });
  }

  if (conflictResolution) {
    conflictResolution.addEventListener("input", function () {
      resolvedConflictContent = conflictResolution.value;
      if (conflictApply) {
        conflictApply.disabled = false;
      }
      setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
    });
  }

  if (conflictUseServer) {
    conflictUseServer.addEventListener("click", function () {
      if (!currentConflict) {
        return;
      }
      setResolutionContent(currentConflict.currentContent || "", "Using the server copy as the resolution. You can edit it before applying.");
    });
  }

  if (conflictUseLocal) {
    conflictUseLocal.addEventListener("click", function () {
      setResolutionContent(currentConflictLocalContent, "Using your queued edit as the resolution. You can edit it before applying.");
    });
  }

  if (conflictUseBoth) {
    conflictUseBoth.addEventListener("click", function () {
      if (!currentConflict) {
        return;
      }
      setResolutionContent(
        buildCombinedResolution(currentConflict.currentContent || "", currentConflictLocalContent),
        "Using both versions as the resolution. Edit the combined content before applying."
      );
    });
  }

  restorePendingConflict();

  if (doneLink) {
    doneLink.addEventListener("click", function (event) {
      event.preventDefault();
      saveThenLeaveEditor();
    });
  }

  function waitForSaveThenNavigate(href) {
    var checkInterval = setInterval(function () {
      if (!isSaving) {
        clearInterval(checkInterval);
        if (currentConflict) {
          setStatus("Conflict");
          if (conflictNotice) {
            conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
          }
          return;
        }
        suppressBeforeUnload = true;
        window.location.href = href;
      }
    }, 100);
    // Timeout after 10 seconds
    setTimeout(function () {
      clearInterval(checkInterval);
    }, 10000);
  }

  // Warn on browser unload if there are unsaved changes
  window.addEventListener("beforeunload", function (e) {
    if (suppressBeforeUnload) {
      return;
    }
    if (getEditorValue() !== lastSavedValue || currentConflict) {
      e.preventDefault();
      e.returnValue = "";
    }
  });

  function editorSurfaceHasFocus(target) {
    if (!target) {
      return false;
    }
    if (target === textarea || target.closest("[data-codemirror-mount], [data-conflict-codemirror-mount], [data-document-editor], .cm-editor")) {
      return true;
    }
    return Boolean(editor && editor.hasTextFocus && editor.hasTextFocus());
  }

  document.addEventListener("keydown", function (e) {
    if ((e.metaKey || e.ctrlKey) && e.key === "s") {
      e.preventDefault();
      saveNow().catch(function () {
        setStatus("Queued");
      });
      return;
    }

    if (e.metaKey && e.key === "Enter") {
      e.preventDefault();
      saveThenLeaveEditor();
      return;
    }

    if (!editorSurfaceHasFocus(e.target)) {
      return;
    }
  }, true);
})();
