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

const SetVarMainTemplatePlugin = require("./SetVarMainTemplatePlugin");

/** @typedef {import("../declarations/WebpackOptions").LibraryCustomUmdObject} LibraryCustomUmdObject */
/** @typedef {import("./Compiler")} Compiler */

/**
 * @param {string[]} accessor the accessor to convert to path
 * @returns {string} the path
 */
const accessorToObjectAccess = accessor => {
	return accessor.map(a => `[${JSON.stringify(a)}]`).join("");
};

/**
 * @param {string=} base the path prefix
 * @param {string|string[]|LibraryCustomUmdObject} accessor the accessor
 * @param {"amd" | "commonjs" | "root"} umdProperty property used when a custom umd object is provided
 * @param {string=} joinWith the element separator
 * @returns {string} the path
 */
const accessorAccess = (base, accessor, umdProperty, joinWith = "; ") => {
	const normalizedAccessor =
		typeof accessor === "object" && !Array.isArray(accessor)
			? accessor[umdProperty]
			: accessor;
	const accessors = Array.isArray(normalizedAccessor)
		? normalizedAccessor
		: [normalizedAccessor];
	return accessors
		.map((_, idx) => {
			const a = base
				? base + accessorToObjectAccess(accessors.slice(0, idx + 1))
				: accessors[0] + accessorToObjectAccess(accessors.slice(1, idx + 1));
			if (idx === accessors.length - 1) return a;
			if (idx === 0 && base === undefined) {
				return `${a} = typeof ${a} === "object" ? ${a} : {}`;
			}
			return `${a} = ${a} || {}`;
		})
		.join(joinWith);
};

class LibraryTemplatePlugin {
	/**
	 * @param {string|string[]|LibraryCustomUmdObject} name name of library
	 * @param {string} target type of library
	 * @param {boolean} umdNamedDefine setting this to true will name the UMD module
	 * @param {string|TODO} auxiliaryComment comment in the UMD wrapper
	 * @param {string|string[]} exportProperty which export should be exposed as library
	 */
	constructor(name, target, umdNamedDefine, auxiliaryComment, exportProperty) {
		this.name = name;
		this.target = target;
		this.umdNamedDefine = umdNamedDefine;
		this.auxiliaryComment = auxiliaryComment;
		this.exportProperty = exportProperty;
	}

	/**
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.thisCompilation.tap("LibraryTemplatePlugin", compilation => {
			if (this.exportProperty) {
				const ExportPropertyMainTemplatePlugin = require("./ExportPropertyMainTemplatePlugin");
				new ExportPropertyMainTemplatePlugin(this.exportProperty).apply(
					compilation
				);
			}
			switch (this.target) {
				case "var":
					if (
						!this.name ||
						(typeof this.name === "object" && !Array.isArray(this.name))
					) {
						throw new Error(
							"library name must be set and not an UMD custom object for non-UMD target"
						);
					}
					new SetVarMainTemplatePlugin(
						`var ${accessorAccess(undefined, this.name, "root")}`,
						false
					).apply(compilation);
					break;
				case "assign":
					new SetVarMainTemplatePlugin(
						accessorAccess(undefined, this.name, "root"),
						false
					).apply(compilation);
					break;
				case "this":
				case "self":
				case "window":
					if (this.name) {
						new SetVarMainTemplatePlugin(
							accessorAccess(this.target, this.name, "root"),
							false
						).apply(compilation);
					} else {
						new SetVarMainTemplatePlugin(this.target, true).apply(compilation);
					}
					break;
				case "global":
					if (this.name) {
						new SetVarMainTemplatePlugin(
							accessorAccess(
								compilation.runtimeTemplate.outputOptions.globalObject,
								this.name,
								"root"
							),
							false
						).apply(compilation);
					} else {
						new SetVarMainTemplatePlugin(
							compilation.runtimeTemplate.outputOptions.globalObject,
							true
						).apply(compilation);
					}
					break;
				case "commonjs":
					if (this.name) {
						new SetVarMainTemplatePlugin(
							accessorAccess("exports", this.name, "commonjs"),
							false
						).apply(compilation);
					} else {
						new SetVarMainTemplatePlugin("exports", true).apply(compilation);
					}
					break;
				case "commonjs2":
				case "commonjs-module":
					new SetVarMainTemplatePlugin("module.exports", false).apply(
						compilation
					);
					break;
				case "amd":
				case "amd-require": {
					const AmdMainTemplatePlugin = require("./AmdMainTemplatePlugin");
					if (this.name && typeof this.name !== "string") {
						throw new Error("library name must be a string for amd target");
					}
					new AmdMainTemplatePlugin({
						name: this.name,
						requireAsWrapper: this.target === "amd-require"
					}).apply(compilation);
					break;
				}
				case "umd":
				case "umd2": {
					const UmdMainTemplatePlugin = require("./UmdMainTemplatePlugin");
					new UmdMainTemplatePlugin(this.name, {
						optionalAmdExternalAsGlobal: this.target === "umd2",
						namedDefine: this.umdNamedDefine,
						auxiliaryComment: this.auxiliaryComment
					}).apply(compilation);
					break;
				}
				case "jsonp": {
					const JsonpExportMainTemplatePlugin = require("./web/JsonpExportMainTemplatePlugin");
					if (typeof this.name !== "string")
						throw new Error("library name must be a string for jsonp target");
					new JsonpExportMainTemplatePlugin(this.name).apply(compilation);
					break;
				}
				case "system": {
					const SystemMainTemplatePlugin = require("./SystemMainTemplatePlugin");
					new SystemMainTemplatePlugin({
						name: this.name
					}).apply(compilation);
					break;
				}
				default:
					throw new Error(`${this.target} is not a valid Library target`);
			}
		});
	}
}

module.exports = LibraryTemplatePlugin;
