const fs = require("fs-extra");
const path = require("path");
const { replaceImports } = require("./replaceImports");
const { tryReadJSON, tryReadFile } = require("./utils");

function deepMerge(o1, o2) {
  Object.keys(o2).forEach((key) => {
    if (
      Object.keys(o1).includes(key) &&
      typeof o1[key] === typeof o2[key] &&
      typeof o1[key] === "object"
    ) {
      const ia1 = Array.isArray(o1[key]);
      const ia2 = Array.isArray(o2[key]);
      if (ia1 && ia2) {
        o1[key].push(...o2[key]);
      } else if (!ia1 && !ia2) {
        Object.assign(o1[key], o2[key]);
      } else {
        o1[key] = { ...o1[key], ...o2[key] };
      }
      return;
    }
    o1[key] = o2[key];
    return;
  });
}

// This function makes sure that the plugin have all (dev)dependencies fixed to a version.
function checkPluginDependenies({manifest, buildOutputPath, log}) {
  const {
    absolutePath,
    manifest: {
      integrate_package_file_dependencies,
      integrate_package_file_dev_dependencies,
    },
  } = manifest;

  log(`checking dependencies for plugin from path: ${absolutePath}`);

  // it was already assured that sulu contains a package.json
  if (
    integrate_package_file_dependencies ||
    integrate_package_file_dev_dependencies
  ) {
    if (!fs.existsSync(path.join(absolutePath, "package.json"))) {
      throw new Error(
        "integrate_package_file_dependencies flag set, but package.json not found"
      );
    }
    const builtJSONPath = path.join(buildOutputPath, "package.json");
    const existingJson = tryReadJSON(builtJSONPath);
    const pluginJson = fs.readJSONSync(path.join(absolutePath, "package.json"));
    if (pluginJson.scripts) {
      Object.assign(existingJson.scripts, pluginJson.scripts);
    }
    if (pluginJson.dependencies) {
      const not_fixed = {};
      Object.keys(pluginJson.dependencies).forEach((key) => {
        if(! String(pluginJson.dependencies[key]).match(/^[=0-9]/))
          not_fixed[key] = pluginJson.dependencies[key];
      });
      if (Object.keys(not_fixed).length)
        throw new Error(
          "the following package.json dependencies do not have fixed versions. Please fix!" + JSON.stringify(not_fixed)
        );
    }
    if (pluginJson.devDependencies) {
      const not_fixed_versions = Object.keys(pluginJson.devDependencies).filter((key) => {
        ! String(pluginJson.devDependencies[key]).match(/^[=0-9]/);
      });
      if (not_fixed_versions.length)
        throw new Error(
          "the following package.json devDependencies do not have fixed versions. Please fix!" + not_fixed_versions.toString()
        );
    }
  }
}

function transferPlugin({
  manifest,
  buildOutputPath,
  log,
  buildManifest,
  dotenv = [],
  revision = 0,
}) {
  const {
    absolutePath,
    manifest: {
      pattern,
      excludePattern,
      integrate_package_file_dependencies,
      integrate_package_file_dev_dependencies,
    },
  } = manifest;
  const pluginName = absolutePath.includes("/") ? absolutePath.split("/").pop() : absolutePath.split("\\").pop();

  log(`Merging plugin from path: ${absolutePath}`);

  // it was already assured that sulu contains a package.json and that they are valid
  if (
    integrate_package_file_dependencies ||
    integrate_package_file_dev_dependencies
  ) {
    const builtJSONPath = path.join(buildOutputPath, "package.json");
    const existingJson = tryReadJSON(builtJSONPath);
    const pluginJson = fs.readJSONSync(path.join(absolutePath, "package.json"));
    if (pluginJson.scripts) {
      Object.assign(existingJson.scripts, pluginJson.scripts);
    }
    deepMerge(pluginJson, existingJson);
    fs.writeJSONSync(builtJSONPath, pluginJson, {
      replacer: null,
      spaces: 2,
    });
  }

  const copiedFiles = [];

  fs.copySync(absolutePath, buildOutputPath, {
    recursive: true,
    dereference: true,
    errorOnExist: false,
    overwrite: true,
    filter: (src, dest) => {
      // do not let anything in node_modules, .git
      if (["node_modules", ".git", "tsconfig"].find((p) => src.includes(p))) {
        return false;
      }

      const { mtime } = fs.lstatSync(src);
      const fnameRel = src.replace(absolutePath, "");

      // as directory is walked one file at a time, let directories through always.
      if (fs.lstatSync(src).isDirectory()) {
        return true;
      }

      // is not a critical file
      const f1 = ![
        "package.json",
        "suluPluginManifest.json",
        "yarn.lock",
        "yarn-error.log",
        ".env",
      ].find((p) => src.includes(p));
      // is matched by pattern
      const f2 = !!new RegExp(pattern).exec(src);
      // is not matched by exclusion pattern
      const f3 = !new RegExp(excludePattern).exec(src);
      // if either:
      // - not in manifest
      // - and:
      //   - in manifest
      //   - came from the same plugin
      //   - timestamp newer than what it was when the file was created
      // - and:
      //   - in manifest
      //   - came from core repository
      const bmanifest = buildManifest[fnameRel];
      const f4 =
        !bmanifest ||
        bmanifest.by === "sulu" ||
        (bmanifest.by === pluginName &&
          bmanifest.sourceModified.valueOf() < new Date(mtime).valueOf());

      const shouldCopy = f1 && f2 && f3 && f4;
      /*       if (src.includes("overriden-file")) {
        console.log(src, dest, shouldCopy, fs.readFileSync(src).toString());
      } */

      if (shouldCopy) {
        buildManifest[fnameRel] = {
          by: pluginName,
          sourceModified: new Date(mtime),
          revision,
        };
      }
      if (buildManifest[fnameRel]) {
        buildManifest[fnameRel].stillExists = true;
      }
      // store js/ts files in list to later convert the imports therein
      if (shouldCopy && dest.match(/.*\.(js|jsx|ts|tsx)/)) {
        copiedFiles.push(dest);
      }
      return shouldCopy;
    },
  });

  dotenv.push(
    ...tryReadFile(path.join(absolutePath, ".env"))
      .split("\n")
      .filter((x) => !!x)
  );

  copiedFiles.forEach((src) => replaceImports({ src }));
}

module.exports = {
  checkPluginDependenies,
  transferPlugin,
};
