// URL-addressable modal router (see spec 2026-05-17-url-addressable-modals).
// Click intercept on a[data-modal] → ?modal=<path> in URL → htmx swap into
// #modal-content → showModal(). Close/popstate/bootstrap in later tasks.
(function () {
  "use strict";

  let currentModalPath = null;  // race-guard against stale swaps

  function openModal(path) {
    const url = new URL(window.location.href);
    url.searchParams.set("modal", path);
    history.pushState({ modal: path }, "", url.toString());
    fetchAndShow(path);
  }

  function fetchAndShow(path) {
    currentModalPath = path;
    if (typeof window.htmx === "undefined") {
      console.error("[modal-router] htmx not loaded; cannot fetch modal");
      return;
    }
    window.htmx.ajax("GET", path, {
      target: "#modal-content",
      swap: "innerHTML",
      headers: { "HX-Modal": "1" },
    }).then(() => {
      // Race guard: if the user clicked again during the fetch, abandon
      // this swap; the newer click will win.
      if (currentModalPath !== path) return;
      const dlg = document.getElementById("modal-container");
      if (dlg && !dlg.open) dlg.showModal();
    }).catch((err) => {
      console.error("[modal-router] fetch failed", err);
    });
  }

  function closeModal() {
    const dlg = document.getElementById("modal-container");
    if (dlg && dlg.open) dlg.close();
  }

  document.addEventListener("DOMContentLoaded", () => {
    const dlg = document.getElementById("modal-container");
    if (!dlg) return;

    // Single source of truth: every close path funnels through the dialog's
    // native 'close' event, so URL/state cleanup runs exactly once no matter
    // who triggered the close (Esc, backdrop, dismiss button, response error,
    // or the legacy modal.js backdrop handler).
    dlg.addEventListener("close", () => {
      currentModalPath = null;
      const url = new URL(window.location.href);
      if (url.searchParams.has("modal")) {
        url.searchParams.delete("modal");
        history.pushState({}, "", url.toString());
      }
      // Tear down any CM6 controllers attached to swapped-in editor textareas
      // so close/reopen cycles don't leak EditorView instances and matchMedia
      // listeners. The bundle exposes controller.destroy() on the controller
      // stored at textarea.__editorController.
      const slot = document.getElementById("modal-content");
      if (slot) {
        for (const ta of slot.querySelectorAll("textarea[data-editor-language]")) {
          const ctrl = ta.__editorController;
          if (ctrl && typeof ctrl.destroy === "function") {
            ctrl.destroy();
          }
          ta.__editorController = null;
        }
        slot.innerHTML = "";
      }
    });

    // Esc key fires 'cancel' on a <dialog>; preventDefault so we control the
    // close path (the default action would close() too, but we want one path).
    dlg.addEventListener("cancel", (event) => {
      event.preventDefault();
      dlg.close();
    });

    // Backdrop click on our own slot. The legacy modal.js handler also
    // matches dialog.modal and closes; this listener is harmless when the
    // legacy one wins because dialog.close() is idempotent.
    dlg.addEventListener("click", (event) => {
      if (event.target === dlg) dlg.close();
    });

    // Bootstrap: if the page loaded with ?modal=<path> already in the URL
    // (refresh, share-link, or browser-history forward), open that modal.
    const initialPath = new URL(window.location.href).searchParams.get("modal");
    if (initialPath) {
      fetchAndShow(initialPath);
    }
  });

  // Browser back/forward: re-evaluate URL state and either fetch+show a new
  // modal or close the current one. Closing routes through dialog.close() so
  // the close-event cleanup fires (no separate state update here).
  window.addEventListener("popstate", () => {
    const path = new URL(window.location.href).searchParams.get("modal");
    if (path) {
      fetchAndShow(path);
    } else {
      closeModal();
    }
  });

  document.addEventListener("click", (event) => {
    const link = event.target.closest("a[data-modal]");
    if (!link) return;
    if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
    if (event.button !== 0) return;
    const href = link.getAttribute("href");
    if (!href) return;
    event.preventDefault();
    openModal(href);
  });

  // Dismiss triggers inside modal content (e.g. the editor's × and Cancel
  // buttons render with data-modal-dismiss). Document-level delegation so
  // HTMX-swapped content gets the behavior without re-binding.
  document.addEventListener("click", (event) => {
    if (event.target.closest("[data-modal-dismiss]")) {
      event.preventDefault();
      closeModal();
    }
  });

  // HTMX response error (4xx/5xx from the modal fetch): close the modal so
  // the user isn't left with an error fragment open and a stale ?modal= URL.
  document.body.addEventListener("htmx:responseError", (event) => {
    const target = event.detail && event.detail.target;
    if (target && target.id === "modal-content") {
      console.warn("[modal-router] server returned error, closing modal", event.detail.xhr && event.detail.xhr.status);
      closeModal();
    }
  });

  // Public API — used by files-overlay.js to open the editor from row clicks
  // that aren't a literal <a data-modal> (existing event delegation).
  window.openModal = openModal;
  window.closeModal = closeModal;
})();
