/**
 * Client-side navigation runtime, served at `/__wrnexus/nav.js`.
 *
 * Progressive enhancement over normal links: intercepts same-origin `<a>`
 * clicks, fetches the target page's HTML, swaps the `#app` container in place,
 * updates history/title/scroll, ensures any framework runtimes the new page
 * needs are present, and re-hydrates.
 *
 * Before replacing the current page, component lifecycle behaviors are
 * explicitly disposed. This ensures `unmount` hooks and watcher cleanups run
 * before the old DOM is removed.
 *
 * Programmatic navigation is exposed as:
 *
 *   window.__wrnexusNavigate(url)
 */

export const NAV_RUNTIME = String.raw`
(function () {
  if (
    !window.history ||
    !history.pushState ||
    !window.fetch ||
    !window.DOMParser
  ) {
    return;
  }

  if (window.__wrnexusNavInstalled) {
    return;
  }

  window.__wrnexusNavInstalled = true;

  var APP_ID = "app";
  var inFlight = null;

  function pathOf(src) {
    return String(src).split("?")[0];
  }

  function isLocalLink(anchor) {
    if (
      !anchor ||
      anchor.hasAttribute("download") ||
      anchor.hasAttribute("data-no-nav")
    ) {
      return false;
    }

    if (anchor.target && anchor.target !== "_self") {
      return false;
    }

    if (anchor.origin !== location.origin) {
      return false;
    }

    var href = anchor.getAttribute("href");

    if (!href || href.charAt(0) === "#") {
      return false;
    }

    var rel = (
      anchor.getAttribute("rel") || ""
    ).toLowerCase();

    return rel.indexOf("external") === -1;
  }

  function loadedScriptPaths() {
    var loaded = {};

    document
      .querySelectorAll("script[src]")
      .forEach(function (script) {
        var src = script.getAttribute("src");

        if (src) {
          loaded[pathOf(src)] = true;
        }
      });

    return loaded;
  }

  /**
   * Append framework runtimes declared by the incoming document but not
   * currently loaded.
   */
  function ensureScripts(doc) {
    var loaded = loadedScriptPaths();

    doc
      .querySelectorAll("script[src]")
      .forEach(function (script) {
        var src = script.getAttribute("src");

        if (!src || loaded[pathOf(src)]) {
          return;
        }

        loaded[pathOf(src)] = true;

        var element =
          document.createElement("script");

        element.src = src;
        element.async = false;

        document.body.appendChild(element);
      });
  }

  /**
   * Run explicit component cleanup before removing the existing page.
   *
   * The reactive runtime also observes removed DOM nodes, but explicit
   * disposal here guarantees that unmount hooks run before replacement.
   */
  function dispose(root) {
    try {
      if (
        typeof window.__wrnexusDisposeBehaviors ===
        "function"
      ) {
        window.__wrnexusDisposeBehaviors(root);
      }
    } catch (error) {
      console.error(
        "[wrnexus] failed to dispose page behaviors",
        error,
      );
    }
  }

  /**
   * Re-hydrate already-loaded runtimes against newly inserted DOM.
   *
   * All framework hydration functions must remain idempotent.
   */
  function rehydrate(root) {
    try {
      if (
        typeof window.__wrnexusHydrateScopes ===
        "function"
      ) {
        window.__wrnexusHydrateScopes(root);
      }
    } catch (error) {
      console.error(
        "[wrnexus] failed to hydrate reactive scopes",
        error,
      );
    }

    try {
      if (
        typeof window.__wrnexusHydrateCsrFetches ===
        "function"
      ) {
        window.__wrnexusHydrateCsrFetches(root);
      }
    } catch (error) {
      console.error(
        "[wrnexus] failed to hydrate CSR bindings",
        error,
      );
    }

    try {
      if (
        window.__wireValidate &&
        typeof window.__wireValidate.init ===
          "function"
      ) {
        window.__wireValidate.init(document);
      }
    } catch (error) {
      console.error(
        "[wrnexus] failed to hydrate validation",
        error,
      );
    }

    try {
      if (
        window.wireTheme &&
        typeof window.wireTheme.bind ===
          "function"
      ) {
        window.wireTheme.bind(document);
      }
    } catch (error) {
      console.error(
        "[wrnexus] failed to hydrate theme bindings",
        error,
      );
    }
  }

  function dispatchNavigationEvent(url) {
    try {
      window.dispatchEvent(
        new CustomEvent("wrnexus:navigated", {
          detail: {
            url: url,
          },
        }),
      );
    } catch (_) {
      // CustomEvent may not be available in very old browsers.
    }
  }

  function hardNavigate(url) {
    location.href = url;
  }

  function render(html, url, isPop) {
    var doc;

    try {
      doc = new DOMParser().parseFromString(
        html,
        "text/html",
      );
    } catch (_) {
      hardNavigate(url);
      return;
    }

    var incomingApp =
      doc.getElementById(APP_ID);

    var currentApp =
      document.getElementById(APP_ID);

    if (!incomingApp || !currentApp) {
      hardNavigate(url);
      return;
    }

    if (doc.title) {
      document.title = doc.title;
    }

    var importedNodes = [];

    for (
      var index = 0;
      index < incomingApp.childNodes.length;
      index++
    ) {
      importedNodes.push(
        document.importNode(
          incomingApp.childNodes[index],
          true,
        ),
      );
    }

    /*
     * Run unmount hooks and watcher cleanup while the current DOM is still
     * connected. This is important for components that remove window or
     * document listeners during lifecycle.unmount.
     */
    dispose(currentApp);

    try {
      /*
       * Avoid innerHTML so navigation works with strict Trusted Types and CSP
       * configurations.
       */
      currentApp.replaceChildren.apply(
        currentApp,
        importedNodes,
      );
    } catch (_) {
      hardNavigate(url);
      return;
    }

    /*
     * Load any runtime required only by the incoming page. Fresh scripts
     * initialise themselves when loaded.
     */
    ensureScripts(doc);

    /*
     * Hydrate runtimes that are already available. Hydration is scoped to
     * #app instead of the entire document for less DOM traversal.
     */
    rehydrate(currentApp);

    if (!isPop) {
      history.pushState(
        {
          wrnexusNav: true,
        },
        "",
        url,
      );

      window.scrollTo(0, 0);
    }

    dispatchNavigationEvent(url);
  }

  function navigate(url, isPop) {
    var token = {};

    inFlight = token;

    fetch(url, {
      headers: {
        "x-wrnexus-nav": "1",
        accept: "text/html",
      },

      credentials: "same-origin",
    })
      .then(function (response) {
        if (inFlight !== token) {
          return null;
        }

        if (response.redirected && response.url) {
          url = response.url;
        }

        var contentType =
          response.headers.get("content-type") ||
          "";

        if (
          contentType.indexOf("text/html") === -1
        ) {
          hardNavigate(url);
          return null;
        }

        var contentType =
          response.headers.get("content-type") ||
          "";

        if (
          contentType.indexOf("text/html") === -1
        ) {
          hardNavigate(url);
          return null;
        }

        return response.text().then(function (text) {
          if (inFlight !== token) {
            return;
          }

          render(text, url, isPop);
        });
      })
      .catch(function () {
        if (inFlight === token) {
          hardNavigate(url);
        }
      });
  }

  document.addEventListener(
    "click",
    function (event) {
      if (
        event.defaultPrevented ||
        event.button !== 0 ||
        event.metaKey ||
        event.ctrlKey ||
        event.shiftKey ||
        event.altKey
      ) {
        return;
      }

      var anchor =
        event.target &&
        event.target.closest
          ? event.target.closest("a")
          : null;

      if (!isLocalLink(anchor)) {
        return;
      }

      if (anchor.href === location.href) {
        event.preventDefault();
        return;
      }

      event.preventDefault();

      navigate(anchor.href, false);
    },
    false,
  );

  window.addEventListener(
    "popstate",
    function () {
      navigate(location.href, true);
    },
  );

  /**
   * Programmatic navigation for forms, actions and application code.
   */
  window.__wrnexusNavigate = function (url) {
    navigate(
      new URL(url, location.href).href,
      false,
    );
  };
})();
`.trim();
