"use strict";

const fieldFuncDefault = (value) => {
	return [Text(value)];
};

function makeId(d) {
	return `${d.test}/${d.channel}`;
}

function rowAttribs(d) {
	return d.fail ? {"class": "fail"} : null;
}

function sectionLink(value, d) {
	const id = makeId(d);
	const a = Tag('a', {href: '#'+id}, value);
	//a.addEventListener('click', () => { $(id).setAttribute('open', true); });
	return [a];
}

// fonts: Twemoji, Noto, OpenMoji
const emojiFontReplacementTable = {
	"🌑":"Twemoji",
	"☀":"Noto",
	"🧲":"Noto",
	"☢":"Noto",
	"🔦":"OpenMoji",
	"🎆":"Noto",
	"💎":"Noto",
	"🏀":"Noto",
	"🖌":"Noto",
	//"❌":"Noto",
	"🏁":"Noto",
	"🛑":"Noto",
	"⛔":"Noto",
}
function emojiFont(emoji) {
	let emojiFontReplacement = emojiFontReplacementTable[emoji];
	emojiFontReplacement = emojiFontReplacement ? " " + emojiFontReplacement : "";
	return emojiFontReplacement;
}

function emojiDetect(string) {
	const regex_emoji = /\p{Extended_Pictographic}/gu; // it's not complete but it's enough for case
	const matches = [...string.matchAll(regex_emoji)];
	return matches;
}
function emojiToTag(string) {
	const results = emojiDetect(string);
	let lastIndex = 0;
	const tagArray = [];
	for (let result of results) {
		let emoji = result[0];
		if (result.index > lastIndex) {
			tagArray.push(Text(string.slice(lastIndex, result.index)));
		}
		tagArray.push(Tag('span', {"class": "emoji" + emojiFont(emoji)}, emoji));
		lastIndex = result.index + emoji.length;
	}
	if (lastIndex < string.length) {
		tagArray.push(Text(string.slice(lastIndex)));
	}
	return tagArray;
}
function channelWithEmoji(value) {
	const replacements = {
		"full": "🖼",
		"basecolor": "🎨",
		"emissive": "☢",
		"nshade": "🥬", //🏔🌄
		"ngeom": "🥒", //⛰
		"lighting": "☀",//🗿
		"direct": "🔦",//🔦🎇☄
		"direct_diffuse": "🔦🎆",
		"direct_specular": "🔦💎",
		"diffuse": "🎆",
		"specular": "💎",
		"material": "🖌",
		"indirect": "🏀",//🏀🏓🥏
		"indirect_specular": "🏀💎",
		"indirect_diffuse": "🏀🎆"
	}
	const emoji = replacements[value];
	if (emoji) {
		return [
			...emojiToTag(emoji),
			Tag("span", {"class": "text-left-margin"}, value)
		];
	} else {
		return [];
	}
}
function colorPct(value) {
	let fixedPercent = value.toFixed(3);
	//fixedPercent = (fixedPercent < 10 ? '0' : '') + fixedPercent;
	let css_class = "";
	if (value < 1) {
		css_class = "green";
	} else
	if (value < 5) {
		css_class = "yellow";
	} else {
		css_class = "red";
	}
	return [Tag("span", {"class": css_class}, fixedPercent)];
}
function isPassed(value) {
	const font = value ? emojiFont("❌") : emojiFont("✔");
	return [
		Tag("span", {"class": "emoji" + font},
			value ? "❌" : "✅" // ✔✅
		)
	];
}

function makeTable(fields, data, attrs_func) {
	const table = Tag('table', null, null, [
		Tag('tr', null, null, (() => {
			let tds = [];
			for (const f of fields) {
				tds.push(Tag('th', {"class": "table-sticky"}, null, [Text(f.label)]));
			}
			return tds;
		})()),
	]);


	for (const di in data) {
		let d = data[di];
		const attrs = attrs_func ? attrs_func(d) : null;
		table.appendChild(Tag('tr', attrs, null, (() => {
			let ret = [];
			for (const f of fields) {
				const value = d[f.field];
				if (value === undefined) {
					ret.push(Tag('td'));
					continue;
				}

				const field_func = f.tags_func ? f.tags_func : fieldFuncDefault;
				const tags = field_func(value, d);

				// if (f.good && f.bad) {
				// 	const rating = linearStep(value, f.good, f.bad);
				// 	const c = {
				// 		r: 224 + 31 * clamp(2 - rating * 2, 0, 1),
				// 		g: 224 + 31 * clamp(rating * 2, 0, 1),
				// 		b: 224,
				// 	};
				// 	attrs = {style: `background-color: rgb(${c.r}, ${c.g}, ${c.b})`};
				// }
				ret.push(Tag('td', null, null, tags));
			}
			return ret;
		})()));
	}

	return table;
}

function buildTestResultsTable(data) {
	const fields = [
		{label: 'Test', field: 'test', tags_func: sectionLink},
		{label: 'Channel', field: 'channel', tags_func: channelWithEmoji},
		{label: 'Δ, %', field: 'diff_pct', tags_func: colorPct},
		{label: 'Passed', field: 'fail', tags_func: isPassed},
	];

	return [makeTable(fields, data, rowAttribs)];
}

// Filter out all success
function filterData(data) {
	return data
	.filter((d) => { return d.diff_pct !== 0; })
	.sort((l, r) => { 
		return l.diff_pct < r.diff_pct ? 1 : -1; // RTFM
	});
}

function buildSummary(data) {
	const total = data.length;
	const fails = data.filter((d) => { return !!d.fail; }).length;
	const fails_pct = fails * 100.0 / total;
	return emojiToTag(`🧪 Tests 💥: ${fails}/${total}🏁 (⚠${fails_pct.toFixed(3)}%)`);
}

function buildTestResultImages(data) {
	return data.flatMap((d) => {
		//return Tag('details', {id: makeId(d)}, null, [
		return Tag('div', {id: makeId(d), "class": "meta-block"}, null, [
			//Tag('summary', null, `${d.test}/${d.channel} δ=${d.diff_pct}`),
			Tag("a", {"class": "text-link", "href": `#${d.test}/${d.channel}`}, null, [
				Tag('h3', null, `${d.test}/${d.channel} δ=${d.diff_pct}`)
			]),
			Tag('div', {"class": "image-container"}, null, [
				Tag('div', {"class": "block block-gold"}, null, [Tag('img', {src: d.image_gold, loading: "lazy", "class": "gold"})]),
				Tag('div', {"class": "block block-test"}, null, [Tag('img', {src: d.image_test, loading: "lazy", "class": "test"})]),
				Tag('div', {"class": "block block-diff"}, null, [Tag('img', {src: d.image_diff, loading: "lazy", "class": "diff"})]),
				Tag('div', {"class": "separator"}, null, null),
			])
			//Tag('img', {src: d.image_flip}),
		]);
	});
}



function buildData(table, images, data, sort, filter) {
	if (sort) {
		data = filterData(data);
	}
	if (filter) {
		data = data.filter((d) => {
			const test = d.test.includes(filter);
			const channel = d.channel.includes(filter);
			return test || channel;
		})
	}


	table.replaceChildren(...buildTestResultsTable(data));
	images.replaceChildren(...buildTestResultImages(data));

	for (let block of images.querySelectorAll(".image-container")) {
		block.addEventListener("click", function() {
			let diffElement = this.querySelector(".block-diff");
			let goldElement = this.querySelector(".block-gold");
			diffElement.classList.toggle("show_diff");
			goldElement.classList.toggle("anim_force_off");
		});
	};

	for (let tr of document.querySelectorAll("table tr")) {
		tr.addEventListener("click", function() {
			this.querySelector("a").click();
		});
	}

	saveToLocalStorage("rendertest_tablesort", sort);
}
const buildDataSlowMode = debounce((table, images, data, sort, filter) => {
	buildData(table, images, data, sort, filter);
}, ()=>{}, 250);


const saveToLocalStorage = debounce((key, value) => {
	localStorage.setItem(key, value);
}, ()=>{}, 250);
const loadFromLocalStorage = (key, default_option) => {
	const value = localStorage.getItem(key);
	if (isNaN(value)) {
		switch(value) { // ugly textual localStorage
			case "true":
				return true;
			case "false":
				return false;
			case "null":
				return default_option ? default_option : null;
			default:
				return value;
		}
	} else
	if (value === null) {
		return default_option ? default_option : null;
	}
	return parseFloat(value); // fix !!"0" = true
}


function uglyChecked(value) { // fast hack
	return value ? {"checked": "checked"} : {};
}


window.onload = () => {
	const theme = loadFromLocalStorage("rendertest_theme", "");

	const linkElements = document.querySelectorAll('link[rel^="stylesheet"][data-theme]');
	function updateLinkRel(linkElements, id) {
		for (const link of linkElements) {
			const themeName = link.getAttribute("data-theme");
			link.disabled = themeName !== id;
			link.rel = themeName === id && link.rel.includes("alternate") 
				? link.rel.replace(" alternate", "") 
				: link.rel + " alternate";
		}
	}
	let themes = [];
	for (const linkElement of linkElements) {
		const themeName = linkElement.getAttribute("data-theme");
		const emojiTags = emojiToTag(linkElement.title);
		let checked = uglyChecked(!linkElement.rel.includes("alternate"));
		if (theme) {
			checked = uglyChecked(theme === themeName);
		}
		themes.push(
			Tag("label", null, null, [
				Tag("input", {"type": "radio", "class": "theme-button", "id": themeName, "name": "theme", ...checked}, null, null,
				"click", (e) => {
					updateLinkRel(linkElements, e.target.id);
					saveToLocalStorage("rendertest_theme", e.target.id);
				}),
				...emojiTags
			])
		)
	};

	function changeAnimationDuration(newDuration) {
		document.documentElement.style.setProperty('--animation-duration', newDuration + 'ms');
	}
	function syncSliderValues(event) {
		changeAnimationDuration(event.target.value);
		const value = event.target.value;
		const input = event.target;
		const otherInput = input === rangeInput ? numberInput : rangeInput;
		otherInput.value = value;
		saveToLocalStorage("rendertest_switchfrequency", value);
	}

	let gridContainer, sidebarOptions, numberInput, rangeInput, table, images, vpaddin_input, filter;

	const switchFrequency = loadFromLocalStorage("rendertest_switchfrequency", 700);
	const tableSort = loadFromLocalStorage("rendertest_tablesort", false);
	const sidebarPos = loadFromLocalStorage("rendertest_sidebarpos", "left");
	const sidebarMode = loadFromLocalStorage("rendertest_sidebarmode", "minimize");
	const sidebarSize = loadFromLocalStorage("rendertest_sidebarsize", "default");
	const vpadding = loadFromLocalStorage("rendertest_vpadding", true);
	const all_diff = loadFromLocalStorage("rendertest_all_diff", false);
	const diffMode = loadFromLocalStorage("rendertest_diffmode", "click");
	const imageCompareMode = loadFromLocalStorage("rendertest_imagecomparemode", "switch");
	const imagePosition = loadFromLocalStorage("rendertest_imageposition", "center");
	let isPaused = loadFromLocalStorage("rendertest_paused", false);

	if (theme) {
		updateLinkRel(linkElements, theme);
	}

	// TODO: simplification and bindings
	gridContainer = Tag("div", {"class": "grid-container"}, null, [
		Tag("div", {"class": "sidebar"}, null, [
			sidebarOptions = Tag("div", {"class": "panel", "id": "options"}, null, [
				Tag("div", {"class": "theme-options"}, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "🎨"), 
						Text(" Theme")
					]),
					...themes
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "🌈"), 
						Text(" Show diff mode on")
					]),
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "diff_mode", "value": "click", ...uglyChecked(diffMode === "click")}, null, null, "input", (e) => {
							images.classList.toggle("diff_separate");
							saveToLocalStorage("rendertest_diffmode", e.target.value);
						}),
						Tag("span", {"class": "emoji"}, "🖱"), 
						Text(" Click (LMB)")
					]),
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "diff_mode", "value": "separate", ...uglyChecked(diffMode === "separate")}, null, null, "input", (e) => {
							images.classList.toggle("diff_separate");
							saveToLocalStorage("rendertest_diffmode", e.target.value);
						}),
						Tag("span", {"class": "emoji"}, "🎏"), 
						Text(" Separate")
					])
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "🤼"), 
						Text(" Image compare mode")
					]),
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "compare_mode", "value": "switch", ...uglyChecked(imageCompareMode === "switch")}, null, null, "input", (e) => {
							images.classList.toggle("split_compare");
							saveToLocalStorage("rendertest_imagecomparemode", e.target.value);
							for (let blocks of images.querySelectorAll(".block-gold")) {
								blocks.removeAttribute("style"); // FIXME
							}
							for (let seps of images.querySelectorAll(".separator")) {
								seps.removeAttribute("style"); // FIXME
							}
						}),
						Tag("span", {"class": "emoji"}, "🎞"), 
						Text(" Switch images")
					]),
					Tag("label", {"title": "under construction"}, null, [
						Tag("input", {"type": "radio", "name": "compare_mode", "value": "split", ...uglyChecked(imageCompareMode === "split")}, null, null, "input", (e) => {
							images.classList.toggle("split_compare");
							saveToLocalStorage("rendertest_imagecomparemode", e.target.value);
						}),
						Tag("span", {"class": "emoji"}, "🪓"), 
						Text(" Split images")
					])
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "🎢"), 
						Text(" Speed switch image")
					]),
					rangeInput = Tag("input", {"type": "range", "style": "width: 140px;", "min": "10", "max": "1000", "value": switchFrequency}, null, null, "input", syncSliderValues),
					numberInput = Tag("input", {"type": "number", "size": "5", "min": "10", "max": "1000", "value": switchFrequency}, null, null, "input", syncSliderValues),
					Text(" ms "),
					Tag("button", null, isPaused ? "resume" : "pause", null, "click", (e) => {
						//e.target.textContent = isPaused ? "❄ pause" : "🔥 resume";
						e.target.textContent = isPaused ? "pause" : "resume";
						changeAnimationDuration(isPaused ? numberInput.value : 0);
						isPaused = !isPaused;
						saveToLocalStorage("rendertest_paused", isPaused);
					})
				]),
				/* future
				Tag("details", null, null, [
					Tag("summary", null, "Advanced options"),
					
				]),
				*/
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "🧻"),
						Text(" Sidebar")
					]),
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "sidebar-mode", "value": "minimize", ...uglyChecked(sidebarMode === "minimize")}, null, null, "input", (e) => {
							sidebarOptions.classList.toggle("sticky");
							saveToLocalStorage("rendertest_sidebarmode", e.target.value);

						}),
						Text(" auto minimize")
					]),
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "sidebar-mode", "value": "always", ...uglyChecked(sidebarMode === "always")}, null, null, "input", (e) => {
							sidebarOptions.classList.toggle("sticky");
							saveToLocalStorage("rendertest_sidebarmode", e.target.value);
						}),
						Text(" show always")
					]),
					Tag("ul", {"class": "list"}, null, [
						Tag("li", null, null, [
							Tag("span", {"class": "emoji Noto"}, "🧲"),
							Text(" position"),
							Tag("label", null, null, [
								Tag("input", {"type": "radio", "name": "sidebar_pos", "value": "left", ...uglyChecked(sidebarPos == "left")}, null, null, "input", (e) => {
									gridContainer.classList.toggle("reversed");
									saveToLocalStorage("rendertest_sidebarpos", e.target.value);
								}),
								Text(" left")
							]),
							Tag("label", null, null, [
								Tag("input", {"type": "radio", "name": "sidebar_pos", "value": "right", ...uglyChecked(sidebarPos == "right")}, null, null, "input", (e) => {
									gridContainer.classList.toggle("reversed");
									saveToLocalStorage("rendertest_sidebarpos", e.target.value);
								}),
								Text(" right")
							]),
						]),
						Tag("li", null, null, [
							Tag("span", {"class": "emoji"}, "📏"),
							Text(" size"),
							Tag("label", null, null, [
								Tag("input", {"type": "radio", "name": "sidebar-size", "value": "default",  ...uglyChecked(sidebarSize == "default")}, null, null, "input", (e) => {
									document.body.classList.toggle("mini");
									saveToLocalStorage("rendertest_sidebarsize", e.target.value);
								}),
								Text(" default")
							]), 
							Tag("label", null, null, [
								Tag("input", {"type": "radio", "name": "sidebar-size", "value": "mini",  ...uglyChecked(sidebarSize == "mini")}, null, null, "input", (e) => {
									document.body.classList.toggle("mini");
									saveToLocalStorage("rendertest_sidebarsize", e.target.value);
								}),
								Text(" mini")
							])
						]), 
					]),
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "⚖"),
						Text(" Image position")
					]), 
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "image_position", "value": "relative", ...uglyChecked(imagePosition === "relative")}, null, null, "input", (e) => {
								images.classList.toggle("center");
								saveToLocalStorage("rendertest_imageposition", e.target.value);
							}),
						Text(" relative")
					]), 
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "image_position", "value": "center", ...uglyChecked(imagePosition === "center")}, null, null, "input", (e) => {
								images.classList.toggle("center");
								saveToLocalStorage("rendertest_imageposition", e.target.value);
							}),
						Text(" center ")
					]),
					Tag("b", null, null, [
						Tag("span", {"class": "emoji Noto"}, "🖼"),
						Tag("label", null, null, [
							Text(" vertical padding"),
							vpaddin_input = Tag("input", {"type": "checkbox", "name": "vpadding", ...uglyChecked(vpadding)}, null, null, "change", (e) => {
								images.classList.toggle("vpadding");
								saveToLocalStorage("rendertest_vpadding", e.target.checked);
							})
						])
					])
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "📷"),
						Tag("label", null, null, [
							Text(" Toggle diff for all"),
							Tag("input", {"type": "checkbox", "name": "all_diff", ...uglyChecked(all_diff)}, null, null, "change", (e) => {
								images.classList.toggle("all_diff");
								//vpaddin_input.disabled = !vpaddin_input.disabled;
								//images.classList.toggle("vpadding");
								saveToLocalStorage("rendertest_all_diff", e.target.checked);
							})
						])
					])
				]),
				Tag("div", null, null, [
					Tag("b", null, null, [
						Tag("span", {"class": "emoji"}, "📊"),
						Text(" Sort table by Failed")
					]), 
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "sort_table", "value": "yes", ...uglyChecked(tableSort)}, null, null, "input", (e) => {
							buildData(table, images, data, true, filter.value);
						}),
						Text(" yes")
					]), 
					Tag("label", null, null, [
						Tag("input", {"type": "radio", "name": "sort_table", "value": "no", ...uglyChecked(!tableSort)}, null, null, "input", (e) => {
							buildData(table, images, data, false, filter.value);
						}),
						Text(" no")
					])
				])
			]),
			Tag("div", {"class": "panel", "id": "table"}, null, [
				Tag("h2", null, "List of things that are not perfect"),
				Tag("label", {"class": "filter sticky"}, "Filter", [
					filter = Tag("input", {"type": "input", "name": "filter", "value": ""}, null, null, "input", (e) => {
						buildDataSlowMode(table, images, data, tableSort, e.target.value);
					}),
				]),
				table = Tag("div", {"id": "fail_table"})
			])
		]),
		Tag("div", {"class": "content"}, null, [
			Tag("h1", null, "Rendertest report"),
			Tag("h2", null, "Summary"),
			Tag("div", {"id": "summary"}, null, [...buildSummary(data)]),
			Tag("h2", null, "Images of things that are not perfect ", [
				Tag("span", {"class": "emoji"}, "⤵")
			]),
			images = Tag("div", {"id": "fail_images", "class": "vpadding"})
		])
	])
	document.body.appendChild(gridContainer);

	buildData(table, images, data, tableSort);

	// TODO: remove this
	if (sidebarPos === "right") {
		gridContainer.classList.toggle("reversed");
	}
	if (sidebarMode === "always") {
		sidebarOptions.classList.toggle("sticky");
	}
	if (sidebarSize === "mini") {
		document.body.classList.toggle("mini");
	}
	if (imagePosition === "center") {
		images.classList.toggle("center");
	}
	if (!vpadding) {
		images.classList.toggle("vpadding");
	}
	if (all_diff) { // TODO: better UX
		images.classList.toggle("all_diff");
		//vpaddin_input.disabled = !vpaddin_input.disabled;
		//images.classList.toggle("vpadding");
	}
	if (diffMode === "separate") {
		images.classList.toggle("diff_separate");
	}
	if (imageCompareMode === "split") {
		images.classList.toggle("split_compare");
	}
	changeAnimationDuration(isPaused ? 0 : numberInput.value);

	// stop animation when not in the viewport
	function handleIntersection(entries, observer) {
		for (let entry of entries) {
			if (!entry.isIntersecting) {
				entry.target.classList.add("anim_off");
			} else {
				entry.target.classList.remove("anim_off");
			}
		}
	}

	// TODO: auto subs&unsubs

	const observer = new IntersectionObserver(handleIntersection, {
		root: null,
		threshold: 0.1
	});
	for (let element of document.querySelectorAll(".block-gold")) {
		observer.observe(element);
	}

	function getPointerPositionPercentage(container, event) {
		const rect = container.getBoundingClientRect();
		let x;
		switch (event.type) {
			case "mousemove":
				x = event.clientX - rect.left;
				break;
			case "touchmove": // TODO: testing
				x = event.touches[0].clientX - rect.left;
				break;
		}
		const percent = (x / rect.width) * 100;
		return percent;
	}
	images.addEventListener("mousemove", (event) => {
		if (images.classList.contains("split_compare")) {
			const target = event.target.closest(".image-container");
			if (target) {
				event.preventDefault();
				const percent = getPointerPositionPercentage(target, event);
				const separator = target.querySelector(".separator");
				const compare = target.querySelector(".block-gold");
				compare.style.width = `${percent}%`;
				separator.style.left = `${percent}%`;
			}
		}
	});
}


