"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.expandElement = exports.expandAllParentsThenScroll = exports.SCROLL_TO_BLOCK_TIMING = void 0;
exports.findNodeWithExpandParents = findNodeWithExpandParents;
exports.isExpandCollapsed = exports.getLocalIdSelector = exports.findParentExpands = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
/**
 * Shared utilities for scrolling to block elements with expand node support.
 * Used by both Confluence's useScrollOnUrlChange and platform renderer's useScrollToBlock.
 */

/**
 * Timing constants for expand animation and DOM update delays.
 * These values are tuned for the expand component's behavior and React's update cycle.
 */
var SCROLL_TO_BLOCK_TIMING = exports.SCROLL_TO_BLOCK_TIMING = {
  /** Minimal delay for DOM/React updates after expanding (no animation in expand component). */
  DOM_UPDATE_DELAY: 50,
  /** Delay when expand operation fails and needs retry. */
  RETRY_DELAY: 100,
  /** Maximum number of retry attempts before giving up and scrolling anyway. */
  MAX_ATTEMPTS: 5,
  /** Maximum depth of nested expands to search (prevents infinite loops). */
  MAX_EXPAND_DEPTH: 2
};
/**
 * Find a node by its localId in an ADF document and determine if it has expand or nestedExpand parents.
 * This is used to identify nodes that may be hidden inside collapsed expands.
 *
 * @param adfDoc - The ADF document to search
 * @param targetLocalId - The localId to search for
 * @returns NodeWithExpandParents if found, undefined otherwise
 */
function findNodeWithExpandParents(adfDoc, targetLocalId) {
  var result;

  /**
   * Recursive helper to traverse the ADF document and track expand ancestors.
   * We use recursion to properly maintain the expand ancestor stack.
   */
  var _traverse = function traverse(node, expandAncestors) {
    var _node$attrs, _node$attrs2;
    // If we already found the target, stop traversing
    if (result) {
      return false;
    }

    // Check if this is the target node by localId
    if ((node === null || node === void 0 || (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.localId) === targetLocalId) {
      result = {
        expandParentLocalIds: (0, _toConsumableArray2.default)(expandAncestors) // Copy the array
      };
      return false; // Stop traversing
    }

    // Check if this node is an expand or nestedExpand
    var isExpand = (node === null || node === void 0 ? void 0 : node.type) === 'expand' || (node === null || node === void 0 ? void 0 : node.type) === 'nestedExpand';
    var newExpandAncestors = isExpand && node !== null && node !== void 0 && (_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 && _node$attrs2.localId ? [].concat((0, _toConsumableArray2.default)(expandAncestors), [node.attrs.localId]) : expandAncestors;

    // Traverse children if they exist
    if (node !== null && node !== void 0 && node.content && Array.isArray(node.content)) {
      var _iterator = _createForOfIteratorHelper(node.content),
        _step;
      try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
          var child = _step.value;
          if (result || !child) {
            break;
          }
          _traverse(child, newExpandAncestors);
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }
    }
    return !result; // Continue if we haven't found it yet
  };

  // Start traversal from the root
  _traverse(adfDoc, []);
  return result;
}

/**
 * Find all parent expand elements that contain the target element.
 * Searches up the DOM tree to find expand or nestedExpand containers.
 *
 * This function limits the search depth to prevent infinite loops and performance issues.
 * The default maxDepth of 2 is chosen because:
 * - Most use cases have 0-2 levels of nesting
 * - Searching deeper can impact performance
 * - Users rarely nest expands more than 2 levels deep
 *
 * @param element - The target element to find parents for.
 * @param maxDepth - Maximum depth to search (default: 2). This is the maximum number of
 *                   expand ancestors to find, starting from the target element.
 * @returns Array of expand containers ordered from outermost to innermost.
 *          For example, if element is inside expand B which is inside expand A,
 *          this returns [expandA, expandB].
 */
var findParentExpands = exports.findParentExpands = function findParentExpands(element) {
  var maxDepth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : SCROLL_TO_BLOCK_TIMING.MAX_EXPAND_DEPTH;
  var expands = [];
  var currentElement = element;
  var depth = 0;
  while (currentElement && depth < maxDepth) {
    // Look for expand container - handles both "expand" and "nestedExpand" types
    var expandContainer = currentElement.closest('[data-node-type="expand"], [data-node-type="nestedExpand"]');
    if (!expandContainer || expands.includes(expandContainer)) {
      break;
    }
    expands.push(expandContainer);
    currentElement = expandContainer.parentElement;
    depth++;
  }

  // Return in reverse order so we expand from outermost to innermost
  return expands.reverse();
};

/**
 * Check if an expand node is currently collapsed.
 *
 * Uses two methods to determine collapse state:
 * 1. First checks aria-expanded attribute on the toggle button (most reliable).
 * 2. Falls back to checking content div visibility via computed styles.
 *
 * @param expandContainer - The expand container element.
 * @returns True if the expand is collapsed, false if expanded or state cannot be determined.
 */
var isExpandCollapsed = exports.isExpandCollapsed = function isExpandCollapsed(expandContainer) {
  // Check for aria-expanded attribute on the toggle button
  var toggleButton = expandContainer.querySelector('[aria-expanded]');
  if (toggleButton && toggleButton instanceof HTMLElement) {
    return toggleButton.getAttribute('aria-expanded') === 'false';
  }

  // Fallback: check if content div is hidden using the actual class name
  var contentDiv = expandContainer.querySelector('.ak-editor-expand__content');
  if (contentDiv && contentDiv instanceof HTMLElement) {
    var computedStyle = window.getComputedStyle(contentDiv);
    return computedStyle.display === 'none' || computedStyle.visibility === 'hidden' || contentDiv.hidden;
  }
  return false;
};

/**
 * Expand a collapsed expand node by clicking its toggle button.
 *
 * This function finds the toggle button with aria-expanded="false" and programmatically
 * clicks it to expand the node. It does not wait for the expansion to complete.
 *
 * @param expandContainer - The expand container element.
 * @returns True if the toggle button was found and clicked, false otherwise.
 */
var expandElement = exports.expandElement = function expandElement(expandContainer) {
  // Find and click the toggle button
  var toggleButton = expandContainer.querySelector('[aria-expanded="false"]');
  if (toggleButton && toggleButton instanceof HTMLElement) {
    toggleButton.click();
    return true;
  }
  return false;
};

/**
 * Expand all parent expands then scroll to the element.
 *
 * This is the main entry point for scrolling to elements that may be hidden inside collapsed expands.
 * It handles nested expands by expanding them one at a time from outermost to innermost,
 * waiting for DOM updates between each expansion.
 *
 * The function uses a recursive approach with retry logic to handle:
 * - Nested expands that need sequential expansion
 * - DOM updates that may take time to reflect
 * - Failed expand operations (e.g., if toggle button is temporarily unavailable)
 * - Disconnected elements (removed from DOM)
 *
 * After all parent expands are open (or max attempts reached), scrolls the element into view
 * with smooth behavior and centered in the viewport.
 *
 * @param element - The target element to scroll to.
 * @param attempt - Current attempt number (used internally for retry logic, starts at 0).
 * @returns A cleanup function that cancels any pending timeouts. Call this when the operation
 *          should be aborted (e.g., component unmount, navigation, or new scroll request).
 */
var _expandAllParentsThenScroll = exports.expandAllParentsThenScroll = function expandAllParentsThenScroll(element) {
  var attempt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  var MAX_EXPAND_DEPTH = SCROLL_TO_BLOCK_TIMING.MAX_EXPAND_DEPTH,
    MAX_ATTEMPTS = SCROLL_TO_BLOCK_TIMING.MAX_ATTEMPTS,
    DOM_UPDATE_DELAY = SCROLL_TO_BLOCK_TIMING.DOM_UPDATE_DELAY,
    RETRY_DELAY = SCROLL_TO_BLOCK_TIMING.RETRY_DELAY;

  // Store timeout ID and nested cleanup function so they can be cancelled.
  var timeoutId = null;
  var nestedCleanup = null;

  // Cleanup function that cancels pending timeout and any nested operations.
  var cleanup = function cleanup() {
    if (timeoutId !== null) {
      clearTimeout(timeoutId);
      timeoutId = null;
    }
    if (nestedCleanup) {
      nestedCleanup();
      nestedCleanup = null;
    }
  };

  // Guard against element being disconnected from DOM or exceeding max attempts.
  if (attempt >= MAX_ATTEMPTS || !element.isConnected) {
    // Max attempts reached or element disconnected, scroll anyway.
    if (element.isConnected) {
      element.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    return cleanup;
  }
  try {
    // Step 1: Find all parent expands (outermost to innermost).
    var parentExpands = findParentExpands(element, MAX_EXPAND_DEPTH);

    // Step 2: Find any collapsed expands (filter out disconnected elements).
    var collapsedExpands = parentExpands.filter(function (expandContainer) {
      return expandContainer.isConnected && isExpandCollapsed(expandContainer);
    });
    if (collapsedExpands.length === 0) {
      // All expands are open (or there are no expands), scroll to element.
      element.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
      return cleanup;
    }

    // Step 3: Expand ONLY the outermost collapsed expand first.
    // This is critical for nested expands - we must expand parent before child.
    var outermostCollapsed = collapsedExpands[0];
    try {
      var expanded = expandElement(outermostCollapsed);
      if (expanded) {
        // Successfully expanded, wait briefly for DOM update then recurse to handle any nested expands.
        // Note: There's no animation, but we need a minimal delay for DOM/React updates.
        timeoutId = setTimeout(function () {
          try {
            // Verify element is still connected before proceeding.
            if (!element.isConnected) {
              return;
            }

            // Recurse to handle any nested collapsed expands or retry if still collapsed.
            nestedCleanup = _expandAllParentsThenScroll(element, attempt + 1);
          } catch (_unused) {
            // Fallback to simple scroll on error.
            if (element.isConnected) {
              element.scrollIntoView({
                behavior: 'smooth',
                block: 'center'
              });
            }
          }
        }, DOM_UPDATE_DELAY);
      } else {
        // Failed to expand, retry with longer delay.
        timeoutId = setTimeout(function () {
          if (element.isConnected) {
            nestedCleanup = _expandAllParentsThenScroll(element, attempt + 1);
          }
        }, RETRY_DELAY);
      }
    } catch (_unused2) {
      // Retry on error.
      timeoutId = setTimeout(function () {
        if (element.isConnected) {
          nestedCleanup = _expandAllParentsThenScroll(element, attempt + 1);
        }
      }, RETRY_DELAY);
    }
  } catch (_unused3) {
    // Fallback to simple scroll on error.
    if (element.isConnected) {
      element.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
  }
  return cleanup;
};
var getLocalIdSelector = exports.getLocalIdSelector = function getLocalIdSelector(localId, container) {
  // Check if the element with data-local-id exists
  var element = container.querySelector("[data-local-id=\"".concat(localId, "\"]"));
  if (element) {
    return element;
  }

  // Special case for decision lists and task lists which already have localId
  element = container.querySelector("[data-decision-list-local-id=\"".concat(localId, "\"]"));
  if (element) {
    return element;
  }
  element = container.querySelector("[data-task-list-local-id=\"".concat(localId, "\"]"));
  if (element) {
    return element;
  }

  // Special case for tables which use data-table-local-id
  element = container.querySelector("[data-table-local-id=\"".concat(localId, "\"]"));
  if (element) {
    return element;
  }

  // Special case for extension, smart cards and media which use lowercase localid
  element = container.querySelector("[localid=\"".concat(localId, "\"]"));
  if (element) {
    return element;
  }
  return null;
};