(function (root, factory) {
  "use strict";

  const api = factory();
  if (typeof module === "object" && module.exports) {
    module.exports = api;
  } else {
    root.LegacyModalFocus = api;
  }
}(typeof globalThis !== "undefined" ? globalThis : this, function () {
  "use strict";

  const defaultModalSelector = "[role='dialog'], [role='alertdialog']";
  const focusableSelector = [
    "a[href]",
    "area[href]",
    "button",
    "input",
    "select",
    "textarea",
    "iframe",
    "object",
    "embed",
    "summary",
    "audio[controls]",
    "video[controls]",
    "[contenteditable]",
    "[tabindex]"
  ].join(",");

  function attribute(element, name) {
    return element && typeof element.getAttribute === "function"
      ? element.getAttribute(name) : null;
  }

  function hasAttribute(element, name) {
    return !!element && typeof element.hasAttribute === "function" &&
      element.hasAttribute(name);
  }

  function computedStyle(element, documentObject) {
    const view = documentObject && documentObject.defaultView;
    if (!view || typeof view.getComputedStyle !== "function") return null;
    try {
      return view.getComputedStyle(element);
    } catch (_) {
      return null;
    }
  }

  function isVisible(element, documentObject) {
    if (!element) return false;
    const documentRef = documentObject || element.ownerDocument;
    if (documentRef && typeof documentRef.contains === "function" &&
        !documentRef.contains(element)) return false;

    for (let current = element; current; current = current.parentElement) {
      if (current.hidden === true || current.inert === true ||
          hasAttribute(current, "hidden") || hasAttribute(current, "inert") ||
          attribute(current, "aria-hidden") === "true") return false;
      const style = computedStyle(current, documentRef);
      if (style && (style.display === "none" || style.visibility === "hidden" ||
          style.visibility === "collapse" || style.contentVisibility === "hidden")) {
        return false;
      }
    }
    return true;
  }

  function isNativeFocusable(element) {
    const tagName = String(element && element.tagName || "").toLowerCase();
    if (["button", "select", "textarea", "iframe", "object", "embed", "summary"]
        .includes(tagName)) return true;
    if (tagName === "input") return String(attribute(element, "type") || "").toLowerCase() !== "hidden";
    if (tagName === "a" || tagName === "area") return hasAttribute(element, "href");
    if (tagName === "audio" || tagName === "video") return hasAttribute(element, "controls");
    return hasAttribute(element, "contenteditable") &&
      attribute(element, "contenteditable") !== "false";
  }

  function isFocusable(element, documentObject) {
    if (!element || typeof element.focus !== "function" ||
        !isVisible(element, documentObject)) return false;
    if (element.disabled === true || hasAttribute(element, "disabled") ||
        attribute(element, "aria-disabled") === "true") return false;
    if (typeof element.matches === "function") {
      try {
        if (element.matches(":disabled")) return false;
      } catch (_) {
        // Minimal WebView test doubles do not have to implement selector matching.
      }
    }

    const tabIndexAttribute = attribute(element, "tabindex");
    if (tabIndexAttribute !== null && Number(tabIndexAttribute) < 0) return false;
    return isNativeFocusable(element) ||
      (tabIndexAttribute !== null && Number(tabIndexAttribute) >= 0);
  }

  function getFocusableElements(modal, documentObject) {
    if (!modal || typeof modal.querySelectorAll !== "function") return [];
    return Array.from(modal.querySelectorAll(focusableSelector)).filter(function (element) {
      return isFocusable(element, documentObject || modal.ownerDocument);
    });
  }

  function findVisibleModals(documentObject, selector) {
    if (!documentObject || typeof documentObject.querySelectorAll !== "function") return [];
    return Array.from(documentObject.querySelectorAll(selector || defaultModalSelector))
      .filter(function (modal) { return isVisible(modal, documentObject); });
  }

  function findVisibleModal(documentObject, selector) {
    const modals = findVisibleModals(documentObject, selector);
    return modals.length > 0 ? modals[modals.length - 1] : null;
  }

  function contains(container, element) {
    return !!container && !!element && typeof container.contains === "function" &&
      container.contains(element);
  }

  function focus(element) {
    if (!element || typeof element.focus !== "function") return false;
    try {
      element.focus({ preventScroll: true });
    } catch (_) {
      try {
        element.focus();
      } catch (_) {
        return false;
      }
    }
    return true;
  }

  function resolveFocusTarget(target) {
    let resolved = target;
    const visited = new Set();
    while (typeof resolved === "function") {
      if (visited.has(resolved)) return null;
      visited.add(resolved);
      try {
        resolved = resolved();
      } catch (_) {
        return null;
      }
    }
    return resolved || null;
  }

  function preferredElement(modal, preferred, documentObject) {
    let candidate = preferred;
    if (typeof candidate === "function") candidate = candidate(modal);
    if (typeof candidate === "string" && typeof modal.querySelector === "function") {
      candidate = modal.querySelector(candidate);
    }
    if (isFocusable(candidate, documentObject) && contains(modal, candidate)) return candidate;

    if (typeof modal.querySelector === "function") {
      candidate = modal.querySelector("[data-modal-initial-focus], [autofocus]");
      if (isFocusable(candidate, documentObject) && contains(modal, candidate)) return candidate;
    }
    const focusable = getFocusableElements(modal, documentObject);
    return focusable.length > 0 ? focusable[0] : null;
  }

  function create(options) {
    const settings = options || {};
    const documentObject = settings.document ||
      (typeof document !== "undefined" ? document : null);
    if (!documentObject) throw new Error("LegacyModalFocus requires a document.");

    const selector = settings.modalSelector || defaultModalSelector;
    const entries = new Map();
    let activeModal = null;
    let observer = null;
    let started = false;

    function entryFor(modal, opener) {
      let entry = entries.get(modal);
      if (!entry) {
        entry = { opener: opener || null, lastFocused: null };
        entries.set(modal, entry);
      }
      return entry;
    }

    function rememberOpener(modal, opener) {
      if (!modal) return null;
      const entry = entryFor(modal, opener || documentObject.activeElement);
      if (!entry.opener) entry.opener = opener || documentObject.activeElement || null;
      return entry.opener;
    }

    function modalContaining(element) {
      for (const modal of entries.keys()) {
        if (contains(modal, element)) return modal;
      }
      return null;
    }

    function unwindReturnTarget(entry, visibleSet) {
      let target = resolveFocusTarget(entry && entry.opener);
      const visited = new Set();
      while (target) {
        const owner = modalContaining(target);
        if (!owner || visibleSet.has(owner)) break;
        if (visited.has(owner)) return null;
        visited.add(owner);
        const ownerEntry = entries.get(owner);
        target = resolveFocusTarget(ownerEntry && ownerEntry.opener);
      }
      return isFocusable(target, documentObject) ? target : null;
    }

    function cleanClosedEntries(visibleSet) {
      for (const modal of Array.from(entries.keys())) {
        if (!visibleSet.has(modal)) entries.delete(modal);
      }
    }

    function sync(preferredFocus) {
      const visible = findVisibleModals(documentObject, selector);
      const visibleSet = new Set(visible);
      const nextModal = visible.length > 0 ? visible[visible.length - 1] : null;
      const previousModal = activeModal;
      const previousEntry = previousModal && entries.get(previousModal);

      if (previousModal && previousEntry && contains(previousModal, documentObject.activeElement)) {
        previousEntry.lastFocused = documentObject.activeElement;
      }

      if (nextModal !== previousModal) {
        if (nextModal) {
          const opener = documentObject.activeElement;
          const nextEntry = entryFor(nextModal, contains(nextModal, opener) ? null : opener);
          activeModal = nextModal;

          if (previousModal && !visibleSet.has(previousModal)) {
            const returned = unwindReturnTarget(previousEntry, visibleSet);
            if (returned && contains(nextModal, returned)) {
              nextEntry.lastFocused = returned;
              focus(returned);
            } else {
              focus(nextEntry.lastFocused && isFocusable(nextEntry.lastFocused, documentObject)
                ? nextEntry.lastFocused
                : preferredElement(nextModal, preferredFocus || settings.preferredFocus,
                  documentObject));
            }
          } else {
            focus(preferredElement(nextModal, preferredFocus || settings.preferredFocus,
              documentObject));
          }
        } else {
          activeModal = null;
          focus(unwindReturnTarget(previousEntry, visibleSet));
        }
      } else if (nextModal && preferredFocus &&
          !contains(nextModal, documentObject.activeElement)) {
        focus(preferredElement(nextModal, preferredFocus, documentObject));
      }

      cleanClosedEntries(visibleSet);
      return activeModal;
    }

    function onFocusIn(event) {
      const modal = activeModal || findVisibleModal(documentObject, selector);
      if (!modal || !contains(modal, event.target)) return;
      entryFor(modal, null).lastFocused = event.target;
    }

    function handleKeydown(event) {
      if (!event || event.defaultPrevented || event.key !== "Tab") return false;
      const modal = activeModal || sync();
      if (!modal) return false;
      const focusable = getFocusableElements(modal, documentObject);
      if (focusable.length === 0) {
        if (typeof event.preventDefault === "function") event.preventDefault();
        if (!hasAttribute(modal, "tabindex") && typeof modal.setAttribute === "function") {
          modal.setAttribute("tabindex", "-1");
        }
        focus(modal);
        return true;
      }

      const current = documentObject.activeElement;
      const index = focusable.indexOf(current);
      let target = null;
      if (event.shiftKey === true && index <= 0) {
        target = focusable[focusable.length - 1];
      } else if (event.shiftKey !== true &&
          (index < 0 || index === focusable.length - 1)) {
        target = focusable[0];
      }
      if (!target) return false;
      if (typeof event.preventDefault === "function") event.preventDefault();
      focus(target);
      return true;
    }

    function start() {
      if (started) return manager;
      started = true;
      documentObject.addEventListener("keydown", handleKeydown, true);
      documentObject.addEventListener("focusin", onFocusIn, true);
      const Observer = settings.MutationObserver ||
        (documentObject.defaultView && documentObject.defaultView.MutationObserver);
      if (typeof Observer === "function") {
        observer = new Observer(function () { sync(); });
        observer.observe(documentObject.body || documentObject.documentElement, {
          attributes: true,
          attributeFilter: ["hidden", "aria-hidden", "inert", "style", "class"],
          childList: true,
          subtree: true
        });
      }
      sync();
      return manager;
    }

    function stop() {
      if (!started) return;
      started = false;
      documentObject.removeEventListener("keydown", handleKeydown, true);
      documentObject.removeEventListener("focusin", onFocusIn, true);
      if (observer) observer.disconnect();
      observer = null;
    }

    const manager = {
      getActiveModal: function () { return activeModal; },
      getFocusableElements: function (modal) {
        return getFocusableElements(modal || activeModal, documentObject);
      },
      handleKeydown: handleKeydown,
      rememberOpener: rememberOpener,
      start: start,
      stop: stop,
      sync: sync
    };
    return manager;
  }

  return {
    create: create,
    findVisibleModal: findVisibleModal,
    findVisibleModals: findVisibleModals,
    getFocusableElements: getFocusableElements,
    isFocusable: isFocusable,
    isVisible: isVisible
  };
}));
