import { ReferenceKind } from '../enums.js';
export var NodeState;
(function (NodeState) {
  NodeState[(NodeState['NOT_VISITED'] = 0)] = 'NOT_VISITED';
  NodeState[(NodeState['IN_PROGRESS'] = 1)] = 'IN_PROGRESS';
  NodeState[(NodeState['VISITED'] = 2)] = 'VISITED';
})(NodeState || (NodeState = {}));
/**
 * CommitOrderCalculator implements topological sorting, which is an ordering
 * algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by
 * using a depth-first searching (DFS) to traverse the graph built in memory.
 * This algorithm have a linear running time based on nodes (V) and dependency
 * between the nodes (E), resulting in a computational complexity of O(V + E).
 *
 * Based on https://github.com/doctrine/orm/blob/master/lib/Doctrine/ORM/Internal/CommitOrderCalculator.php
 * @internal
 */
export class CommitOrderCalculator {
  /** Matrix of nodes, keys are provided hashes and values are the node definition objects. */
  #nodes = new Map();
  /** Volatile variable holding calculated nodes during sorting process. */
  #sortedNodeList = [];
  /**
   * Checks for node existence in graph.
   */
  hasNode(hash) {
    return this.#nodes.has(hash);
  }
  /**
   * Adds a new node to the graph, assigning its hash.
   */
  addNode(hash) {
    this.#nodes.set(hash, { hash, state: 0 /* NodeState.NOT_VISITED */, dependencies: new Map() });
  }
  /**
   * Adds a new dependency (edge) to the graph using their hashes.
   */
  addDependency(from, to, weight) {
    this.#nodes.get(from).dependencies.set(to, { from, to, weight });
  }
  discoverProperty(prop, entityName) {
    const toOneOwner =
      (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner) || prop.kind === ReferenceKind.MANY_TO_ONE;
    const toManyOwner = prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !prop.pivotEntity;
    if (!toOneOwner && !toManyOwner) {
      return;
    }
    const propertyType = prop.targetMeta?.root._id;
    if (propertyType == null || !this.hasNode(propertyType)) {
      return;
    }
    this.addDependency(propertyType, entityName, prop.nullable || prop.persist === false ? 0 : 1);
  }
  /**
   * Return a valid order list of all current nodes.
   * The desired topological sorting is the reverse post order of these searches.
   *
   * @internal Highly performance-sensitive method.
   */
  sort() {
    for (const vertex of this.#nodes.values()) {
      if (vertex.state !== 0 /* NodeState.NOT_VISITED */) {
        continue;
      }
      this.visit(vertex);
    }
    const sortedList = this.#sortedNodeList.reverse();
    this.#nodes = new Map();
    this.#sortedNodeList = [];
    return sortedList;
  }
  /**
   * Visit a given node definition for reordering.
   *
   * @internal Highly performance-sensitive method.
   */
  visit(node) {
    node.state = 1 /* NodeState.IN_PROGRESS */;
    for (const edge of node.dependencies.values()) {
      const target = this.#nodes.get(edge.to);
      switch (target.state) {
        case 2 /* NodeState.VISITED */:
          break; // Do nothing, since node was already visited
        case 1 /* NodeState.IN_PROGRESS */:
          this.visitOpenNode(node, target, edge);
          break;
        case 0 /* NodeState.NOT_VISITED */:
          this.visit(target);
      }
    }
    if (node.state !== 2 /* NodeState.VISITED */) {
      node.state = 2 /* NodeState.VISITED */;
      this.#sortedNodeList.push(node.hash);
    }
  }
  /**
   * Visits all target's dependencies if in cycle with given node
   */
  visitOpenNode(node, target, edge) {
    if (!target.dependencies.has(node.hash) || target.dependencies.get(node.hash).weight >= edge.weight) {
      return;
    }
    for (const edge of target.dependencies.values()) {
      const targetNode = this.#nodes.get(edge.to);
      if (targetNode.state === 0 /* NodeState.NOT_VISITED */) {
        this.visit(targetNode);
      }
    }
    target.state = 2 /* NodeState.VISITED */;
    this.#sortedNodeList.push(target.hash);
  }
}
