// @flow strict-local

/*::
import type {
  Asset,
  Bundle,
  BundleGroup,
  MutableBundleGraph,
  PluginOptions,
} from "@parcel/types";
import type { SchemaEntity } from "@parcel/utils";
*/

const invariant = require('assert');
const {Bundler} = require('@parcel/plugin');
const {loadConfig, validateSchema} = require('@parcel/utils');
const nullthrows = require('nullthrows');
const path = require('path');
const {encodeJSONKeyComponent} = require('@parcel/diagnostic');

module.exports = (new Bundler({
  // RULES:
  // 1. If dep.isAsync or dep.isEntry, start a new bundle group.
  // 2. If an asset is a different type than the current bundle, make a parallel bundle in the same bundle group.
  // 3. If an asset is already in a parent bundle in the same entry point, exclude from child bundles.
  // 4. If an asset is only in separate isolated entry points (e.g. workers, different HTML pages), duplicate it.
  // 5. If the sub-graph from an asset is >= 30kb, and the number of parallel requests in the bundle group is < 5, create a new bundle containing the sub-graph.
  // 6. If two assets are always seen together, put them in the same extracted bundle

  bundle({bundleGraph}) {
    let bundleRoots /* : Map<Bundle, Array<Asset>> */ = new Map();
    let bundlesByEntryAsset /* : Map<Asset, Bundle> */ = new Map();

    // Step 1: create bundles for each of the explicit code split points.
    bundleGraph.traverse({
      enter: (node, context, actions) => {
        if (node.type !== 'dependency') {
          return {
            ...context,
            bundleGroup: context && context.bundleGroup,
            bundleByType: context && context.bundleByType,
            parentNode: node,
            parentBundle:
              bundlesByEntryAsset.get(node.value) ||
              (context && context.parentBundle),
          };
        }

        let dependency = node.value;
        // if (bundleGraph.isDependencySkipped(dependency)) {
        //   actions.skipChildren();
        //   return;
        // }

        let assets = bundleGraph.getDependencyAssets(dependency);
        let resolution = bundleGraph.getResolvedAsset(dependency);
        let bundleGroup = context && context.bundleGroup;
        // Create a new bundle for entries, lazy/parallel dependencies, isolated/inline assets.
        if (
          resolution &&
          (!bundleGroup ||
            dependency.priority === 'lazy' ||
            dependency.priority === 'parallel' ||
            resolution.bundleBehavior === 'isolated' ||
            resolution.bundleBehavior === 'inline')
        ) {
          let bundleByType /*: Map<string, Bundle> */ =
            (context && context.bundleByType) || new Map();

          // Only create a new bundle group for entries, lazy dependencies, and isolated assets.
          // Otherwise, the bundle is loaded together with the parent bundle.
          if (
            !bundleGroup ||
            dependency.priority === 'lazy' ||
            resolution.bundleBehavior === 'isolated'
          ) {
            bundleGroup = bundleGraph.createBundleGroup(
              dependency,
              nullthrows(
                dependency.target ||
                  (context &&
                    context.bundleGroup &&
                    context.bundleGroup.target),
              ),
            );

            bundleByType = new Map();
          }

          for (let asset of assets) {
            let bundle = bundleGraph.createBundle({
              entryAsset: asset,
              needsStableName:
                dependency.bundleBehavior === 'inline' ||
                asset.bundleBehavior === 'inline'
                  ? false
                  : dependency.isEntry || dependency.needsStableName,
              bundleBehavior: dependency.bundleBehavior || asset.bundleBehavior,
              target: bundleGroup.target,
            });
            bundleByType.set(bundle.type, bundle);
            bundlesByEntryAsset.set(asset, bundle);
            bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);

            // The bundle may have already been created, and the graph gave us back the original one...
            if (!bundleRoots.has(bundle)) {
              bundleRoots.set(bundle, [asset]);
            }

            // If the bundle is in the same bundle group as the parent, create an asset reference
            // between the dependency, the asset, and the target bundle.
            if (bundleGroup === (context && context.bundleGroup)) {
              bundleGraph.createAssetReference(dependency, asset, bundle);
            }
          }

          return {
            bundleGroup,
            bundleByType,
            parentNode: node,
            parentBundle: context && context.parentBundle,
          };
        }

        invariant(context != null);
        invariant(context.parentNode.type === 'asset');
        invariant(context.parentBundle != null);
        invariant(bundleGroup != null);
        let parentAsset = context.parentNode.value;
        let parentBundle = context.parentBundle;
        let bundleByType = nullthrows(context.bundleByType);

        for (let asset of assets) {
          if (parentAsset.type === asset.type) {
            continue;
          }

          let existingBundle = bundleByType.get(asset.type);
          if (existingBundle) {
            // If a bundle of this type has already been created in this group,
            // merge this subgraph into it.
            nullthrows(bundleRoots.get(existingBundle)).push(asset);
            bundlesByEntryAsset.set(asset, existingBundle);
            bundleGraph.createAssetReference(dependency, asset, existingBundle);
          } else {
            let bundle = bundleGraph.createBundle({
              uniqueKey: asset.id,
              env: asset.env,
              type: asset.type,
              target: bundleGroup.target,
              needsStableName:
                asset.bundleBehavior === 'inline' ||
                dependency.bundleBehavior === 'inline' ||
                (dependency.priority === 'parallel' &&
                  !dependency.needsStableName)
                  ? false
                  : parentBundle.needsStableName,
              bundleBehavior: dependency.bundleBehavior || asset.bundleBehavior,
              isSplittable: asset.isBundleSplittable || true,
              pipeline: asset.pipeline,
            });
            bundleByType.set(bundle.type, bundle);
            bundlesByEntryAsset.set(asset, bundle);
            bundleGraph.createAssetReference(dependency, asset, bundle);

            // The bundle may have already been created, and the graph gave us back the original one...
            if (!bundleRoots.has(bundle)) {
              bundleRoots.set(bundle, [asset]);
            }
          }
        }

        return {
          ...context,
          parentNode: node,
        };
      },
    });

    for (let [bundle, rootAssets] of bundleRoots) {
      for (let asset of rootAssets) {
        bundleGraph.addEntryToBundle(asset, bundle, dependency => {
          let resolution = bundleGraph.getResolvedAsset(dependency);
          return (
            bundleGraph.isDependencySkipped(dependency) &&
            (!resolution || path.basename(resolution.filePath) !== 'b.js')
          );
        });
      }
    }
  },

  optimize({bundleGraph, config}) {},
}) /*: Bundler */);
