import posthtml from "posthtml";
import urls from "@11ty/posthtml-urls";
import { FilePathUtil } from "./FilePathUtil.js";

import { arrayDelete } from "./ArrayUtil.js";

class HtmlTransformer {
	// feature test for Eleventy Bundle Plugin
	static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true;

	static TYPES = ["callbacks", "plugins"];

	constructor() {
		// execution order is important (not order of addition/object key order)
		this.callbacks = {};
		this.posthtmlProcessOptions = {};
		this.plugins = {};
	}

	get aggregateBench() {
		if (!this.userConfig) {
			throw new Error("Internal error: Missing `userConfig` in HtmlTransformer.");
		}
		return this.userConfig.benchmarkManager.get("Aggregate");
	}

	setUserConfig(config) {
		this.userConfig = config;
	}

	static prioritySort(a, b) {
		if (b.priority > a.priority) {
			return 1;
		}
		if (a.priority > b.priority) {
			return -1;
		}
		return 0;
	}

	// context is important as it is used in html base plugin for page specific URL
	static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) {
		let inst = posthtml();

		// already sorted by priority when added
		for (let { fn: plugin, options } of plugins) {
			inst.use(plugin(Object.assign({}, context, options)));
		}

		// Run the built-ins last
		if (callbacks.length > 0) {
			inst.use(
				urls({
					eachURL: (url, attrName, tagName) => {
						for (let { fn: callback } of callbacks) {
							// already sorted by priority when added
							url = callback.call(context, url, { attribute: attrName, tag: tagName });
						}

						return url;
					},
				}),
			);
		}

		return inst;
	}

	_add(extensions, addType, value, options = {}) {
		options = Object.assign(
			{
				priority: 0,
			},
			options,
		);

		let extensionsArray = (extensions || "").split(",");
		for (let ext of extensionsArray) {
			let target = this[addType];
			if (!target[ext]) {
				target[ext] = [];
			}

			target[ext].push({
				// *could* fallback to function name, `value.name`
				name: options.name, // for `remove` and debugging
				fn: value, // callback or plugin
				priority: options.priority, // sorted in descending order
				enabled: options.enabled || (() => true),
				options: options.pluginOptions,
			});

			target[ext].sort(HtmlTransformer.prioritySort);
		}
	}

	addPosthtmlPlugin(extensions, plugin, options = {}) {
		this._add(extensions, "plugins", plugin, options);
	}

	// match can be a plugin function or a filter callback(plugin => true);
	remove(extensions, match) {
		for (let removeType of HtmlTransformer.TYPES) {
			for (let ext of (extensions || "").split(",")) {
				this[removeType][ext] = arrayDelete(this[removeType][ext], match);
			}
		}
	}

	addUrlTransform(extensions, callback, options = {}) {
		this._add(extensions, "callbacks", callback, options);
	}

	setPosthtmlProcessOptions(options) {
		Object.assign(this.posthtmlProcessOptions, options);
	}

	isTransformable(extension, context) {
		return (
			this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0
		);
	}

	getCallbacks(extension, context) {
		let callbacks = this.callbacks[extension] || [];
		return callbacks.filter(({ enabled }) => {
			if (!enabled || typeof enabled !== "function") {
				return true;
			}
			return enabled(context);
		});
	}

	getPlugins(extension) {
		let plugins = this.plugins[extension] || [];
		return plugins.filter(({ enabled }) => {
			if (!enabled || typeof enabled !== "function") {
				return true;
			}
			return enabled();
		});
	}

	static async transformStandalone(content, callback, posthtmlProcessOptions = {}) {
		let posthtmlInstance = this._getPosthtmlInstance([
			{
				fn: callback,
				enabled: () => true,
			},
		]);
		let result = await posthtmlInstance.process(content, posthtmlProcessOptions);
		return result.html;
	}

	async transformContent(outputPath, content, context) {
		let extension = FilePathUtil.getFileExtension(outputPath);
		if (!this.isTransformable(extension, context)) {
			return content;
		}

		let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`);
		bench.before();
		let callbacks = this.getCallbacks(extension, context);
		let plugins = this.getPlugins(extension);
		let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context);
		let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions);
		bench.after();
		return result.html;
	}
}

export { HtmlTransformer };
