import { app } from "../../scripts/app.js";
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { GroupNodeConfig, GroupNodeHandler } from "./groupNode.js";

// Adds the ability to save and add multiple nodes as a template
// To save:
// Select multiple nodes (ctrl + drag to select a region or ctrl+click individual nodes)
// Right click the canvas
// Save Node Template -> give it a name
//
// To add:
// Right click the canvas
// Node templates -> click the one to add
//
// To delete/rename:
// Right click the canvas
// Node templates -> Manage
//
// To rearrange:
// Open the manage dialog and Drag and drop elements using the "Name:" label as handle

const id = "Comfy.NodeTemplates";

class ManageTemplates extends ComfyDialog {
	constructor() {
		super();
		this.element.classList.add("comfy-manage-templates");
		this.templates = this.load();
		this.draggedEl = null;
		this.saveVisualCue = null;
		this.emptyImg = new Image();
		this.emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';

		this.importInput = $el("input", {
			type: "file",
			accept: ".json",
			multiple: true,
			style: { display: "none" },
			parent: document.body,
			onchange: () => this.importAll(),
		});
	}

	createButtons() {
		const btns = super.createButtons();
		btns[0].textContent = "Close";
		btns[0].onclick = (e) => {
			clearTimeout(this.saveVisualCue);
			this.close();
		};
		btns.unshift(
			$el("button", {
				type: "button",
				textContent: "Export",
				onclick: () => this.exportAll(),
			})
		);
		btns.unshift(
			$el("button", {
				type: "button",
				textContent: "Import",
				onclick: () => {
					this.importInput.click();
				},
			})
		);
		return btns;
	}

	load() {
		const templates = localStorage.getItem(id);
		if (templates) {
			return JSON.parse(templates);
		} else {
			return [];
		}
	}

	store() {
		localStorage.setItem(id, JSON.stringify(this.templates));
	}

	async importAll() {
		for (const file of this.importInput.files) {
			if (file.type === "application/json" || file.name.endsWith(".json")) {
				const reader = new FileReader();
				reader.onload = async () => {
					var importFile = JSON.parse(reader.result);
					if (importFile && importFile?.templates) {
						for (const template of importFile.templates) {
							if (template?.name && template?.data) {
								this.templates.push(template);
							}
						}
						this.store();
					}
				};
				await reader.readAsText(file);
			}
		}

		this.importInput.value = null;

		this.close();
	}

	exportAll() {
		if (this.templates.length == 0) {
			alert("No templates to export.");
			return;
		}

		const json = JSON.stringify({ templates: this.templates }, null, 2); // convert the data to a JSON string
		const blob = new Blob([json], { type: "application/json" });
		const url = URL.createObjectURL(blob);
		const a = $el("a", {
			href: url,
			download: "node_templates.json",
			style: { display: "none" },
			parent: document.body,
		});
		a.click();
		setTimeout(function () {
			a.remove();
			window.URL.revokeObjectURL(url);
		}, 0);
	}

	show() {
		// Show list of template names + delete button
		super.show(
			$el(
				"div",
				{},
				this.templates.flatMap((t,i) => {
					let nameInput;
					return [
						$el(
							"div",
							{
								dataset: { id: i },
								className: "tempateManagerRow",
								style: {
									display: "grid",
									gridTemplateColumns: "1fr auto",
									border: "1px dashed transparent",
									gap: "5px",
									backgroundColor: "var(--comfy-menu-bg)"
								},
								ondragstart: (e) => {
									this.draggedEl = e.currentTarget;
									e.currentTarget.style.opacity = "0.6";
									e.currentTarget.style.border = "1px dashed yellow";
									e.dataTransfer.effectAllowed = 'move';
									e.dataTransfer.setDragImage(this.emptyImg, 0, 0);
								},
								ondragend: (e) => {
									e.target.style.opacity = "1";
									e.currentTarget.style.border = "1px dashed transparent";
									e.currentTarget.removeAttribute("draggable");

									// rearrange the elements in the localStorage
									this.element.querySelectorAll('.tempateManagerRow').forEach((el,i) => {
										var prev_i = el.dataset.id;

										if ( el == this.draggedEl && prev_i != i ) {
											this.templates.splice(i, 0, this.templates.splice(prev_i, 1)[0]);
										}
										el.dataset.id = i;
									});
									this.store();
								},
								ondragover: (e) => {
									e.preventDefault();
									if ( e.currentTarget == this.draggedEl )
										return;

									let rect = e.currentTarget.getBoundingClientRect();
									if (e.clientY > rect.top + rect.height / 2) {
										e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget.nextSibling);
									} else {
										e.currentTarget.parentNode.insertBefore(this.draggedEl, e.currentTarget);
									}
								}
							},
							[
								$el(
									"label",
									{
										textContent: "Name: ",
										style: {
											cursor: "grab",
										},
										onmousedown: (e) => {
											// enable dragging only from the label
											if (e.target.localName == 'label')
												e.currentTarget.parentNode.draggable = 'true';
										}
									},
									[
										$el("input", {
											value: t.name,
											dataset: { name: t.name },
											style: {
												transitionProperty: 'background-color',
												transitionDuration: '0s',
											},
											onchange: (e) => {
												clearTimeout(this.saveVisualCue);
												var el = e.target;
												var row = el.parentNode.parentNode;
												this.templates[row.dataset.id].name = el.value.trim() || 'untitled';
												this.store();
												el.style.backgroundColor = 'rgb(40, 95, 40)';
												el.style.transitionDuration = '0s';
												this.saveVisualCue = setTimeout(function () {
													el.style.transitionDuration = '.7s';
													el.style.backgroundColor = 'var(--comfy-input-bg)';
												}, 15);
											},
											onkeypress: (e) => {
												var el = e.target;
												clearTimeout(this.saveVisualCue);
												el.style.transitionDuration = '0s';
												el.style.backgroundColor = 'var(--comfy-input-bg)';
											},
											$: (el) => (nameInput = el),
										})
									]
								),
								$el(
									"div",
									{},
									[
										$el("button", {
											textContent: "Export",
											style: {
												fontSize: "12px",
												fontWeight: "normal",
											},
											onclick: (e) => {
												const json = JSON.stringify({templates: [t]}, null, 2); // convert the data to a JSON string
												const blob = new Blob([json], {type: "application/json"});
												const url = URL.createObjectURL(blob);
												const a = $el("a", {
													href: url,
													download: (nameInput.value || t.name) + ".json",
													style: {display: "none"},
													parent: document.body,
												});
												a.click();
												setTimeout(function () {
													a.remove();
													window.URL.revokeObjectURL(url);
												}, 0);
											},
										}),
										$el("button", {
											textContent: "Delete",
											style: {
												fontSize: "12px",
												color: "red",
												fontWeight: "normal",
											},
											onclick: (e) => {
												const item = e.target.parentNode.parentNode;
												item.parentNode.removeChild(item);
												this.templates.splice(item.dataset.id*1, 1);
												this.store();
												// update the rows index, setTimeout ensures that the list is updated
												var that = this;
												setTimeout(function (){
													that.element.querySelectorAll('.tempateManagerRow').forEach((el,i) => {
														el.dataset.id = i;
													});
												}, 0);
											},
										}),
									]
								),
							]
						)
					];
				})
			)
		);
	}
}

app.registerExtension({
	name: id,
	setup() {
		const manage = new ManageTemplates();

		const clipboardAction = async (cb) => {
			// We use the clipboard functions but dont want to overwrite the current user clipboard
			// Restore it after we've run our callback
			const old = localStorage.getItem("litegrapheditor_clipboard");
			await cb();
			localStorage.setItem("litegrapheditor_clipboard", old);
		};

		const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
		LGraphCanvas.prototype.getCanvasMenuOptions = function () {
			const options = orig.apply(this, arguments);

			options.push(null);
			options.push({
				content: `Save Selected as Template`,
				disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
				callback: () => {
					const name = prompt("Enter name");
					if (!name?.trim()) return;

					clipboardAction(() => {
						app.canvas.copyToClipboard();
						let data = localStorage.getItem("litegrapheditor_clipboard");
						data = JSON.parse(data);
						const nodeIds = Object.keys(app.canvas.selected_nodes);
						for (let i = 0; i < nodeIds.length; i++) {
							const node = app.graph.getNodeById(nodeIds[i]);
							const nodeData = node?.constructor.nodeData;
							
							let groupData = GroupNodeHandler.getGroupData(node);
							if (groupData) {
								groupData = groupData.nodeData;
								if (!data.groupNodes) {
									data.groupNodes = {};
								}
								data.groupNodes[nodeData.name] = groupData;
								data.nodes[i].type = nodeData.name;
							}
						}

						manage.templates.push({
							name,
							data: JSON.stringify(data),
						});
						manage.store();
					});
				},
			});

			// Map each template to a menu item
			const subItems = manage.templates.map((t) => {
				return {
					content: t.name,
					callback: () => {
						clipboardAction(async () => {
							const data = JSON.parse(t.data);
							await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {});
							localStorage.setItem("litegrapheditor_clipboard", t.data);
							app.canvas.pasteFromClipboard();
						});
					},
				};
			});

			subItems.push(null, {
				content: "Manage",
				callback: () => manage.show(),
			});

			options.push({
				content: "Node Templates",
				submenu: {
					options: subItems,
				},
			});

			return options;
		};
	},
});
