import {isString, isFunction} from 'underscore';
import {WatchSet} from '../fs/watch';

function reportMissingOption(name) {
  throw new Error(`must provide options.${name} when creating SourceArch`);
}

/**
 * SourceArch
 *
 * Used in ./package-source.js.
 */
export default class SourceArch {
  constructor(pkg, {
    kind, // required
    arch, // required
    sourceRoot, // required
    getFiles, // required
    uses = [],
    implies = [],
    declaredExports = null,
    // Do not include the source files in watchSet. They will be added at
    // compile time when the sources are actually read.
    watchSet = new WatchSet(),
  }) {
    isString(kind) || reportMissingOption('kind');
    isString(arch) || reportMissingOption('arch');
    isString(sourceRoot) || reportMissingOption('sourceRoot');
    isFunction(getFiles) || reportMissingOption('getFiles');

    this.pkg = pkg;

    // Kind of this sourceArchitecture. At the moment, there are really
    // three options -- package, plugin, and app. We use these in linking.
    this.kind = kind;

    // The architecture (fully or partially qualified) that can use this
    // unibuild.
    this.arch = arch;

    // Absolute path of the root directory of this package or application.
    this.sourceRoot = sourceRoot;

    // Packages used. The ordering is significant only for determining
    // import symbol priority (it doesn't affect load order), and a given
    // package could appear more than once in the list, so code that
    // consumes this value will need to guard appropriately. Each element
    // in the array has keys:
    // - package: the package name
    // - constraint: the constraint on the version of the package to use,
    //   as a string (may be null)
    // - unordered: If true, we don't want the package's imports and we
    //   don't want to force the package to load before us. We just want
    //   to ensure that it loads if we load.
    // - weak: If true, we don't *need* to load the other package, but
    //   if the other package ends up loaded in the target, it must
    //   be forced to load before us. We will not get its imports
    //   or plugins.
    // It is an error for both unordered and weak to be true, because such
    // a dependency would have no effect.
    //
    // In most places, instead of using 'uses' directly, you want to use
    // something like compiler.eachUsedUnibuild so you also take into
    // account implied packages.
    //
    // Note that if `package` starts with 'isobuild:', it actually
    // represents a dependency on a feature of the Isobuild build tool,
    // not a real package. You need to be aware of this when processing a
    // `uses` array, which is another reason to use eachUsedUnibuild
    // instead.
    this.uses = uses;

    // Packages which are "implied" by using this package. If a unibuild X
    // uses this unibuild Y, and Y implies Z, then X will effectively use
    // Z as well (and get its imports and plugins).  An array of objects
    // of the same type as the elements of this.uses (although for now
    // unordered and weak are not allowed).
    this.implies = implies;

    // A function that returns the source files for this
    // architecture. Object with keys `sources` and `assets`, where each
    // is an array of objects with keys "relPath" and "fileOptions". Null
    // if loaded from isopack.
    //
    // fileOptions is optional and represents arbitrary options passed to
    // "api.addFiles"; they are made available on to the plugin as
    // compileStep.fileOptions.
    //
    // This is a function rather than a literal array because for an app,
    // we need to know the file extensions registered by the plugins in
    // order to compute the sources list, so we have to wait until build
    // time (after we have loaded any plugins, including local plugins in
    // this package) to compute this.
    this.getFiles = getFiles;

    // Object whose keys are relative paths of local node_modules
    // directories in this package or application, for the given
    // architecture. Does not include the .npm/package/node_modules
    // directory installed by Npm.depends. Should be populated when
    // getFiles is called.
    this.localNodeModulesDirs = Object.create(null);

    // Symbols that this architecture should export. List of symbols (as
    // strings).
    this.declaredExports = declaredExports;

    // Files and directories that we want to monitor for changes in
    // development mode, as a watch.WatchSet. In the latest refactoring of
    // the code, this does not include source files or directories, but
    // only control files such as package.js and .meteor/packages, since
    // the rest are not determined until compile time.
    this.watchSet = watchSet;
  }
}
