import { DefaultDependencyKind, DependencyKind, Package } from '../Packages';
import PackagesGraphEdge from './PackagesGraphEdge';

/**
 * A graph node that refers to the single package.
 */
export default class PackagesGraphNode {
  /**
   * The package represented by the node.
   */
  pkg: Package;

  /**
   * The package name.
   */
  name: string;

  /**
   * Indicates how deep the node is placed in the graph.
   * Depth of nodes without incoming edges is equal to `0`.
   */
  depth: number = 0;

  /**
   * Edges connecting this node with its dependencies.
   */
  outgoingEdges: PackagesGraphEdge[] = [];

  /**
   * Edges connecting this node with its dependents.
   */
  incomingEdges: PackagesGraphEdge[] = [];

  constructor(pkg: Package) {
    this.pkg = pkg;
    this.name = pkg.packageName;
  }

  getOutgoingEdgeForNode(node: PackagesGraphNode): PackagesGraphEdge | null {
    return this.outgoingEdges.find((edge) => edge.destination === node) ?? null;
  }

  getIncomingEdgeForNode(node: PackagesGraphNode): PackagesGraphEdge | null {
    return this.incomingEdges.find((edge) => edge.origin === node) ?? null;
  }

  getAllDependentEdges(kinds: DependencyKind[] = DefaultDependencyKind): PackagesGraphEdge[] {
    const allDependentEdges = this.incomingEdges
      .map((edge) => {
        if (!edge.isCyclic && kinds.includes(edge.getDominantKind())) {
          return [edge, ...edge.origin.getAllDependentEdges(kinds)];
        }
        return [];
      })
      .flat();

    return [...new Set(allDependentEdges)];
  }

  getAllDependents(kinds: DependencyKind[] = DefaultDependencyKind): PackagesGraphNode[] {
    return [...new Set(this.getAllDependentEdges(kinds).map((edge) => edge.origin))];
  }

  getAllDependencyEdges(kinds: DependencyKind[] = DefaultDependencyKind): PackagesGraphEdge[] {
    const allDependencyEdges = this.outgoingEdges
      .map((edge) => {
        if (!edge.isCyclic && kinds.includes(edge.getDominantKind())) {
          return [edge, ...edge.destination.getAllDependencyEdges(kinds)];
        }
        return [];
      })
      .flat();

    return [...new Set(allDependencyEdges)];
  }

  getAllDependencies(kinds: DependencyKind[] = DefaultDependencyKind): PackagesGraphNode[] {
    return [...new Set(this.getAllDependencyEdges(kinds).map((edge) => edge.destination))];
  }

  getOutgoingEdgesOfKinds(kinds: DependencyKind[]): PackagesGraphEdge[] {
    return this.outgoingEdges.filter((edge) => {
      return kinds.some((kind) => edge.isOfKind(kind));
    });
  }

  isDependentOf(node: PackagesGraphNode): boolean {
    return !!this.getOutgoingEdgeForNode(node);
  }

  isDependencyOf(node: PackagesGraphNode): boolean {
    return !!this.getIncomingEdgeForNode(node);
  }
}
