/**
 * Shared utilities for scrolling to block elements with expand node support.
 * Used by both Confluence's useScrollOnUrlChange and platform renderer's useScrollToBlock.
 */

import { expandElement } from './expandElement';
import { isExpandCollapsed } from './isExpandCollapsed';
import { SCROLL_TO_BLOCK_TIMING } from './SCROLL_TO_BLOCK_TIMING';
/**
 * 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
 */
export function findNodeWithExpandParents(adfDoc, targetLocalId) {
  let result;

  /**
   * Recursive helper to traverse the ADF document and track expand ancestors.
   * We use recursion to properly maintain the expand ancestor stack.
   */
  const 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 ? void 0 : (_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.localId) === targetLocalId) {
      result = {
        expandParentLocalIds: [...expandAncestors] // Copy the array
      };
      return false; // Stop traversing
    }

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

    // Traverse children if they exist
    if (node !== null && node !== void 0 && node.content && Array.isArray(node.content)) {
      for (const child of node.content) {
        if (result || !child) {
          break;
        }
        traverse(child, newExpandAncestors);
      }
    }
    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].
 */
export const findParentExpands = (element, maxDepth = SCROLL_TO_BLOCK_TIMING.MAX_EXPAND_DEPTH) => {
  const expands = [];
  let currentElement = element;
  let depth = 0;
  while (currentElement && depth < maxDepth) {
    // Look for expand container - handles both "expand" and "nestedExpand" types
    const 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();
};

/**
 * 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).
 */
export const expandAllParentsThenScroll = (element, attempt = 0, scrollFn) => {
  const {
    MAX_EXPAND_DEPTH,
    MAX_ATTEMPTS,
    DOM_UPDATE_DELAY,
    RETRY_DELAY
  } = SCROLL_TO_BLOCK_TIMING;
  const doScroll = scrollFn !== null && scrollFn !== void 0 ? scrollFn : el => el.scrollIntoView({
    behavior: 'smooth',
    block: 'center'
  });

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

  // Cleanup function that cancels pending timeout and any nested operations.
  const 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) {
      doScroll(element);
    }
    return cleanup;
  }
  try {
    // Step 1: Find all parent expands (outermost to innermost).
    const parentExpands = findParentExpands(element, MAX_EXPAND_DEPTH);

    // Step 2: Find any collapsed expands (filter out disconnected elements).
    const collapsedExpands = parentExpands.filter(expandContainer => expandContainer.isConnected && isExpandCollapsed(expandContainer));
    if (collapsedExpands.length === 0) {
      // All expands are open (or there are no expands), scroll to element.
      doScroll(element);
      return cleanup;
    }

    // Step 3: Expand ONLY the outermost collapsed expand first.
    // This is critical for nested expands - we must expand parent before child.
    const outermostCollapsed = collapsedExpands[0];
    try {
      const 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(() => {
          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, scrollFn);
          } catch {
            // Fallback to simple scroll on error.
            if (element.isConnected) {
              doScroll(element);
            }
          }
        }, DOM_UPDATE_DELAY);
      } else {
        // Failed to expand, retry with longer delay.
        timeoutId = setTimeout(() => {
          if (element.isConnected) {
            nestedCleanup = expandAllParentsThenScroll(element, attempt + 1, scrollFn);
          }
        }, RETRY_DELAY);
      }
    } catch {
      // Retry on error.
      timeoutId = setTimeout(() => {
        if (element.isConnected) {
          nestedCleanup = expandAllParentsThenScroll(element, attempt + 1, scrollFn);
        }
      }, RETRY_DELAY);
    }
  } catch {
    // Fallback to simple scroll on error.
    if (element.isConnected) {
      doScroll(element);
    }
  }
  return cleanup;
};
// eslint-disable-next-line @atlaskit/editor/no-re-export
export { SCROLL_TO_BLOCK_TIMING } from './SCROLL_TO_BLOCK_TIMING';
// eslint-disable-next-line @atlaskit/editor/no-re-export
export { isExpandCollapsed } from './isExpandCollapsed';
// eslint-disable-next-line @atlaskit/editor/no-re-export
export { expandElement } from './expandElement';
// eslint-disable-next-line @atlaskit/editor/no-re-export
export { getLocalIdSelector } from './getLocalIdSelector';