import { ExtensionManifest } from '../extension';
import { extensionFactory } from '../factory';
import ExtensionPotentialCircular from '../exceptions/extension-potential-circular';
import { Extension } from '../extension';
import { DependencyGraphOptions, Edge } from './extension-graph';

function getName(manifest: any) {
  return Reflect.getMetadata('harmony:name', manifest) || manifest.id || manifest.name;
}

/**
 * build vertices and edges from the given extension
 */
export function fromExtension(extension: ExtensionManifest, options: DependencyGraphOptions = {}) {
  const vertices: { [id: string]: Extension } = {};
  let edges: { sourceId: string; targetId: string; edge: Edge }[] = [];

  function iterate(root: ExtensionManifest) {
    const id = options.getName ? options.getName(root) : getName(root);
    if (vertices[id]) return;

    const instance = extensionFactory(root);
    const validDeps = instance.dependencies.filter(dep => dep).map(dep => extensionFactory(dep));
    if (instance.dependencies.length > validDeps.length) {
      throw new ExtensionPotentialCircular(instance, validDeps);
    }
    vertices[id] = instance;
    const newEdges = validDeps.map(dep => {
      return {
        sourceId: id,
        targetId: dep.name,
        edge: {
          type: 'dependency'
        }
      };
    });

    edges = edges.concat(newEdges);

    // @ts-ignore
    instance.dependencies.forEach(dep => iterate(dep));
  }

  iterate(extension);

  let vertexArray: { id: string; node: Extension }[] = [];
  for (let [key, value] of Object.entries(vertices)) {
    vertexArray.push({ id: key, node: value });
  }

  return {
    vertices: vertexArray,
    edges
  };
}

/**
 * build vertices and edges from the given list of extensions
 */
export function fromExtensions(extensions: ExtensionManifest[], options: DependencyGraphOptions = {}) {
  const perExtension = extensions.map(ext => fromExtension(ext, options));

  return perExtension.reduce(
    (acc, subgraph) => {
      acc.edges = acc.edges.concat(subgraph.edges);
      acc.vertices = acc.vertices.concat(subgraph.vertices);

      return acc;
    },
    { vertices: [], edges: [] }
  );
}
