// @ts-nocheck
const fs = require("node:fs");
const path = require("node:path");

const getPropertyValue = function (property) {
	return this[property];
};

export default class FakeDocument {
	constructor(basePath, options = {}) {
		this.head = this.createElement("head");
		this.body = this.createElement("body");
		this.baseURI = "https://test.cases/path/index.html";
		this._elementsByTagName = new Map([
			["head", [this.head]],
			["body", [this.body]]
		]);
		this._basePath = basePath;
		this.currentScript = undefined;
		this._options = options || {};
	}

	createElement(type) {
		return new FakeElement(this, type, this._basePath);
	}

	_onElementAttached(element) {
		const type = element._type;
		let list = this._elementsByTagName.get(type);
		if (list === undefined) {
			list = [];
			this._elementsByTagName.set(type, list);
		}
		list.push(element);
	}

	_onElementRemoved(element) {
		const type = element._type;
		const list = this._elementsByTagName.get(type);
		const idx = list.indexOf(element);
		list.splice(idx, 1);
	}

	getElementsByTagName(name) {
		return this._elementsByTagName.get(name) || [];
	}

	querySelectorAll(name) {
		return this._elementsByTagName.get(name) || [];
	}

	getComputedStyle(element) {
		const style = { getPropertyValue };
		const links = this.getElementsByTagName("link");
		for (const link of links) {
			for (const rule of link.sheet.cssRules) {
				if (rule.selectorText === element._type) {
					Object.assign(style, rule.style);
				}
			}
		}
		return style;
	}
}

export class FakeElement {
	constructor(document, type, basePath) {
		this._document = document;
		this._type = type;
		this._basePath = basePath;
		this._children = [];
		this._attributes = Object.create(null);
		this._src = undefined;
		this._href = undefined;
		this.rel = undefined;
		this.parentNode = undefined;
		this.sheet = type === "link" ? new FakeSheet(this, basePath) : undefined;
	}

	cloneNode() {
		return new FakeElement(this._document, this._type, this._basePath)
	}

	addEventListener() {}

	insertBefore(node, before) {
		this._document._onElementAttached(node);
		node.parentNode = this;
		this._children.unshift(node);
		Promise.resolve().then(() => {
			if (node.onload) {
				node.onload({ type: "load", target: node });
			}
		});
	}

	appendChild(node) {
		this._document._onElementAttached(node);
		this._children.push(node);
		node.parentNode = this;
		if (node._type === "link") {
			setTimeout(() => {
				if (node.onload) node.onload({ type: "load", target: node });
			}, 100);
		} else if (node._type === "script") {
			Promise.resolve().then(() => {
				if (typeof this._document._options.onScript === "function") {
					this._document._options.onScript(node);
				}
				if (node.onload) {
					node.onload({
						type: "load",
						target: node
					});
				}
			});
		} else {
			if (node.onload) {
				node.onload({ type: "load", target: node });
			}
		}
	}

	removeChild(node) {
		const idx = this._children.indexOf(node);
		if (idx >= 0) {
			this._children.splice(idx, 1);
			this._document._onElementRemoved(node);
			node.parentNode = undefined;
		}
	}

	setAttribute(name, value) {
		if (this._type === "link" && name === "href") {
			this.href(value);
		} else {
			this._attributes[name] = value;
		}
	}

	removeAttribute(name) {
		delete this._attributes[name];
	}

	getAttribute(name) {
		if (this._type === "link" && name === "href") {
			return this.href;
		}
		return this._attributes[name];
	}

	_toRealUrl(value) {
		if (/^\//.test(value)) {
			return `https://test.cases${value}`;
		} else if (/^\.\.\//.test(value)) {
			return `https://test.cases${value.slice(2)}`;
		} else if (/^\.\//.test(value)) {
			return `https://test.cases/path${value.slice(1)}`;
		} else if (/^\w+:\/\//.test(value)) {
			return value;
		} else if (/^\/\//.test(value)) {
			return `https:${value}`;
		} else {
			return `https://test.cases/path/${value}`;
		}
	}

	set src(value) {
		if (this._type === "script") {
			this._src = this._toRealUrl(value);
		}
	}

	get children() {
		return this._children;
	}

	get src() {
		return this._src;
	}

	set href(value) {
		if (this._type === "link") {
			this._href = this._toRealUrl(value);
		}
	}

	get href() {
		return this._href;
	}
}

export class FakeSheet {
	constructor(element, basePath) {
		this._element = element;
		this._basePath = basePath;
		this.cachedCss = undefined
		this.cachedCssRules = undefined
	}

	get css() {
		if (this.cachedCss) {
			return this.cachedCss
		}

		let css = fs.readFileSync(
			path.resolve(
				this._basePath,
				this._element.href
					.replace(/^https:\/\/test\.cases\/path\//, "")
					.replace(/^https:\/\/example\.com\//, "")
			),
			"utf-8"
		);

		css = css.replace(/@import url\("([^"]+)"\);/g, (match, url) => {
			if (!/^https:\/\/test\.cases\/path\//.test(url)) {
				return `@import url("${url}");`;
			}

			if (url.startsWith("#")) {
				return url;
			}

			return fs.readFileSync(
				path.resolve(
					this._basePath,
					url.replace(/^https:\/\/test\.cases\/path\//, "")
				),
				"utf-8"
			);
		});

		this.cachedCss = css
		return css;
	}

	get cssRules() {
		if (this.cachedCssRules) {
			return this.cachedCssRules
		}
		const walkCssTokens = require("./walkCssTokens");
		const rules = [];
		let currentRule = { getPropertyValue };
		let selector = undefined;
		let last = 0;
		const processDeclaration = str => {
			const colon = str.indexOf(":");
			if (colon > 0) {
				const property = str.slice(0, colon).trim();
				const value = str.slice(colon + 1);
				currentRule[property] = value;
			}
		};
		const filepath = /file:\/\//.test(this._element.href)
			? new URL(this._element.href)
			: path.resolve(
					this._basePath,
					this._element.href
						.replace(/^https:\/\/test\.cases\/path\//, "")
						.replace(/^https:\/\/example\.com\/public\/path\//, "")
						.replace(/^https:\/\/example\.com\//, "")
				);
		let css = fs.readFileSync(filepath.replace(/\?hmr=\d*/, ""), "utf-8");
		css = css.replace(/@import url\("([^"]+)"\);/g, (match, url) => {
			if (!/^https:\/\/test\.cases\/path\//.test(url)) {
				return url;
			}

			if (url.startsWith("#")) {
				return url;
			}

			return fs.readFileSync(
				path.resolve(
					this._basePath,
					url.replace(/^https:\/\/test\.cases\/path\//, "")
				),
				"utf-8"
			);
		})
		.replace(/\/\*[\s\S]*?\*\//g, '')
		.replace("//", "");
		walkCssTokens(css, {
			isSelector() {
				return selector === undefined;
			},
			leftCurlyBracket(source, start, end) {
				if (selector === undefined) {
					selector = source.slice(last, start).trim();
					last = end;
				}
				return end;
			},
			rightCurlyBracket(source, start, end) {
				processDeclaration(source.slice(last, start));
				rules.push({
					selectorText: selector,
					style: currentRule,
					// hack cssText, css hmr needs this fields to detect changes
					cssText: css
				});
				last = end;
				selector = undefined;
				currentRule = { getPropertyValue };
				return end;
			},
			semicolon(source, start, end) {
				processDeclaration(source.slice(last, start));
				last = end;
				return end;
			}
		});
		this.cachedCssRules = rules
		return rules;
	}
}
