import path from "node:path";
import { TemplatePath } from "@11ty/eleventy-utils";
import isValidUrl from "./ValidUrl.js";
import { isGlobMatch } from "./GlobMatcher.js";

class HtmlRelativeCopy {
	#userConfig;
	#matchingGlobs = new Set();
	#matchingGlobsArray;
	#dirty = false;
	#paths = new Set();
	#failOnError = true;
	#copyOptions = {
		dot: false, // differs from standard passthrough copy
	};

	isEnabled() {
		return this.#matchingGlobs.size > 0;
	}

	setFailOnError(failOnError) {
		this.#failOnError = Boolean(failOnError);
	}

	setCopyOptions(opts) {
		if (opts) {
			Object.assign(this.#copyOptions, opts);
		}
	}

	setUserConfig(userConfig) {
		if (!userConfig || userConfig.constructor.name !== "UserConfig") {
			throw new Error(
				"Internal error: Missing `userConfig` or was not an instance of `UserConfig`.",
			);
		}
		this.#userConfig = userConfig;
	}

	addPaths(paths = []) {
		for (let path of paths) {
			this.#paths.add(TemplatePath.getDir(path));
		}
	}

	get matchingGlobs() {
		if (this.#dirty || !this.#matchingGlobsArray) {
			this.#matchingGlobsArray = Array.from(this.#matchingGlobs);
			this.#dirty = false;
		}

		return this.#matchingGlobsArray;
	}

	addMatchingGlob(glob) {
		if (glob) {
			if (Array.isArray(glob)) {
				for (let g of glob) {
					this.#matchingGlobs.add(g);
				}
			} else {
				this.#matchingGlobs.add(glob);
			}
			this.#dirty = true;
		}
	}

	isSkippableHref(rawRef) {
		if (
			this.#matchingGlobs.size === 0 ||
			!rawRef ||
			path.isAbsolute(rawRef) ||
			isValidUrl(rawRef)
		) {
			return true;
		}
		return false;
	}

	isCopyableTarget(target) {
		if (!isGlobMatch(target, this.matchingGlobs)) {
			return false;
		}

		return true;
	}

	exists(filePath) {
		return this.#userConfig.exists(filePath);
	}

	getAliasedPath(ref) {
		for (let dir of this.#paths) {
			let found = TemplatePath.join(dir, ref);
			if (this.isCopyableTarget(found) && this.exists(found)) {
				return found;
			}
		}
	}

	getFilePathRelativeToProjectRoot(ref, contextFilePath) {
		let dir = TemplatePath.getDirFromFilePath(contextFilePath);
		return TemplatePath.join(dir, ref);
	}

	copy(fileRef, tmplInputPath, tmplOutputPath) {
		// original ref is a full URL or no globs exist
		if (this.isSkippableHref(fileRef)) {
			return;
		}

		// Relative to source file’s input path
		let source = this.getFilePathRelativeToProjectRoot(fileRef, tmplInputPath);
		if (!this.isCopyableTarget(source)) {
			return;
		}

		if (!this.exists(source)) {
			// Try to alias using `options.paths`
			let alias = this.getAliasedPath(fileRef);
			if (!alias) {
				if (this.#failOnError) {
					throw new Error(
						"Missing input file for `html-relative` Passthrough Copy file: " +
							TemplatePath.absolutePath(source),
					);
				}

				// don’t fail on error
				return;
			}

			source = alias;
		}

		let target = this.getFilePathRelativeToProjectRoot(fileRef, tmplOutputPath);

		// We use a Set here to allow passthrough copy manager to properly error on conflicts upstream
		// Only errors when different inputs write to the same output
		// Also errors if attempts to write outside the output folder.
		this.#userConfig.emit("eleventy#copy", {
			source,
			target,
			options: this.#copyOptions,
		});
	}
}

export { HtmlRelativeCopy };
