// Two coexisting modal mechanisms, one module. See AGENTS.md
// "URL-addressable modal templates" for the convention that decides which
// one a new modal should use.
//
//   Inline modal — content is pre-rendered into the page HTML.
//     Open via [data-inline-modal-open="<dialog-id>"] or
//     window.modals.openInline(<id-or-element>).
//     Close via [data-inline-modal-close] inside the dialog, Esc, or
//     a backdrop click.
//
//   Routed modal — content is fetched on demand from a URL and lands in
//     the persistent <dialog id="modal-container"> slot. URL state via
//     ?modal=<path>, with browser history integration so refresh and
//     share-link both reopen the modal.
//     Open via <a data-routed-modal href="<path>"> or
//     window.modals.openRouted(<path>).
//     Close via [data-routed-modal-dismiss], Esc, backdrop, browser
//     back, programmatic window.modals.closeRouted(), or a 4xx/5xx
//     response from the modal fetch.
//
// Both pipelines share the same close-event semantics: dialog.close()
// is the single mutation point. All sources of "close" funnel through
// it so URL/state cleanup and CM6 controller teardown run exactly once.
(function () {
  "use strict";

  // ---------------------------------------------------------------- inline
  // Event delegation at document level so partials swapped in via HTMX (or
  // any later DOM mutation) still get modal behaviour without re-binding.

  function openInline(idOrEl) {
    const dialog = typeof idOrEl === "string" ? document.getElementById(idOrEl) : idOrEl;
    if (dialog && typeof dialog.showModal === "function" && !dialog.open) {
      dialog.showModal();
    }
  }

  function closeInline(dialogOrEl) {
    const dialog = (dialogOrEl && dialogOrEl.tagName === "DIALOG")
      ? dialogOrEl
      : (dialogOrEl && dialogOrEl.closest && dialogOrEl.closest("dialog.modal")) || null;
    if (dialog && dialog.open) dialog.close();
  }

  // ---------------------------------------------------------------- routed
  // Click intercept on a[data-routed-modal] → ?modal=<path> in URL → htmx
  // swap into #modal-content → showModal() on the outer slot.

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

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

  function fetchAndShowRouted(path) {
    currentRoutedPath = path;
    if (typeof window.htmx === "undefined") {
      console.error("[modals] htmx not loaded; cannot fetch routed 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 (currentRoutedPath !== path) return;
      const dlg = document.getElementById("modal-container");
      if (dlg && !dlg.open) dlg.showModal();
    }).catch((err) => {
      console.error("[modals] routed fetch failed", err);
    });
  }

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

  // ---------------------------------------------------------------- shared
  // Document-level click delegation handles all four attribute hooks in one
  // pass. Order matters: dismiss/close checks first (more specific), then
  // open triggers, then the generic backdrop-on-any-modal fallback.

  document.addEventListener("click", (event) => {
    // Inline open
    const inlineOpener = event.target.closest("[data-inline-modal-open]");
    if (inlineOpener) {
      const id = inlineOpener.getAttribute("data-inline-modal-open");
      const dialog = document.getElementById(id);
      if (dialog) {
        event.preventDefault();
        openInline(dialog);
      }
      return;
    }

    // Inline close
    const inlineCloser = event.target.closest("[data-inline-modal-close]");
    if (inlineCloser) {
      event.preventDefault();
      closeInline(inlineCloser);
      return;
    }

    // Routed open — links only (preserves Cmd/Ctrl-click for new-tab and
    // works without JS as a full-page navigation fallback).
    const routedLink = event.target.closest("a[data-routed-modal]");
    if (routedLink && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey && event.button === 0) {
      const href = routedLink.getAttribute("href");
      if (href) {
        event.preventDefault();
        openRouted(href);
        return;
      }
    }

    // Routed dismiss
    if (event.target.closest("[data-routed-modal-dismiss]")) {
      event.preventDefault();
      closeRouted();
      return;
    }

    // Generic backdrop close — catches inline dialogs whose backdrop is
    // clicked. The routed slot binds its own backdrop listener (below)
    // because it also needs to coordinate with its 'close' state cleanup.
    if (event.target.matches && event.target.matches("dialog.modal") && event.target.id !== "modal-container") {
      event.target.close();
    }
  });

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

    // Single source of truth for the routed modal: every close path funnels
    // through the dialog's native 'close' event, so URL/state cleanup and
    // CM6 controller teardown run exactly once regardless of who triggered
    // the close (Esc, backdrop, dismiss button, response error, programmatic).
    slot.addEventListener("close", () => {
      currentRoutedPath = 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 content = document.getElementById("modal-content");
      if (content) {
        for (const ta of content.querySelectorAll("textarea[data-editor-language]")) {
          const ctrl = ta.__editorController;
          if (ctrl && typeof ctrl.destroy === "function") {
            ctrl.destroy();
          }
          ta.__editorController = null;
        }
        content.innerHTML = "";
      }
    });

    // Esc fires 'cancel' on a <dialog>. preventDefault, then close() so a
    // single path drives the URL sync.
    slot.addEventListener("cancel", (event) => {
      event.preventDefault();
      slot.close();
    });

    // Backdrop click on the routed slot specifically.
    slot.addEventListener("click", (event) => {
      if (event.target === slot) slot.close();
    });

    // Bootstrap: if the page loaded with ?modal=<path> already in the URL
    // (refresh or share-link landing), open that modal. Back/forward within
    // an already-loaded session is handled by the popstate listener below.
    const initialPath = new URL(window.location.href).searchParams.get("modal");
    if (initialPath) {
      fetchAndShowRouted(initialPath);
    }
  });

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

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

  // Public API
  window.modals = { openInline, closeInline, openRouted, closeRouted };
})();
