/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { ConcatSource } = require("webpack-sources");
const ExternalModule = require("../ExternalModule");
const Template = require("../Template");
const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");

/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */
/** @typedef {import("../util/Hash")} Hash */
/**
 * Defines the shared type used by this module.
 * @template T
 * @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T>
 */

/**
 * Defines the amd library plugin options type used by this module.
 * @typedef {object} AmdLibraryPluginOptions
 * @property {LibraryType} type
 * @property {boolean=} requireAsWrapper
 */

/**
 * Defines the amd library plugin parsed type used by this module.
 * @typedef {object} AmdLibraryPluginParsed
 * @property {string} name
 * @property {string} amdContainer
 */

/**
 * Represents the amd library plugin runtime component.
 * @typedef {AmdLibraryPluginParsed} T
 * @extends {AbstractLibraryPlugin<AmdLibraryPluginParsed>}
 */
class AmdLibraryPlugin extends AbstractLibraryPlugin {
	/**
	 * Creates an instance of AmdLibraryPlugin.
	 * @param {AmdLibraryPluginOptions} options the plugin options
	 */
	constructor(options) {
		super({
			pluginName: "AmdLibraryPlugin",
			type: options.type
		});
		/** @type {AmdLibraryPluginOptions["requireAsWrapper"]} */
		this.requireAsWrapper = options.requireAsWrapper;
	}

	/**
	 * Returns preprocess as needed by overriding.
	 * @param {LibraryOptions} library normalized library option
	 * @returns {T} preprocess as needed by overriding
	 */
	parseOptions(library) {
		const { name, amdContainer } = library;
		if (this.requireAsWrapper) {
			if (name) {
				throw new Error(
					`AMD library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}`
				);
			}
		} else if (name && typeof name !== "string") {
			throw new Error(
				`AMD library name must be a simple string or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}`
			);
		}
		const _name = /** @type {string} */ (name);
		const _amdContainer = /** @type {string} */ (amdContainer);
		return { name: _name, amdContainer: _amdContainer };
	}

	/**
	 * Returns source with library export.
	 * @param {Source} source source
	 * @param {RenderContext} renderContext render context
	 * @param {LibraryContext<T>} libraryContext context
	 * @returns {Source} source with library export
	 */
	render(
		source,
		{ chunkGraph, chunk, runtimeTemplate },
		{ options, compilation }
	) {
		const modern = runtimeTemplate.supportsArrowFunction();
		const modules = chunkGraph
			.getChunkModules(chunk)
			.filter(
				(m) =>
					m instanceof ExternalModule &&
					(m.externalType === "amd" || m.externalType === "amd-require")
			);
		const externals = /** @type {ExternalModule[]} */ (modules);
		const externalsDepsArray = JSON.stringify(
			externals.map((m) =>
				typeof m.request === "object" && !Array.isArray(m.request)
					? m.request.amd
					: m.request
			)
		);
		const externalsArguments = externals
			.map(
				(m) =>
					`__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
						`${chunkGraph.getModuleId(m)}`
					)}__`
			)
			.join(", ");

		const iife = runtimeTemplate.isIIFE();
		const fnStart =
			(modern
				? `(${externalsArguments}) => {`
				: `function(${externalsArguments}) {`) +
			(iife || !chunk.hasRuntime() ? " return " : "\n");
		const fnEnd = iife ? ";\n}" : "\n}";

		let amdContainerPrefix = "";
		if (options.amdContainer) {
			amdContainerPrefix = `${options.amdContainer}.`;
		}

		if (this.requireAsWrapper) {
			return new ConcatSource(
				`${amdContainerPrefix}require(${externalsDepsArray}, ${fnStart}`,
				source,
				`${fnEnd});`
			);
		} else if (options.name) {
			const name = compilation.getPath(options.name, {
				chunk
			});

			return new ConcatSource(
				`${amdContainerPrefix}define(${JSON.stringify(
					name
				)}, ${externalsDepsArray}, ${fnStart}`,
				source,
				`${fnEnd});`
			);
		} else if (externalsArguments) {
			return new ConcatSource(
				`${amdContainerPrefix}define(${externalsDepsArray}, ${fnStart}`,
				source,
				`${fnEnd});`
			);
		}
		return new ConcatSource(
			`${amdContainerPrefix}define(${fnStart}`,
			source,
			`${fnEnd});`
		);
	}

	/**
	 * Processes the provided chunk.
	 * @param {Chunk} chunk the chunk
	 * @param {Hash} hash hash
	 * @param {ChunkHashContext} chunkHashContext chunk hash context
	 * @param {LibraryContext<T>} libraryContext context
	 * @returns {void}
	 */
	chunkHash(chunk, hash, chunkHashContext, { options, compilation }) {
		hash.update("AmdLibraryPlugin");
		if (this.requireAsWrapper) {
			hash.update("requireAsWrapper");
		} else if (options.name) {
			hash.update("named");
			const name = compilation.getPath(options.name, {
				chunk
			});
			hash.update(name);
		} else if (options.amdContainer) {
			hash.update("amdContainer");
			hash.update(options.amdContainer);
		}
	}
}

module.exports = AmdLibraryPlugin;
