/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const blacklist = require('metro-config/src/defaults/blacklist');
const invariant = require('invariant');

const {Logger} = require('metro-core');
const {fromRawMappings, toSegmentTuple} = require('metro-source-map');

import type Server from './Server';
import type {ConfigT} from 'metro-config/src/configTypes.flow';

exports.createBlacklist = blacklist;
exports.sourceMaps = {fromRawMappings, compactMapping: toSegmentTuple};
exports.createServer = createServer;
exports.Logger = Logger;

type PublicBundleOptions = {|
  +dev?: boolean,
  +entryFile: string,
  +inlineSourceMap?: boolean,
  +minify?: boolean,
  +platform?: string,
  +runModule?: boolean,
  +sourceMapUrl?: string,
|};

/**
 * This is a public API, so we don't trust the value and purposefully downgrade
 * it as `mixed`. Because it understands `invariant`, Flow ensure that we
 * refine these values completely.
 */
function assertPublicBundleOptions(bo: mixed): PublicBundleOptions {
  invariant(
    typeof bo === 'object' && bo != null,
    'bundle options must be an object',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.dev === undefined || typeof bo.dev === 'boolean',
    'bundle options field `dev` must be a boolean',
  );
  const {entryFile} = bo;
  invariant(
    typeof entryFile === 'string',
    'bundle options must contain a string field `entryFile`',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.inlineSourceMap === undefined || typeof bo.inlineSourceMap === 'boolean',
    'bundle options field `inlineSourceMap` must be a boolean',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.minify === undefined || typeof bo.minify === 'boolean',
    'bundle options field `minify` must be a boolean',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.platform === undefined || typeof bo.platform === 'string',
    'bundle options field `platform` must be a string',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.runModule === undefined || typeof bo.runModule === 'boolean',
    'bundle options field `runModule` must be a boolean',
  );
  invariant(
    // eslint-disable-next-line lint/strictly-null
    bo.sourceMapUrl === undefined || typeof bo.sourceMapUrl === 'string',
    'bundle options field `sourceMapUrl` must be a boolean',
  );
  return {entryFile, ...bo};
}

exports.build = async function(
  options: ConfigT,
  bundleOptions: PublicBundleOptions,
): Promise<{code: string, map: string}> {
  // TODO: Find out if this is used at all
  // // eslint-disable-next-line lint/strictly-null
  // if (options.targetBabelVersion !== undefined) {
  //   process.env.BABEL_VERSION = String(options.targetBabelVersion);
  // }
  var server = createNonPersistentServer(options);
  const ServerClass = require('./Server');

  const result = await server.build({
    ...ServerClass.DEFAULT_BUNDLE_OPTIONS,
    ...assertPublicBundleOptions(bundleOptions),
    bundleType: 'todo',
  });

  server.end();

  return result;
};

exports.getOrderedDependencyPaths = async function(
  options: ConfigT,
  depOptions: {
    +entryFile: string,
    +dev: boolean,
    +platform: string,
    +minify: boolean,
  },
): Promise<Array<string>> {
  var server = createNonPersistentServer(options);

  try {
    return await server.getOrderedDependencyPaths(depOptions);
  } finally {
    server.end();
  }
};

function createServer(options: ConfigT): Server {
  // Some callsites may not be Flowified yet.
  invariant(
    options.transformer.assetRegistryPath != null,
    'createServer() requires assetRegistryPath',
  );

  const ServerClass = require('./Server');
  return new ServerClass(options);
}

function createNonPersistentServer(config: ConfigT): Server {
  return createServer(config);
}