import path from "path";
import fs from "fs";

import { chain, map, assign } from "lodash";
import * as t from "babel-types";
import template from "interlock/lib/util/template";
import initBundle from "interlock/lib/compile/bundles/init";


function getTemplate (templateName, transform) {
  transform = transform || (node => node);
  const absPath = path.join(__dirname, `../templates/${templateName}.jst`);
  const templateStr = fs.readFileSync(absPath, "utf-8")
    // Remove ESlint rule exclusions from parsed templates.
    .replace(/\s*\/\/\s*eslint-disable-line.*/g, "");
  const _template = template(templateStr);
  return opts => transform(_template(opts));
}

const overrideGetUrlTmpl = getTemplate("override-get-url", body => body);

function getSeedsByPath (definitions, moduleSeeds) {
  return chain(definitions)
    .map((def, relPath) => [ moduleSeeds[relPath].path, def ])
    .fromPairs()
    .value();
}

export default function (opts = {}) {
  const {
    baseUrl = "/"
  } = opts;

  return (override, transform) => {
    // Prepend `h2:` to each module's moduleId.  This disambiguates
    // H2 modules from standard modules, and signifies that the module
    // should be loaded using a non-standard mechanism, to
    transform("generateModuleId", module => {
      return assign({}, module, {
        id: `h2:${module.id}`
      });
    });

    // Generate a single "bundle" for each input module.  Make sure
    // to use the specified output filename if it was provided.
    override("partitionBundles", function (moduleSeeds, moduleMaps) {
      const entryModuleDefs = getSeedsByPath(this.opts.entry, moduleSeeds);
      const splitModuleDefs = getSeedsByPath(this.opts.split, moduleSeeds);

      const bundleSeedsPs = map(moduleMaps.byAbsPath, (module, absPath) => {
        const entryConfig = entryModuleDefs[absPath];
        const splitConfig = splitModuleDefs[absPath];

        return initBundle.call(this, {
          dest: entryConfig && entryConfig.dest ||
            splitConfig && splitConfig.dest,
          module,
          moduleHashes: [ module.hash ],
          isEntryPt: !!entryConfig,
          type: module.type
        });
      });

      return Promise.all(bundleSeedsPs);
    });

    // We'll be modifying the behavior of the default provider, such
    // that when a moduleId prefixed with `h2:` comes along, a URL
    // will be generated for it directly, rather than consulting the
    // moduleId-to-bundleUrl hash created here.
    override("getUrls", () => ({}));

    override("constructRegisterUrls", () => {
      return overrideGetUrlTmpl({
        BASE_URL: t.stringLiteral(baseUrl)
      });
    });
  };
}

@@ -67,16 +67,24 @@
 , assign
+, filter
  %7D from 
@@ -89,24 +89,24 @@
 m %22lodash%22;%0A
-
 import * as 
@@ -989,16 +989,89 @@
 rl = %22/%22
+,%0A    pushManifest = %22push-manifest.json%22,%0A    pushManifestPretty = false
 %0A  %7D = o
@@ -2725,16 +2725,16 @@
 aseUrl)%0A
-
       %7D)
@@ -2742,16 +2742,1229 @@
 %0A    %7D);
+%0A%0A    // Build a bundle dependency graph, such that when a JS bundle is%0A    // requested, the server can know what other files will be requested%0A    // once the bundle loads in the client browser.%0A    function buildPushManifest (bundles, manifestFilename) %7B%0A      const manifestableBundles = filter(bundles, bundle =%3E bundle.module);%0A%0A      const bundlesByModuleId = chain(manifestableBundles)%0A        .map(bundle =%3E %5B bundle.module.id, bundle %5D)%0A        .fromPairs()%0A        .value();%0A%0A      const bundleDepGraph = chain(manifestableBundles)%0A        .map(bundle =%3E %7B%0A          const deepDependencyFilenames = map(bundle.module.deepDependencies,%0A            dependency =%3E bundlesByModuleId%5Bdependency.id%5D.dest%0A          );%0A          return %5B bundle.dest, deepDependencyFilenames %5D;%0A        %7D)%0A        .fromPairs()%0A        .value();%0A%0A      const output = pushManifestPretty ?%0A        JSON.stringify(bundleDepGraph, null, 2) :%0A        JSON.stringify(bundleDepGraph);%0A%0A      return %7B%0A        raw: output,%0A        dest: manifestFilename%0A      %7D;%0A    %7D%0A%0A    transform(%22emitRawBundles%22, bundles =%3E %7B%0A      return pushManifest ?%0A        bundles.concat(buildPushManifest(bundles, pushManifest)) :%0A        bundles;%0A    %7D);
 %0A  %7D;%0A%7D%0A

Add push-manifest output to H2 plugin.