const _ = require(`lodash`);
const path = require(`path`);

/**
 * @typedef {Object} TraversePackagesDepsReturn
 * @property {Object} depTree Lookup table to check dependants for given package.
 * Used to determine which packages need to be published.
 */

/**
 * Compile final list of packages to watch
 * This will include packages explicitly defined packages and all their dependencies
 * Also creates dependency graph that is used later to determine which packages
 * would need to be published when their dependencies change
 * @param {Object} $0
 * @param {String} $0.root Path to root of medusa monorepo repository
 * @param {String[]} $0.packages Initial array of packages to watch
 * This can be extracted from project dependencies or explicitly set by `--packages` flag
 * @param {String[]} $0.monoRepoPackages Array of packages in medusa monorepo
 * @param {String[]} [$0.seenPackages] Array of packages that were already traversed.
 * This makes sure dependencies are extracted one time for each package and avoid any
 * infinite loops.
 * @param {DepTree} [$0.depTree] Used internally to recursively construct dependency graph.
 * @return {TraversePackagesDepsReturn}
 */
const traversePackagesDeps = ({
  root,
  packages,
  monoRepoPackages,
  seenPackages = [...packages],
  depTree = {},
}) => {
  packages.forEach((p) => {
    if (p.startsWith("@medusajs")) {
      p = p.split("/")[1];
    }
    let pkgJson;
    try {
      pkgJson = require(path.join(root, `packages`, p, `package.json`));
    } catch {
      console.error(`"${p}" package doesn't exist in monorepo.`);
      // remove from seenPackages
      seenPackages = seenPackages.filter((seenPkg) => seenPkg !== p);
      return;
    }

    const fromMonoRepo = _.intersection(
      Object.keys({ ...pkgJson.dependencies }),
      monoRepoPackages
    );

    fromMonoRepo.forEach((pkgName) => {
      depTree[pkgName] = (depTree[pkgName] || new Set()).add(p);
    });

    // only traverse not yet seen packages to avoid infinite loops
    const newPackages = _.difference(fromMonoRepo, seenPackages);

    if (newPackages.length) {
      newPackages.forEach((depFromMonorepo) => {
        seenPackages.push(depFromMonorepo);
      });

      traversePackagesDeps({
        root,
        packages: fromMonoRepo,
        monoRepoPackages,
        seenPackages,
        depTree,
      });
    }
  });
  return { seenPackages, depTree };
};

exports.traversePackagesDeps = traversePackagesDeps;
