/**
 * Detects nodes that are part of circular dependency chains.
 *
 * Uses a depth-first search (DFS) algorithm with three-color marking to
 * identify cycles in the dependency graph. When a cycle is detected, all nodes
 * in that cycle are added to the result set.
 *
 * The algorithm tracks three states for each node:
 *
 * - Not visited: Node hasn't been processed yet
 * - Visiting: Currently in the DFS path (gray in three-color marking)
 * - Visited: Completely processed (black in three-color marking).
 *
 * A cycle is detected when we encounter a node that is already in the
 * "visiting" state, meaning we've found a back edge in the graph.
 *
 * @example
 *
 * ```ts
 * const nodes = [
 *   { name: 'A', dependencies: ['B'], dependencyNames: ['A'] },
 *   { name: 'B', dependencies: ['C'], dependencyNames: ['B'] },
 *   { name: 'C', dependencies: ['A'], dependencyNames: ['C'] },
 * ]
 * const circularNodes = computeNodesInCircularDependencies(nodes)
 * // Returns: Set containing all three nodes (A, B, C)
 * ```
 *
 * @template T - Type of sorting node with dependencies.
 * @param elements - Array of nodes with dependency information.
 * @returns Set of nodes that participate in circular dependencies.
 */
function computeNodesInCircularDependencies(elements) {
  let elementsInCycles = /* @__PURE__ */ new Set()
  let visitingElements = /* @__PURE__ */ new Set()
  let visitedElements = /* @__PURE__ */ new Set()
  /**
   * Performs depth-first search to detect cycles starting from the given
   * element.
   *
   * Recursively traverses the dependency graph, maintaining a path of the
   * current traversal. If a node in the current path is encountered again, a
   * cycle is detected and all nodes in the cycle are marked.
   *
   * @param element - Current node being visited.
   * @param path - Array of nodes in the current DFS path.
   */
  function depthFirstSearch(element, path) {
    if (visitedElements.has(element)) {
      return
    }
    if (visitingElements.has(element)) {
      let cycleStartIndex = path.indexOf(element)
      /* v8 ignore else -- @preserve Visiting path already contains the element when this branch executes. */
      if (cycleStartIndex !== -1) {
        for (let cycleElements of path.slice(cycleStartIndex)) {
          elementsInCycles.add(cycleElements)
        }
      }
      return
    }
    visitingElements.add(element)
    path.push(element)
    for (let dependency of element.dependencies) {
      let dependencyElement = elements
        .filter(currentElement => currentElement !== element)
        .find(currentElement =>
          currentElement.dependencyNames.includes(dependency),
        )
      /* v8 ignore next -- @preserve Dependencies are pre-filtered; missing entries are defensive fallback. */
      if (dependencyElement) {
        depthFirstSearch(dependencyElement, [...path])
      }
    }
    visitingElements.delete(element)
    visitedElements.add(element)
  }
  for (let element of elements) {
    if (!visitedElements.has(element)) {
      depthFirstSearch(element, [])
    }
  }
  return elementsInCycles
}
export { computeNodesInCircularDependencies }
