import assert from "assert";
import { readFileSync as fsReadFileSync } from "fs";

import { Meteor } from "meteor/meteor";
import { WebAppInternals } from "meteor/webapp";

import {
  methodNameStats,
  packageName,
  typeBundle,
  typeNodeModules,
  typePackage,
} from "./common.js";

if (Meteor.isProduction) {
  console.warn([
    `=> The "${packageName}" package is currently enabled. Visit your`,
    "application in a web browser to view the client bundle analysis and",
    "'meteor remove' the package before building/deploying the final bundle.",
  ].join(" "));
} else {
  console.warn([
    "=> In order to provide accurate measurements using minified bundles,",
    `the "${packageName}" package requires running 'meteor --production'`,
    "to simulate production bundling."
  ].join(" "));
}

function getStatBundles() {
  const statFileFilter = f =>
    f.type === "json" &&
    f.absolutePath &&
    f.absolutePath.endsWith(".stats.json");

  // Read the stat file, but if it's in any way unusable just return null.
  const readOrNull = file => {
    try {
      return JSON.parse(fsReadFileSync(file, "utf8"));
    } catch (err) {
      return null;
    }
  };

  return Object.keys(WebAppInternals.staticFiles)
    .map(staticFile => WebAppInternals.staticFiles[staticFile])
    .filter(statFileFilter)
    .map(statFile => ({
      name: statFile.hash,
      stats: readOrNull(statFile.absolutePath),
    }));
}

function _childModules(node) {
  return Object.keys(node)
    .map(module => {
      const result = {
        name: module,
        type: typeNodeModules,
      };

      if (typeof node[module] === "object") {
        result.children = _childModules(node[module]);
      } else {
        result.size = node[module];
      }

      return result;
    });
}

function d3TreeFromStats(stats) {
  assert.strictEqual(typeof stats, "object",
    "Must pass a stats object");
  assert.strictEqual(typeof stats.minifiedBytesByPackage, "object",
    "Stats object must contain a `minifiedBytesByPackage` object");

  const sizeOrDetail = (name, node) => {
    const result = {
      name,
      type: typePackage,
    };

    // A non-leaf is: [size (Number), limb (Object)]
    // A leaf is size (Number)
    if (Array.isArray(node)) {
      const [, detail] = node;
      result.children = _childModules(detail);
    } else {
      result.size = node;
    }

    return result;
  };

  // Main entry into the stats is the `minifiedBytesByPackage` attribute.
  return Object.keys(stats.minifiedBytesByPackage)
    .map(name =>
      sizeOrDetail(name
        // Change the "packages/bundle.js" name to "(bundle)"
        .replace(/^[^\/]+\/(.*)\.js$/, "($1)"),
          stats.minifiedBytesByPackage[name]));
}

Meteor.methods({
  [methodNameStats]() {
    const statBundles = getStatBundles();

    // Silently return no data if not simulating production.
    if (! Meteor.isProduction) {
      return null;
    }

    if (! (statBundles && statBundles.length)) {
      throw new Meteor.Error("no-stats-bundles", "Unable to retrieve stats");
    }

    const validStatBundles = statBundles.filter(statBundle => {
      if (statBundle &&
        statBundle.stats &&
        statBundle.stats.minifier &&
        statBundle.stats.minifier.name === "standard-minifier-js" &&
        statBundle.stats.minifier.version.startsWith("2.1.")
      ) {
        return true;
      }
    });

    if (! validStatBundles.length) {
      throw new Meteor.Error("no-valid-stats", "No valid stats bundles")
    }

    return {
      name: "main",
      children: validStatBundles.map((statBundle, index, array) => ({
        // TODO: If multiple bundles, could
        // show abbr. bundle names with:
        //   `...${bundle.name.substr(-3)}`,
        name: "bundle" + (array.length > 1 ? ` (${index + 1})` : ""),
        type: typeBundle,
        children: d3TreeFromStats(statBundle.stats),
      })),
    };
  }
});

@@ -3073,456 +3073,8 @@
  %7D%0A%0A
-    const validStatBundles = statBundles.filter(statBundle =%3E %7B%0A      if (statBundle &&%0A        statBundle.stats &&%0A        statBundle.stats.minifier &&%0A        statBundle.stats.minifier.name === %22standard-minifier-js%22 &&%0A        statBundle.stats.minifier.version.startsWith(%222.1.%22)%0A      ) %7B%0A        return true;%0A      %7D%0A    %7D);%0A%0A    if (! validStatBundles.length) %7B%0A      throw new Meteor.Error(%22no-valid-stats%22, %22No valid stats bundles%22)%0A    %7D%0A%0A
     
@@ -3122,14 +3122,9 @@
 en: 
-validS
+s
 tatB

Remove check for standard-minifier-js@2.1.x from `bundle-visualizer`.